SynapSynapDocs
Guides

SynapEvent Schema Reference

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