Real-time Bridge Architecture
Understand the left/middle/right bridge spans and the event contracts that power the replicator.
Real-time Bridge Architecture
┌─────────────────────┐ websocket feed ┌─────────────────────┐
│ Left Bridge │ (Convex subscription + │ Right Bridge │
│ Entity Auth Core │────── versioned events ─────▶│ Customer Replicator│
│ (Convex mutations) │ │ + Local DB Adapter │
└─────────────────────┘◀──── applyExternalMutation ──└─────────────────────┘
▲ │
│ ▼
Convex actions / queries Customer application + DB
Data flows
Outbound (Entity Auth → Customer DB)
- Convex mutation writes canonical data and calls
appendWorkspaceEvent. - Event stored in
workspace_eventswith{ workspaceTenantId, version, payload }. - Replicator fetches ordered events via websocket subscription.
- Adapter applies event inside customer DB and advances the cursor.
Inbound (Customer DB → Entity Auth)
- Local change triggers call to
applyExternalMutationwith{ eventType, payload, dedupeKey }. - Convex action authenticates, validates, and invokes the canonical mutation.
- Mutation emits the same event path; all replicas observe convergence.
Event envelope
export type WorkspaceEvent = {
workspaceTenantId: string;
version: number;
entityKind: string;
entityId: string;
eventType: string;
payload: Json;
source: {
mutation: string;
actorId: string | null;
origin: "entity-auth" | "customer-replicator";
};
emittedAt: number;
traceId?: string;
schemaVersion?: number;
};
Payload schemas live in docs/internal/workspace-events.md and are mirrored inside the replicator adapters.
Guarantees
- Ordering: lamport-style
versionper workspace; resume viaafterVersion. - Idempotency: adapters upsert/delete with version checks.
- Replay: offset store persists last version and supports catch-up.
- Conflict resolution: Entity Auth stays authoritative; inbound mutations reuse canonical validation.
Replicator runtime internals
runBridge(options)orchestrates subscription polling, adapter application, and cursor updates.adapters/directory exposes helpers likecreatePrismaAdapter()andmemoryAdapter().storage/sqlite.tspersists offsets by default; implement custom stores as needed.- Lifecycle hooks (
onEvent,onBatchProcessed,onHeartbeat,onExit) provide monitoring points.
Deployment shapes
- Long-running worker: run
node runner.mjsunder system supervisors. - Serverless batch: instantiate
runBridgewith customwaitFnfor single-use batch jobs. - Edge/adapter-specific: tune
eventsPerBatchand handler logic to match latency requirements.
See the playbooks for concrete integrations.