Skip to content

Event Schema Validation ​

Synode validates generated events against Zod schemas before they reach the output adapter. This catches data quality issues during generation rather than downstream.

Defining Event Schemas ​

Use defineEventSchema to create a Zod object schema for an event's payload.

ts
import { z } from 'zod';
import { defineEventSchema } from '@synode/core';

const pageViewSchema = defineEventSchema({
  url: z.string().url(),
  title: z.string().min(1),
  referrer: z.string().optional(),
});

const purchaseSchema = defineEventSchema({
  orderId: z.string(),
  total: z.number().positive(),
  currency: z.enum(['USD', 'EUR', 'GBP']),
  items: z.array(
    z.object({
      productId: z.string(),
      quantity: z.number().int().positive(),
    }),
  ),
});

defineEventSchema is a thin wrapper around z.object(). You can also pass any z.ZodType directly.

Configuring Validation ​

Pass an EventSchemaConfig to generate via the eventSchema option.

Single Schema (All Events) ​

Apply one schema to every event:

ts
import { generate } from '@synode/core';

await generate(journey, {
  users: 1000,
  eventSchema: {
    schema: pageViewSchema,
    mode: 'strict',
  },
});

Per-Event-Name Schema Map ​

Map event names to their specific schemas. Events not in the map pass through without validation.

ts
await generate(journeys, {
  users: 1000,
  eventSchema: {
    schema: {
      page_view: pageViewSchema,
      purchase: purchaseSchema,
      add_to_cart: addToCartSchema,
    },
    mode: 'warn',
  },
});

Validation Modes ​

ModeOn FailureEffect
'strict'Throws SynodeValidationErrorGeneration stops immediately
'warn'Logs summary, keeps eventEvent reaches adapter, failure recorded
'skip'Drops event silentlyEvent never reaches adapter, failure recorded

Default mode is 'strict'.

  • strict: Throws SynodeValidationError on first invalid event. Best for development.
  • warn: Keeps all events, prints summary to stderr. Best for staging/CI.
  • skip: Drops invalid events silently. Best for producing clean output.

SynodeValidationError ​

Thrown in strict mode. Contains the failed event and structured validation issues.

ts
import { SynodeValidationError } from '@synode/core';

try {
  await generate(journey, {
    users: 100,
    eventSchema: { schema: purchaseSchema, mode: 'strict' },
  });
} catch (err) {
  if (err instanceof SynodeValidationError) {
    console.error('Event:', err.event.name);
    console.error('Issues:', err.issues);
    // issues: [{ path: ['total'], message: 'Expected number, received string', code: 'invalid_type' }]
  }
}

ValidationIssue Shape ​

ts
interface ValidationIssue {
  path: (string | number)[]; // field path in the payload
  message: string; // human-readable error
  code: string; // Zod error code
}

Telemetry Integration ​

When both debug: true and eventSchema are configured, the validation summary is included in the telemetry report.

ts
await generate(journey, {
  users: 5000,
  eventSchema: {
    schema: { page_view: pageViewSchema, purchase: purchaseSchema },
    mode: 'warn',
  },
  debug: true,
  telemetryPath: './telemetry.json',
});

The telemetry report includes:

json
{
  "validation": {
    "eventsValidated": 5000,
    "eventsValid": 4850,
    "eventsInvalid": 150,
    "validationErrors": [
      { "eventName": "purchase", "path": "total", "message": "Expected number, received string" }
    ]
  }
}

Validation errors are capped at 50 entries to prevent unbounded memory growth.