Architecture & Wiring
<- Back to 02. Core Concepts | Next: 04. Integration Guide ->
1. Layers
corehas no knowledge of the bus or of workers. It normalizes commands, resolves store targets and applies mutations.cdcturns store streams intoStoreCdcEventand moves them to and from the bus.bridgecomposescoreandcdcinto a bidirectional bus attachment with echo suppression.workercomposescoreandcdcbehind apostMessageprotocol.
2. Command Path (bus -> store)
Key properties:
- Commands for the same
collection:idare applied in arrival order; other documents proceed in parallel. - A command with a
mutationIdthat is in flight or already applied is dropped, which makes redelivery idempotent. durable: trueadditionally awaitsflush()on the target.
3. Change Path (store -> bus)
Why the loop cannot form
- A bus command is recorded in the ledger before the store write.
- The store emits a CDC event with the same
mutationId. - The bridge sees the id in the ledger and does not publish to the bus.
- If another bridge forwards a published event back as a command with the original
sourceId, the originating bridge ignores it.
Because the ledger is count-bounded (not time-bounded), suppression does not depend on timing; only ids older than the last ledgerCapacity applied mutations are forgotten.
4. Bus Topology
| Channel | Group | Topic | Direction | Producer |
|---|---|---|---|---|
<prefix>.COMMAND | in or out | any (often the collection, or a command verb) | -> store | application |
<prefix>.<collection>.COMMAND | in or out | any | -> store | application (bound bridges only) |
<prefix>.ENTITY_CHANGED | out | collection | <- store | attachStoreBridge, createCdcBridge, publishCdcToBus |
<prefix>.<collection>.CHANGED | out | entity id | <- store | same, with secondaryChannelPerCollection |
The default prefix is STORE. Several bridges with different prefixes can share one bus, for example one per bounded context.
5. Worker Topology
Handshake and requests
Lifecycle rules enforced by the client:
- Every request has a timeout (
requestTimeoutMs, default 10000;initTimeoutMs, default 5000). - A
CLOSEmessage or portcloseevent marks the clientdeadand rejects all pending requests withWorkerDeadError. - A port
errorevent is fatal undererrorPolicy: 'fatal'(default) and only reported onerrors$under'log'. - Requests are checked for cloneability before sending, so a non-cloneable payload rejects immediately instead of breaking the port.
Batching
With batch: 'microtask', requests issued in the same tick are queued and flushed once:
A single queued request is sent unwrapped. INIT is never batched.
6. Combining Bridge and Worker
The two halves are independent. A common layout keeps the store in a worker and puts a bus on each side:
- Inside the worker,
attachStoreBridge(workerBus, store)lets worker-side services exchange commands and CDC on a local bus. - On the main thread,
createWorkerStoreClient(worker)provides typed calls andclient.cdc$; that stream can be forwarded to the main-thread bus withpublishCdcToBus(mainBus, client.cdc$).
See 04. Integration Guide for runnable setups.
<- Back to 02. Core Concepts | Next: 04. Integration Guide ->