@asterql/sync
One connection per session for AsterQL state sync: a multiplexed,
scope-subscribed WebSocket client speaking the @asterql/view-protocol
sync wire vocabulary, with per-scope handlers and cursor resume.
Install
npm install @asterql/sync @asterql/view-protocol
Usage
import { SyncClient } from "@asterql/sync";
import { EntityStore } from "@asterql/store";
const client = new SyncClient({
url: "wss://app.example.com/api/sync/socket",
});
const store = new EntityStore();
store.cursors.beginScope("run:run_1", 0);
const unsubscribe = client.subscribe("run:run_1", {
onEnvelope: (envelope) => store.ingest(envelope).result.kind,
since: () => store.cursors.scopeLastSeq("run:run_1"),
onSnapshotRequired: async () => {
},
});
unsubscribe();
Semantics
- One socket, many scopes, many stores. Each
subscribe(scope, handler)
owns that scope's ingestion: an identity scope can drive cache
invalidation while each run scope feeds its own store, all over one
connection. Subscriptions are reference-counted per scope.
- Cursor resume.
sub.since comes from the handler's since(); omitted
means live-from-now (the server starts at its current head). Reconnects
resubscribe every scope from its cursor, so missed envelopes replay from
the server's ledger.
- Gap replay. A handler returning
"gap" from onEnvelope triggers a
replay request from its cursor; out-of-order events are never applied.
- Bootstrap handoff.
scopeError snapshot_required (cursor beyond the
server's replay horizon) calls onSnapshotRequired(); after the caller
re-seeds, the client resubscribes from the new cursor.
- Liveness. Heartbeat ping/pong (two missed pongs recycle the socket)
and exponential reconnect backoff.
Server expectations
The host owns the endpoint: authenticate at the upgrade handshake,
authorize per scope, persist envelopes in per-scope ledgers with
contiguous sequence numbers, replay from sub.since, and emit a seed
snapshot as each scope's first ledger entry so replay-from-0 is
deterministic. The reference server lives in Fridayland
(packages/server/src/sync-socket.ts).