@helipod/sync 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +944 -0
- package/dist/index.js +1354 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/protocol.ts","../src/change.ts","../src/subscription-manager.ts","../src/resume-registry.ts","../src/handler.ts","../src/classify.ts","../src/commit-differ.ts","../src/session-controllers.ts","../src/client-reducer.ts"],"sourcesContent":["/**\n * The reactive sync protocol — the client↔server message catalog and the version model.\n *\n * State is **version-bracketed**: every `Transition` advances `startVersion → endVersion`,\n * and a client applies one only if its `startVersion` matches the client's current version.\n * A missed frame leaves a gap the client detects and resyncs from — so dropping a frame\n * (backpressure) degrades to a resync, never to silent divergence. The `ServerMessage` union\n * is versioned-by-shape and deliberately extensible (e.g. the non-commit `Broadcast` kind for\n * the ephemeral path) so the wire can later gain a binary delta encoding.\n */\nimport type { JSONValue } from \"@helipod/values\";\nimport type { Change } from \"./change\";\n\nexport interface StateVersion {\n /** Bumped when the set of subscribed queries changes. */\n querySet: number;\n /** The latest commit timestamp reflected (0 = none). */\n ts: number;\n}\n\nexport const INITIAL_VERSION: StateVersion = { querySet: 0, ts: 0 };\n\nexport function versionsEqual(a: StateVersion, b: StateVersion): boolean {\n return a.querySet === b.querySet && a.ts === b.ts;\n}\n\nexport function compareStateVersion(a: StateVersion, b: StateVersion): -1 | 0 | 1 {\n if (a.querySet !== b.querySet) return a.querySet < b.querySet ? -1 : 1;\n if (a.ts !== b.ts) return a.ts < b.ts ? -1 : 1;\n return 0;\n}\n\n/** Two brackets are contiguous when the next starts exactly where the previous ended. */\nexport function isContiguous(prevEnd: StateVersion, nextStart: StateVersion): boolean {\n return versionsEqual(prevEnd, nextStart);\n}\n\n/**\n * `resultHash` (subscription resume, design 2025-11-28): the client's last-known server-minted\n * fingerprint for this query, echoed back on resubscribe. Present only when the subscription was\n * previously `answered` with a defined value; absent on a first subscribe, a prior `QueryFailed`,\n * or an old client that predates this field — all of which fall through to today's full send.\n */\nexport interface QueryRequest {\n queryId: number;\n udfPath: string;\n args: JSONValue;\n resultHash?: string;\n /**\n * DLR Stage 3 — the client's `maxObservedTs` at resume time; lets the server skip the re-run\n * when nothing touched the query's read-set since. Present only on a resume resubscribe;\n * absent on a fresh subscribe.\n */\n sinceTs?: number;\n}\n\n/**\n * The durable per-tab client identity for a resend-safe mutation (Receipted Outbox, verdict §(b)/(e)).\n * `clientId` is minted once per tab-session; `seq` is a per-tab monotone counter. The `(identity,\n * clientId, seq)` triple is the write-once dedup key; `identity` is the session's ambient token,\n * supplied server-side (never trusted from the client). Both fields absent → today's unconditional\n * path, bit-for-bit — a mutation with no `clientId` writes no receipt and reads no classification.\n */\nexport interface ClientMutationRef {\n clientId: string;\n seq: number;\n}\n\n/** One entry of a {@link MutationBatch} — the same shape a standalone `Mutation` carries. */\nexport interface MutationBatchEntry {\n requestId: string;\n udfPath: string;\n args: JSONValue;\n clientId?: string;\n seq?: number;\n}\n\n/**\n * A per-seq verdict as it travels on the wire (`ConnectAck.results`, verdict §(e)). `verdict`\n * distinguishes a replayed success (`applied`), a replayed terminal failure (`failed`), a\n * loudly-disowned pruned/holed seq (`stale`), and a never-seen seq the client must (re)send\n * (`unknown`). `commitTs`/`value`/`valueMissing`/`code` mirror {@link MutationResponse}'s replay shape.\n */\nexport interface ClientMutationVerdict {\n clientId: string;\n seq: number;\n verdict: \"applied\" | \"failed\" | \"stale\" | \"unknown\";\n commitTs?: number;\n value?: JSONValue;\n valueMissing?: true;\n code?: string;\n}\n\nexport type ClientMessage =\n // `Connect` activates from the reserved no-op (verdict §(e)): `clientId`/`held`/`ackedThrough` are\n // the resume handshake — the server classifies `held` into `ConnectAck.results` and prunes\n // `ackedThrough` (the contiguous settled-prefix). Absent → today's no-op Connect, bit-for-bit.\n | { type: \"Connect\"; sessionId: string; clientId?: string; held?: ClientMutationRef[]; ackedThrough?: ClientMutationRef[]; supportsQueryDiff?: true }\n | { type: \"ModifyQuerySet\"; add: QueryRequest[]; remove: number[] }\n | { type: \"Mutation\"; requestId: string; udfPath: string; args: JSONValue; clientId?: string; seq?: number }\n // `MutationBatch` (verdict §(e)): the offline drain's chunk shape — the server applies `entries`\n // SEQUENTIALLY and replies with one `MutationResponse` per entry it settles (chunk semantics). A\n // mid-batch TERMINAL failure (deterministic app error, coded verdict) records + responds and\n // CONTINUES to the next entry; a TRANSIENT failure (retryable/infra) responds that entry's\n // failure and STOPS the drain — later entries get no response, so a causally-dependent unit can\n // never apply after an earlier transient failure (the FIFO drain obligation). See\n // `SyncProtocolHandler.processMutation`'s doc comment for the full classification rule.\n | { type: \"MutationBatch\"; entries: MutationBatchEntry[] }\n | { type: \"Action\"; requestId: string; udfPath: string; args: JSONValue }\n | { type: \"EphemeralPublish\"; topic: string; event: JSONValue }\n | { type: \"SetAuth\"; token: string | null }\n | { type: \"SetAdminAuth\"; key: string };\n\nexport type StateModification =\n // `hash` (subscription resume): the server's own fingerprint of `value` — see `hashValue` in\n // handler.ts. Attached at every construction site (subscribe answer AND reactive re-run pushes)\n // so the client's stored hash is always current at disconnect time. Optional only for wire\n // compatibility with a hand-constructed message; the server never omits it.\n | { type: \"QueryUpdated\"; queryId: number; value: JSONValue; hash?: string }\n | { type: \"QueryFailed\"; queryId: number; error: string }\n | { type: \"QueryRemoved\"; queryId: number }\n // Subscription resume: the fresh re-run's hash matched the client-echoed `resultHash` — no\n // `value` to send. Sent ONLY from the subscribe-answer path (`doModifyQuerySet`), never from a\n // reactive re-run push (a push only happens because the read-set was intersected; sending\n // `QueryUnchanged` there is out of scope — see the design doc's Non-goals).\n | { type: \"QueryUnchanged\"; queryId: number }\n // DLR 2a: an incremental row diff for a DIFFABLE query. `changes` apply to the client's keyed\n // row-map; `checksum` is the server's drift fingerprint of the resulting map (client verifies).\n // A DIFFABLE query's INITIAL answer is a QueryDiff \"reset\" (add-all over an empty map). Sent only\n // to a session that advertised `supportsQueryDiff` on Connect; RERUN queries use QueryUpdated.\n //\n // `reset` (DLR 2a follow-up, reset semantics; DLR 2b widened the shape): set on EVERY re-baseline\n // of a DIFFABLE sub's row-map — the initial subscribe answer, and any later re-answer that\n // recomputes the sub's value/byId/range from scratch (e.g. a `SetAuth` refresh) rather than\n // incrementally diffing a single write. The client must clear its row-map before applying\n // `changes` when `reset` is present, exactly like the initial-subscribe case, instead of merging\n // onto whatever map it had — a re-baseline can legitimately swap which document(s) the sub even\n // tracks (an identity-scoped `db.get` whose target id changes under a `SetAuth`), so applying it\n // as an incremental edit onto the OLD map would leave a stale entry behind. Absent on every\n // ordinary incremental diff — additive, an old client that doesn't know the field simply applies\n // `changes` as an edit onto its running map, which is correct for every non-reset QueryDiff.\n //\n // DLR 2a's DIFFABLE_BYID reset keeps emitting the bare `true` it always has (bit-for-bit — no\n // consuming code anywhere reads `reset` today, so there is nothing to migrate, but the wire bytes\n // of an already-shipped path are left untouched on principle). DLR 2b's new DIFFABLE_RANGE reset\n // emits the richer `{ mode: \"range\", orderDir }` descriptor instead, since a range client needs to\n // know it's rebuilding an ORDERED list (and in which direction), not a single row. A client that\n // only checks `reset` for truthiness (the only pattern used anywhere today) treats both forms\n // identically; `mode`/`orderDir` are there for a future client that wants to special-case range\n // resets (e.g. to keep an existing scroll position) without a wire renegotiation.\n //\n // DLR 2c's new `{ mode: \"page\" }` reset is the `.paginate()` sibling of `range`: same ordered\n // add-all reset shape, but carries the page's own fixed metadata (`nextCursor`/`hasMore`/\n // `scanCapped`) alongside `orderDir` so a diff-capable client can keep its pagination controls\n // in sync WITHOUT a separate `QueryUpdated` round-trip for the metadata. A client that only checks\n // `reset` for truthiness still treats it identically to `byid`/`range`.\n //\n // `hash` (DLR 2b Task 10 — resume/DIFFABLE integration): the server-minted result fingerprint of\n // the reset's fresh value, the SAME `hashValue` fingerprint a RERUN `QueryUpdated` already carries.\n // Present ONLY on a reset (the answer to a subscribe/resync) — an incremental diff has no\n // standalone \"result\" to fingerprint against a future resubscribe, so it stays hashless, exactly\n // as before this field existed. The client stores it as `Subscription.lastHash` and echoes it back\n // as `resultHash` on a later resubscribe, the same resume contract `QueryUpdated.hash` already\n // established — this is what lets a DIFFABLE sub resume via `QueryUnchanged` instead of always\n // paying for a full reset on reconnect. For a `page` reset the hash is over the WHOLE\n // `PaginationResult` (the page rows plus `nextCursor`/`hasMore`), not just the row array.\n | {\n type: \"QueryDiff\";\n queryId: number;\n changes: Change[];\n checksum: string;\n reset?:\n | true\n | { mode: \"byid\" | \"range\"; orderDir?: \"asc\" | \"desc\" }\n | { mode: \"page\"; orderDir: \"asc\" | \"desc\"; nextCursor: string | null; hasMore: boolean; scanCapped: boolean };\n hash?: string;\n };\n\nexport type ServerMessage =\n | { type: \"Transition\"; startVersion: StateVersion; endVersion: StateVersion; modifications: StateModification[] }\n // `ts` (W1) carries the mutation's commitTs — the wire-level ack an optimistic-update gate\n // consumes to know when it is safe to drop a client-side pending layer (never on the ack\n // alone: the gate waits until the session's OWN reactive feed has observed this ts too).\n // Optional, not because it is sometimes skippable, but because the server omits it on the\n // one path where sending it would be a lie — see the send-site invariant check at\n // handler.ts's `handleMutation`. Additive: old clients that don't know the field ignore it.\n // `replayed`/`valueMissing` (Receipted Outbox, verdict §(e)): a replay-ack for a resent mutation\n // whose verdict was already recorded — NO commit happened on this call; `ts` is the ORIGINAL\n // commitTs (keeps the client optimistic gate sound). `value` is omitted (not sent) when the\n // recorded verdict had no value (the crash-window or 64KB-cap case) — then `valueMissing: true`.\n | { type: \"MutationResponse\"; requestId: string; success: true; value?: JSONValue; ts?: number; replayed?: true; valueMissing?: true }\n // `code` (verdict §(e)): the terminal verdict code (a recorded `failed` verdict's error code, or\n // `\"STALE_CLIENT\"`) — the client's coded-vs-codeless retry policy keys off its presence.\n | { type: \"MutationResponse\"; requestId: string; success: false; error: string; code?: string }\n | { type: \"ActionResponse\"; requestId: string; success: true; value: JSONValue }\n | { type: \"ActionResponse\"; requestId: string; success: false; error: string }\n // `ConnectAck` (verdict §(e)): the resume-handshake reply — the capability proof that arms the\n // client's park-and-resend. `known: false` → the server has neither records nor a floor for the\n // presented history (a swept/foreign timeline) → the client resets (`onClientReset`). `deploymentId`\n // hardens the same-timeline proof (verdict §(g) hazard 15). `results` classifies each presented\n // `held` seq. NO `tableNumbers` (rejected — couples client caches to the interim registry).\n | { type: \"ConnectAck\"; known: boolean; results: ClientMutationVerdict[]; deploymentId: string }\n | { type: \"Broadcast\"; topic: string; event: JSONValue }\n | { type: \"FatalError\"; message: string }\n | { type: \"Ping\" };\n\nexport function parseClientMessage(raw: string): ClientMessage {\n return JSON.parse(raw) as ClientMessage;\n}\n\nexport function encodeServerMessage(msg: ServerMessage): string {\n return JSON.stringify(msg);\n}\n","/**\n * The DLR row-diff vocabulary shared by the server (emit) and the client (apply). A DIFFABLE query's\n * materialized value is a keyed `Map<docId, RowVersion>`; `applyChanges` is the ONE apply used on both\n * sides so they cannot drift in how a diff materializes. `driftChecksum` is an order-independent XOR\n * fold over `(key, ts)` — a cheap safety net: the client recomputes it after applying and, on\n * mismatch, scoped-resyncs that one query. See docs/dev/architecture/reactivity-differential-log-tail.md §4.3-4.4.\n */\nimport type { JSONValue } from \"@helipod/values\";\n\nexport type Change =\n | { t: \"add\"; key: string; row: JSONValue; ts: number; orderKey?: string }\n | { t: \"remove\"; key: string }\n | { t: \"edit\"; key: string; row: JSONValue; ts: number; orderKey?: string };\n\nexport interface RowVersion {\n row: JSONValue;\n ts: number;\n orderKey?: string;\n}\n\n/** Apply changes to a keyed row-map, copy-on-write. Returns a NEW map (callers rely on a fresh\n * reference to fire listeners). `add`/`edit` set `{row, ts, orderKey}`; `remove` deletes the key. */\nexport function applyChanges(rows: Map<string, RowVersion>, changes: readonly Change[]): Map<string, RowVersion> {\n const out = new Map(rows);\n for (const c of changes) {\n if (c.t === \"remove\") out.delete(c.key);\n else out.set(c.key, { row: c.row, ts: c.ts, orderKey: c.orderKey });\n }\n return out;\n}\n\n/** FNV-1a 32-bit of `\"<key> <ts> <orderKey>\"` per row, XOR-folded to 32 bits. Order-independent so server and\n * client agree regardless of iteration order. Hex string. */\nexport function driftChecksum(rows: Map<string, RowVersion>): string {\n let acc = 0;\n for (const [key, rv] of rows) {\n let h = 0x811c9dc5;\n const mix = (byte: number): void => { h ^= byte; h = Math.imul(h, 0x01000193) >>> 0; };\n for (let i = 0; i < key.length; i++) mix(key.charCodeAt(i) & 0xff);\n mix(0x00);\n const tsStr = String(rv.ts);\n for (let i = 0; i < tsStr.length; i++) mix(tsStr.charCodeAt(i) & 0xff);\n mix(0x00);\n const ok = rv.orderKey ?? \"\";\n for (let i = 0; i < ok.length; i++) mix(ok.charCodeAt(i) & 0xff);\n acc = (acc ^ (h >>> 0)) >>> 0;\n }\n return acc.toString(16).padStart(8, \"0\");\n}\n","/**\n * Tracks live subscriptions (a query + the ranges/tables its read set touched) and answers\n * \"which subscriptions does this write invalidate?\". Range-bearing subscriptions are indexed in a\n * per-keyspace augmented interval tree (`IntervalIndex`) so matching is O(log N + k) in the number\n * of live subscriptions, not O(N). Subscriptions without read ranges fall back to table-level\n * matching via the `byTable` index. Read ranges are deserialized ONCE at registration, never per\n * write. Semantics are identical to the retired linear scan (proven by the differential oracle test).\n */\nimport type { JSONValue } from \"@helipod/values\";\nimport { deserializeKeyRange, IntervalIndex, type KeyRange, type SerializedKeyRange } from \"@helipod/index-key-codec\";\n\nexport type MatchMode = \"table\" | \"range\";\n\nexport interface Subscription {\n sessionId: string;\n queryId: number;\n udfPath: string;\n args: JSONValue;\n /** Tables this subscription's read set touched (table-level match key / fallback). */\n tables: string[];\n /** Precise read ranges (range-level match key — surgical invalidation). */\n readRanges: readonly SerializedKeyRange[];\n /**\n * M2c: global (D1) tables this subscription's read set touched (a `.global()` read produces no\n * `readRanges` entry, so these are NOT already covered by `tables`/`readRanges` above). Field +\n * threading only — NOT yet indexed/matched here; Task 4 adds the dedicated `byGlobalTable` index,\n * `findAffectedByRanges` matching, and a `subscribedGlobalTables()` accessor that read it.\n */\n globalTables?: string[];\n /** DIFFABLE_BYID marker + the by-id read descriptor; absent ⇒ RERUN. Set at subscribe (classify). */\n byId?: import(\"./classify\").ByIdRead;\n /** DIFFABLE_RANGE marker + the range read descriptor; absent ⇒ RERUN. Set at subscribe (classify). */\n range?: import(\"./classify\").RangeRead;\n /**\n * DLR Stage 3: the resume-registry key this sub was registered under, captured at subscribe time.\n * Release MUST use this stored key — never a key re-derived from `session.identity` at teardown,\n * because `SetAuth` can mutate `session.identity` in place after subscribe, which would otherwise\n * release a different key than `upsert` created (a silent no-op → permanent registry leak).\n */\n resumeKey?: string;\n}\n\nfunction subKey(sessionId: string, queryId: number): string {\n return `${sessionId} ${queryId}`;\n}\n\nexport class SubscriptionManager {\n private readonly byKey = new Map<string, Subscription>();\n private readonly byTable = new Map<string, Set<string>>();\n private readonly byRange = new IntervalIndex<string>();\n private readonly tableFallbackKeys = new Set<string>();\n private readonly deserializedRanges = new Map<string, KeyRange[]>();\n /**\n * M2c: index from global (D1) table name -> subscription keys whose read set touched it, via\n * `Subscription.globalTables`. Populated UNCONDITIONALLY in `add` (independent of\n * `readRanges`/`tableFallbackKeys`) so a MIXED subscription — one with both local `readRanges`\n * and a global-table read — still lands here. Matched by a THIRD, ungated loop in\n * `findAffectedByRanges`; the existing table-fallback loop is gated on `tableFallbackKeys`\n * precisely because it must NOT fire for a sub with non-empty `readRanges`, but global-table\n * reads have no `readRanges` entry of their own, so that gate would wrongly exclude a mixed sub.\n */\n private readonly byGlobalTable = new Map<string, Set<string>>();\n\n add(sub: Subscription): void {\n const key = subKey(sub.sessionId, sub.queryId);\n this.removeKey(key); // refresh: drop any stale index entries for this sub\n this.byKey.set(key, sub);\n for (const table of sub.tables) {\n let set = this.byTable.get(table);\n if (!set) { set = new Set(); this.byTable.set(table, set); }\n set.add(key);\n }\n if (sub.readRanges.length > 0) {\n const ranges = sub.readRanges.map(deserializeKeyRange);\n this.deserializedRanges.set(key, ranges);\n for (const range of ranges) this.byRange.insert(range, key);\n } else {\n this.tableFallbackKeys.add(key);\n }\n for (const t of sub.globalTables ?? []) {\n let s = this.byGlobalTable.get(t);\n if (!s) { s = new Set(); this.byGlobalTable.set(t, s); }\n s.add(key);\n }\n }\n\n private removeKey(key: string): void {\n const existing = this.byKey.get(key);\n if (!existing) return;\n const ranges = this.deserializedRanges.get(key);\n if (ranges) {\n for (const range of ranges) this.byRange.remove(range, key);\n this.deserializedRanges.delete(key);\n }\n this.tableFallbackKeys.delete(key);\n for (const table of existing.tables) this.byTable.get(table)?.delete(key);\n for (const t of existing.globalTables ?? []) {\n const s = this.byGlobalTable.get(t);\n s?.delete(key);\n if (s && s.size === 0) this.byGlobalTable.delete(t);\n }\n this.byKey.delete(key);\n }\n\n remove(sessionId: string, queryId: number): void {\n this.removeKey(subKey(sessionId, queryId));\n }\n\n removeSession(sessionId: string): void {\n const prefix = `${sessionId} `;\n for (const key of [...this.byKey.keys()]) if (key.startsWith(prefix)) this.removeKey(key);\n }\n\n get(sessionId: string, queryId: number): Subscription | undefined {\n return this.byKey.get(subKey(sessionId, queryId));\n }\n\n /**\n * Subscriptions whose read set intersects the given write ranges (surgical invalidation),\n * unioned with table-fallback subscriptions (empty readRanges) touched by the write tables.\n * Same result set as the retired linear scan, computed in O(log N + k) per write range.\n */\n findAffectedByRanges(writeRanges: readonly SerializedKeyRange[], writeTables: readonly string[]): Subscription[] {\n const keys = new Set<string>();\n // Range subs: overlap query per write range.\n for (const w of writeRanges) {\n const wr = deserializeKeyRange(w);\n for (const key of this.byRange.queryOverlaps(wr)) keys.add(key);\n }\n // Fallback subs (empty readRanges): table match only.\n for (const table of writeTables) {\n const set = this.byTable.get(table);\n if (!set) continue;\n for (const key of set) if (this.tableFallbackKeys.has(key)) keys.add(key);\n }\n // Global-table subs (M2c): ungated by tableFallbackKeys — a mixed sub (non-empty readRanges\n // PLUS a global-table read) must still match, since the global read left no readRanges entry.\n for (const table of writeTables) {\n const set = this.byGlobalTable.get(table);\n if (set) for (const key of set) keys.add(key);\n }\n const out: Subscription[] = [];\n for (const key of keys) {\n const sub = this.byKey.get(key);\n if (sub) out.push(sub);\n }\n return out;\n }\n\n /** Subscriptions whose read set touched any of the given tables (deduped). */\n findAffectedByTables(tables: readonly string[]): Subscription[] {\n const out = new Map<string, Subscription>();\n for (const table of tables) {\n const keys = this.byTable.get(table);\n if (!keys) continue;\n for (const key of keys) {\n const sub = this.byKey.get(key);\n if (sub) out.set(key, sub);\n }\n }\n return [...out.values()];\n }\n\n /** All subscriptions for a session (e.g. to re-run them when identity changes). */\n forSession(sessionId: string): Subscription[] {\n const prefix = `${sessionId} `;\n const out: Subscription[] = [];\n for (const [key, sub] of this.byKey) if (key.startsWith(prefix)) out.push(sub);\n return out;\n }\n\n get size(): number {\n return this.byKey.size;\n }\n\n /** Global (D1) table names with at least one live subscriber (M2c). */\n subscribedGlobalTables(): string[] {\n return [...this.byGlobalTable.keys()];\n }\n}\n","/**\n * The compute-saving half of reconnect resume (DLR Stage 3): for every LIVE query\n * (identity+path+args), tracks the read set it last executed with and the timestamp through\n * which its result is known-current (`lastInvalidatedTs`). A commit advances that timestamp for\n * every registry entry whose ranges/tables intersect the write — using the SAME range-indexed\n * matcher as `SubscriptionManager` (an `IntervalIndex` keyed by keyspace plus a `byTable`\n * fallback) so this stays O(log N + k), not a linear scan.\n *\n * Entries are TTL-retained across disconnect: a query with zero live subscribers is not evicted\n * immediately (a reconnect within `TTL_MS` should still be able to resume it), but it MUST stay\n * indexed the whole time it's retained — a write landing during that \"gap\" still has to advance\n * `lastInvalidatedTs`, or a resuming client would wrongly believe its stale result is current.\n */\nimport { deserializeKeyRange, IntervalIndex, type KeyRange, type SerializedKeyRange } from \"@helipod/index-key-codec\";\nimport type { JSONValue } from \"@helipod/values\";\n\nexport const TTL_MS = 60_000;\n\nexport function regKey(identity: string | null, path: string, argsJson: JSONValue): string {\n return `${identity ?? \"\"} ${path} ${JSON.stringify(argsJson)}`;\n}\n\ninterface ResumeEntry {\n readRanges: readonly SerializedKeyRange[];\n tables: readonly string[];\n /** M2c: global (D1) tables this entry's query read — carried through resume so a resumed sub\n * keeps its global-table membership. Not yet indexed here (Task 4 adds the matching side). */\n globalTables: readonly string[];\n lastInvalidatedTs: number;\n wasDiffable: boolean;\n refCount: number;\n /** Set when refCount drops to 0; cleared again on retain. Entry is swept once this passes. */\n expiresAtMs?: number;\n}\n\nexport interface ResumeLookup {\n readRanges: readonly SerializedKeyRange[];\n tables: readonly string[];\n globalTables: readonly string[];\n lastInvalidatedTs: number;\n wasDiffable: boolean;\n}\n\nexport class ResumeRegistry {\n private readonly entries = new Map<string, ResumeEntry>();\n private readonly byTable = new Map<string, Set<string>>();\n private readonly byRange = new IntervalIndex<string>();\n private readonly tableFallbackKeys = new Set<string>();\n private readonly deserializedRanges = new Map<string, KeyRange[]>();\n\n upsert(\n key: string,\n readRanges: readonly SerializedKeyRange[],\n tables: readonly string[],\n atTs: number,\n wasDiffable: boolean,\n globalTables: readonly string[] = [],\n ): void {\n const existing = this.entries.get(key);\n const lastInvalidatedTs = Math.max(existing?.lastInvalidatedTs ?? atTs, atTs);\n const refCount = existing?.refCount ?? 0;\n this.unindex(key); // drop old range/table membership before re-indexing (ranges may have changed)\n this.entries.set(key, { readRanges, tables, globalTables, lastInvalidatedTs, wasDiffable, refCount, expiresAtMs: undefined });\n this.index(key, readRanges, tables);\n }\n\n private index(key: string, readRanges: readonly SerializedKeyRange[], tables: readonly string[]): void {\n for (const table of tables) {\n let set = this.byTable.get(table);\n if (!set) { set = new Set(); this.byTable.set(table, set); }\n set.add(key);\n }\n if (readRanges.length > 0) {\n const ranges = readRanges.map(deserializeKeyRange);\n this.deserializedRanges.set(key, ranges);\n for (const range of ranges) this.byRange.insert(range, key);\n } else {\n this.tableFallbackKeys.add(key);\n }\n }\n\n private unindex(key: string): void {\n const existing = this.entries.get(key);\n if (!existing) return;\n const ranges = this.deserializedRanges.get(key);\n if (ranges) {\n for (const range of ranges) this.byRange.remove(range, key);\n this.deserializedRanges.delete(key);\n }\n this.tableFallbackKeys.delete(key);\n for (const table of existing.tables) this.byTable.get(table)?.delete(key);\n }\n\n /**\n * Advances `lastInvalidatedTs` for every entry whose read set intersects the write — including\n * entries with refCount 0 that are only TTL-retained (they remain indexed until swept).\n */\n advanceOnCommit(writtenRanges: readonly SerializedKeyRange[], writtenTables: readonly string[], commitTs: number): void {\n const keys = new Set<string>();\n for (const w of writtenRanges) {\n const wr = deserializeKeyRange(w);\n for (const key of this.byRange.queryOverlaps(wr)) keys.add(key);\n }\n for (const table of writtenTables) {\n const set = this.byTable.get(table);\n if (!set) continue;\n for (const key of set) if (this.tableFallbackKeys.has(key)) keys.add(key);\n }\n for (const key of keys) {\n const entry = this.entries.get(key);\n if (entry) entry.lastInvalidatedTs = Math.max(entry.lastInvalidatedTs, commitTs);\n }\n }\n\n lookup(key: string): ResumeLookup | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n return { readRanges: entry.readRanges, tables: entry.tables, globalTables: entry.globalTables, lastInvalidatedTs: entry.lastInvalidatedTs, wasDiffable: entry.wasDiffable };\n }\n\n retain(key: string): void {\n const entry = this.entries.get(key);\n if (!entry) return;\n entry.refCount++;\n entry.expiresAtMs = undefined;\n }\n\n release(key: string, nowMs: number): void {\n const entry = this.entries.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) entry.expiresAtMs = nowMs + TTL_MS;\n }\n\n /** Evicts entries with no live subscribers whose TTL has elapsed. Removes them from both indexes. */\n sweep(nowMs: number): void {\n for (const [key, entry] of this.entries) {\n if (entry.refCount <= 0 && entry.expiresAtMs !== undefined && entry.expiresAtMs <= nowMs) {\n this.unindex(key);\n this.entries.delete(key);\n }\n }\n }\n\n /** @internal test/debug only — a live entry's current refCount, or undefined if no such entry. */\n __refCount(key: string): number | undefined {\n return this.entries.get(key)?.refCount;\n }\n\n /** @internal test/debug only — a live entry's pending TTL expiry, or undefined (not pending / no\n * such entry). */\n __expiresAtMs(key: string): number | undefined {\n return this.entries.get(key)?.expiresAtMs;\n }\n}\n","/**\n * `SyncProtocolHandler` — turns client messages into engine calls and pushes reactive\n * updates. The reactive heart: a `Mutation` runs, its written tables become a\n * `WriteInvalidation`, and `notifyWrites` recomputes every subscription that read those\n * tables and pushes a version-bracketed `Transition`. Ephemeral `Broadcast`s take a separate\n * path that never touches the engine. It talks only to abstract `SyncWebSocket` /\n * `SyncUdfExecutor`, so the same handler runs in-process (Tier 0) or as a fleet node (Tier 2).\n */\nimport { createHash } from \"node:crypto\";\nimport { convexToJson, type JSONValue, type Value } from \"@helipod/values\";\nimport { isRetryableError, isHelipodError } from \"@helipod/errors\";\nimport type { SerializedKeyRange } from \"@helipod/index-key-codec\";\nimport type { WrittenDoc } from \"@helipod/transactor\";\nimport type { DiffableRange, DiffablePage } from \"@helipod/executor\";\nimport {\n encodeServerMessage,\n parseClientMessage,\n INITIAL_VERSION,\n type ClientMessage,\n type ServerMessage,\n type StateModification,\n type StateVersion,\n type ClientMutationRef,\n type ClientMutationVerdict,\n type MutationBatchEntry,\n} from \"./protocol\";\nimport { tableOfKeyspaceId } from \"@helipod/index-key-codec\";\nimport { SubscriptionManager, type Subscription } from \"./subscription-manager\";\nimport { classifyByIdRead, rangeReadFromDiffable, pageReadFromDiffable } from \"./classify\";\nimport { byIdChangesFor, byIdResetChanges, rangeChangesFor, rangeResetChanges } from \"./commit-differ\";\nimport { driftChecksum, type RowVersion } from \"./change\";\nimport { ResumeRegistry, regKey } from \"./resume-registry\";\nimport {\n SessionBackpressureController,\n SessionHeartbeatController,\n type BackpressureOptions,\n type HeartbeatOptions,\n} from \"./session-controllers\";\n\n/** How often the handler sweeps every session's send queue for a drain/abandon opportunity. */\nconst FLUSH_SWEEP_MS = 1000;\n\n/** The minimal socket the handler needs (abstract — WS, Durable Object, or loopback). */\nexport interface SyncWebSocket {\n send(data: string): void;\n readonly bufferedAmount: number;\n close(): void;\n /**\n * Send a transport-level ping; invoke `onPong` when the matching pong arrives. OPTIONAL — a\n * socket that omits it (the in-process loopback, which has no peer to die) is exempt from\n * heartbeat reaping. Real WebSocket transports implement it.\n */\n ping?(onPong: () => void): void;\n}\n\n/** Today's fresh-run mutation result (a real commit happened), tagged so the handler discriminates\n * it from a {@link MutationReplay}. */\nexport interface MutationRan {\n replayed?: false;\n value: Value;\n tables: string[];\n writeRanges: readonly SerializedKeyRange[];\n commitTs: number;\n forwarded?: boolean;\n}\n\n/**\n * A replay of a prior verdict (Receipted Outbox, verdict §(c)) — NO commit happened on this call.\n * The classification at the OWNER (`runMutation`'s `dedup` path) hit a recorded verdict (or the\n * floor), so the mutation is NOT re-run. The handler must therefore skip `notifyWrites` AND the G4\n * pending-frontier (nothing was written this call — verdict §(c) Risk R7).\n */\nexport interface MutationReplay {\n replayed: true;\n verdict: \"applied\" | \"failed\" | \"stale\";\n /** The ORIGINAL commitTs for an `applied`/`failed` record (keeps the client gate sound); absent\n * for `stale` (no commit ever happened). */\n commitTs?: number;\n /** Present only for `applied` with a recorded return value. */\n value?: Value;\n /** `applied` whose value was never recorded (crash-window) or exceeded the 64KB cap. */\n valueMissing?: true;\n /** The terminal verdict code for `failed` (the recorded error code) or `\"STALE_CLIENT\"` for `stale`. */\n code?: string;\n}\n\nexport type RunMutationResult = MutationRan | MutationReplay;\n\n/** Runs UDFs for the sync tier. Backed by the executor; returns table sets + precise read ranges for matching. */\nexport interface SyncUdfExecutor {\n runQuery(udfPath: string, args: JSONValue, identity?: string | null): Promise<{ value: Value; tables: string[]; readRanges: readonly SerializedKeyRange[]; globalTables: string[]; diffableRange?: DiffableRange; diffablePage?: DiffablePage }>;\n /**\n * `origin` (G4, client-sync verdict §(d) item 2): the committing session's id, threaded onto the\n * commit's `OplogDelta.origin` so the fan-out can advance THAT session's own `version.ts` past its\n * commit even when it touched nothing the session subscribes to. `forwarded` (fleet): true when\n * the mutation committed on ANOTHER node (no local oplog) — its origin tag couldn't ride this\n * node's local fan-out, so the handler advances the origin frontier via a drain-gated fallback.\n *\n * `dedup` (Receipted Outbox, verdict §(c)): the durable `(clientId, seq)` — absent = today's\n * unconditional path, bit-for-bit (no classification read, no receipt write). Present → the OWNER's\n * `runMutation` impl classifies: a recorded/floored verdict short-circuits to a {@link MutationReplay}\n * (no commit); a miss runs the mutation with the dedup key rideng the commit meta (the receipts\n * guard writes the `applied` receipt atomically). The handler only threads `dedup` down and\n * interprets the discriminated return — it NEVER reads the classification store itself (it runs on\n * any node, incl. a fleet follower; the read must run where the commit runs — verdict §(c) repair 3).\n */\n runMutation(udfPath: string, args: JSONValue, identity?: string | null, origin?: string, dedup?: ClientMutationRef): Promise<RunMutationResult>;\n runAdminQuery(udfPath: string, args: JSONValue): Promise<{ value: Value; tables: string[]; readRanges: readonly SerializedKeyRange[]; globalTables: string[]; diffableRange?: DiffableRange; diffablePage?: DiffablePage }>;\n /** One-shot, non-reactive: an action has no read/write set of its own to fan out. */\n runAction(udfPath: string, args: JSONValue, identity?: string | null): Promise<{ value: Value }>;\n /**\n * Classify a presented `(identity, clientId, seq)` for the `Connect` resume handshake (verdict\n * §(e)) — the read-only sibling of `runMutation`'s dedup path. Returns the recorded verdict, or\n * `\"stale\"` (below the floor, no record), or `\"unknown\"` (never seen — the client should resend).\n * Optional: an executor without receipts support (or an old one) omits it → `Connect` degrades to\n * `known: false` with empty results.\n */\n classifyClientMutation?(identity: string | null, clientId: string, seq: number): Promise<ClientMutationVerdict>;\n /** Ack-prune the contiguous settled prefix `seq <= ackedThrough` for `(identity, clientId)` on a\n * `Connect` (verdict §(c) Retention). Optional (same reason as `classifyClientMutation`). */\n pruneClientMutations?(identity: string | null, clientId: string, ackedThrough: number): Promise<void>;\n /** The deployment-id stamp for `ConnectAck` (verdict §(g) hazard 15 — same-timeline proof). */\n deploymentId?(): string;\n}\n\n/** A committed write's invalidation — the transactor→sync fan-out payload (Tier 2: from a stream). */\nexport interface WriteInvalidation {\n tables: string[];\n /** Precise write ranges for surgical (range-level) invalidation. */\n ranges: readonly SerializedKeyRange[];\n commitTs: number;\n /** Written docs for local row-diffing (§DLR 2a). Absent → affected DIFFABLE subs fall back to RERUN. */\n writtenDocs?: WrittenDoc[];\n /**\n * M2c Critical fix: true for a GLOBAL (D1-backed) table invalidation, sourced from\n * `GlobalReactivityPoller` rather than the local MVCC commit path. Global reactivity has its OWN\n * clock (D1's `_global_versions` counter) — `commitTs` on a global invalidation is a harmless\n * placeholder (`0`), never an oracle-issued local timestamp. A `global` invalidation is\n * FRONTIER-NEUTRAL: it must never advance `session.version.ts` (the client-facing local-ts\n * frontier) or the local-ts `ResumeRegistry` — mixing the two clock domains on that one shared\n * scalar silently breaks local optimistic-update gating and local reconnect-resume for any\n * session with both a local and a global subscription (see `doNotifyWrites`, `sendSessionTransition`,\n * and `doModifyQuerySet`'s `globalTables.length > 0` reconnect bypass). Absent/false = a normal\n * local commit — byte-identical to pre-M2c behavior.\n */\n global?: boolean;\n}\n\nexport interface SyncProtocolHandlerOptions {\n /** Exclude the mutating session from the reactive transition (it has the MutationResponse). */\n excludeOriginFromTransition?: boolean;\n /**\n * Whether a mutation handled here triggers `notifyWrites` inline (default true). Set false\n * when an external write-fan-out drives invalidation (so commits via OTHER paths — e.g. HTTP\n * — also push, and there's no double-notify).\n */\n autoNotifyOnMutation?: boolean;\n /** Validate an admin key presented via `SetAdminAuth`. Defaults to `() => false` (no admin). */\n verifyAdmin?: (key: string) => boolean;\n /** Per-session outbound flow control (queue caps, slow-client drops). Defaults apply if omitted. */\n backpressure?: BackpressureOptions;\n /** Per-session ping/pong liveness reaping. Defaults apply if omitted. */\n heartbeat?: HeartbeatOptions;\n /**\n * Disarm the handler's process-shaped background timers: the periodic `setInterval` flush/resume\n * sweep AND every per-session heartbeat ping. Defaults to `false` — the long-lived process host\n * (`Bun.serve`/`node:http`) is byte-for-byte unchanged.\n *\n * WHY (the Cloudflare Durable Object host, Slice 3): a DO **hibernates after ~seconds idle** to\n * scale to zero, keeping its WebSockets alive while discarding in-memory state. On a DO these\n * timers are actively harmful, not merely useless: (a) a `setInterval` sweep does not keep a DO\n * alive and is silently lost on hibernation — dead weight; (b) an app-level `socket.ping` heartbeat\n * would **wake the DO on every ping**, destroying the scale-to-zero economics that are the entire\n * point of the DO host. Keepalive on a DO instead moves to the runtime-level\n * `setWebSocketAutoResponse` (a ping/pong the runtime answers WITHOUT waking the object). So the DO\n * host constructs the handler with this set; the process host never does. Additive + off by default\n * so nothing but the DO host observes any change. See\n * `docs/superpowers/specs/2026-03-20-do-host-slice3-design.md` §8.1.\n */\n disableBackgroundTimers?: boolean;\n}\n\ninterface Session {\n sessionId: string;\n socket: SyncWebSocket;\n version: StateVersion;\n identity: string | null;\n privileged: boolean;\n /** The single outbound chokepoint — every server→client frame for this session goes through it. */\n bp: SessionBackpressureController;\n /** Transport-level liveness; reaps half-open connections. No-op for ping-less sockets (loopback). */\n hb: SessionHeartbeatController;\n /** DLR 2a: this session's client advertised `supportsQueryDiff` on `Connect`. Defaults to `false`\n * (an old client that predates `QueryDiff`, or one that hasn't sent `Connect` yet) — the emit\n * side (Task 5) must check this before ever sending a `QueryDiff` modification. */\n supportsQueryDiff: boolean;\n}\n\nfunction errMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/**\n * The well-known symbol the executor stamps a committed-ts onto an error thrown AFTER its transaction\n * committed (a `commitThenThrow`). Read via `Symbol.for` (the global registry) rather than importing\n * the executor so `@helipod/sync` keeps its executor coupling TYPE-ONLY — no runtime dependency\n * edge, matching how it already treats `DiffableRange`/`transactor`/`index-key-codec`. MUST stay\n * byte-identical to `@helipod/executor`'s `COMMITTED_TS_ERROR_KEY` (guarded by a cross-package\n * assertion in this handler's tests). See `SyncProtocolHandler.originResponseGates`.\n */\nconst COMMITTED_TS_ERROR_KEY = Symbol.for(\"helipod.executor.committedTs\");\n\n/** The committed-ts the executor stamped on a post-commit error, or `undefined` for a pre-commit\n * throw (no commit → no origin-response gate was ever registered). */\nfunction committedTsOfError(e: unknown): number | undefined {\n if (e !== null && typeof e === \"object\") {\n const ts = (e as Record<PropertyKey, unknown>)[COMMITTED_TS_ERROR_KEY];\n if (typeof ts === \"number\") return ts;\n }\n return undefined;\n}\n\n/** Same `${sessionId} ${queryId}` composite key `SubscriptionManager` uses internally (it doesn't\n * export its own) — `byIdRowMap` is keyed identically so the two stay trivially correlated. */\nfunction subKey(sessionId: string, queryId: number): string {\n return `${sessionId} ${queryId}`;\n}\n\n/**\n * Server-minted result fingerprint (subscription resume, design 2025-11-28). Hashes THIS server's\n * own serialization of the value — the client stores and echoes it opaquely, so attach-site and\n * compare-site using this SAME helper is the entire contract; a cross-version server simply\n * mismatches (falls through to a full send), never crashes or lies.\n */\nfunction hashValue(value: JSONValue): string {\n return \"sha256:\" + createHash(\"sha256\").update(JSON.stringify(value)).digest(\"hex\");\n}\n\nexport class SyncProtocolHandler {\n private readonly sessions = new Map<string, Session>();\n private readonly subscriptions = new SubscriptionManager();\n private notifyTail: Promise<void> = Promise.resolve();\n /**\n * G4 fleet fallback (client-sync verdict §(d) item 2): sessionId → the commitTs of a FORWARDED\n * mutation whose origin tag couldn't ride this (forwarding) node's local fan-out. Satisfied with\n * an empty ts-advancing Transition once the drain processes a commit at-or-above it (gated on the\n * drain's last-processed commitTs — see `sweepPendingFrontiers`). Holds at most one entry per\n * in-flight forwarded mutation per session; cleared on satisfy or disconnect, so the sweep it\n * drives stays tiny (usually empty on a single-node deployment, where nothing is ever forwarded).\n */\n private readonly pendingFrontiers = new Map<string, number>();\n /**\n * DLR 2a CommitDiffer state: `${sessionId} ${queryId}` -> the current materialized 0-or-1-row map\n * for a DIFFABLE_BYID sub whose client is diff-capable. EPHEMERAL — reseeded on every subscribe\n * (`doModifyQuerySet`'s reset), updated on every incremental diff (`doNotifyWrites`), and dropped on\n * unsubscribe/disconnect. NOT a durable CVR: if lost (process restart, or a sub falling back to the\n * RERUN/QueryUpdated path for one turn) the drift checksum's client-side mismatch check is the\n * backstop that resyncs the one affected query — see `change.ts`'s `driftChecksum` doc comment.\n */\n private readonly byIdRowMap = new Map<string, Map<string, RowVersion>>();\n /**\n * DLR 2b — the response-before-Transition gate (replaces the fragile, timer-starvable\n * `setTimeout(0)`). `commitTs` → a one-shot latch a diff-capable origin's OWN reactive Transition\n * parks on inside `doNotifyWrites`, released once `processMutation` has actually enqueued that\n * commit's `MutationResponse` onto the session's outbound queue.\n *\n * WHY commit-time registration (via {@link registerOriginResponseGate}, called from the runtime's\n * fan-out subscribe callback) rather than lazily inside `doNotifyWrites`: the fan-out drain is\n * SERIAL, so under load `doNotifyWrites` for a commit can run long after that commit's response was\n * already sent (the drain is backed up behind a flood). Registering the gate inside `doNotifyWrites`\n * would then happen AFTER the release — the release would find no gate (no-op), and the late gate\n * would park FOREVER, wedging the whole `notifyTail` (the backpressure-flood regression). The\n * subscribe callback instead fires SYNCHRONOUSLY inside the commit (before `runMutation` resolves,\n * hence before the response can be sent), so the gate always exists before its release, whatever the\n * drain backlog.\n *\n * WHY a microtask latch, not `setTimeout(0)`: the release runs as `processMutation` resumes after\n * `await runMutation` — a MICROTASK, which a tight `await`-loop of mutations (`for (…) await\n * client.mutation(…)`) drains between every iteration. The old timer sat in Node's TIMER phase,\n * which that same loop STARVES; because the yield sat ON the single `notifyTail`, a starved timer\n * stalled the entire fan-out chain. A microtask cannot be starved and cannot stall the tail.\n *\n * Scoped to a diff-capable LOCAL origin session (see `registerOriginResponseGate`), so every gate\n * created here is balanced by exactly one release from that session's own `processMutation` — on\n * EVERY post-commit outcome: the success path releases inline, and a commit-then-throw (or any\n * throw after the commit) releases from its catch via the `committedTs` the executor stamps on the\n * error (see `releaseOriginResponseGate`/`committedTsOfError`). Entries are transient: one per\n * in-flight diff-capable-origin commit, created at commit and dropped on release (or on\n * `disconnect`, which resolves+drops any still-pending gate for the vanishing session so a\n * mid-flight teardown can never strand a parked `doNotifyWrites`). The `sessionId` is retained so\n * that disconnect backstop can find a session's gates in this commitTs-keyed map.\n */\n private readonly originResponseGates = new Map<number, { promise: Promise<void>; resolve: () => void; sessionId: string }>();\n /**\n * DLR Stage 3: the compute-saving half of reconnect resume (see `resume-registry.ts`'s doc\n * comment). Populated on every subscribe (`doModifyQuerySet`), advanced on every commit\n * (`doNotifyWrites`, unconditionally — independent of `bySession`, so an entry with zero live\n * subscribers still advances during its TTL-retained \"gap\"), and retain/release-tracked across\n * subscribe/unsubscribe/disconnect. Not yet CONSULTED anywhere (that's a later task) — this task\n * only keeps it correctly populated.\n */\n private readonly resumeRegistry = new ResumeRegistry();\n /**\n * M2c fix: callbacks registered via {@link onGlobalSubscribe}, fired whenever a subscription is\n * (re-)registered with a non-empty `globalTables` read set — from `doModifyQuerySet` (fresh\n * subscribe, or a resume-skip carrying a retained global-table read set), from `handleSetAuth`'s\n * re-exec (an identity flip can make a query newly read a global table), and from\n * `sendSessionTransition`'s live-re-run (a data/identity-dependent query can shift onto a global\n * table across a RERUN refresh). See `DriverContext.onGlobalSubscribe`'s doc comment\n * (`@helipod/component`) for why this exists — closing the busy-DO-late-subscribe gap in the\n * M2c global-reactivity poller.\n */\n private readonly globalSubscribeListeners: Array<() => void> = [];\n private readonly verifyAdmin: (key: string) => boolean;\n /** Periodic drain sweep — drains recovered clients and abandons terminally-slow queues. */\n private sweepTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n private readonly executor: SyncUdfExecutor,\n private readonly options: SyncProtocolHandlerOptions = {},\n ) {\n this.verifyAdmin = options.verifyAdmin ?? (() => false);\n // A DO host disarms this sweep (`disableBackgroundTimers`) — a `setInterval` is lost on\n // hibernation and can't keep a DO alive, so it's pure dead weight there; the DO drives the same\n // per-session flush inline on each `webSocketMessage`/fan-out turn instead. Every process host\n // leaves it on (the default), byte-for-byte unchanged.\n if (!options.disableBackgroundTimers) {\n this.sweepTimer = setInterval(() => {\n for (const session of this.sessions.values()) session.bp.flush();\n // DLR Stage 3: also sweep expired resume-registry entries here, not only on commit\n // (`doNotifyWrites`) — otherwise a fully IDLE server (no commits) never evicts a released\n // entry past its TTL. Bounded, memory-only cleanup; the on-commit sweep still handles the busy case.\n this.resumeRegistry.sweep(Date.now());\n }, FLUSH_SWEEP_MS);\n // Don't keep the process alive for the sweep (Node); loopback-only usage exits cleanly.\n (this.sweepTimer as { unref?: () => void }).unref?.();\n }\n }\n\n connect(sessionId: string, socket: SyncWebSocket): void {\n // The undroppable-queue-overflow cap terminates the session through the SAME reap-and-close\n // path a dead heartbeat uses (see session-controllers.ts) — one place that owns \"this session\n // is being torn down\", not two independently-evolving ones.\n const bp = new SessionBackpressureController(socket, this.options.backpressure, undefined, () => this.reap(sessionId));\n const hb = new SessionHeartbeatController(socket, () => this.reap(sessionId), this.options.heartbeat);\n this.sessions.set(sessionId, { sessionId, socket, version: { ...INITIAL_VERSION }, identity: null, privileged: false, bp, hb, supportsQueryDiff: false });\n // `disableBackgroundTimers` (the DO host) skips the per-session ping heartbeat: an app-level ping\n // would wake a hibernated DO on every beat, defeating scale-to-zero (runtime-level\n // `setWebSocketAutoResponse` handles keepalive there instead). A DO socket also omits `ping`\n // entirely, so `start()` is already a no-op for it — this makes the intent explicit and holds\n // even if a DO socket ever gained a `ping`. Every process host leaves it armed (the default).\n if (!this.options.disableBackgroundTimers) hb.start();\n }\n\n disconnect(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n session?.hb.stop();\n // DLR Stage 3: release this session's resume-registry entries BEFORE dropping its\n // subscriptions — by each sub's STORED `resumeKey` (never a key re-derived from the\n // possibly-since-changed `session.identity`). A sub with zero remaining subscribers (across\n // ALL sessions) stays TTL-retained, not evicted — see `resumeRegistry`'s doc comment.\n for (const sub of this.subscriptions.forSession(sessionId)) {\n if (sub.resumeKey) this.resumeRegistry.release(sub.resumeKey, Date.now());\n }\n this.subscriptions.removeSession(sessionId);\n this.sessions.delete(sessionId);\n this.pendingFrontiers.delete(sessionId);\n this.clearByIdRowMapForSession(sessionId);\n // Backstop: resolve+drop any origin-response gate still pending for this vanishing session, so a\n // mutation that committed (registering a gate) but disconnected before its `processMutation`\n // released it can never leave a `doNotifyWrites` parked forever (DLR 2b review). Under normal\n // operation `processMutation` releases the gate on every outcome, so this loop is usually empty.\n for (const [commitTs, gate] of this.originResponseGates) {\n if (gate.sessionId === sessionId) this.releaseOriginResponseGate(commitTs);\n }\n }\n\n /** Drop every `byIdRowMap` entry for a session (disconnect/reap) — ephemeral per-sub state, never\n * durable, so there's nothing to persist on the way out. */\n private clearByIdRowMapForSession(sessionId: string): void {\n const prefix = `${sessionId} `;\n for (const key of [...this.byIdRowMap.keys()]) if (key.startsWith(prefix)) this.byIdRowMap.delete(key);\n }\n\n /** Reap a session whose heartbeat went dead: close the socket, then tear down like a disconnect. */\n private reap(sessionId: string): void {\n this.sessions.get(sessionId)?.socket.close();\n this.disconnect(sessionId);\n }\n\n /** Stop the background sweep. Call on shutdown; sessions must already be disconnected. */\n dispose(): void {\n if (this.sweepTimer !== null) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = null;\n }\n }\n\n /** @internal test/debug only — the live ResumeRegistry (DLR Stage 3), for tests to assert\n * population/advance/retain-release wiring without duplicating its internals. */\n get __resumeRegistry(): ResumeRegistry {\n return this.resumeRegistry;\n }\n\n /** @internal test/debug only — the live SubscriptionManager (M2c), for tests to assert\n * registration wiring (e.g. `globalTables`) without duplicating its internals. */\n get __subscriptions(): SubscriptionManager {\n return this.subscriptions;\n }\n\n /**\n * M2c Task 6: global (D1-backed) table names with at least one live subscriber right now —\n * delegates to `SubscriptionManager.subscribedGlobalTables()`. This is the ONLY thing a\n * `GlobalReactivityPoller` needs from the sync tier to decide which tables are worth polling D1\n * for (its other dependency, `notifyWrites`, already exists on this class). A public method\n * (unlike `__subscriptions` above) because it is a real runtime dependency wired onto\n * `DriverContext`, not a test-only escape hatch.\n */\n subscribedGlobalTables(): string[] {\n return this.subscriptions.subscribedGlobalTables();\n }\n\n /**\n * M2c fix: register `cb` to fire whenever a subscription is (re-)registered with a non-empty\n * global-table read set — a fresh `doModifyQuerySet` subscribe/resume, a `handleSetAuth` re-exec,\n * or a `sendSessionTransition` live-re-run (see the `globalSubscribeListeners` field doc for all\n * three sites). A driver (e.g. `GlobalReactivityPollerDriver`) uses this to arm itself on any of\n * these late/renewed subscribes, rather than relying solely on a boot-time force-arm that a busy\n * (never-hibernating) DO may never repeat. Not unsubscribed — callers are drivers with the same\n * lifetime as this handler.\n */\n onGlobalSubscribe(cb: () => void): void {\n this.globalSubscribeListeners.push(cb);\n }\n\n /** Fire every registered {@link onGlobalSubscribe} listener. Swallows listener throws (never let a\n * driver's own bug break subscription registration). */\n private fireGlobalSubscribe(): void {\n for (const cb of this.globalSubscribeListeners) {\n try {\n cb();\n } catch (e) {\n console.error(\"[sync] onGlobalSubscribe listener threw:\", e);\n }\n }\n }\n\n private send(session: Session, msg: ServerMessage): void {\n // MutationResponse/ActionResponse are undroppable under backpressure (§(d) item 4 of the\n // client-sync verdict): a dropped Transition self-heals via the version-gap resync, but a\n // dropped response has no bracket and no retransmit — it would strand the mutation/action\n // as permanently \"inflight\" on an otherwise-healthy connection. They're small, rare, and\n // per-request, so always queuing (never dropping) them is cheap.\n const undroppable = msg.type === \"MutationResponse\" || msg.type === \"ActionResponse\";\n session.bp.send(encodeServerMessage(msg), undroppable);\n }\n\n /**\n * `MutationResponse.ts` (W1) must be the mutation's real commitTs — a client-side optimistic-\n * update gate treats it as an ack signal, and a `0` (or absent) commitTs there would either\n * false-close the gate immediately or wedge a pending layer forever. `commitTs` SHOULD always\n * be a positive integer for a committed mutation; the one known way it can leak as `<= 0` is\n * the `?? 0n` fallback for a forwarded-fleet-write whose owner commitTs didn't make it back\n * (`runtime-embedded/src/runtime.ts`). This codebase has no existing dev/prod split (no\n * `NODE_ENV`/`__DEV__` convention anywhere in `packages/`), so this is unconditional: log\n * loudly every time, and never put a lying `0` on the wire — omit `ts` instead, which is\n * exactly the pre-W1 wire shape every client already knows how to handle.\n */\n private mutationResponseTs(commitTs: number): number | undefined {\n if (commitTs > 0) return commitTs;\n console.error(\n `[sync] MutationResponse: commitTs invariant violated (expected > 0, got ${commitTs}); omitting ts from the wire`,\n );\n return undefined;\n }\n\n async handleMessage(sessionId: string, raw: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`unknown session: ${sessionId}`);\n session.hb.noteActivity(); // any inbound frame is liveness credit\n const msg: ClientMessage = parseClientMessage(raw);\n switch (msg.type) {\n case \"Connect\":\n return this.handleConnect(session, msg);\n case \"ModifyQuerySet\":\n return this.handleModifyQuerySet(session, msg);\n case \"Mutation\":\n return this.handleMutation(session, msg);\n case \"MutationBatch\":\n return this.handleMutationBatch(session, msg);\n case \"Action\":\n return this.handleAction(session, msg);\n case \"EphemeralPublish\":\n this.publishEphemeral(msg.topic, msg.event, sessionId);\n return;\n case \"SetAuth\":\n return this.handleSetAuth(session, msg);\n case \"SetAdminAuth\":\n return this.handleSetAdminAuth(session, msg);\n }\n }\n\n /** Run a subscription's query — privileged for _admin:* on a privileged session; else identity-scoped. */\n private async execSub(session: Session, udfPath: string, args: JSONValue): Promise<{ value: Value; tables: string[]; readRanges: readonly SerializedKeyRange[]; globalTables: string[]; diffableRange?: DiffableRange; diffablePage?: DiffablePage }> {\n if (udfPath.startsWith(\"_admin:\")) {\n if (!session.privileged) throw new Error(\"Forbidden: admin subscription requires admin auth\");\n return this.executor.runAdminQuery(udfPath, args);\n }\n return this.executor.runQuery(udfPath, args, session.identity);\n }\n\n /**\n * G1 hardening (client-sync verdict §(d) item 3): a query-set change is SERIALIZED with the\n * reactive fan-out on the same `notifyTail`, per handler. The shipped code ran MQS inline while\n * `notifyWrites` ran on the tail, so a concurrent invalidation could deliver a NEWER value and\n * then MQS deliver an OLDER one under contiguous brackets — a silent base regression (with\n * optimistic layers, \"your own committed write vanishes\"). Enqueuing MQS on the tail makes the two\n * strictly ordered: the enqueued unit reads `session.version` at EXECUTION time (inside\n * `doModifyQuerySet`), so its bracket chains contiguously off whatever notify ran just before it.\n * `execSub`→`runQuery` never re-enters this tail (it's a pure engine read), so there is no\n * deadlock — subscribe just waits behind any pending notifies (the accepted latency cost).\n */\n private handleModifyQuerySet(\n session: Session,\n msg: Extract<ClientMessage, { type: \"ModifyQuerySet\" }>,\n ): Promise<void> {\n const run = this.notifyTail.then(() => this.doModifyQuerySet(session, msg));\n this.notifyTail = run.catch(() => undefined);\n return run;\n }\n\n private async doModifyQuerySet(\n session: Session,\n msg: Extract<ClientMessage, { type: \"ModifyQuerySet\" }>,\n ): Promise<void> {\n const modifications: StateModification[] = [];\n for (const q of msg.add) {\n try {\n // DLR Stage 3 — the reconnect COMPUTE-SKIP: a resume resubscribe carries `sinceTs` (the\n // client's own last-observed frontier). If the resume registry proves this query's read set\n // hasn't been touched by any commit since then, the cached client result is PROVABLY still\n // valid — answer `QueryUnchanged` without paying for a re-run. Diffable subs (byId/range/page)\n // are excluded: they have their own fingerprint/QueryDiff resume path below, and mixing the\n // two would bypass their reset-seeding (`byIdRowMap`) invariants. A missing entry (TTL-evicted)\n // or a `lastInvalidatedTs` above `sinceTs` (a write landed during the gap) falls through to the\n // normal `execSub` re-run below.\n if (q.sinceTs !== undefined) {\n const rrKey = regKey(session.identity, q.udfPath, q.args);\n const entry = this.resumeRegistry.lookup(rrKey);\n // M2c Critical fix: a query that reads at least one GLOBAL (D1-backed) table can never take\n // this compute-skip. `entry.lastInvalidatedTs` lives in the LOCAL-ts domain (advanced only\n // by local commits — see `doNotifyWrites`'s `!invalidation.global` guard), so it structurally\n // cannot reflect a global-table change (the poller never advances it, by design). A\n // global-reading query — pure-global or mixed local+global — therefore ALWAYS re-runs on\n // (re)subscribe/reconnect; this is always safe (never stale), at the documented cost of\n // forgoing the compute-skip for global reads (a deferred v2 optimization, not built here —\n // see the M2c critical-fix brief's scope discipline). A pure-local query (`globalTables`\n // empty) is unaffected and keeps the existing skip below.\n if (entry && !entry.wasDiffable && entry.globalTables.length === 0 && entry.lastInvalidatedTs <= q.sinceTs) {\n // The skipped sub registers with the RETAINED read set — correct, since an unchanged\n // result means an unchanged read set. Must carry the SAME `rrKey` as both `resumeKey` (so\n // a later release targets the right entry) and the `retain` below (paired with THIS\n // subscription, mirroring the populate-site invariant in the execSub path below).\n this.subscriptions.add({\n sessionId: session.sessionId,\n queryId: q.queryId,\n udfPath: q.udfPath,\n args: q.args,\n tables: [...entry.tables],\n readRanges: entry.readRanges,\n globalTables: [...entry.globalTables],\n byId: undefined,\n resumeKey: rrKey,\n });\n this.resumeRegistry.retain(rrKey);\n // M2c fix: a resumed sub retaining a non-empty global-table read set is still a live\n // global subscription — a driver that disarmed itself (e.g. zero subscribers at its last\n // tick) needs the same wake-up this path's fresh-subscribe sibling gives below.\n if (entry.globalTables.length > 0) this.fireGlobalSubscribe();\n modifications.push({ type: \"QueryUnchanged\", queryId: q.queryId });\n continue;\n }\n }\n const { value, tables, readRanges, globalTables, diffableRange, diffablePage } = await this.execSub(session, q.udfPath, q.args);\n // Subscription registration is UNCONDITIONAL and always fresh, whether or not the result\n // turns out unchanged below — a write-after-Unchanged-resume must still invalidate.\n const byId = classifyByIdRead(value, readRanges) ?? undefined;\n const range = diffableRange ? rangeReadFromDiffable(diffableRange) : undefined;\n // DLR 2c: a page IS a range for invalidation purposes (two-sided bounds + pageMeta) — the\n // existing range differ (`rangeChangesFor`) already handles it unchanged, see `doNotifyWrites`.\n const page = diffablePage ? pageReadFromDiffable(diffablePage) : undefined;\n // DLR Stage 3: capture the resume-registry key at subscribe time and store it ON the sub, so\n // release uses the SAME key `upsert` created it under — even if `SetAuth` later mutates\n // `session.identity` in place (a re-derived key would miss the entry → permanent leak).\n const rrKey = regKey(session.identity, q.udfPath, q.args);\n this.subscriptions.add({ sessionId: session.sessionId, queryId: q.queryId, udfPath: q.udfPath, args: q.args, tables, readRanges, globalTables, byId, range: page ?? range, resumeKey: rrKey });\n // M2c fix: a fresh subscription touching at least one global table wakes any listener (e.g.\n // the global-reactivity poller driver) that wants to arm itself on a late subscribe — the\n // busy-DO-late-subscribe gap this hook exists to close (see `onGlobalSubscribe`'s doc comment).\n if (globalTables.length > 0) this.fireGlobalSubscribe();\n // Populate the resume registry for this (identity, path, args). ALWAYS pair `upsert` with\n // `retain` — an `upsert` on a refCount-0 TTL-pending entry clears `expiresAtMs`; without a\n // paired `retain` it would leak (never swept again).\n const wasDiffable = !!(diffableRange || diffablePage || byId);\n this.resumeRegistry.upsert(rrKey, readRanges, tables, session.version.ts, wasDiffable, globalTables);\n this.resumeRegistry.retain(rrKey);\n const json = convexToJson(value);\n if (page && session.supportsQueryDiff) {\n // DLR 2c: a DIFFABLE_PAGE sub's initial/resumed answer — same reset-with-hash contract as\n // the range arm below, but the reset descriptor also carries the page's own fixed metadata\n // (`nextCursor`/`hasMore`/`scanCapped`) so a diff-capable client's pagination controls stay\n // in sync without a separate `QueryUpdated` round-trip. The passthrough guarantee (the\n // executor's `.paginate()` return shape) means `json.page` IS the ordered page rows.\n const orderedRows = (json as { page: JSONValue[] }).page;\n const { changes, next } = rangeResetChanges(page, orderedRows, session.version.ts);\n this.byIdRowMap.set(subKey(session.sessionId, q.queryId), next);\n const hash = hashValue(json);\n if (q.resultHash !== undefined && q.resultHash === hash) {\n modifications.push({ type: \"QueryUnchanged\", queryId: q.queryId });\n } else {\n modifications.push({\n type: \"QueryDiff\",\n queryId: q.queryId,\n changes,\n checksum: driftChecksum(next),\n reset: {\n mode: \"page\",\n orderDir: page.order,\n nextCursor: page.pageMeta!.nextCursor,\n hasMore: page.pageMeta!.hasMore,\n scanCapped: page.pageMeta!.scanCapped,\n },\n hash,\n });\n }\n } else if (range && session.supportsQueryDiff) {\n // DLR 2b Task 10: a DIFFABLE_RANGE sub's initial/resumed answer to a diff-capable client is\n // fingerprinted with the SAME strong `hashValue` a RERUN `QueryUpdated` uses, so subscription\n // resume (design 2025-11-28) works for a diffable sub too. A matching echoed `resultHash`\n // means the fresh result is byte-identical to what the client already has — reply\n // `QueryUnchanged` (no changes on the wire) instead of a full reset.\n //\n // CRITICAL: `byIdRowMap` is seeded EITHER WAY. A reconnect is a fresh server session with an\n // empty `byIdRowMap` (see `disconnect`/`clearByIdRowMapForSession`) — even when the client's\n // baseline is unchanged and nothing is sent, THIS session still needs a materialized row-map\n // on file so a LATER incremental write can diff against it (`sendSessionTransition`'s range\n // arm) instead of finding an empty `prevMap` and computing a wrong diff (spurious `add`s for\n // rows the client already has).\n const { changes, next } = rangeResetChanges(range, json as JSONValue[], session.version.ts);\n this.byIdRowMap.set(subKey(session.sessionId, q.queryId), next);\n const hash = hashValue(json);\n if (q.resultHash !== undefined && q.resultHash === hash) {\n modifications.push({ type: \"QueryUnchanged\", queryId: q.queryId });\n } else {\n modifications.push({\n type: \"QueryDiff\",\n queryId: q.queryId,\n changes,\n checksum: driftChecksum(next),\n reset: { mode: \"range\", orderDir: range.order },\n hash,\n });\n }\n } else if (byId && session.supportsQueryDiff) {\n // DLR 2a/2b Task 10: a DIFFABLE_BYID sub's initial/resumed answer — same resume integration\n // as the range arm above (fingerprint + QueryUnchanged-on-match + unconditional seed).\n //\n // Reset-ts nuance: `execSub`'s return shape (`{value, tables, readRanges}`) doesn't surface\n // the document's own engine commit ts, only the value. Rather than plumb a new field through\n // the whole `SyncUdfExecutor` interface for this one call site, we use the session's own\n // current confirmed ts (`session.version.ts`, unchanged by a ModifyQuerySet — it only bumps\n // `querySet`) as the reset row's ts. This is safe: the checksum only needs client/server\n // agreement on THIS row-map (both sides compute it the same way over whatever ts is chosen),\n // and the very next real write to this id carries its OWN true commit ts through\n // `byIdChangesFor`, so any placeholder-ts imprecision self-corrects on the first write.\n const { changes, next } = byIdResetChanges(byId.docId, json, session.version.ts);\n this.byIdRowMap.set(subKey(session.sessionId, q.queryId), next);\n const hash = hashValue(json);\n if (q.resultHash !== undefined && q.resultHash === hash) {\n modifications.push({ type: \"QueryUnchanged\", queryId: q.queryId });\n } else {\n modifications.push({ type: \"QueryDiff\", queryId: q.queryId, changes, checksum: driftChecksum(next), reset: true, hash });\n }\n } else {\n const hash = hashValue(json);\n if (q.resultHash !== undefined && q.resultHash === hash) {\n modifications.push({ type: \"QueryUnchanged\", queryId: q.queryId });\n } else {\n modifications.push({ type: \"QueryUpdated\", queryId: q.queryId, value: json, hash });\n }\n }\n } catch (e) {\n modifications.push({ type: \"QueryFailed\", queryId: q.queryId, error: errMessage(e) });\n }\n }\n for (const queryId of msg.remove) {\n // DLR Stage 3: release by the sub's STORED `resumeKey` (the key `upsert` created it under),\n // never a key re-derived from the possibly-since-changed `session.identity`.\n const removedSub = this.subscriptions.get(session.sessionId, queryId);\n if (removedSub?.resumeKey) {\n this.resumeRegistry.release(removedSub.resumeKey, Date.now());\n }\n this.subscriptions.remove(session.sessionId, queryId);\n this.byIdRowMap.delete(subKey(session.sessionId, queryId));\n modifications.push({ type: \"QueryRemoved\", queryId });\n }\n // A query-set change bumps querySet (keeps ts).\n const start = session.version;\n const end: StateVersion = { querySet: start.querySet + 1, ts: start.ts };\n session.version = end;\n this.send(session, { type: \"Transition\", startVersion: start, endVersion: end, modifications });\n }\n\n private async handleMutation(\n session: Session,\n msg: Extract<ClientMessage, { type: \"Mutation\" }>,\n ): Promise<void> {\n await this.processMutation(session, msg);\n }\n\n /**\n * A drained-outbox chunk (verdict §(e)): ONE inbound message carrying N entries. Applied\n * SEQUENTIALLY (`await` each in order) — the client sends only one unacked chunk at a time and\n * relies on per-client FIFO, so units MUST commit in order. One `MutationResponse` is emitted per\n * entry as it settles, EXCEPT when a unit fails TRANSIENTLY (see `processMutation`'s doc comment):\n * that unit still gets its failure response, but the loop then STOPS — the remaining entries get\n * NO response at all, preserving the FIFO drain obligation (a causally-dependent later unit must\n * never apply after an earlier transient/infra failure). The client's one-unacked-chunk-at-a-time\n * protocol resends the whole chunk on the next attempt; per-seq receipts make that resend safe\n * (an already-applied unit replay-acks instead of re-running).\n */\n private async handleMutationBatch(\n session: Session,\n msg: Extract<ClientMessage, { type: \"MutationBatch\" }>,\n ): Promise<void> {\n for (const entry of msg.entries) {\n const outcome = await this.processMutation(session, entry);\n if (outcome === \"stop\") break;\n }\n }\n\n /**\n * The per-unit mutation core shared by `Mutation` and `MutationBatch` — threads the durable\n * `(clientId, seq)` down to the OWNER's classification (verdict §(c)), sends the response, and\n * (for a fresh commit only) fans out. A `MutationReplay` return skips `notifyWrites` AND the G4\n * pending-frontier entirely (nothing was written this call — Risk R7): its `commitTs` is the\n * ORIGINAL, long past the current frontier, so arming a frontier or fanning out would be a lie.\n *\n * Returns `\"continue\" | \"stop\"` — meaningful only to `handleMutationBatch`'s drain loop (a\n * standalone `Mutation` ignores it). A thrown error is classified via the executor's retryable\n * discipline (`isRetryableError`, `@helipod/errors` — the same classification\n * `handleDedupError`'s dedup path already applies when deciding whether to record a verdict):\n * - TERMINAL (not retryable — a deterministic app error, a coded verdict failure/replay) means the\n * executor already recorded whatever verdict applies; the batch drain CONTINUES past it (a\n * poison unit never blocks the rest — matches the spec's documented mid-batch-continue case).\n * - TRANSIENT (retryable — infra/conflict) means nothing durable happened for this unit; the batch\n * drain STOPS here so a later, causally-dependent unit can never apply out of order relative to\n * it. The remaining units get no response and the client's FIFO resend picks them back up.\n */\n private async processMutation(\n session: Session,\n unit: { requestId: string; udfPath: string; args: JSONValue; clientId?: string; seq?: number },\n ): Promise<\"continue\" | \"stop\"> {\n const dedup: ClientMutationRef | undefined =\n unit.clientId !== undefined && unit.seq !== undefined ? { clientId: unit.clientId, seq: unit.seq } : undefined;\n try {\n // G4: pass this session's id as `origin` so the commit's fan-out advances its own frontier.\n const r = await this.executor.runMutation(unit.udfPath, unit.args, session.identity, session.sessionId, dedup);\n if (r.replayed) {\n // A replay commits nothing — no fan-out, no frontier. `applied`/`stale`/`failed` map to the\n // wire: `applied` → success+ts (+value|valueMissing); `failed`/`stale` → failure+code.\n if (r.verdict === \"applied\") {\n this.send(session, {\n type: \"MutationResponse\",\n requestId: unit.requestId,\n success: true,\n replayed: true,\n ts: r.commitTs !== undefined ? this.mutationResponseTs(r.commitTs) : undefined,\n ...(r.valueMissing ? { valueMissing: true } : { value: convexToJson(r.value as Value) }),\n });\n } else {\n this.send(session, {\n type: \"MutationResponse\",\n requestId: unit.requestId,\n success: false,\n error: r.code ?? (r.verdict === \"stale\" ? \"STALE_CLIENT\" : \"mutation failed\"),\n code: r.code ?? (r.verdict === \"stale\" ? \"STALE_CLIENT\" : undefined),\n });\n }\n return \"continue\";\n }\n const { value, tables, writeRanges, commitTs, forwarded } = r;\n this.send(session, {\n type: \"MutationResponse\",\n requestId: unit.requestId,\n success: true,\n value: convexToJson(value),\n ts: this.mutationResponseTs(commitTs),\n });\n // DLR 2b: this commit's MutationResponse is now enqueued on the session's outbound queue AHEAD\n // of the commit's own reactive Transition. Release the gate registered at commit time (when the\n // origin is a diff-capable session) so `doNotifyWrites` may now flush the origin's own Transition\n // strictly behind the response. This runs as `processMutation` resumes after `await runMutation`\n // — a microtask, so a tight `await`-loop of mutations can't starve it. A no-op when no gate was\n // registered (inline mode, or a diff-incapable origin). See `originResponseGates`.\n this.releaseOriginResponseGate(commitTs);\n if (forwarded && commitTs > 0) {\n // G4 fleet fallback: the origin tag rode a fan-out on ANOTHER node, so it can't reach this\n // node's `doNotifyWrites`. Record the frontier; `sweepPendingFrontiers` advances this\n // session's `version.ts` once the drain locally processes a commit at-or-above `commitTs`.\n const prev = this.pendingFrontiers.get(session.sessionId);\n if (prev === undefined || commitTs > prev) this.pendingFrontiers.set(session.sessionId, commitTs);\n }\n if (this.options.autoNotifyOnMutation !== false) {\n await this.notifyWrites({ tables, ranges: writeRanges, commitTs }, session.sessionId);\n }\n return \"continue\";\n } catch (e) {\n // DLR 2b leak fix: a `commitThenThrow` (or any throw AFTER the transaction committed) reaches\n // THIS catch, not the success release above — but its commit already fired the fan-out, which\n // registered an origin-response gate at commit time. Release it here, keyed by the `commitTs`\n // the executor stamped on the error, or a diff-capable subscribed origin's `doNotifyWrites`\n // parks on the never-resolved gate forever and wedges the whole node's reactive drain. A no-op\n // when the throw was PRE-commit (no `committedTs` on the error → no gate was ever registered)\n // or when no gate was registered for this commit (diff-incapable origin). See\n // `releaseOriginResponseGate`.\n const committedTs = committedTsOfError(e);\n if (committedTs !== undefined) this.releaseOriginResponseGate(committedTs);\n // Thread the thrown error's typed `code` (when it's one of ours) onto the wire — a genuinely\n // FRESH (non-replayed) failure previously sent `error` with no `code`, even though the wire\n // shape supports one; only the dedup-replay branch above populated it. That silently starved\n // the outbox drain's coded-vs-codeless retry policy (client.ts/outbox-drain.ts key off\n // `.code`): a fresh terminal app error was misclassified as transient (whole-chunk revert +\n // backoff) instead of settling immediately.\n //\n // But only a TERMINAL error gets a code: the wire invariant is \"coded ⇒ terminal, server-\n // recorded verdict\" (mirrors `handleDedupError`'s own `!isRetryableError(e)` gate — only a\n // non-retryable failure ever gets a recorded verdict). A retryable `HelipodError` (OCC\n // conflict, timeout, rate limit, service-unavailable) still HAS a `.code`, but threading it\n // through here would make the drain settle a transient failure as terminal — durable mutation\n // lost, or on a `MutationBatch` \"stop\", the coded path skips `revertActive` and wedges the\n // chunk (re-review FIX 1).\n this.send(session, {\n type: \"MutationResponse\",\n requestId: unit.requestId,\n success: false,\n error: errMessage(e),\n code: isHelipodError(e) && !isRetryableError(e) ? e.code : undefined,\n });\n // Ordering note: `releaseOriginResponseGate` above runs BEFORE this `send`, and that ordering is\n // intentional and harmless — the release only SCHEDULES a microtask (it un-parks a gated\n // `doNotifyWrites`), so this synchronous `send` still puts the `MutationResponse` on the wire\n // first; the released drain can only run once this catch yields.\n // See the doc comment above: TRANSIENT (retryable) stops the batch drain; TERMINAL continues.\n return isRetryableError(e) ? \"stop\" : \"continue\";\n }\n }\n\n /**\n * The `Connect` resume handshake (verdict §(e)): activated from the reserved no-op. Classifies each\n * presented `held` seq into `ConnectAck.results`, ack-prunes the `ackedThrough` contiguous\n * settled-prefix, and stamps the `deploymentId` (same-timeline proof, §(g) hazard 15). `known`\n * is false when the client presents history the server recognizes NONE of (a swept/foreign timeline\n * → the client resets). A bare `Connect` (no `clientId`/`held`/`ackedThrough`, or an executor with\n * no receipts support) stays the pre-Outbox no-op: no ConnectAck is sent, bit-for-bit.\n */\n private async handleConnect(\n session: Session,\n msg: Extract<ClientMessage, { type: \"Connect\" }>,\n ): Promise<void> {\n // DLR 2a: record the capability regardless of the resume-handshake fields below — a client\n // with no `clientId`/`held`/`ackedThrough` can still advertise `supportsQueryDiff`.\n session.supportsQueryDiff = msg.supportsQueryDiff === true;\n // Old-client / no-receipts path: a Connect with no resume fields is the reserved no-op.\n if (msg.clientId === undefined && msg.held === undefined && msg.ackedThrough === undefined) return;\n if (!this.executor.classifyClientMutation || !this.executor.deploymentId) return;\n\n const results: ClientMutationVerdict[] = [];\n let recognizedAny = false;\n let presentedAny = false;\n for (const ref of msg.held ?? []) {\n presentedAny = true;\n const v = await this.executor.classifyClientMutation(session.identity, ref.clientId, ref.seq);\n if (v.verdict !== \"unknown\") recognizedAny = true;\n results.push(v);\n }\n for (const ref of msg.ackedThrough ?? []) {\n presentedAny = true;\n // A floor exists (or gets created) for an acked client, so the server \"knows\" it even with no\n // held records left — classify at the acked seq to detect a recognized floor before pruning.\n if (this.executor.classifyClientMutation) {\n const v = await this.executor.classifyClientMutation(session.identity, ref.clientId, ref.seq);\n if (v.verdict !== \"unknown\") recognizedAny = true;\n }\n await this.executor.pruneClientMutations?.(session.identity, ref.clientId, ref.seq);\n }\n this.send(session, {\n type: \"ConnectAck\",\n known: presentedAny ? recognizedAny : true,\n results,\n deploymentId: this.executor.deploymentId(),\n });\n }\n\n /**\n * A one-shot request→value call — NOT reactive (an action has no read/write set of its own).\n * Deliberately does NOT call `notifyWrites`: any mutation the action invoked via\n * `ctx.runMutation` already fanned out through that mutation's own commit.\n */\n private async handleAction(\n session: Session,\n msg: Extract<ClientMessage, { type: \"Action\" }>,\n ): Promise<void> {\n try {\n const { value } = await this.executor.runAction(msg.udfPath, msg.args, session.identity);\n this.send(session, { type: \"ActionResponse\", requestId: msg.requestId, success: true, value: convexToJson(value) });\n } catch (e) {\n this.send(session, { type: \"ActionResponse\", requestId: msg.requestId, success: false, error: errMessage(e) });\n }\n }\n\n /**\n * Reactive fan-out: recompute subscriptions a write touched and push transitions. Calls are\n * serialized so per-session version brackets advance monotonically (concurrent notifies\n * would otherwise reorder and trigger false client resyncs).\n */\n notifyWrites(invalidation: WriteInvalidation, originSessionId?: string): Promise<void> {\n const run = this.notifyTail.then(() => this.doNotifyWrites(invalidation, originSessionId));\n this.notifyTail = run.catch(() => undefined);\n return run;\n }\n\n private async doNotifyWrites(invalidation: WriteInvalidation, originSessionId?: string): Promise<void> {\n // DLR Stage 3: advance the resume registry ONCE per commit, independent of `bySession` below —\n // an entry with zero live subscribers (TTL-retained across a disconnect \"gap\") must still see\n // its `lastInvalidatedTs` advance, or a resuming client would wrongly trust a stale result.\n // Piggyback a bounded opportunistic sweep here too (no separate timer needed).\n //\n // M2c Critical fix: a GLOBAL invalidation's `commitTs` is a harmless placeholder in the local-ts\n // domain (see `WriteInvalidation.global`'s doc), not a real local commit timestamp — advancing\n // the LOCAL-TS resume registry with it would corrupt every entry's `lastInvalidatedTs` (including\n // pure-local entries whose read set happens to intersect via the table-fallback match, though a\n // global table name never collides with a local one in practice) with a clock the local-ts\n // reconnect-skip (`doModifyQuerySet`) doesn't understand. A global-reading entry never uses this\n // registry for its resume decision anyway (Change 4 always re-runs it), so skipping the advance\n // here costs nothing. `sweep` is unrelated bookkeeping (TTL eviction) — it still runs either way.\n if (!invalidation.global) {\n this.resumeRegistry.advanceOnCommit(invalidation.ranges ?? [], invalidation.tables, invalidation.commitTs);\n }\n this.resumeRegistry.sweep(Date.now());\n\n // Use surgical range-level matching: only re-run subscriptions whose read ranges overlap the write ranges.\n const affected = this.subscriptions.findAffectedByRanges(invalidation.ranges ?? [], invalidation.tables);\n\n const bySession = new Map<string, Subscription[]>();\n for (const sub of affected) {\n if (this.options.excludeOriginFromTransition && sub.sessionId === originSessionId) continue;\n const list = bySession.get(sub.sessionId) ?? [];\n list.push(sub);\n bySession.set(sub.sessionId, list);\n }\n\n // Response-before-Transition ordering (client-sync verdict §(d); DLR 2b). The committing\n // session's own `MutationResponse` (which carries the commitTs the client's optimistic gate keys\n // off) MUST reach the client BEFORE this commit's Transition — only then is the optimistic layer\n // marked `completed` and dropped ATOMICALLY as the authoritative row ingests (drop-on-observed-\n // inclusion, never a transient temp+real duplicate frame). But the sync handler and the fan-out\n // are decoupled (`autoNotifyOnMutation: false`): the fan-out kicks this notify SYNCHRONOUSLY\n // inside the commit (within `runMutation`), so `doNotifyWrites` is scheduled on a microtask AHEAD\n // of the response — whose own microtask is only scheduled once `runMutation` resolves back in\n // `processMutation`. The RERUN (`QueryUpdated`) arm incidentally re-orders correctly by awaiting\n // `execSub` (a real query), which yields long enough for the response to flush first. The\n // synchronous DIFFABLE (by-id / range `QueryDiff`) arms have no such yield, so their Transition\n // raced — and beat — the response, leaving the layer `inflight` at ingest (the 2b regression).\n //\n // Restore the invariant at the source for the diff path with a MICROTASK gate rather than a\n // macrotask yield: when the origin is a diff-capable client about to receive its OWN commit's\n // Transition, park that Transition on the gate registered at COMMIT time for this `commitTs`\n // (`originResponseGates`), and let it resume only once `processMutation` has actually enqueued this\n // commit's `MutationResponse` (which releases the gate — see `releaseOriginResponseGate`). The\n // prior fix used `await setTimeout(0)`, which runs in Node's TIMER phase; a tight `await`-loop of\n // mutations (`for (…) await client.mutation(…)`) STARVES that phase, so the timer never fired and —\n // because this yield sits ON the single `notifyTail` — it BLOCKED the entire fan-out chain (the\n // backpressure-flood regression: a stalled victim received ZERO fan-out). The gate instead resumes\n // on a MICROTASK (the response send is `processMutation` resuming after `await runMutation`), and\n // microtasks drain between every `await` of that loop — so it cannot be starved and cannot stall\n // the `notifyTail`. Registration lives at commit time (not here) precisely because the serial drain\n // can run this method long after the response was sent; see `originResponseGates`' doc comment.\n //\n // SCOPED to the origin's own Transition only (DLR 2b review): the gate exists solely to let the\n // origin's `MutationResponse` flush ahead of ITS Transition — a non-origin session has no response\n // of its own to order against, so delaying its send too is pure unnecessary fan-out latency (and,\n // worse, skews any timing-sensitive backpressure test aimed at a non-origin victim). Every\n // non-origin session is therefore computed+sent FIRST, with no added delay; only the origin's own\n // compute+send (if it's diff-capable and present in `bySession`) parks on the gate. A\n // diff-incapable origin has no synchronous QueryDiff race to guard against, so it is NOT skipped\n // out of the main loop — it sends inline in its natural iteration position, exactly like any\n // non-origin session.\n const originIsDiffCapable =\n !!originSessionId && bySession.has(originSessionId) && this.sessions.get(originSessionId)?.supportsQueryDiff === true;\n\n for (const [sessionId, subs] of bySession) {\n if (originIsDiffCapable && sessionId === originSessionId) continue; // handled below, after the gate\n const session = this.sessions.get(sessionId);\n if (!session) continue;\n await this.sendSessionTransition(session, subs, invalidation);\n }\n\n if (originIsDiffCapable) {\n const originSubs = bySession.get(originSessionId!)!;\n const originSession = this.sessions.get(originSessionId!)!;\n // Park until this commit's `MutationResponse` has been enqueued. The gate was registered at\n // commit time; if the response already flushed (the drain ran this method late), it's already\n // released (absent) and we proceed immediately. Either way, the origin Transition never precedes\n // its own response on the wire.\n const gate = this.originResponseGates.get(invalidation.commitTs);\n if (gate) await gate.promise;\n await this.sendSessionTransition(originSession, originSubs, invalidation);\n }\n\n // M2c Critical fix: both of these (`advanceOriginFrontier`/`sweepPendingFrontiers`) advance\n // `session.version.ts` from `invalidation.commitTs` via `emitEmptyFrontier` — the SAME local-ts\n // frontier `sendSessionTransition` is guarded for above. A GLOBAL invalidation never has a real\n // local origin session (the poller never threads one through `notifyWrites`) and its `commitTs`\n // is always the harmless placeholder `0` (Change 5), so in practice neither of these fires for a\n // global invalidation today — but gate both explicitly anyway: the invariant this fix establishes\n // (\"a global invalidation never advances the local-ts frontier\") must hold structurally, not by\n // incidentally relying on every current caller happening to pass `commitTs: 0`/no origin.\n if (!invalidation.global) {\n // G4 primary origin-frontier guarantee: the committing session must see its own `version.ts`\n // advance past its commit. If this commit touched some of ITS subscriptions it is in `bySession`\n // and the loop above already advanced its ts alongside the write's own modifications — so the ts\n // advance NEVER precedes the modifications it confirms (ordering correct by construction). Only\n // when the commit touched NOTHING it subscribes to (absent from `bySession`) do we emit a\n // standalone empty (`modifications: []`) ts-advancing Transition here.\n this.advanceOriginFrontier(originSessionId, bySession, invalidation.commitTs);\n\n // G4 fleet fallback: a FORWARDED mutation's commit fanned out on the OWNER node, so its origin\n // tag never reached this forwarding node — `handleMutation` recorded a pending frontier instead.\n // Now that the drain has locally processed a commit at `invalidation.commitTs` (the drain's\n // last-processed ts), satisfy any pending frontier at-or-below it that a session's own\n // subscription update this drain didn't already cover.\n this.sweepPendingFrontiers(invalidation.commitTs, bySession);\n }\n }\n\n /**\n * DLR 2b — register the response-before-Transition gate for `commitTs`, at COMMIT time. Called\n * SYNCHRONOUSLY from the runtime's fan-out subscribe callback (which fires inside the commit,\n * before `runMutation` resolves and thus before this commit's `MutationResponse` can be sent), so\n * the gate reliably exists before {@link releaseOriginResponseGate} runs — no matter how backed up\n * the serial fan-out drain is. Public because the decoupled runtime owns the commit-time seam.\n *\n * Registers ONLY for a diff-capable LOCAL origin session — exactly the case `doNotifyWrites` parks\n * on, and exactly the case whose own `processMutation` will release it, so every gate is balanced\n * (no leak). A no-origin commit, a foreign/absent session, or a diff-incapable session registers\n * nothing. Idempotent per `commitTs`.\n */\n registerOriginResponseGate(commitTs: number, originSessionId: string | undefined): void {\n if (originSessionId === undefined) return;\n if (this.sessions.get(originSessionId)?.supportsQueryDiff !== true) return;\n if (this.originResponseGates.has(commitTs)) return;\n let resolve!: () => void;\n const promise = new Promise<void>((r) => (resolve = r));\n this.originResponseGates.set(commitTs, { promise, resolve, sessionId: originSessionId });\n }\n\n /**\n * DLR 2b — release (and drop) the response gate for `commitTs`. Called right after the commit's\n * `MutationResponse` is enqueued, so the parked origin Transition flushes strictly behind it. A\n * no-op when no gate is registered (a diff-incapable / no-origin commit never registered one) — so\n * an ordinary commit costs nothing here.\n */\n private releaseOriginResponseGate(commitTs: number): void {\n const gate = this.originResponseGates.get(commitTs);\n if (gate !== undefined) {\n this.originResponseGates.delete(commitTs);\n gate.resolve();\n }\n }\n\n /**\n * Compute one session's modifications for this commit (by-id / range `QueryDiff` incremental arms,\n * or the RERUN `QueryUpdated`/`QueryFailed` arm) and send its Transition. Extracted from\n * `doNotifyWrites`'s per-session loop body (byte-identical logic) so the origin session's own call\n * can be deferred past the response-ordering macrotask yield while every non-origin session is\n * computed+sent immediately, with no shared logic duplicated between the two call sites.\n */\n private async sendSessionTransition(\n session: Session,\n subs: Subscription[],\n invalidation: WriteInvalidation,\n ): Promise<void> {\n const modifications: StateModification[] = [];\n for (const sub of subs) {\n try {\n // NOTE (DLR 2b Task 10): a session's `supportsQueryDiff` can flip true asynchronously\n // mid-session, independent of when a given sub was last (re)answered — e.g. an outbox\n // client's capability rides its resume `Connect`, sent AFTER its own resync's\n // `ModifyQuerySet` (`onTransportReopened`'s ordering; see `client.ts`). That can let a write\n // take the incremental-diff shortcut below even for a sub this SERVER SESSION never actually\n // seeded a row-map for (`this.byIdRowMap.get(key) ?? new Map()` silently substitutes an empty\n // one) — but that empty substitution is the SAME pre-existing behavior the RERUN-fallback\n // arm below already deliberately relies on (a range sub's map is unconditionally dropped\n // there, expecting a LATER incremental write to reseed off nothing but its own written docs)\n // — see `commit-differ-handler.test.ts`'s \"RERUN fallback ... re-seeds via a fresh add-all\"\n // and the SetAuth re-thread test, both of which pin this. Task 10 does not touch this\n // invalidation-loop behavior (its own remit is the subscribe-answer path); the client-side\n // residual this CAN expose (a diff-capable-but-never-actually-reset client rendering wrong)\n // is instead guarded at the source of truth for render shape — `reconcile.ts`'s\n // `ingestTransition` — which resyncs rather than trusting an uninitialized `renderMode`.\n const key = subKey(sub.sessionId, sub.queryId);\n if (sub.range && session.supportsQueryDiff && invalidation.writtenDocs) {\n // DLR 2b: a DIFFABLE_RANGE sub with a diff-capable client and a commit that carried its\n // written docs gets an incremental QueryDiff — no execSub re-run needed. Unlike a by-id\n // sub (which only ever cares about writes at its OWN key), a range sub must consider\n // EVERY write in its TABLE: a write anywhere in the table can enter or exit the range\n // (an insert, an update that crosses the bounds/filter, a delete). `writtenDocs` is\n // filtered to the sub's table via `tableOfKeyspaceId` — `sub.range.keyspace` is an INDEX\n // keyspace (`index:<tableNumber>:<indexName>`) while `wd.keyspace` is always a PRIMARY\n // keyspace (`table:<tableNumber>`), but both embed the identical `encodeStorageTableId`\n // table-number string (verified against `indexKeyspaceId`/`tableKeyspaceId`'s shared\n // encoding in `@helipod/index-key-codec`), so comparing the parsed table id is the\n // provably correct match — not a coincidental string prefix trick.\n const subTable = tableOfKeyspaceId(sub.range.keyspace);\n const wds = invalidation.writtenDocs.filter((w) => tableOfKeyspaceId(w.keyspace) === subTable);\n const prevMap = this.byIdRowMap.get(key) ?? new Map<string, RowVersion>();\n const { changes, next } = rangeChangesFor(sub.range, prevMap, wds);\n this.byIdRowMap.set(key, next);\n // Pushed even with an empty `changes` array (e.g. every written doc in the table this\n // commit was outside the sub's bounds/filter) — an empty QueryDiff still advances the\n // client's version frontier under this Transition's bracket; the client no-ops it.\n modifications.push({ type: \"QueryDiff\", queryId: sub.queryId, changes, checksum: driftChecksum(next) });\n continue;\n }\n if (sub.byId && session.supportsQueryDiff && invalidation.writtenDocs) {\n // DLR 2a: a DIFFABLE_BYID sub with a diff-capable client and a commit that carried its\n // written docs gets an incremental QueryDiff — no execSub re-run needed (a write to this\n // id can only change the single row's VALUE, never the shape of what a future `db.get(id)`\n // reads, so `sub.byId` itself is trusted as-is here — no reclassification necessary).\n const wd = invalidation.writtenDocs.find(\n (w) => w.keyspace === sub.byId!.keyspace && w.key === sub.byId!.key,\n );\n const prevMap = this.byIdRowMap.get(key) ?? new Map<string, RowVersion>();\n const { changes, next } = byIdChangesFor(sub.byId, prevMap, wd);\n this.byIdRowMap.set(key, next);\n modifications.push({ type: \"QueryDiff\", queryId: sub.queryId, changes, checksum: driftChecksum(next) });\n continue;\n }\n const { value, tables, readRanges, globalTables, diffableRange, diffablePage } = await this.execSub(session, sub.udfPath, sub.args);\n // Recompute `byId`/`range` from THIS fresh (value, readRanges, diffableRange) instead of\n // spreading the sub's stale classification — a query whose read shape changes across a\n // refresh (data/identity-dependent branching) must not keep carrying a `byId`/`range` that\n // no longer matches what it actually reads now (Task 3 review follow-up: a stale `byId`\n // here would drive a WRONG diff the next time this sub takes a branch above).\n const byId = classifyByIdRead(value, readRanges) ?? undefined;\n const range = diffableRange ? rangeReadFromDiffable(diffableRange) : undefined;\n // DLR 2c: same reasoning for a page sub — without this, a page's RERUN fallback (a commit\n // with no `writtenDocs`) would silently DROP its classification (`diffablePage` was never\n // read here, so `range` would resolve to `undefined` and overwrite the sub's page range via\n // `...sub`'s spread below), permanently reverting it to RERUN even once `writtenDocs` starts\n // flowing again on a later commit.\n const page = diffablePage ? pageReadFromDiffable(diffablePage) : undefined;\n this.subscriptions.add({ ...sub, tables, readRanges, globalTables, byId, range: page ?? range }); // refresh the read set\n // M2c fix: this live-re-run can register a sub whose `globalTables` just turned non-empty\n // (a data/identity-dependent branch newly reads a global table) — a driver that disarmed\n // itself needs the same wake-up the fresh-subscribe/resume-skip paths above already give.\n if (globalTables.length > 0) this.fireGlobalSubscribe();\n // DLR Stage 3 (whole-branch review fix): keep the resume registry's read-set in LOCKSTEP with\n // this fresh re-run. A data/identity-dependent query can shift its read ranges here (e.g.\n // `get(user)` then a range keyed on `user.currentRoom`); a registry still frozen at the\n // ORIGINAL subscribe would then miss a gap write to the NEW range → `lastInvalidatedTs`\n // wouldn't advance → a wrong reconnect skip → SILENT STALE DATA. Re-upsert re-indexes the\n // entry under the current ranges; `advanceOnCommit` already moved its `lastInvalidatedTs` to\n // this commit, so the ts is a no-op — only the ranges/tables/wasDiffable change. (No `retain`:\n // the sub is live, so refCount ≥ 1 and `expiresAtMs` is already unset — no leak.)\n if (sub.resumeKey) {\n this.resumeRegistry.upsert(sub.resumeKey, readRanges, tables, Number(invalidation.commitTs), !!(page ?? range) || !!byId, globalTables);\n }\n // Reset-semantics follow-up: if the sub's byId just transitioned away from what it was\n // (or vanished entirely), drop any old byIdRowMap entry — it's keyed to a byId this\n // refresh has just superseded. Left alone, a LATER incremental write (once this sub is\n // diff-capable again) would diff against a stale prev-map keyed to the OLD id and\n // accumulate a second, never-pruned entry (the identity-flip bug this closes). A sub\n // that keeps the SAME byId across this refresh is untouched — its map stays accurate.\n if (sub.byId && (!byId || byId.keyspace !== sub.byId.keyspace || byId.key !== sub.byId.key)) {\n this.byIdRowMap.delete(subKey(sub.sessionId, sub.queryId));\n }\n // Same idea for a range sub, but unconditional: this RERUN branch means a full re-scan\n // (not an incremental diff) answered this turn — a write anywhere in the table could have\n // shifted the range's MEMBERSHIP (not just one row's value) with no per-doc diff tracking\n // it, so the old row-map's membership snapshot predates this RERUN and must not be reused\n // by a later incremental diff. Drop it unconditionally (not gated on the range classification\n // itself changing, unlike byId above). Re-seed happens via a DRIFT-TRIGGERED RESYNC, NOT a\n // fresh QueryDiff reset: the client ingests the `QueryUpdated` below (which reverts the sub to\n // plain RERUN rendering — clears `renderMode`/`diffRows`, Finding 2 in `layered-store.ts`),\n // then on the next write the server emits an INCREMENTAL QueryDiff off the now-empty map\n // (carrying only that commit's written docs, not full membership). The client sees an\n // incremental diff against an uninitialized render mode and resyncs (`reconcile.ts`'s\n // uninitialized-render-mode guard), which is what re-establishes a clean baseline.\n if (sub.range) {\n this.byIdRowMap.delete(subKey(sub.sessionId, sub.queryId));\n }\n const json = convexToJson(value);\n modifications.push({ type: \"QueryUpdated\", queryId: sub.queryId, value: json, hash: hashValue(json) });\n } catch (e) {\n modifications.push({ type: \"QueryFailed\", queryId: sub.queryId, error: errMessage(e) });\n }\n }\n const start = session.version;\n // M2c Critical fix: a GLOBAL invalidation's `commitTs` lives in D1's version-counter domain, not\n // the local-ts domain `session.version.ts` represents — advancing the frontier from it would leap\n // the client's observed local-ts arbitrarily (e.g. contaminating local optimistic-update gating\n // and local reconnect resume; see `WriteInvalidation.global`'s doc). Deliver the fresh\n // modifications (the client still gets its `QueryUpdated`) but keep `endVersion` at the session's\n // CURRENT local-ts frontier — a no-op advance. The client's `observedTs = max(observedTs, endTs)`\n // handles `endTs === observedTs` as a pure no-op, so this is wire-compatible with an unmodified\n // client.\n const end: StateVersion = { querySet: start.querySet, ts: invalidation.global ? start.ts : invalidation.commitTs };\n session.version = end;\n this.send(session, { type: \"Transition\", startVersion: start, endVersion: end, modifications });\n }\n\n /** Emit a standalone empty ts-advancing Transition — advances `session.version.ts` to `ts` with no\n * modifications. The one construct that closes a client's optimistic-update gate for a commit that\n * touched nothing the session subscribes to. Callers guard `ts > session.version.ts` (monotone). */\n private emitEmptyFrontier(session: Session, ts: number): void {\n const start = session.version;\n const end: StateVersion = { querySet: start.querySet, ts };\n session.version = end;\n this.send(session, { type: \"Transition\", startVersion: start, endVersion: end, modifications: [] });\n }\n\n /** G4 primary: advance the LOCAL origin session's frontier when its own commit missed all its\n * subscriptions. A local commit supersedes any stale forwarded fallback entry for that session. */\n private advanceOriginFrontier(\n originSessionId: string | undefined,\n bySession: Map<string, Subscription[]>,\n commitTs: number,\n ): void {\n if (!originSessionId || bySession.has(originSessionId)) return;\n const session = this.sessions.get(originSessionId);\n if (!session || commitTs <= session.version.ts) return;\n this.emitEmptyFrontier(session, commitTs);\n this.pendingFrontiers.delete(originSessionId);\n }\n\n /** G4 fleet fallback: satisfy pending forwarded-mutation frontiers now that the drain reached\n * `drainTs`. A frontier still above `drainTs` waits for a later drain; one already covered by the\n * session's own subscription update (in `bySession` this drain, or an earlier ts advance) clears\n * without a redundant frame; otherwise an empty ts-advance to the frontier is emitted. */\n private sweepPendingFrontiers(drainTs: number, bySession: Map<string, Subscription[]>): void {\n if (this.pendingFrontiers.size === 0) return;\n for (const [sessionId, frontierTs] of this.pendingFrontiers) {\n if (frontierTs > drainTs) continue; // the forwarded commit hasn't drained locally yet\n const session = this.sessions.get(sessionId);\n if (session && session.version.ts < frontierTs && !bySession.has(sessionId)) {\n this.emitEmptyFrontier(session, frontierTs);\n }\n this.pendingFrontiers.delete(sessionId);\n }\n }\n\n private async handleSetAdminAuth(session: Session, msg: Extract<ClientMessage, { type: \"SetAdminAuth\" }>): Promise<void> {\n session.privileged = this.verifyAdmin(msg.key);\n // The client sends SetAdminAuth before subscribing; no re-run needed here.\n }\n\n private async handleSetAuth(session: Session, msg: Extract<ClientMessage, { type: \"SetAuth\" }>): Promise<void> {\n session.identity = msg.token;\n const subs = this.subscriptions.forSession(session.sessionId);\n const modifications: StateModification[] = [];\n for (const sub of subs) {\n try {\n const { value, tables, readRanges, globalTables, diffableRange, diffablePage } = await this.execSub(session, sub.udfPath, sub.args);\n // Recompute `byId`/`range` from THIS fresh (value, readRanges, diffableRange) instead of\n // spreading the sub's stale classification — an identity change can change WHAT a query\n // reads (e.g. an identity-scoped `db.get`, or an identity-scoped range's bounds/filters),\n // so a stale `byId`/`range` here would drive a wrong diff on a later write.\n const byId = classifyByIdRead(value, readRanges) ?? undefined;\n const range = diffableRange ? rangeReadFromDiffable(diffableRange) : undefined;\n // DLR 2c: same reasoning for a page sub — a fresh page (with fresh two-sided bounds) is\n // threaded through as `range` (a page IS a range for invalidation), same as the\n // subscribe-answer path in `doModifyQuerySet`.\n const page = diffablePage ? pageReadFromDiffable(diffablePage) : undefined;\n // DLR Stage 3: an identity change RE-KEYS this sub's resume-registry entry. The read-set was\n // captured under the OLD identity; under the NEW identity the query can read different rows,\n // so the entry must move to `regKey(newIdentity, ...)`. This keeps the load-bearing invariant\n // that `sub.resumeKey === regKey(session.identity, path, args)` at all times — which is what\n // makes the live-re-run upsert (in `sendSessionTransition`) correctly keyed. Upsert the new\n // key first (so `retain` finds it), then retain-new + release-old only when the key actually\n // changed (refCount stays balanced: original subscribe retained the old key). A reconnect\n // under the new identity now finds the migrated entry; a reconnect under the OLD identity\n // misses (its entry TTL-sweeps) → re-run, never a stale skip.\n const newResumeKey = regKey(session.identity, sub.udfPath, sub.args);\n this.resumeRegistry.upsert(newResumeKey, readRanges, tables, session.version.ts, !!(page ?? range) || !!byId, globalTables);\n if (sub.resumeKey !== newResumeKey) {\n this.resumeRegistry.retain(newResumeKey);\n if (sub.resumeKey) this.resumeRegistry.release(sub.resumeKey, Date.now());\n }\n this.subscriptions.add({ ...sub, tables, readRanges, globalTables, byId, range: page ?? range, resumeKey: newResumeKey });\n // M2c fix: an identity flip (SetAuth) can change WHAT a query reads (see the comment on\n // `byId`/`range` above) — including newly reading a global table it didn't before. Wake any\n // disarmed driver the same way the fresh-subscribe/live-re-run paths already do.\n if (globalTables.length > 0) this.fireGlobalSubscribe();\n const key = subKey(session.sessionId, sub.queryId);\n const json = convexToJson(value);\n if (byId && session.supportsQueryDiff) {\n // Reset semantics: this is a RE-BASELINE, not an incremental diff off a single write — the\n // sub's byId may have just changed to a DIFFERENT (keyspace, key, docId) (e.g. an\n // identity-scoped `db.get(ctx.identity)` whose target flips under this very SetAuth), so\n // the row-map must be reseeded from THIS fresh value, never carried forward from whatever\n // (possibly now-stale) map was on file. Reusing the old map here is exactly the bug this\n // fixes: the next incremental write would diff against the OLD id's stale prev-map and\n // accumulate a second, never-pruned entry.\n const { changes, next } = byIdResetChanges(byId.docId, json, session.version.ts);\n this.byIdRowMap.set(key, next);\n modifications.push({ type: \"QueryDiff\", queryId: sub.queryId, changes, checksum: driftChecksum(next), reset: true });\n } else {\n // Not DIFFABLE post-refresh (or a non-capable session): unchanged RERUN path. Drop any\n // stale byIdRowMap entry so a LATER re-classification back to DIFFABLE never resumes\n // incremental diffing from a map keyed to a byId this refresh has just superseded.\n this.byIdRowMap.delete(key);\n modifications.push({ type: \"QueryUpdated\", queryId: sub.queryId, value: json, hash: hashValue(json) });\n }\n } catch (e) {\n modifications.push({ type: \"QueryFailed\", queryId: sub.queryId, error: errMessage(e) });\n }\n }\n const start = session.version;\n const end: StateVersion = { querySet: start.querySet + 1, ts: start.ts };\n session.version = end;\n this.send(session, { type: \"Transition\", startVersion: start, endVersion: end, modifications });\n }\n\n /** Ephemeral broadcast (presence/typing) — bypasses the engine entirely. */\n publishEphemeral(topic: string, event: JSONValue, fromSessionId?: string): void {\n for (const [sessionId, session] of this.sessions) {\n if (sessionId === fromSessionId) continue;\n this.send(session, { type: \"Broadcast\", topic, event });\n }\n }\n}\n","/**\n * Classify a subscription as DIFFABLE_BYID (a single `db.get(id)`) vs RERUN, from its recorded read\n * set + result. A by-id read records EXACTLY one point range in a table's primary keyspace\n * (`table:<enc>`, NOT `index:...`), and returns a single document object or `null`. Anything else —\n * multiple ranges, an index/collect read, a span range, an array result — is RERUN (safe fallback).\n */\nimport type { Value } from \"@helipod/values\";\nimport { deserializeKeyRange, keySuccessor, compareKeyBytes, type SerializedKeyRange } from \"@helipod/index-key-codec\";\nimport type { FilterExpr } from \"@helipod/query-engine\";\n\nexport interface ByIdRead {\n keyspace: string;\n /** base64 of the point-range start bytes (== the doc's primary-key bytes). */\n key: string;\n /** the public document id (the diff Change.key), taken from the returned doc's `_id`. */\n docId: string;\n}\n\nfunction isPointRange(r: SerializedKeyRange): boolean {\n if (!r.keyspace.startsWith(\"table:\")) return false;\n if (r.end === null) return false;\n const { start, end } = deserializeKeyRange(r);\n if (end === null) return false;\n const succ = keySuccessor(start);\n return compareKeyBytes(end, succ) === 0; // end === start followed by 0x00 => a single-key point range\n}\n\n/** A single doc object (has a string `_id`) — not an array, not null-here. */\nfunction singleDocId(value: Value): string | null {\n if (value === null || value === undefined) return \"\"; // absent doc — still by-id, docId unknown-but-empty\n if (Array.isArray(value)) return null;\n if (typeof value === \"object\") {\n const id = (value as Record<string, unknown>)[\"_id\"];\n return typeof id === \"string\" ? id : null;\n }\n return null;\n}\n\nexport function classifyByIdRead(value: Value, readRanges: readonly SerializedKeyRange[]): ByIdRead | null {\n if (readRanges.length !== 1) return null;\n const r = readRanges[0]!;\n if (!isPointRange(r)) return null;\n const docId = singleDocId(value);\n if (docId === null) return null; // array or non-doc scalar => RERUN\n return { keyspace: r.keyspace, key: r.start, docId };\n}\n\n/** A page's fixed metadata, as returned to the guest by `db.query(...).paginate()` — see\n * `DiffablePage`'s doc comment in `packages/executor/src/executor.ts`. */\nexport interface PageMeta {\n nextCursor: string | null;\n hasMore: boolean;\n scanCapped: boolean;\n}\n\nexport interface RangeRead {\n keyspace: string;\n bounds: SerializedKeyRange;\n filters: FilterExpr[];\n order: \"asc\" | \"desc\";\n fields: string[];\n /** Present iff this range is a page (DLR Stage 2c) — the page's own fixed metadata. */\n pageMeta?: PageMeta;\n}\n\n/** Adapt the executor's DiffableRange (identical shape) into the sync tier's RangeRead. Kept as a\n * named boundary so the two packages don't share a type import path the differ also depends on. */\nexport function rangeReadFromDiffable(d: RangeRead): RangeRead {\n return { keyspace: d.keyspace, bounds: d.bounds, filters: d.filters, order: d.order, fields: d.fields };\n}\n\n/** Adapt the executor's DiffablePage (structurally RangeRead & {pageMeta}) into the sync tier's\n * RangeRead, carrying `pageMeta` through verbatim. Kept as a named boundary for the same reason as\n * `rangeReadFromDiffable` above. */\nexport function pageReadFromDiffable(d: RangeRead & { pageMeta: PageMeta }): RangeRead {\n return { keyspace: d.keyspace, bounds: d.bounds, filters: d.filters, order: d.order, fields: d.fields, pageMeta: d.pageMeta };\n}\n","/**\n * The server-side CommitDiffer for DIFFABLE_BYID subscriptions (§DLR 2a). Derives the row `Change[]`\n * a commit implies for a single by-id subscription, from the commit's `WrittenDoc` (no re-read, no\n * re-run of the UDF). Pure + unit-tested in isolation from the handler, which owns the per-sub\n * `byIdRowMap` lifecycle (seed on subscribe/reset, update on each diff, drop on unsubscribe).\n */\nimport type { JSONValue } from \"@helipod/values\";\nimport type { ByIdRead, RangeRead } from \"./classify\";\nimport { applyChanges, type Change, type RowVersion } from \"./change\";\nimport type { WrittenDoc } from \"@helipod/transactor\";\nimport type { DocumentValue } from \"@helipod/docstore\";\nimport { evaluateFilter, extractIndexKey } from \"@helipod/query-engine\";\nimport { deserializeKeyRange, keyInRange, serializeKeyRange } from \"@helipod/index-key-codec\";\n\n/**\n * Given the sub's current row-map and the matching written doc (or `undefined` if this commit's\n * `writtenDocs` had no entry at the sub's `(keyspace, key)` — i.e. the invalidation matched this sub\n * by range/table but the write wasn't actually at its id, which shouldn't happen for a true by-id\n * point-range match but is handled defensively as a no-op), return the changes to apply plus the\n * resulting row-map. `wd === undefined` emits no changes and returns `prev` unchanged.\n */\nexport function byIdChangesFor(\n byId: ByIdRead,\n prev: Map<string, RowVersion>,\n wd: WrittenDoc | undefined,\n): { changes: Change[]; next: Map<string, RowVersion> } {\n if (!wd) return { changes: [], next: prev };\n if (wd.keyspace !== byId.keyspace) {\n // Defensive: the caller (handler.ts) is expected to only ever pass a `wd` it already matched\n // to `byId`'s own keyspace (see the `writtenDocs.find(...)` call site). A mismatch here would\n // mean a caller bug, not a runtime condition to silently recover from — surface it loudly\n // rather than misapplying a foreign-table write to this sub's row-map.\n console.error(\n `[sync] byIdChangesFor: wd.keyspace \"${wd.keyspace}\" !== byId.keyspace \"${byId.keyspace}\" (caller bug)`,\n );\n }\n const docId = wd.docId;\n let change: Change;\n if (wd.newRow === null) change = { t: \"remove\", key: docId };\n else if (!wd.wasPresent || !prev.has(docId)) change = { t: \"add\", key: docId, row: wd.newRow, ts: wd.ts };\n else change = { t: \"edit\", key: docId, row: wd.newRow, ts: wd.ts };\n const changes = [change];\n return { changes, next: applyChanges(prev, changes) };\n}\n\n/** The initial reset for a DIFFABLE_BYID sub: an add for the current doc (if present), else empty\n * (no doc → no changes, empty map). `row === null` means \"no document\" (subscribing to an id that\n * doesn't (yet) exist), not a tombstone — there is nothing to remove from an empty starting map. */\nexport function byIdResetChanges(\n docId: string,\n row: JSONValue | null,\n ts: number,\n): { changes: Change[]; next: Map<string, RowVersion> } {\n const next = new Map<string, RowVersion>();\n if (row === null) return { changes: [], next };\n next.set(docId, { row, ts });\n return { changes: [{ t: \"add\", key: docId, row, ts }], next };\n}\n\n/**\n * The server-side CommitDiffer for DIFFABLE_RANGE subscriptions (§DLR 2b). Derives the row\n * `Change[]` a commit implies for a single index-range subscription, from the commit's\n * `WrittenDoc`s (no re-read, no re-run of the UDF) — the membership diff mirrored below.\n *\n * `toBase64`/`fromBase64` deliberately go through `serializeKeyRange`/`deserializeKeyRange`\n * (the SAME base64 codec `SerializedKeyRange.start` already uses, via a throwaway keyspace)\n * rather than hand-rolling a second `btoa`/`atob` pair — so `orderKeyFor`'s output decodes with\n * the exact bytes-in bytes-out contract the rest of the range machinery (`keyInRange`,\n * `deserializeKeyRange`) already relies on.\n */\nfunction toBase64(bytes: Uint8Array): string {\n return serializeKeyRange({ keyspace: \"\", start: bytes, end: null }).start;\n}\n\nfunction fromBase64(b64: string): Uint8Array {\n return deserializeKeyRange({ keyspace: \"\", start: b64, end: null }).start;\n}\n\n/**\n * The base64 index-entry key for `row` under `range.fields`: `extractIndexKey` (the engine's OWN\n * key extraction, `@helipod/query-engine`) already appends the system `_creationTime`/`_id`\n * tiebreak fields, so this is byte-identical to the doc's real stored index entry — no hand\n * concatenation needed. Used both for membership bounds-checking and as the client's sort key.\n */\nexport function orderKeyFor(range: RangeRead, row: JSONValue): string {\n const key = extractIndexKey(row as unknown as DocumentValue, range.fields);\n return toBase64(key);\n}\n\n/** `[start, end)` bounds check against `range.bounds`, end EXCLUSIVE (matches `KeyRange`/\n * `keyInRange` semantics — the same helper the executor's own range scans use). */\nfunction inBounds(range: RangeRead, orderKeyB64: string): boolean {\n const bounds = deserializeKeyRange(range.bounds);\n return keyInRange(fromBase64(orderKeyB64), bounds);\n}\n\n/** All of `range.filters` (the query's `.where()` residual filters, evaluated with the same\n * `evaluateFilter` the query runtime itself uses) pass against `row`. */\nfunction passesFilters(range: RangeRead, row: JSONValue): boolean {\n return range.filters.every((f) => evaluateFilter(row as unknown as DocumentValue, f));\n}\n\n/** The initial reset for a DIFFABLE_RANGE sub: one `add` per doc, in the caller's already-sorted\n * order (the fresh scan's own result order) — carries each doc's `orderKey` so the client can\n * maintain sort order without re-deriving it. */\nexport function rangeResetChanges(\n range: RangeRead,\n orderedDocs: readonly JSONValue[],\n ts: number,\n): { changes: Change[]; next: Map<string, RowVersion> } {\n const changes: Change[] = [];\n const next = new Map<string, RowVersion>();\n for (const row of orderedDocs) {\n const key = String((row as { _id: unknown })._id);\n const orderKey = orderKeyFor(range, row);\n changes.push({ t: \"add\", key, row, ts, orderKey });\n next.set(key, { row, ts, orderKey });\n }\n return { changes, next };\n}\n\n/**\n * The membership diff for a commit against a DIFFABLE_RANGE sub: for each `WrittenDoc` in the\n * sub's table, compute `before` (was this doc's id already in the sub's row-map) and `after` (is\n * the write's new row non-tombstone, in-bounds, AND filter-passing). `!before && after` is an\n * `add`; `before && after` is an `edit` (a \"move\" — an in-range reorder — is just an edit whose\n * `orderKey` differs from before, no separate change kind); `before && !after` is a `remove`\n * (covers both an actual delete AND a write that crosses OUT of the range/filter); anything else\n * (`!before && !after`, e.g. a write to a doc never in and still not in this range) is a no-op —\n * no `Change` emitted for it.\n */\nexport function rangeChangesFor(\n range: RangeRead,\n prev: Map<string, RowVersion>,\n writtenDocs: readonly WrittenDoc[],\n): { changes: Change[]; next: Map<string, RowVersion> } {\n const changes: Change[] = [];\n for (const wd of writtenDocs) {\n const key = wd.docId;\n const before = prev.has(key);\n const orderKey = wd.newRow !== null ? orderKeyFor(range, wd.newRow) : undefined;\n const after =\n wd.newRow !== null && orderKey !== undefined && inBounds(range, orderKey) && passesFilters(range, wd.newRow);\n if (!before && after) changes.push({ t: \"add\", key, row: wd.newRow!, ts: wd.ts, orderKey: orderKey! });\n else if (before && after) changes.push({ t: \"edit\", key, row: wd.newRow!, ts: wd.ts, orderKey: orderKey! });\n else if (before && !after) changes.push({ t: \"remove\", key });\n // !before && !after => no-op, no change emitted.\n }\n return { changes, next: applyChanges(prev, changes) };\n}\n","/**\n * Per-session flow-control controllers — the server half of Foundation seam 6 (fleet hardening).\n *\n * A reactive fan-out can push faster than a client can read. Two failure modes matter:\n * - **A slow reader** whose OS send buffer fills — without a cap, queued frames grow unbounded and\n * exhaust server memory. `SessionBackpressureController` bounds that: it becomes the SINGLE\n * outbound chokepoint for a session, sending straight through when the socket has room, queueing\n * (up to a frame cap) when it doesn't, and DROPPING frames once the queue is full or the client\n * has been backpressured for too long. Dropped frames are safe — the client resyncs from its last\n * acknowledged version — so we favour dropping over stalling the whole node. `MutationResponse`/\n * `ActionResponse` frames are the one exception: they carry `undroppable: true` and are never\n * dropped by cap or timeout (they only queue behind the cap; the queue's timeout-abandon path\n * also spares them) — a dropped response has no version bracket and no retransmit, so losing one\n * would strand a client-side mutation as permanently \"inflight\" instead of self-healing.\n * But \"never dropped\" cannot mean \"never bounded\" — a client that floods mutations into its own\n * deliberately-slow-reading socket would otherwise grow the undroppable queue without limit and\n * exhaust server memory, the exact resource-exhaustion hole the droppable cap exists to close.\n * So undroppable frames get their OWN cap (`maxUndroppableQueuedFrames`, counted separately from\n * `maxQueuedFrames` — a session's droppable-Transition backlog never affects how much undroppable-\n * response headroom it has, and vice versa). Crucially, exceeding that cap must NOT silently drop\n * the frame — that would corrupt exactly the \"inflight\" invariant this exemption exists to\n * protect. Instead it TERMINATES the session (`onOverflow`, wired by the handler to the same\n * reap-and-close path a dead heartbeat uses). A closed transport is protocol-safe: the client's\n * own close/reconnect handling turns every in-flight request into an explicit unknown-outcome\n * error, which is the honest outcome here — not a silent gap the client believes never happened.\n * - **A dead-but-not-closed connection** (half-open TCP: the peer vanished, no FIN/RST). Nothing\n * reads, nothing errors; the session lingers forever holding subscriptions. `SessionHeartbeat-\n * Controller` reaps it via transport-level ping/pong liveness — NOT inbound-message silence (an\n * idle-but-healthy client sends nothing yet must never be reaped).\n *\n * Both are transport-agnostic: they talk only to `SyncWebSocket`. A socket that cannot ping (the\n * in-process loopback — `bufferedAmount` is always 0, there is no peer to die) is transparently\n * exempt from heartbeat, and its sends always pass straight through the backpressure controller, so\n * loopback behaviour is byte-identical to before these controllers existed.\n */\nimport type { SyncWebSocket } from \"./handler\";\n\nexport interface BackpressureOptions {\n /** Above this `bufferedAmount`, frames queue instead of sending. Default 1 MiB. */\n highWaterBytes?: number;\n /** Queue depth past which new frames are dropped (drop-newest). Default 200. */\n maxQueuedFrames?: number;\n /** Sustained-backpressure duration after which the queue is abandoned to drops. Default 30s. */\n slowClientTimeoutMs?: number;\n /**\n * Cap on queued undroppable (MutationResponse/ActionResponse) frames, counted SEPARATELY from\n * `maxQueuedFrames` (a session's droppable backlog never eats into this budget or vice versa).\n * Defaults to `maxQueuedFrames`'s own (effective, post-default) value — same order of magnitude\n * headroom, no new tuning knob to reason about by default. Exceeding it does not drop the frame\n * (see the class doc) — it terminates the session via `onOverflow`.\n */\n maxUndroppableQueuedFrames?: number;\n}\n\nconst DEFAULT_HIGH_WATER = 1024 * 1024;\nconst DEFAULT_MAX_QUEUED = 200;\nconst DEFAULT_SLOW_CLIENT_MS = 30_000;\n\n/**\n * The single outbound chokepoint for one session. Every server→client frame goes through `send`.\n * A frame is delivered immediately when the socket buffer is below high-water and nothing is\n * already queued; otherwise it queues (FIFO). Once the queue hits `maxQueuedFrames` the newest\n * frame is dropped; once backpressure has been sustained past `slowClientTimeoutMs` the entire\n * queue is abandoned to drops. Drops are counted and warned about exactly once per episode (an\n * episode ends when the session fully drains back to an empty queue with a below-high-water buffer).\n */\n/** One queued frame, tagged with whether it may ever be dropped. */\ninterface QueuedFrame {\n data: string;\n /** True for MutationResponse/ActionResponse — never dropped by cap or by timeout-abandon. */\n undroppable: boolean;\n}\n\nexport class SessionBackpressureController {\n private readonly highWaterBytes: number;\n private readonly maxQueuedFrames: number;\n private readonly slowClientTimeoutMs: number;\n private readonly maxUndroppableQueuedFrames: number;\n private readonly queue: QueuedFrame[] = [];\n private _droppedFrames = 0;\n /** Count of undroppable frames currently sitting in `queue` — the separate overflow budget. */\n private undroppableQueuedCount = 0;\n /** True once `onOverflow` has fired, so a dying session can't fire it twice. */\n private overflowed = false;\n /** Wall-clock ms at which the current backpressure episode began, or null if not backpressured. */\n private backpressureSince: number | null = null;\n /** True once any frame has been dropped in the current episode; resets on full drain. */\n private _droppedThisEpisode = false;\n\n constructor(\n private readonly socket: SyncWebSocket,\n opts: BackpressureOptions = {},\n private readonly now: () => number = () => Date.now(),\n /**\n * Fires exactly once when the undroppable queue overflows its cap. The controller only\n * decides \"this session must die\" — it has no session registry to tear down itself, so it\n * hands off to whatever the owner wires here (the handler reuses the same reap-and-close path\n * a dead heartbeat uses). Defaults to a no-op so standalone/unit use of this class doesn't\n * require wiring one up.\n */\n private readonly onOverflow: () => void = () => {},\n ) {\n this.highWaterBytes = opts.highWaterBytes ?? DEFAULT_HIGH_WATER;\n this.maxQueuedFrames = opts.maxQueuedFrames ?? DEFAULT_MAX_QUEUED;\n this.slowClientTimeoutMs = opts.slowClientTimeoutMs ?? DEFAULT_SLOW_CLIENT_MS;\n this.maxUndroppableQueuedFrames = opts.maxUndroppableQueuedFrames ?? this.maxQueuedFrames;\n }\n\n get droppedFrames(): number {\n return this._droppedFrames;\n }\n\n /** True once anything was dropped since the last fully-drained state (the per-episode warn flag). */\n get droppedThisEpisode(): boolean {\n return this._droppedThisEpisode;\n }\n\n /**\n * The ONLY way frames leave a session. Sends now, queues, or drops per the class contract.\n * `undroppable` (default false) exempts a frame from BOTH drop paths below — the cap check\n * and the sustained-backpressure abandon — so it only ever queues or sends, never vanishes.\n */\n send(data: string, undroppable = false): void {\n // Drain first so a recovered client immediately gets both its backlog and this frame in order.\n this.flush();\n if (this.queue.length === 0 && this.socket.bufferedAmount < this.highWaterBytes) {\n this.socket.send(data);\n return;\n }\n if (undroppable) {\n // The separate, hard cap: once the client is backpressured AND has this many undroppable\n // responses already queued behind it, queuing forever is indistinguishable from the\n // unbounded-memory hole this whole class exists to close. There is no lower-harm move here\n // (dropping would corrupt the \"never silently drop a response\" invariant) — so the session\n // dies instead, with a distinct, greppable reason.\n if (this.undroppableQueuedCount >= this.maxUndroppableQueuedFrames) {\n this.overflow();\n return;\n }\n this.queue.push({ data, undroppable: true });\n this.undroppableQueuedCount += 1;\n return;\n }\n // Backpressured: mark the episode start on first entry.\n if (this.backpressureSince === null) this.backpressureSince = this.now();\n // Give up on a client that has been backpressured too long — abandon the whole backlog + this frame\n // (undroppable frames already queued survive; see `dropQueue`).\n if (this.now() - this.backpressureSince >= this.slowClientTimeoutMs) {\n this.dropQueue();\n this.countDrop();\n return;\n }\n // Bounded queue: past the cap, drop the newest (this) frame — the client resyncs regardless.\n if (this.queue.length >= this.maxQueuedFrames) {\n this.countDrop();\n return;\n }\n this.queue.push({ data, undroppable: false });\n }\n\n /**\n * Deliver as many queued frames as the socket buffer will take. Called before each send and on a\n * periodic sweep, so a client that recovers (or goes terminally slow) without new traffic still\n * gets its queue drained (or abandoned). Resets the episode once fully drained.\n */\n flush(): void {\n while (this.queue.length > 0 && this.socket.bufferedAmount < this.highWaterBytes) {\n const frame = this.queue.shift() as QueuedFrame;\n if (frame.undroppable) this.undroppableQueuedCount -= 1;\n this.socket.send(frame.data);\n }\n if (this.queue.length === 0 && this.socket.bufferedAmount < this.highWaterBytes) {\n // Fully caught up — end the episode so a later re-entry warns afresh.\n this.backpressureSince = null;\n this._droppedThisEpisode = false;\n return;\n }\n // Still backpressured (buffer high and/or frames stuck in the queue).\n if (this.backpressureSince === null) this.backpressureSince = this.now();\n if (this.queue.length > 0 && this.now() - this.backpressureSince >= this.slowClientTimeoutMs) {\n this.dropQueue();\n }\n }\n\n /** Abandon the queue to drops — EXCEPT undroppable frames, which stay queued for a later flush. */\n private dropQueue(): void {\n const survivors = this.queue.filter((f) => f.undroppable);\n const droppedCount = this.queue.length - survivors.length;\n if (droppedCount === 0) return;\n this._droppedFrames += droppedCount;\n this.queue.length = 0;\n this.queue.push(...survivors);\n this.markEpisodeDropped();\n }\n\n private countDrop(): void {\n this._droppedFrames += 1;\n this.markEpisodeDropped();\n }\n\n /**\n * The undroppable queue exceeded its cap. Fires `onOverflow` exactly once (a session that's\n * already dying doesn't need a second kill signal) with a distinct, greppable log reason —\n * deliberately NOT reusing the backpressure-drop warning text, since this is a different failure\n * mode (session termination, not a dropped frame) that ops needs to be able to tell apart.\n */\n private overflow(): void {\n if (this.overflowed) return;\n this.overflowed = true;\n console.warn(\n `[sync] undroppable-queue-overflow: terminating session (queued undroppable frames >= cap=${this.maxUndroppableQueuedFrames})`,\n );\n this.onOverflow();\n }\n\n private markEpisodeDropped(): void {\n if (this._droppedThisEpisode) return;\n this._droppedThisEpisode = true;\n // Exactly one warn per episode — re-warns only after a full drain resets the flag.\n console.warn(`[sync] backpressure: dropping frames for slow client (total dropped=${this._droppedFrames})`);\n }\n}\n\nexport interface HeartbeatOptions {\n /** How often to send a transport-level ping. Default 30s. */\n pingIntervalMs?: number;\n /** Consecutive unanswered pings before the session is declared dead. Default 2. */\n missedPongLimit?: number;\n}\n\nconst DEFAULT_PING_INTERVAL_MS = 30_000;\nconst DEFAULT_MISSED_PONG_LIMIT = 2;\n\n/**\n * Transport-level ping/pong liveness for one session. Every `pingIntervalMs` it sends a ping and\n * increments a miss counter; a pong (or any inbound activity via `noteActivity`) resets it to zero.\n * After `missedPongLimit` consecutive unanswered pings the session is declared dead and `onDead`\n * fires exactly once. A socket without a `ping` capability (loopback) is exempt: `start()` is a\n * no-op, so its session is never reaped.\n */\nexport class SessionHeartbeatController {\n private readonly pingIntervalMs: number;\n private readonly missedPongLimit: number;\n private timer: ReturnType<typeof setInterval> | null = null;\n private missed = 0;\n private dead = false;\n\n constructor(\n private readonly socket: SyncWebSocket,\n private readonly onDead: () => void,\n opts: HeartbeatOptions = {},\n ) {\n this.pingIntervalMs = opts.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;\n this.missedPongLimit = opts.missedPongLimit ?? DEFAULT_MISSED_PONG_LIMIT;\n }\n\n /** Begin pinging. No-op when the socket cannot ping (loopback exemption) or already started. */\n start(): void {\n if (!this.socket.ping) return;\n if (this.timer !== null) return;\n this.missed = 0;\n this.dead = false;\n this.timer = setInterval(() => this.tick(), this.pingIntervalMs);\n }\n\n stop(): void {\n if (this.timer !== null) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n /** Any inbound message is liveness credit — resets the consecutive-miss counter. */\n noteActivity(): void {\n this.missed = 0;\n }\n\n private tick(): void {\n // Register a pong handler that resets the miss counter, then send the ping. A ping counts as a\n // miss the instant it's outstanding; the pong (arriving before the next tick) cancels it.\n this.socket.ping?.(() => {\n this.missed = 0;\n });\n this.missed += 1;\n if (this.missed >= this.missedPongLimit) this.fireDead();\n }\n\n private fireDead(): void {\n if (this.dead) return;\n this.dead = true;\n this.stop();\n this.onDead();\n }\n}\n","/**\n * The client-side reducer: applies `ServerMessage`s to local state. It enforces the\n * version-bracket contract — a `Transition` is applied only if its `startVersion` matches the\n * client's current version; otherwise a frame was missed and the client must **resync from\n * scratch**. This is what makes server-side frame drops (backpressure) safe. The real client\n * SDK (M10) builds on this.\n */\nimport type { JSONValue } from \"@helipod/values\";\nimport { versionsEqual, INITIAL_VERSION, type ServerMessage, type StateVersion } from \"./protocol\";\n\nexport interface MutationOutcome {\n success: boolean;\n value?: JSONValue;\n error?: string;\n}\n\nexport interface SyncClientState {\n version: StateVersion;\n queries: Map<number, JSONValue>;\n needsResync: boolean;\n mutationResults: Map<string, MutationOutcome>;\n broadcasts: Array<{ topic: string; event: JSONValue }>;\n}\n\nexport function createClientState(): SyncClientState {\n return {\n version: { ...INITIAL_VERSION },\n queries: new Map(),\n needsResync: false,\n mutationResults: new Map(),\n broadcasts: [],\n };\n}\n\nexport function applyServerMessage(state: SyncClientState, msg: ServerMessage): void {\n switch (msg.type) {\n case \"Transition\": {\n if (!versionsEqual(msg.startVersion, state.version)) {\n state.needsResync = true; // missed a frame → resync from scratch\n return;\n }\n for (const mod of msg.modifications) {\n if (mod.type === \"QueryUpdated\") state.queries.set(mod.queryId, mod.value);\n else if (mod.type === \"QueryRemoved\") state.queries.delete(mod.queryId);\n // QueryFailed: leave the previous value; a real client would surface the error.\n }\n state.version = msg.endVersion;\n return;\n }\n case \"MutationResponse\":\n state.mutationResults.set(\n msg.requestId,\n msg.success ? { success: true, value: msg.value } : { success: false, error: msg.error },\n );\n return;\n case \"Broadcast\":\n state.broadcasts.push({ topic: msg.topic, event: msg.event });\n return;\n default:\n return;\n }\n}\n"],"mappings":"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","names":["deserializeKeyRange","IntervalIndex","deserializeKeyRange","deserializeKeyRange","subKey","rrKey"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@helipod/sync",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"clean": "rm -rf dist .turbo"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@helipod/errors": "0.1.0",
|
|
27
|
+
"@helipod/index-key-codec": "0.1.0",
|
|
28
|
+
"@helipod/query-engine": "0.1.0",
|
|
29
|
+
"@helipod/values": "0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@helipod/docstore": "0.1.0",
|
|
33
|
+
"@helipod/docstore-sqlite": "0.1.0",
|
|
34
|
+
"@helipod/executor": "0.1.0",
|
|
35
|
+
"@helipod/id-codec": "0.1.0",
|
|
36
|
+
"@helipod/transactor": "0.1.0",
|
|
37
|
+
"@types/node": "^22.10.5",
|
|
38
|
+
"tsup": "^8.3.5",
|
|
39
|
+
"typescript": "^5.7.2",
|
|
40
|
+
"vitest": "^2.1.8"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/helipod-sh/helipod.git",
|
|
48
|
+
"directory": "packages/sync"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/helipod-sh/helipod"
|
|
51
|
+
}
|