Aller au contenu principal

Event Flow

Complete end-to-end flow from user action to frontend update


Overview

This diagram shows the complete production event flow in Synap, from user clicking a button to real-time updates across all connected clients.


Complete Flow Diagram


Step-by-Step Breakdown

Phase 1: User Intent (< 50ms)

User clicks "Create Note" in UI

// Frontend
await client.entities.create({
type: 'note',
title: 'Meeting Notes'
});

// Returns immediately with:
{ eventId: 'evt_abc123' }

What happens:

  1. tRPC validates authentication
  2. publishEvent() dual-writes:
    • TimescaleDB ← Permanent audit trail
    • Inngest ← Trigger workers
  3. Returns to user (non-blocking)

Phase 2: Permission Check (< 100ms)

permissionValidator worker processes event

// Automatic - triggered by Inngest
if (user === owner && action === 'create') {
await publishEvent({
type: 'entities.create.approved',
data: {...}
});
}

Outcomes:

  • Approved → Emit .approved event
  • Rejected → Emit .rejected event (stops here)
  • ⏸️ Pending → Store for user approval (AI proposals)

Phase 3: Execution (< 200ms)

entitiesWorker creates the entity

// Listens to .approved events
await db.insert(entities).values({
id: entityId,
userId,
type: 'note',
title: 'Meeting Notes',
...
});

await publishEvent({
type: 'entities.create.validated',
data: { entityId }
});

// NEW: Real-time update
await fetch('http://localhost:3001/bridge/emit', {
method: 'POST',
body: JSON.stringify({
event: 'entity:created',
workspaceId: 'workspace-123',
data: { entityId, title: 'Meeting Notes' }
})
});

Phase 4: Real-Time Update (< 50ms)

Socket.IO bridge broadcasts to all clients

// Socket.IO bridge
io.of('/presence')
.to(`workspace:${workspaceId}`)
.emit('entity:created', {
entityId,
title: 'Meeting Notes',
userId
});

All connected clients receive update:

  • TanStack Query cache invalidates
  • UI re-renders with new entity
  • Users see change instantly

Total Latency

PhaseOperationTime
1API → publishEvent → Return~50ms
2Permission validation~100ms
3DB operation + validated event~200ms
4Real-time broadcast~50ms
TotalUser click → All clients updated~400ms

Note: User sees optimistic update immediately (~50ms), then confirmation after validation (~400ms).


Fast-Path Optimization

Not all operations require the full 3-phase validation flow. Synap uses ValidationPolicy to route events intelligently:

When to Use Fast-Path

Operations that are:

  • High-frequency: Chat messages, view tracking
  • User-owned: Starring/pinning entities
  • Low-risk: Thread metadata updates
  • Reversible: Actions that can be easily undone

Fast-path events skip GlobalValidator and go straight to execution:

User Action → API → .validated Event → Executor → .completed Event

Total time: ~50-100ms (4x faster!)

When to Require Validation

Operations that are:

  • Data creation: New entities, documents
  • Deletions: Prevent accidental loss
  • AI operations: Agent creation, enrichments
  • Sensitive changes: Permission updates, workspace settings

Standard flow includes permission check:

User Action → API → .requested Event → GlobalValidator → .validated Event → Executor → .completed Event

Total time: ~200-500ms

Configuration

Workspace owners can customize which operations use fast-path:

// Example: Require approval for all chat messages
await client.workspaces.updateSettings({
workspaceId: "ws-123",
settings: {
validationRules: {
conversation_message: {
create: true, // Override default (was fast-path)
update: true,
delete: true
}
}
}
});

See Validation Policy for complete details on configuration and decision logic.


Event Audit Trail

After this flow completes, TimescaleDB contains:

SELECT type, timestamp, user_id, data
FROM events_timescale
WHERE subject_id = 'note_123'
ORDER BY timestamp;

Results:

entities.create.requested  | 2024-12-26 14:45:00.100 | alice | {...}
entities.create.approved | 2024-12-26 14:45:00.200 | alice | {...}
entities.create.validated | 2024-12-26 14:45:00.400 | alice | {...}

Complete history preserved forever


Error Handling

If Permission Check Fails

User → tRPC → publishEvent() → Inngest

permissionValidator

NOT OWNER → REJECT

No .approved event

Worker never triggers

Entity not created ✅

If Inngest Fails

publishEvent() tries:
1. Write to TimescaleDB ✅
2. Send to Inngest ❌ (fails)
3. Mark event as inngest_pending: true
4. Background job retries later

Event is never lost, even if workers are down.


Comparison with Traditional

AspectTraditional APISynap Event Flow
Permission CheckIn API routeIn worker
Audit TrailManual loggingAutomatic (events)
Real-time UpdatesPolling/webhooksSocket.IO
AI ApprovalHard to implementBuilt-in
Fault TolerancePartial state riskEvent log + retries
Latency~100ms~400ms (with audit)

Next Steps