Architecture Overview
WebSocket Connection
Client Connection Lifecycle
Server WebSocket Handler
apps/server/src/services/socket-service.ts
Connection Authentication
- Client requests socket ID: HTTP POST to
/api/client/socket/init - Server generates socket ID: Temporary ID stored in Redis (60s TTL)
- Client connects: WebSocket connection to
/api/client/socket?id={socketId} - Server validates: Looks up context from Redis, authenticates
- Connection established: Socket bound to user’s device ID
Rate Limiting
WebSocket connections are rate-limited to prevent abuse:Synchronization Protocol
Message Types
The sync protocol defines several message types:Input Message (Client → Server)
Output Message (Server → Client)
Synchronizer Types
Brainbox supports multiple data streams, each with its own synchronizer:| Type | Description | Location (Client) | Location (Server) |
|---|---|---|---|
nodes.updates | Node CRUD operations | packages/client/src/handlers/ | apps/server/src/synchronizers/node-updates.ts |
document.updates | Rich text changes (Yjs) | packages/client/src/handlers/ | apps/server/src/synchronizers/document-updates.ts |
collaborations | Workspace memberships | packages/client/src/handlers/ | apps/server/src/synchronizers/collaborations.ts |
node.reactions | Emoji reactions | packages/client/src/handlers/ | apps/server/src/synchronizers/node-reactions.ts |
node.interactions | View/edit presence | packages/client/src/handlers/ | apps/server/src/synchronizers/node-interactions.ts |
node.tombstones | Soft deletes | packages/client/src/handlers/ | apps/server/src/synchronizers/node-tombstones.ts |
users | User profile updates | packages/client/src/handlers/ | apps/server/src/synchronizers/users.ts |
Client-Side: Handlers
Client handlers process incoming sync messages and update the local database. Location:packages/client/src/handlers/
Handler Implementation Pattern
Mediator Pattern
TheMediator class routes WebSocket messages to appropriate handlers:
packages/client/src/handlers/mediator.ts:1
Server-Side: Synchronizers
Server synchronizers fetch data from PostgreSQL and broadcast to connected clients. Location:apps/server/src/synchronizers/
Base Synchronizer
All synchronizers extendBaseSynchronizer:
apps/server/src/synchronizers/base.ts:7
Example: Node Updates Synchronizer
apps/server/src/synchronizers/node-updates.ts:15
Event-Driven Broadcasting
Synchronizers react to server-side events:Synchronizer Registration
Sync Flow Examples
Example 1: Creating a Page
Example 2: Editing a Document
Example 3: Offline → Online Sync
Cursor-Based Synchronization
How Cursors Work
Cursors track sync progress for each data stream:Incremental Sync
Server only returns data newer than cursor:Cursor Updates
Client updates cursor after processing each batch:Performance Optimization
Batching
Updates are sent in batches to reduce message overhead:Debouncing
Client debounces rapid updates to avoid flooding the server:Connection Pooling
Server reuses PostgreSQL connections:Error Handling
Connection Errors
Sync Errors
Conflict Resolution
When server version conflicts with local:- CRDT data (documents): Yjs automatically merges
- Metadata (node attributes): Server version wins
- User notified: Show conflict indicator in UI
Monitoring and Debugging
Debug Logging
Metrics
Key metrics to monitor:- Active connections: Number of WebSocket connections
- Messages/second: Sync throughput
- Sync lag: Time between local write and server confirmation
- Queue depth: Pending mutations per client
Next Steps
- CRDT Implementation - How Yjs handles conflicts
- Local-First Architecture - Client database design
- Monorepo Structure - Code organization