<![CDATA[Documentation]]> <![CDATA[Attributes]]> <![CDATA[Building datafiles]]> <![CDATA[Catalog]]> ` to choose another dedicated export location. Eventvisor refuses unsafe locations such as the project root, your home directory, or a directory containing the project. Add `--no-assets` when only generated Catalog data is needed. To serve an existing export: ```sh npx eventvisor catalog serve --port 3000 ``` Entity and assertion URLs are shareable. Keys are URL encoded so namespaced definitions remain addressable. Schema pages show the shared structure and where it is used. Their Target count is derived from the events and attributes selected by each Target.]]> <![CDATA[Command Line Interface]]> `. The kebab-case `--root-directory-path` and `--projectDirectoryPath` forms are also accepted. ## Inspecting a project ```sh npx eventvisor config npx eventvisor list event npx eventvisor info event pageView npx eventvisor find-usage attribute userId npx eventvisor list schema npx eventvisor find-usage schema identifier npx eventvisor find-usage --unused-attributes --unused-schemas --unused-destinations ``` Supported entity types include events, attributes, destinations, effects, Schemas, Targets, and tests. Commands that print data support JSON where appropriate. Add `--pretty` when formatted JSON is easier to inspect. ## Runtime simulation ```sh npx eventvisor simulate pageView --value='{"url":"https://example.com"}' npx eventvisor benchmark pageView -n 1000000 --value='{"url":"https://example.com"}' ``` The benchmark warms up the SDK before measuring. Output reports minimum, average, maximum, p50, p95, and p99 duration for individual evaluations in microseconds. ## Selection Project commands accept `--set` in Set projects. Build, test, simulation, benchmark, and code generation accept repeatable `--tag` and `--target` options. ```sh npx eventvisor generate-code --language typescript --out-dir src/generated --target checkout --target account ``` ## Promotion ```sh npx eventvisor promote --from development --to staging npx eventvisor promote --from development --to staging --target checkout --apply --audit ``` Promotion is available to Set projects. Preview is the default. See [Promotions](/docs/promotion/). ## Project plugins Register project-specific commands in `eventvisor.config.js` with a type-safe plugin: ```js const { definePlugin } = require("@eventvisor/core"); module.exports = { plugins: [ definePlugin({ command: "publish-preview", description: "publish a preview datafile", options: {}, examples: [], async handler({ datasource }) { console.log(await datasource.readRevision()); }, }), ], }; ``` Custom commands run with the same resolved project configuration and datasource as built-in commands. Duplicate command names are ignored with a warning. ## Version ```sh npx eventvisor --version ```]]> <![CDATA[Code generation]]> <![CDATA[Concepts]]> datafiles/eventvisor-web.json targets/backend.yml -> datafiles/eventvisor-backend.json ``` Tags are selection metadata. They can help a Target select related definitions, but tags do not generate files by themselves. Eventvisor follows references and keeps required dependencies. For example, an included event retains its required attributes, destination overrides, quarantine destination, triggered effects, and the definitions those effects use. ## Runtime pipeline When an application calls `track()`, the SDK processes the event locally: ```text lookup -> required attributes -> validation -> conditions -> sampling -> event transforms -> effects -> destination rules and transforms -> transport modules ``` The SDK owns governance. [Transport modules](/docs/transports/) own delivery behaviour such as immediate sending, batching, retries, browser lifecycle handling, and flushing. Read the [event pipeline](/docs/pipeline/) for the exact processing and readiness contract. ## Modules [Modules](/docs/modules/) add capabilities to an SDK instance without adding vendor-specific behaviour to the core runtime. A module can provide one or more of these capabilities: - transport events to a destination - resolve lookup values - run effect handlers - persist attributes or effect state - flush queued work and release resources Applications choose which modules they trust and install. A datafile can refer to a module by name, but it cannot install code into an application. ## Sets and promotions [Sets](/docs/sets/) contain several isolated Eventvisor projects in one repository. They are useful when development, staging, and production need genuinely different definitions, tests, state, and datafiles. [Promotions](/docs/promotion/) preview and merge definitions from one Set to another. Targets still exist inside each Set. The two concepts solve different problems: - Sets are independent authoring and release lanes. - Targets are application-specific output slices within one lane. ## Git based workflow A typical change follows this path: ```text edit definitions -> lint and test -> pull request review -> build datafiles -> deploy to your CDN -> applications refresh datafiles ``` The [Catalog](/docs/catalog/) gives engineering, product, analytics, and governance teams a readable view of definitions, tests, dependencies, Targets, and Git history. Continue with the [quick start](/docs/quick-start/) to build and consume a small project.]]> <![CDATA[Conditions]]> <![CDATA[Configuration]]> /` | | `parser` | `"yml"` | Built in `yml` or `json` parser, or a custom parser object | | `prettyDatafile` | `false` | Format generated JSON with indentation | | `stringify` | `true` | Stringify supported runtime expressions for compact datafiles | | `onValidationFailure` | `"drop"` | Default policy for invalid events | | `datafileNamePattern` | `"eventvisor-%s.json"` | Output filename pattern, which must contain `%s` | Entity tags are optional. When present, every tag must be declared in `tags`. The validation failure policy accepts `"drop"`, `"deliverWithWarning"`, or a quarantine destination: ```js {% path="eventvisor.config.js" %} module.exports = { onValidationFailure: { action: "quarantine", destination: "invalidEvents", }, }; ``` An event can override the project policy. See [Events](/docs/events/#validation-failure-policy). ## Sets and promotion flows Enable [Sets](/docs/sets/) when one repository contains isolated authoring trees. `promotionFlows` can then restrict the allowed [promotion](/docs/promotion/) directions: ```js {% path="eventvisor.config.js" %} module.exports = { sets: true, promotionFlows: [ { from: "development", to: "staging" }, { from: "staging", to: "production" }, ], }; ``` When `promotionFlows` is omitted, any two different Sets can be used as the source and destination. ## Directory paths The default authoring directories are `events`, `attributes`, `destinations`, `effects`, `schemas`, `targets`, and `tests`. Their configuration properties follow the `DirectoryPath` naming pattern, such as `schemasDirectoryPath`. Generated state, datafiles, Sets, and Catalog output use `systemDirectoryPath`, `datafilesDirectoryPath`, `setsDirectoryPath`, and `catalogExportDirectoryPath`. Use `` in a configured path when it should resolve from the selected project root: ```js {% path="eventvisor.config.js" %} module.exports = { eventsDirectoryPath: "/tracking/events", }; ``` ## Parsers Use the built in JSON parser by setting `parser: "json"`. A custom parser provides an extension plus `parse` and `stringify` functions: ```js {% path="eventvisor.config.js" %} module.exports = { parser: { extension: "custom", parse(content, filePath) { return parseCustomFormat(content, filePath); }, stringify(value, filePath) { return stringifyCustomFormat(value, filePath); }, }, }; ``` See [Parsers](/docs/parsers/) for the complete contract. ## Datasource and plugins Project configuration can also register typed CLI plugins with `definePlugin()` from `@eventvisor/core`. Plugins receive the resolved project, datasource, and parsed options. Command names must not conflict with built-in commands. Advanced projects can replace the filesystem datasource by providing an `adapter` constructor. Most projects should keep the default filesystem adapter. ## Inspect resolved configuration Run `npx eventvisor config` to inspect the resolved paths and options. ```sh {% title="Command" %} $ npx eventvisor config $ npx eventvisor config --json --pretty ```]]> <![CDATA[Contributing]]> <![CDATA[Deployment]]> <![CDATA[Cloudflare Pages]]> Secrets and variables > Actions` section: - `CLOUDFLARE_ACCOUNT_ID` - `CLOUDFLARE_API_TOKEN` ## Repository settings Make sure you have `Read and write permissions` enabled in your GitHub repository's `Settings > Actions > General > Workflow permissions` section. ## Workflows We will be covering two workflows for our set up with GitHub Actions. ### Checks This workflow will be triggered on every push to the repository targeting any non-master or non-main branches. This will help identify any issues with your Pull Requests early before you merge them to your main branch. ```yml {% path=".github/workflows/checks.yml" %} name: Checks on: push: branches-ignore: - main jobs: checks: name: Checks runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Lint run: npx eventvisor lint - name: Test specs run: npx eventvisor test - name: Build run: npx eventvisor build ``` ### Publish This workflow is intended to be run on every push to your main (or master) branch, and is supposed to handle uploading of your generated datafiles to Cloudflare Pages: ```yml {% path=".github/workflows/publish.yml" %} name: Publish on: push: branches: - main jobs: publish: name: Publish runs-on: ubuntu-latest permissions: contents: write packages: write timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Lint run: npx eventvisor lint - name: Test specs run: npx eventvisor test - name: Build run: npx eventvisor build - name: Upload to Cloudflare Pages run: | echo "It works." > datafiles/index.html npx wrangler pages deploy datafiles --project-name="YOUR_CLOUDFLARE_PAGES_PROJECT_NAME" env: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} ## # The steps below are only needed if you wish to maintain incremental revision numbers. # # If you are building datafiles using --revision-from-hash flag, you can skip these steps. # - name: Git configs run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Push back to origin run: | git add .eventvisor/* git commit -m "[skip ci] Revision $(cat .eventvisor/REVISION)" git push ``` Once uploaded, your datafiles will be accessible as: `https://.pages.dev//eventvisor-.json`. You may want to take it a step further by setting up custom domains (or subdomains) for your Cloudflare Pages project. Otherwise, you are good to go. Learn how to consume datafiles from URLs directly using [SDKs](/docs/sdks). ## Full example See [https://github.com/eventvisor/eventvisor-example-cloudflare](https://github.com/eventvisor/eventvisor-example-cloudflare) for a full example.]]> <![CDATA[GitHub Actions]]> Actions > General > Workflow permissions` section. ## Workflows We will be covering two workflows for our set up with GitHub Actions. ### Checks This workflow will be triggered on every push to the repository targeting any non-master or non-main branches. This will help identify any issues with your Pull Requests early before you merge them to your main branch. ```yml {% path=".github/workflows/checks.yml" %} name: Checks on: push: branches-ignore: - main jobs: checks: name: Checks runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Lint run: npx eventvisor lint - name: Test specs run: npx eventvisor test - name: Build run: npx eventvisor build ``` ### Publish This workflow is intended to be run on every push to your main (or master) branch, and is supposed to handle uploading of your generated datafiles as well: ```yml {% path=".github/workflows/publish.yml" %} name: Publish on: push: branches: - main jobs: ci: name: Publish runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write packages: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Lint run: npx eventvisor lint - name: Test specs run: npx eventvisor test - name: Build run: npx eventvisor build - name: Upload datafiles run: echo "Uploading..." # Upload "datafiles" directory content based on your CDN set up ## # The steps below are only needed if you wish to maintain incremental revision numbers. # # If you are building datafiles using --revision-from-hash flag, you can skip these steps. # - name: Git configs run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Push back to origin run: | git add .eventvisor/* git commit -m "[skip ci] Revision $(cat .eventvisor/REVISION)" git push ``` If you want an example of an actual uploading step, see [Cloudflare Pages](/docs/deployment/cloudflare-pages/) guide. ## Sequential builds It is possible you might want to run the publish workflow sequentially for every merged Pull Requests, in case multiple Pull Requests are merged in quick succession. ### Queue You can consider using [softprops/turnstyle](https://github.com/softprops/turnstyle) GitHub Action to run publish workflow of all your merged Pull Requests sequentially. ### Branch protection rules Next to it, you can also make it stricter by requiring all Pull Request authors to have their branches up to date from latest main branch before merging: - [Managing suggestions to update pull request branches](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-suggestions-to-update-pull-request-branches) - [Create branch protection rule](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule#creating-a-branch-protection-rule) (see #7) - Require branches to be up to date before merging]]> <![CDATA[Destinations]]> <![CDATA[Diagnostics]]> { console.log(diagnostic.level, diagnostic.code, diagnostic.message); }); ``` Each diagnostic contains `level`, `code`, `message`, and `details`. Module reports include `moduleName`, and failures can include the original `error`. Error diagnostics also emit the SDK `error` event. ## Stable codes Codes are intended for monitoring and alerting. Important families include: | Area | Examples | | ---------------------------- | -------------------------------------------------------------------------------------------------- | | Datafiles and initialization | `invalid_datafile`, `initialization_failed` | | Events and attributes | `event_not_found`, `event_validation_failed`, `attribute_not_found`, `attribute_validation_failed` | | Conditions and sampling | `conditions_parse_failed`, `condition_evaluation_failed`, `sample_by_invalid` | | Transforms | `transform_conversion_failed`, `transform_invalid_input`, `transform_output_invalid` | | Effects | `effect_handler_failed`, `effect_reentrancy_blocked` | | Modules | `duplicate_module`, `module_setup_failed`, `module_transport_failed`, `module_flush_failed` | | Queueing transports | `http_queue_full`, `http_delivery_failed`, `beacon_queue_full`, `beacon_delivery_failed` | Treat codes as identifiers. Human readable messages may gain more context over time. Module authors can subscribe and report through the module API: ```js setup(api) { const unsubscribe = api.onDiagnostic(handleDiagnostic); api.reportDiagnostic({ level: "info", code: "custom_ready", message: "Custom module is ready", details: {}, }); } ``` Subscriptions created through a module API are removed when that module is removed or the instance is closed.]]> <![CDATA[Effects]]> console.log("Marketing script injected"); selector: body # update the state - transforms: - type: set target: injected value: true ``` ### Transforms Similar to [attributes](/docs/attributes), [events](/docs/events) and [destinations](/docs/destinations), each step can have `transforms` property too. But in the case of effects, it is for transforming its internal state only and not the event or attribute's payload. Learn more in [handlers](/docs/handlers) page. ### Conditions If you need step-specific conditions, you can add a `conditions` property to the individual step itself. ### Continue on error By default, if a certain step fails to be handled, the effect will stop processing the remaining steps if any. If you wish to continue even upon failure, you can add `continueOnError: true` to the step. ## Persistence Internal state of an effect is persisted only in memory by default, throughout the lifecycle of the SDK instance. If you wish to maintain the state beyond that, imagine a full application restart, you can use a storage layer. If you are aiming for browser-based applications, the browser's localStorage is a good choice via [module-localstorage](/docs/modules/localstorage). ```yml {% path="effects/marketing-pixel.yml" %} # ... persist: localstorage ``` Learn more in [persistence](/docs/persistence) page. ## Setting up the SDK In your application, install the Eventvisor SDK along with your desired module(s): ```{% title="Command" %} $ npm install --save \ @eventvisor/sdk \ @eventvisor/module-pixel \ @eventvisor/module-localstorage ``` And now in your application, initialize the SDK along with the module: ```js {% path="your-app/index.js" %} import { createEventvisor } from "@eventvisor/sdk"; import { createPixelModule } from "@eventvisor/module-pixel"; import { createLocalStorageModule } from "@eventvisor/module-localstorage"; const eventvisor = createEventvisor({ datafile: { ... }, modules: [ createPixelModule(), createLocalStorageModule(), ], }); ``` Now when you load your application and track the `pageView` event the first time, you should see the marketing pixel injected into the page. ## Archiving You can archive an effect by adding `archived: true` to the effect definition: ```yml {% path="effects/myEffect.yml" %} # ... archived: true ```]]> <![CDATA[Environments]]> `. State uses `.eventvisor/sets/`, generated code can be emitted per set, and the Catalog includes a set selector. ## Promote changes between environments Restrict allowed promotion directions in `eventvisor.config.js`: ```js {% path="eventvisor.config.js" %} module.exports = { sets: true, promotionFlows: [ { from: "development", to: "staging" }, { from: "staging", to: "production" }, ], }; ``` Preview first, then apply the reviewed plan: ```sh npx eventvisor promote --from=development --to=staging npx eventvisor promote --from=development --to=staging --apply --audit ``` See [Promotions](/docs/promotion/) for selection, conflict, protection, and rollback behaviour. ## When an attribute is enough You can model an environment as an ordinary attribute when all environments intentionally share the same definitions and datafile. Sets are safer when release lanes must remain independently buildable and deployable. For more testing patterns, including matrices and environment-specific routing expectations, initialize `test-environments`: ```sh {% title="Command" %} $ npx @eventvisor/cli init --project=test-environments ``` ## Related - [Sets](/docs/sets/) - [Promotions](/docs/promotion/)]]> <![CDATA[Events]]> <![CDATA[Handlers]]> console.log("Hello world!"); selector: body ``` ## Creating custom handlers To create a custom handler, you can create a new custom [module](/docs/modules) with at least the `handle` method implemented: ### Defining module ```ts {% path="your-app/custom.ts" %} export function createCustomModule() { return { name: "custom", handle: async ({ effectName, step }) => { const { params } = step; console.log("Custom handler called for effect:", effectName); }, }; }; ``` ### SDK setup And then set it up when initializing the SDK: ```js {% path="your-app/index.js" %} import { createEventvisor } from "@eventvisor/sdk"; import { createCustomModule } from "./custom"; const eventvisor = createEventvisor({ modules: [ createCustomModule(), ], }); ``` ### Handler usage Now from the [effects](/docs/effects), we can use the custom handler like this: ```yml {% path="effects/myEffect.yml" %} # ... steps: - handler: custom params: key: value ```]]> <![CDATA[JSON Schema]]> <![CDATA[Linting]]> <![CDATA[LLM documentation]]> <![CDATA[Lookups]]> { return "some value"; }, }; }; ``` ### SDK setup And then set it up when initializing the SDK: ```js {% path="your-app/index.js" %} import { createEventvisor } from "@eventvisor/sdk"; import { createCustomModule } from "./custom"; const eventvisor = createEventvisor({ modules: [ createCustomModule(), ], }); ``` ### Lookup usage Later in your definitions, you can use the custom lookup like this: ```yml conditions: - lookup: custom.myKey operator: equals value: myValue ```]]> <![CDATA[Migration guides]]> <![CDATA[Migrating from pre-release packages to v1]]> window.__cspNonce, }); ``` Only enable scripts when the datafile publishing path is trusted and protected. Review the [security guide](/docs/security/) and apply a restrictive Content Security Policy. --- ## React React hooks now use explicit Eventvisor names: {% row %} {% column %} ```js {% title="Before" %} import { useInstance, isReady, useEventvisor, } from "@eventvisor/react"; ``` {% /column %} {% column %} ```js {% title="After" %} import { useEventvisorInstance, useEventvisorReady, useEventvisor, } from "@eventvisor/react"; ``` {% /column %} {% /row %} `useEventvisorAttribute` and `useEventvisorAttributes` provide reactive attribute values. Hooks now throw a clear error outside `EventvisorProvider`, reset readiness when the provider instance changes, and return stable bound operations for an unchanged instance. --- ## Verification checklist Before deploying v1 datafiles: 1. Upgrade every consuming SDK and module package. 2. Rewrite direct multi-child `not` groups with an explicit `or` when preserving the 0.x meaning. 3. Review every sampling rule against the corrected percentage and range behaviour. 4. Add at least one Target to every project or Set. 5. Update datafile URLs from `eventvisor-tag-.json` to `eventvisor-.json`. 6. Remove `statesDirectoryPath` and obsolete test fields. 7. Update custom modules and React hook imports. 8. Run project linting and tests. 9. Build every Target and inspect the generated file list. 10. Exercise representative events with `simulate` and a staging application. ```sh {% title="Command" %} $ npx eventvisor lint $ npx eventvisor test $ npx eventvisor build ``` For Set projects, repeat focused checks for every release lane before promotion or deployment.]]> <![CDATA[Modules]]> { return "some value"; }, /** * Transport: used in destinations */ transport: async ({ payload, eventName, eventLevel, error, destinationName, }) => { // send the payload somewhere here... // if tracked event is an error, then `error` will be the error object }, /** * Handle: used in effects */ handle: async ({ effectName, effect, step }) => { const { params } = step; console.log("Custom handler called for effect:", effectName); }, /** * Persistence: used in attributes and effects */ readFromStorage: async ({ key }) => { return "some value"; }, writeToStorage: async ({ key, value }) => { // write value to the storage layer here... }, removeFromStorage: async ({ key }) => { // remove value from the storage layer here... }, }; } ``` You are advised to have a function that returns the module object, so that it enables others to customize the module further as needed. ### TypeScript usage You can make use of the `EventvisorModule` type for type safety: ```ts {% path="your-app/custom.ts" %} import type { EventvisorModule } from "@eventvisor/sdk"; export function createCustomModule(): EventvisorModule { return { name: "custom", // ... }; } ``` `setup(api)` receives revision and diagnostic helpers. Effect handlers also receive `api.track()`. Use that method for nested tracking so Eventvisor can detect effect cycles. Queueing modules should implement `flush()`. `await eventvisor.flush()` runs every module flush in parallel, and `close()` flushes before closing modules. ## Module setup Now we can register this module when initializing the SDK: ```js {% path="your-app/index.js" %} import { createEventvisor } from "@eventvisor/sdk"; import { createCustomModule } from "./custom"; const eventvisor = createEventvisor({ modules: [ createCustomModule(), ], }); ``` ## Usage examples ### Lookup Lookups can be performed in [conditions](/docs/conditions) and [transforms](/docs/transforms): ```yml conditions: - lookup: custom.myKey operator: equals value: myValue ``` ### Transport Transports can be used in [destinations](/docs/destinations): ```yml {% path="destinations/browser.yml" %} # ... transport: custom ``` ### Handler Handlers can be used in [effects](/docs/effects): ```yml {% path="effects/myEffect.yml" %} # ... steps: - handler: custom params: key: value ``` ### Persist Storage modules can be used to persist data across sessions: ```yml {% path="effects/myEffect.yml" %} # ... persist: custom ``` Learn more in [persistence](/docs/persistence) page.]]> <![CDATA[module-amplitude-browser]]> <![CDATA[module-beacon]]> <![CDATA[module-console]]> <![CDATA[module-datadog-browser]]> <![CDATA[module-ga4]]> <![CDATA[module-gtm]]> <![CDATA[module-http]]> <![CDATA[module-localstorage]]> <![CDATA[module-mixpanel-browser]]> <![CDATA[module-newrelic-browser]]> <![CDATA[module-pixel]]> window.__cspNonce, }), ] }); ``` Leave `allowScripts` disabled when only markup such as tracking images is needed. When scripts are disabled, script elements are ignored and a diagnostic is reported. When enabled, use a restrictive Content Security Policy and supply a nonce from trusted application code. See [Security](/docs/security/). ## Usage example In your [effect](/docs/effects) definition: ```yml {% path="effects/myEffect.yml" %} # ... steps: - handler: pixel params: snippet: | selector: body ``` ### Variables You can make use of [sources](/docs/sources) in pixel's snippet as variables: ```html ```]]> <![CDATA[module-segment-browser]]> <![CDATA[module-sentry-browser]]> <![CDATA[module-timestamp]]> <![CDATA[module-uuid]]> <![CDATA[Monorepo]]> <![CDATA[Parsers]]> TOML.parse(content), stringify: (content) => TOML.stringify(content), }, } ```]]> <![CDATA[Persistence]]> { return "some value"; }, writeToStorage: async ({ key, value }) => { // write value to the storage layer here... }, removeFromStorage: async ({ key }) => { // remove value from the storage layer here... }, }; }; ``` ### SDK setup And then set it up when initializing the SDK: ```js {% path="your-app/index.js" %} import { createEventvisor } from "@eventvisor/sdk"; import { createCustomModule } from "./custom"; const eventvisor = createEventvisor({ modules: [ createCustomModule(), ], }); ``` Because there's a storage layer involved now, and when SDK is first initialized it will attempt to read values if already persisted, you should wait until the SDK is ready before tracking any events or setting attributes: ```js await eventvisor.onReady(); ``` This is the only use case where the `.onReady()` method is expected to be utilized. Otherwise, SDKs are ready to be used as soon as they are initialized. ### Storage usage Now from the [effects](/docs/effects), we can use the custom storage layer like this: ```yml {% path="effects/myEffect.yml" %} # ... persist: custom ```]]> <![CDATA[Event pipeline]]> <![CDATA[Projects]]> <![CDATA[Promotions]]> <![CDATA[Quick start]]> res.json()); // initialize const eventvisor = createEventvisor({ datafile: datafile, modules: [ createConsoleModule(), ], }); ``` ### Set attributes Now that we have the SDK instance ready, we can start setting attributes: ```js await eventvisor.setAttribute("deviceId", "device-123"); await eventvisor.setAttribute("userId", "user-456"); ``` ### Track events Now the more interesting part for tracking events: ```js await eventvisor.track("pageView", { path: "/home", }); ``` Doing above will show us a warning in the console, because the payload of this event does not honour the schema that we defined for it earlier. We can try doing it the correct way now: ```js await eventvisor.track("pageView", { url: "https://www.yoursite.com/home", }); ``` Now you should see the tracked event in your application's console immediately. If you indeed see it, it means you have successfully set up Eventvisor and you are ready to start tracking your events. ## Next steps This was a very simple setup, but there's a lot more that you can do with Eventvisor. Several concepts that can help you make the most out of Eventvisor below: - [Testing](/docs/testing) declaratively - [Effects](/docs/effects) & [handlers](/docs/handlers) - Writing your own [modules](/docs/modules) - [Lookups](/docs/lookups) for enriching payloads - Multiple [environments](/docs/environments) - [Parsers](/docs/parsers) for going beyond YAML files]]> <![CDATA[Roadmap]]> <![CDATA[Sampling]]> <![CDATA[Reusable Schemas]]> <![CDATA[SDKs]]> <![CDATA[Browser SDK]]> <![CDATA[Java SDK]]> report( diagnostic.getCode(), diagnostic.getDetails() )) ); ``` Diagnostics use stable codes and cannot interrupt SDK behaviour. Error and fatal diagnostics also emit the `ERROR` SDK event. ## Updating datafiles Datafiles merge by default. Pass `true` to replace the current datafile. ```java eventvisor.setDatafile(nextDatafile).join(); eventvisor.setDatafile(nextDatafile, true).join(); ``` ## Lifecycle ```java eventvisor.flush().join(); eventvisor.close().join(); ``` Removing a module flushes it before closing it. Closing the instance flushes transports, closes modules, and removes event and diagnostic subscriptions. See the [Eventvisor Java repository](https://github.com/eventvisor/eventvisor-java) for installation details and the complete API.]]> <![CDATA[JavaScript SDK]]> response.json()); const eventvisor = createEventvisor({ datafile, initialAttributes: { deviceId: "device-123", }, }); await eventvisor.onReady(); ``` `createEventvisor()` is the runtime factory. Use the exported `Eventvisor` type when an instance must be passed through your application. ## Events ```js const tracked = await eventvisor.track("pageView", { url: "https://example.com/home", }); ``` The promise resolves to the final transformed event, or `null` when the event is rejected. Public SDK operations are processed in call order. This prevents a `setAttribute()` followed immediately by `track()` from racing, even when the application keeps both promises and awaits them together. Destination transports run in parallel. The promise waits for all selected attempts, but transport modules define whether an attempt is immediate delivery or queue acceptance. See the [event pipeline](/docs/pipeline/). ## Attributes ```js await eventvisor.setAttribute("userId", "user-123"); eventvisor.getAttributeValue("userId"); eventvisor.getAttributes(); eventvisor.isAttributeSet("userId"); await eventvisor.removeAttribute("userId"); ``` ## Modules Modules provide transports, handlers, lookups, and storage. ```js import { createConsoleModule } from "@eventvisor/module-console"; const eventvisor = createEventvisor({ datafile, modules: [createConsoleModule()], }); const removeModule = eventvisor.addModule({ name: "custom", setup(api) { api.reportDiagnostic({ level: "info", code: "custom_ready", message: "Custom module is ready", details: {}, }); }, async close() { // Release subscriptions and resources. }, }); await removeModule?.(); ``` Modules can also be removed by name with `await eventvisor.removeModule("custom")`. Duplicate module names are rejected and reported as diagnostics. ## Diagnostics Use diagnostics for structured SDK and module reports. ```js const eventvisor = createEventvisor({ datafile, logLevel: "warn", onDiagnostic(diagnostic) { sendToObservabilityPlatform(diagnostic); }, }); const unsubscribe = eventvisor.onDiagnostic((diagnostic) => { console.log(diagnostic.level, diagnostic.code, diagnostic.message); }); unsubscribe(); ``` Diagnostics contain `level`, `code`, `message`, `details`, and optional module or error information. Error diagnostics also trigger the SDK `error` event. See [Diagnostics](/docs/diagnostics/) for stable code families and monitoring guidance. ## Updating a datafile Datafiles merge by default. This is useful when an application loads more than one target or product area. ```js await eventvisor.setDatafile(additionalDatafile); ``` Pass `true` to replace the current datafile completely: ```js await eventvisor.setDatafile(nextDatafile, true); ``` Invalid JSON is reported with the message `Could not parse datafile` and does not replace the active datafile. ## Events from the SDK ```js const unsubscribe = eventvisor.on("datafile_set", ({ replaced }) => { console.log({ replaced }); }); ``` Available events are `ready`, `datafile_set`, `attribute_set`, `attribute_removed`, `event_tracked`, and `error`. ## Child instances `spawn()` creates an independent instance using the current datafile. Pass child-specific modules, diagnostics, and attributes as needed. ```js const child = eventvisor.spawn({ initialAttributes: { application: "checkout" }, }); ``` ## Closing ```js await eventvisor.flush(); await child.close(); await eventvisor.close(); ``` `flush()` asks all modules to attempt queued work. Closing flushes modules, then releases module resources, diagnostic subscriptions, and SDK event listeners. ## Datafile metadata ```js eventvisor.getRevision(); eventvisor.getSchemaVersion(); ```]]> <![CDATA[Node.js SDK]]> <![CDATA[React SDK]]> , ); ``` ## Readiness ```jsx import { useEventvisorReady } from "@eventvisor/react"; function App() { const ready = useEventvisorReady(); return ready ? : ; } ``` ## SDK operations ```jsx import { useEventvisor } from "@eventvisor/react"; function CheckoutButton() { const { track, setAttribute, removeAttribute, getAttributeValue, isAttributeSet } = useEventvisor(); return ( ); } ``` The returned methods are bound to the active instance and remain stable until the provider instance changes. ## Direct instance access ```jsx import { useEventvisorInstance } from "@eventvisor/react"; const eventvisor = useEventvisorInstance(); ``` ## Reactive attributes Read one attribute and re-render when it changes: ```jsx import { useEventvisorAttribute } from "@eventvisor/react"; const userId = useEventvisorAttribute("userId"); ``` Read the complete reactive attribute map: ```jsx import { useEventvisorAttributes } from "@eventvisor/react"; const { userId, plan } = useEventvisorAttributes(); ``` Both hooks update after attributes are set or removed and after a datafile update changes attribute state. Hooks throw a clear error when used outside `EventvisorProvider`.]]> <![CDATA[React Native SDK]]> <![CDATA[Security]]> window.__cspNonce, }); ``` Use a restrictive Content Security Policy. The configured nonce replaces any nonce supplied by the datafile snippet. Treat compromise of the datafile origin as a potential application security incident. ## Delivery and privacy Transport modules receive governed event payloads and selected attributes. Review each transport's endpoint, authentication, retention, regional routing, and privacy controls. Eventvisor does not make a third party destination compliant by itself. Eventvisor rejects unsafe source and transform paths that could traverse object prototypes. Transformed transport payloads must also remain JSON compatible. Functions, symbols, non-finite numbers, circular values, and other values that cannot be transported safely are rejected with a diagnostic. HTTP and Beacon queues keep snapshots of accepted payloads, so later application mutations cannot change an event that is waiting for delivery. Queue limits should be finite and chosen for the memory available on the target platform. Report vulnerabilities privately through GitHub Security Advisories instead of a public issue.]]> <![CDATA[Sets]]> `. Generated datafiles are written below `datafiles/sets/`. The Catalog exports each set below `out/sets/` and provides a set selector in the navigation. ## Sets and promotions Preview changes between release lanes, then apply the reviewed plan: ```sh npx eventvisor promote --from development --to staging npx eventvisor promote --from development --to staging --apply --audit ``` See [Promotions](/docs/promotion/) for flow restrictions, filters, dependency handling, and conflict policies. ## Sets as environments Sets are a good fit when development, staging, and production must have independent definitions and generated datafiles. The same event key can have different validation, transforms, routing, or tests in every release lane. See the complete [`project-environments`](https://github.com/eventvisor/eventvisor/tree/main/projects/project-environments) and [`project-test-environments`](https://github.com/eventvisor/eventvisor/tree/main/projects/project-test-environments) references. ## Related - [Targets](/docs/targets/) - [Environments](/docs/environments/) - [Promotions](/docs/promotion/) - [Testing](/docs/testing/)]]> <![CDATA[Skills for AI agents]]> `/eventvisor create an order_completed event with an order ID, total, currency, and item count` ### Reuse a Schema > `/eventvisor create a reusable customer Schema and use it in the signed_up and checkout_completed events` ### Route events by consent > `/eventvisor route checkout events to the warehouse only when analytics consent is true` ### Add sampling > `/eventvisor sample debug events at 5 percent in production while keeping all of them in development` ### Add validation behaviour > `/eventvisor validate purchase payloads and quarantine invalid events for investigation` ### Generate matrix tests > `/eventvisor add matrix tests for mobile and desktop checkout events in NL and DE` ### Debug delivery > `/eventvisor explain why page_view is not reaching the Segment destination` ### Find unused definitions > `/eventvisor find unused attributes, Schemas, and destinations in this project` ### Work with Targets > `/eventvisor create a production Target for the checkout team and build its datafile` ### Promote between Sets > `/eventvisor preview and promote the checkout definitions from staging to production` ### Explore the Catalog > `/eventvisor open the Catalog and help me inspect the order_completed pipeline` ### Integrate an application > `/eventvisor integrate Eventvisor into this React application and report diagnostics to our monitoring service` ### Create a module > `/eventvisor create a custom destination module that batches events and flushes them when the application closes` ### Upgrade to v1 > `/eventvisor review this project for Eventvisor v1 migration changes and update it safely` ## Updating the skill Run the following command to update an installed skill: ```sh {% title="Command" %} $ npx skills update eventvisor ``` The skill source is available in the [Eventvisor repository](https://github.com/eventvisor/eventvisor/tree/main/skills/eventvisor).]]> <![CDATA[Sources]]> ``` Learn more in [lookups](/docs/lookups) page. ## Multiple source values Direct source properties can accept arrays when a transform or sampling key needs several values: ```yml payload: - product.id - product.category ``` The SDK resolves them in declaration order. Plain `source` paths such as `attributes.userId` remain available when that is more concise.]]> <![CDATA[Tags]]> <![CDATA[Targets]]> .json` for each Target. Create target definitions in `targets/`: ```yml # targets/checkout.yml description: Checkout web application tag: web includeEvents: checkout.* excludeEvents: checkout.internal.* includeAttributes: - userId - sessionId includeDestinations: "*" includeEffects: checkout.* pretty: true stringify: true revisionFromHash: true ``` The include and exclude fields accept one pattern, an array of patterns, or `"*"`. A single `*` inside a pattern provides glob-like matching. Targets support `includeEvents`, `includeAttributes`, `includeDestinations`, and `includeEffects`, with a corresponding `exclude` field for each entity type. ## Tags and targets `tag` selects entities carrying one tag. `tags` can express OR or AND matching: ```yml tags: and: [web, checkout] ``` Target selectors combine with tag selectors using AND semantics. Eventvisor also includes runtime dependencies. For example, an included effect brings in the events and attributes that can trigger it. Included events retain their `requiredAttributes` and definitions referenced through direct or generic source paths such as `attribute: userId` and `source: attributes.userId`. Collection sources such as `source: attributes` retain the complete referenced collection. An explicit exclusion cannot remove a dependency required by an included definition. The build fails and lists every conflicting dependency, so a Target cannot produce a datafile that is known to be incomplete. ## Build ```sh npx eventvisor build --target checkout npx eventvisor build --target checkout --target account ``` The builder retains referenced attributes, destination overrides, quarantine destinations, and triggered effects. Dependencies used by those definitions are resolved transitively. Reusable Schemas are build dependencies rather than Target selectors. Any Schema referenced by a selected event or attribute is resolved transitively and inlined into the generated datafile. `pretty` controls formatting for the target datafile. `stringify` controls compact condition stringification. `revisionFromHash` produces a stable content-derived revision.]]> <![CDATA[Testing]]> <![CDATA[Transforms]]> <![CDATA[Transports]]> { // send the payload somewhere here... // metadata also includes the datafile revision and optional validation details // if tracked event is an error, then `error` will be the error object }, }; }; ``` Transport options include `revision`. When `deliverWithWarning` is active they also include `validation.valid: false` and validation errors. Quarantine transport calls receive a stable envelope as their payload. ### SDK setup And then set it up when initializing the SDK: ```js {% path="your-app/index.js" %} import { createEventvisor } from "@eventvisor/sdk"; import { createCustomModule } from "./custom"; const eventvisor = createEventvisor({ modules: [ createCustomModule(), ], }); ``` ### Transport usage Later in your destination definition, you can use the custom transport like this: ```yml {% path="destinations/browser.yml" %} # ... transport: custom ```]]> <![CDATA[Use cases]]> <![CDATA[Audit history]]> <![CDATA[Data enrichment]]> <![CDATA[Deprecating events]]> <![CDATA[Filtering events]]> <![CDATA[Governance]]> <![CDATA[Marketing pixels]]> ` tag that is injected into the page, or - **Script tag**: custom JavaScript code via ` selector: body ``` We just defined a new effect that: - gets triggered every time we track `page_view` event, and - proceeds to apply a handler called `pixel`, which contains our snippet to inject into the page ## Module setup To make sure our application can honour this effect, we need to set it up with the right [module](/docs/modules): ```{% title="Command" %} $ npm install --save @eventvisor/module-pixel ``` And initializing the SDK with it: ```js import { createEventvisor } from "@eventvisor/sdk"; import { createPixelModule } from "@eventvisor/module-pixel"; const eventvisor = createEventvisor({ datafile: { ... }, modules: [ createPixelModule(), ], }); ``` Learn more in [module-pixel](/docs/modules/pixel) page. ## Avoiding multiple injections If you are building a single-page application (SPA), it is possible you will be tracking the `page_view` event multiple times throughout the lifecycle of your SDK instance. This will lead to multiple injections of the marketing pixel into the page, which we may want avoid. Effect's internal state can come to the rescue here: ```yml {% path="effects/marketing-pixel.yml" %} description: Marketing pixel tags: - web on: event_tracked: - page_view # we start with an initial internal state state: injected: false # we only proceed with the steps, if the internal state is false conditions: - state: injected operator: equals value: false steps: - handler: pixel params: snippet: | selector: body # upon successful handling of pixel module, # we set the internal state to true - transforms: - type: set target: injected value: true ``` This way, we can avoid the steps being executed multiple times, when the effect is triggered multiple times. ## Remembering upon page reload If you are interested in triggering it only once for the user session (even upon full page reload), you can use the [persistence](/docs/persistence) layer to achieve that: ```yml {% path="effects/marketing-pixel.yml" %} # ... persist: localstorage ``` And make sure your application has [module-localstorage](/docs/modules/localstorage) installed: ```{% title="Command" %} $ npm install --save @eventvisor/module-localstorage ``` And set up when initializing the SDK: ```js import { createEventvisor } from "@eventvisor/sdk"; import { createPixelModule } from "@eventvisor/module-pixel"; import { createLocalStorageModule } from "@eventvisor/module-localstorage"; const eventvisor = createEventvisor({ datafile: { ... }, modules: [ createPixelModule(), createLocalStorageModule(), ], }); ``` This will make sure the effect's internal state is persisted in browser's localStorage, and if the script was injected already once before, it will not be injected again. ## Usage of variables Snippets can also make use of variables, which can reference [sources](/docs/sources), like: ```html ``` The `page_view` event's `url` property will be available as `{{ payload.url }}` in the snippet.]]> <![CDATA[Microfrontends architecture]]> res.json()) const eventvisor = createEventvisor({ datafile: datafile, }); ``` ## Benefits We have seen how we can use Eventvisor to manage all our event definitions and their routing configurations in a microfrontends architecture in a single place declaratively, even if those events overlap and are used in multiple microfrontends together. The freedom and flexibility that microfrontends architecture brings in is great, but it also comes with its own set of challenges. Eventvisor can help you manage your events in a microfrontends architecture bringing all parties together with a strong reviews and approval workflow, and make sure your events are consistent across all your microfrontends for your users.]]> <![CDATA[Migrating vendors]]> <![CDATA[Ownership]]> <![CDATA[Remote configuration]]> res.json()); const eventvisor = createEventvisor({ datafile: datafile, }); ``` Learn more in [JavaScript SDK](/docs/sdks/javascript) page. ## Refreshing datafile The idea of remotely controlling your applications mean also updating the datafile while your application is already running. Eventvisor SDK can help you here via its `setDatafile()` method: ```js // fetch again const latestDatafile = await fetch(DATAFILE_URL).then(res => res.json()); eventvisor.setDatafile(latestDatafile); ``` ## When to refresh Your datafile refreshing strategy can be achieved in three different ways: - periodically, like every 15 minutes or so - manually fetching and setting datafile after a specific user activity, or - listening to a webhook or WebSocket event in your application, and refreshing it on demand ## Modules set up It is important to understand that if your latest configuration is making use of any new [modules](/docs/modules), then your application is expected to be set up properly early on to honour them.]]> <![CDATA[Routing]]> <![CDATA[Saving ingestion costs]]> <![CDATA[Tracking errors]]> { if (error) { // transport the `error` object here return; } // transport the `payload` here if not an error }, }; } ``` Learn more in [modules](/docs/modules) page. ## Benefits Because errors are now tracked via Eventvisor, you can benefit from: - [Conditional routing](/docs/use-cases/routing) - [Filtering](/docs/use-cases/filtering) - [Sampling](/docs/sampling) - [Transformations](/docs/transforms) ...and more out of the box.]]> <![CDATA[Validation warnings]]>