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
| Field | Type | Description | Example |
|---|---|---|---|
id | string (UUID) | Unique event identifier | "123e4567-e89b-12d3-a456-426614174000" |
version | 'v1' (literal) | Schema version | 'v1' ⚠️ Not 1 |
type | string | Event type descriptor | 'note.created', 'entity.updated' |
data | Record<string, unknown> | Event payload | { content: "..." } |
userId | string | User who triggered event | "user-123" |
source | EventSource (enum) | Event origin | 'api' |
timestamp | Date | When event occurred | new Date() |
Optional Fields
| Field | Type | Description | Example |
|---|---|---|---|
subjectId | string (UUID) | Related aggregate ID | "entity-456" |
correlationId | string (UUID) | Request correlation | "req-789" |
causationId | string (UUID) | Causing event ID | "event-012" |
requestId | string (UUID) | HTTP request ID | "req-345" |
EventSource Enum
The source field must be one of these values:
| Value | Description | When to Use |
|---|---|---|
'api' | From API requests | HTTP/REST/tRPC endpoints |
'automation' | From automated processes | Scheduled tasks, triggers |
'sync' | From synchronization | Data sync operations |
'migration' | From data migration | Schema migrations, imports |
'system' | From system operations | Internal 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.mdfor common pitfalls
Need Help?
Common Issues:
- Version as number → Use
'v1'(string literal) - Invalid source → Check enum values above
- Missing fields → Ensure all required fields present
- Date serialization → Use EventRepository or convert to ISO string
Further Reading:
- Event Sourcing patterns
- CQRS architecture
- Database testing guide