@asterql/sync
v0.3.1
Published
Multiplexed scope-subscribed sync client: one connection, server event envelopes, cursor resume.
Downloads
393
Maintainers
Readme
@asterql/sync
One connection per session for AsterQL state sync: a multiplexed,
scope-subscribed client that ingests ServerEventEnvelopes into an
@asterql/store EntityStore, with per-scope cursor resume.
Install
npm install @asterql/sync @asterql/store @asterql/view-protocolUsage
import { EntityStore } from "@asterql/store";
import { SyncClient } from "@asterql/sync";
const store = new EntityStore();
const client = new SyncClient({
url: "wss://app.example.com/api/sync/socket",
store,
// browser: defaults to globalThis.WebSocket; Node/TUI: pass `ws`
onSnapshotRequired: async (scope) => {
// fetch a registered view (Protocol A), seed the store, then
// store.cursors.beginScope(scope, seqFromSnapshot)
},
});
const unsubscribe = client.subscribe("sync:org_1:user_1");
// later
unsubscribe();Semantics
- One socket, many scopes.
subscribe(scope)is reference-counted; the connection opens lazily with the first scope and unsubscribes a scope when its last consumer releases. - Cursor resume. Every
subcarriessincefrom the store's scope cursors, so reconnects replay exactly the missed envelopes from the server's ledger. - Gap replay. An envelope that lands ahead of the cursor triggers a replay request; the store never applies out-of-order events.
- Bootstrap handoff. When the server answers
snapshot_required(cursor beyond its replay horizon), the client callsonSnapshotRequired(scope), waits for the caller to re-seed the store, then resubscribes. - Liveness. Heartbeat ping/pong (two missed pongs recycle the socket) and exponential reconnect backoff.
The wire vocabulary lives in @asterql/view-protocol
(SyncUpstreamMessage / SyncDownstreamMessage); the server side is
host-owned: authenticate at the handshake, authorize per scope, replay from a
per-scope envelope ledger.
