Skip to main content

SynapEvent Schema Reference

Version: v1
Last Updated: 2025-12-04


Overview

All events in the Synap system must conform to the SynapEvent schema. This schema is defined using Zod and provides runtime validation for all event data.

Location: packages/types/src/synap-event.ts


Required Fields

FieldTypeDescriptionExample
idstring (UUID)Unique event identifier"123e4567-e89b-12d3-a456-426614174000"
version'v1' (literal)Schema version'v1' ⚠️ Not 1
typestringEvent type descriptor'note.created', 'entity.updated'
dataRecord<string, unknown>Event payload{ content: "..." }
userIdstringUser who triggered event"user-123"
sourceEventSource (enum)Event origin'api'
timestampDateWhen event occurrednew Date()

Optional Fields

FieldTypeDescriptionExample
subjectIdstring (UUID)Related aggregate ID"entity-456"
correlationIdstring (UUID)Request correlation"req-789"
causationIdstring (UUID)Causing event ID"event-012"
requestIdstring (UUID)HTTP request ID"req-345"

EventSource Enum

The source field must be one of these values:

ValueDescriptionWhen to Use
'api'From API requestsHTTP/REST/tRPC endpoints
'automation'From automated processesScheduled tasks, triggers
'sync'From synchronizationData sync operations
'migration'From data migrationSchema migrations, imports
'system'From system operationsInternal system events

Generated Event Pattern

All table events follow the {table}.{action}.{modifier} pattern:

// Pattern: table.action.modifier
GeneratedEventTypes.entities['create.requested']
GeneratedEventTypes.entities['create.validated']
GeneratedEventTypes.entities['update.requested']
GeneratedEventTypes.entities['update.validated']
GeneratedEventTypes.entities['delete.requested']
GeneratedEventTypes.entities['delete.validated']

// Same pattern for all tables:
GeneratedEventTypes.documents['create.requested']
GeneratedEventTypes.chatThreads['create.requested']
GeneratedEventTypes.conversationMessages['create.requested']
// ... and so on for all 9 core tables

See: Event Types Catalog for all 55 event types


Creating Events

Basic Example

import type { SynapEvent } from '@synap/types';
import { GeneratedEventTypes } from '@synap/types';

const event: SynapEvent = {
id: crypto.randomUUID(),
version: 'v1',
type: GeneratedEventTypes.entities['create.requested'],
userId: 'user-123',
data: {
type: 'note',
title: 'Meeting Notes',
content: 'Q1 roadmap discussion...',
},
source: 'api',
timestamp: new Date(),
};

With Optional Fields

const event: SynapEvent = {
id: crypto.randomUUID(),
version: 'v1',
type: 'entity.updated',
userId: 'user-123',
data: { changes: { title: 'New Title' } },
source: 'api',
timestamp: new Date(),
// Optional fields
subjectId: 'entity-456',
correlationId: 'req-789',
requestId: 'req-345',
};

Common Mistakes

❌ Wrong: Number Version

const event = {
version: 1, // ❌ Type error!
// ...
};

✅ Correct: String Literal

const event: SynapEvent = {
version: 'v1', // ✅ Correct
// ...
};

❌ Wrong: Invalid Source

const event = {
source: 'user', // ❌ Not in enum!
source: 'frontend', // ❌ Not valid!
// ...
};

✅ Correct: Valid Enum Value

const event: SynapEvent = {
source: 'api', // ✅ Valid
source: 'automation', // ✅ Valid
// ...
};

❌ Wrong: Missing Required Fields

const event = {
id: crypto.randomUUID(),
type: 'note.created',
// ❌ Missing: version, userId, data, source, timestamp
};

✅ Correct: All Required Fields

const event: SynapEvent = {
id: crypto.randomUUID(),
version: 'v1',
type: 'note.created',
userId: 'user-123',
data: { content: 'Note' },
source: 'api',
timestamp: new Date(),
};

Validation

Events are validated at runtime using Zod:

import { SynapEventSchema } from '@synap/types';

// Validate event
const result = SynapEventSchema.safeParse(event);

if (!result.success) {
console.error('Invalid event:', result.error);
} else {
console.log('Valid event:', result.data);
}

Storage Considerations

Date Serialization

When storing events in PostgreSQL via postgres.js:

// ❌ Wrong: Date object directly
await sql`INSERT INTO events (...) VALUES (${event.timestamp})`;

// ✅ Correct: Convert to ISO string
await sql`INSERT INTO events (...) VALUES (${event.timestamp.toISOString()})`;

EventRepository Usage

The EventRepository handles serialization automatically:

import { eventRepository } from '@synap/database';

// ✅ EventRepository handles Date conversion
await eventRepository.append(event);

Testing Events

Creating Test Events

import type { SynapEvent } from '@synap/types';

const createTestEvent = (userId: string): SynapEvent => ({
id: crypto.randomUUID(),
version: 'v1',
type: 'note.created',
userId,
data: { content: 'Test content' },
source: 'api', // Always use 'api' in tests
timestamp: new Date(),
});

// Usage in tests
const event = createTestEvent('test-user-123');

Type Safety

TypeScript provides full type safety:

import type { SynapEvent } from '@synap/types';

function processEvent(event: SynapEvent) {
// TypeScript knows all fields
console.log(event.id); // ✅ string
console.log(event.version); // ✅ 'v1'
console.log(event.source); // ✅ EventSource enum
console.log(event.data); // ✅ Record<string, unknown>
}

Schema Definition

Full Zod schema (for reference):

export const SynapEventSchema = z.object({
id: z.string().uuid(),
version: z.literal('v1'),
type: z.string(),
data: z.record(z.unknown()),
userId: z.string(),
source: z.enum(['api', 'automation', 'sync', 'migration', 'system']),
timestamp: z.date(),
subjectId: z.string().uuid().optional(),
correlationId: z.string().uuid().optional(),
causationId: z.string().uuid().optional(),
requestId: z.string().uuid().optional(),
});

export type SynapEvent = z.infer<typeof SynapEventSchema>;

Best Practices

1. Always Use Type Annotation

const event: SynapEvent = { /* ... */ };  // ✅ Catches errors early

2. Generate UUIDs Properly

id: crypto.randomUUID()  // ✅ Browser-safe
id: uuid() // ✅ If using uuid package

3. Use Current Timestamp

timestamp: new Date()    // ✅ Current time

4. Meaningful Event Types

type: 'note.created'     // ✅ Clear action
type: 'created' // ❌ Too vague

5. Structured Data Payload

data: {                  // ✅ Structured
content: '...',
metadata: { ... }
}

data: { raw: '...' } // ❌ Unstructured

References

  • Schema Definition: packages/types/src/synap-event.ts
  • Repository: packages/database/src/repositories/event-repository.ts
  • Tests: packages/database/src/__tests__/event-repository.test.ts
  • Error Report: See error_resolution_report.md for common pitfalls

Need Help?

Common Issues:

  1. Version as number → Use 'v1' (string literal)
  2. Invalid source → Check enum values above
  3. Missing fields → Ensure all required fields present
  4. Date serialization → Use EventRepository or convert to ISO string

Further Reading:

  • Event Sourcing patterns
  • CQRS architecture
  • Database testing guide