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.
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:
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.
await generate(journeys, {
users: 1000,
eventSchema: {
schema: {
page_view: pageViewSchema,
purchase: purchaseSchema,
add_to_cart: addToCartSchema,
},
mode: 'warn',
},
});Validation Modes
| Mode | On Failure | Effect |
|---|---|---|
'strict' | Throws SynodeValidationError | Generation stops immediately |
'warn' | Logs summary, keeps event | Event reaches adapter, failure recorded |
'skip' | Drops event silently | Event never reaches adapter, failure recorded |
Default mode is 'strict'.
- strict: Throws
SynodeValidationErroron 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.
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
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.
await generate(journey, {
users: 5000,
eventSchema: {
schema: { page_view: pageViewSchema, purchase: purchaseSchema },
mode: 'warn',
},
debug: true,
telemetryPath: './telemetry.json',
});The telemetry report includes:
{
"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.
