@supalive/core 1.15.0 → 1.17.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.
Files changed (150) hide show
  1. package/dist/index-B2nE1y9E.d.ts +2384 -0
  2. package/dist/index-B2nE1y9E.d.ts.map +1 -0
  3. package/dist/index-B4UxN1ZL.d.ts +2365 -0
  4. package/dist/index-B4UxN1ZL.d.ts.map +1 -0
  5. package/dist/index-B5hAqW3O.d.ts +2402 -0
  6. package/dist/index-B5hAqW3O.d.ts.map +1 -0
  7. package/dist/index-B8JFAyw4.d.ts +2377 -0
  8. package/dist/index-B8JFAyw4.d.ts.map +1 -0
  9. package/dist/index-BI3MfAyI.d.ts +2399 -0
  10. package/dist/index-BI3MfAyI.d.ts.map +1 -0
  11. package/dist/index-BL90lDw7.d.ts +2400 -0
  12. package/dist/index-BL90lDw7.d.ts.map +1 -0
  13. package/dist/index-BvryqYUm.d.ts +2394 -0
  14. package/dist/index-BvryqYUm.d.ts.map +1 -0
  15. package/dist/index-CfcrWLLm.d.ts +2377 -0
  16. package/dist/index-CfcrWLLm.d.ts.map +1 -0
  17. package/dist/index-DYkOBnw_.d.ts +2378 -0
  18. package/dist/index-DYkOBnw_.d.ts.map +1 -0
  19. package/dist/index-DcM2uKpT.d.ts +2393 -0
  20. package/dist/index-DcM2uKpT.d.ts.map +1 -0
  21. package/dist/index-DsvdhOKL.d.ts +2400 -0
  22. package/dist/index-DsvdhOKL.d.ts.map +1 -0
  23. package/dist/index-ggi1DrSG.d.ts +2336 -0
  24. package/dist/index-ggi1DrSG.d.ts.map +1 -0
  25. package/dist/mysql-07ROfbBZ.d.ts +114 -0
  26. package/dist/mysql-07ROfbBZ.d.ts.map +1 -0
  27. package/dist/mysql-BJaQhVGE.d.ts +114 -0
  28. package/dist/mysql-BJaQhVGE.d.ts.map +1 -0
  29. package/dist/mysql-BKs5PTSR.d.ts +114 -0
  30. package/dist/mysql-BKs5PTSR.d.ts.map +1 -0
  31. package/dist/mysql-BTigT6mP.d.ts +114 -0
  32. package/dist/mysql-BTigT6mP.d.ts.map +1 -0
  33. package/dist/mysql-C7Km_lgc.d.ts +114 -0
  34. package/dist/mysql-C7Km_lgc.d.ts.map +1 -0
  35. package/dist/mysql-CXymz1Eu.d.ts +114 -0
  36. package/dist/mysql-CXymz1Eu.d.ts.map +1 -0
  37. package/dist/mysql-ClUK7S2j.js +623 -0
  38. package/dist/mysql-ClUK7S2j.js.map +1 -0
  39. package/dist/mysql-CtpWqOhO.d.ts +114 -0
  40. package/dist/mysql-CtpWqOhO.d.ts.map +1 -0
  41. package/dist/mysql-Cxro4f6T.d.ts +114 -0
  42. package/dist/mysql-Cxro4f6T.d.ts.map +1 -0
  43. package/dist/mysql-DNiYIZCK.d.ts +114 -0
  44. package/dist/mysql-DNiYIZCK.d.ts.map +1 -0
  45. package/dist/mysql-DS1s1c5M.d.ts +114 -0
  46. package/dist/mysql-DS1s1c5M.d.ts.map +1 -0
  47. package/dist/mysql-fTKZ1zaF.js +623 -0
  48. package/dist/mysql-fTKZ1zaF.js.map +1 -0
  49. package/dist/mysql-qTszEKAU.d.ts +114 -0
  50. package/dist/mysql-qTszEKAU.d.ts.map +1 -0
  51. package/dist/mysql-qW13LcGQ.d.ts +114 -0
  52. package/dist/mysql-qW13LcGQ.d.ts.map +1 -0
  53. package/dist/overlap-checker-1TwhkAwy.js +259 -0
  54. package/dist/overlap-checker-1TwhkAwy.js.map +1 -0
  55. package/dist/overlap-checker-CGlxWcyb.js +259 -0
  56. package/dist/overlap-checker-CGlxWcyb.js.map +1 -0
  57. package/dist/postgres-B0fqJe1X.d.ts +118 -0
  58. package/dist/postgres-B0fqJe1X.d.ts.map +1 -0
  59. package/dist/postgres-B5-5vgLJ.d.ts +118 -0
  60. package/dist/postgres-B5-5vgLJ.d.ts.map +1 -0
  61. package/dist/postgres-BATDLPml.d.ts +118 -0
  62. package/dist/postgres-BATDLPml.d.ts.map +1 -0
  63. package/dist/postgres-BUVYjnaT.d.ts +118 -0
  64. package/dist/postgres-BUVYjnaT.d.ts.map +1 -0
  65. package/dist/postgres-Bk_8PFRT.d.ts +118 -0
  66. package/dist/postgres-Bk_8PFRT.d.ts.map +1 -0
  67. package/dist/postgres-Bx79_JEy.d.ts +118 -0
  68. package/dist/postgres-Bx79_JEy.d.ts.map +1 -0
  69. package/dist/postgres-C3w741VH.d.ts +118 -0
  70. package/dist/postgres-C3w741VH.d.ts.map +1 -0
  71. package/dist/postgres-CAZYYgbZ.d.ts +118 -0
  72. package/dist/postgres-CAZYYgbZ.d.ts.map +1 -0
  73. package/dist/postgres-CN03Espg.js +868 -0
  74. package/dist/postgres-CN03Espg.js.map +1 -0
  75. package/dist/postgres-CRPJdZx2.js +868 -0
  76. package/dist/postgres-CRPJdZx2.js.map +1 -0
  77. package/dist/postgres-DCXnKaJr.d.ts +118 -0
  78. package/dist/postgres-DCXnKaJr.d.ts.map +1 -0
  79. package/dist/postgres-DgCYPf0j.d.ts +118 -0
  80. package/dist/postgres-DgCYPf0j.d.ts.map +1 -0
  81. package/dist/postgres-Dnshux6k.d.ts +118 -0
  82. package/dist/postgres-Dnshux6k.d.ts.map +1 -0
  83. package/dist/postgres-bhVzeRf2.d.ts +118 -0
  84. package/dist/postgres-bhVzeRf2.d.ts.map +1 -0
  85. package/dist/query-LJranz0c.js +795 -0
  86. package/dist/query-LJranz0c.js.map +1 -0
  87. package/dist/query-q1ZvKrZO.js +762 -0
  88. package/dist/query-q1ZvKrZO.js.map +1 -0
  89. package/dist/realtime_db-B0Tin87E.js +312 -0
  90. package/dist/realtime_db-B0Tin87E.js.map +1 -0
  91. package/dist/realtime_db-DVhhasJj.js +312 -0
  92. package/dist/realtime_db-DVhhasJj.js.map +1 -0
  93. package/dist/router-Bk0eLfmB.js +285 -0
  94. package/dist/router-Bk0eLfmB.js.map +1 -0
  95. package/dist/router-DP2ThAwh.js.map +1 -1
  96. package/dist/src/client/index.d.ts +2 -2
  97. package/dist/src/client/index.js +95 -20
  98. package/dist/src/client/index.js.map +1 -1
  99. package/dist/src/exports/mysql.d.ts +1 -1
  100. package/dist/src/exports/mysql.js +1 -1
  101. package/dist/src/exports/postgres.d.ts +1 -1
  102. package/dist/src/exports/postgres.js +1 -1
  103. package/dist/src/exports/procedure.d.ts +2 -2
  104. package/dist/src/exports/procedure.js +1 -1
  105. package/dist/src/exports/schema-sql.d.ts +1 -1
  106. package/dist/src/exports/server.d.ts +13 -6
  107. package/dist/src/exports/server.d.ts.map +1 -1
  108. package/dist/src/exports/server.js +802 -252
  109. package/dist/src/exports/server.js.map +1 -1
  110. package/dist/src/exports/sub-manager-worker-entry.js +1 -1
  111. package/dist/src/exports/types.d.ts +2 -2
  112. package/dist/src/exports/types.js +1 -1
  113. package/dist/sub-worker-dispatch-D7jfw6ys.js +826 -0
  114. package/dist/sub-worker-dispatch-D7jfw6ys.js.map +1 -0
  115. package/dist/sub-worker-dispatch-qiSs5Xkm.js +826 -0
  116. package/dist/sub-worker-dispatch-qiSs5Xkm.js.map +1 -0
  117. package/dist/types_client_rpc-CWZmlWmM.js +145 -0
  118. package/dist/types_client_rpc-CWZmlWmM.js.map +1 -0
  119. package/dist/types_client_rpc-D4td5W0Y.js +141 -0
  120. package/dist/types_client_rpc-D4td5W0Y.js.map +1 -0
  121. package/dist/types_server-AXVa_2mF.d.ts +684 -0
  122. package/dist/types_server-AXVa_2mF.d.ts.map +1 -0
  123. package/dist/types_server-BhC19R2a.js.map +1 -1
  124. package/dist/types_server-BmIgDd8d.d.ts +614 -0
  125. package/dist/types_server-BmIgDd8d.d.ts.map +1 -0
  126. package/dist/types_server-C6B9_b1R.d.ts +662 -0
  127. package/dist/types_server-C6B9_b1R.d.ts.map +1 -0
  128. package/dist/types_server-CUPM08l2.d.ts +684 -0
  129. package/dist/types_server-CUPM08l2.d.ts.map +1 -0
  130. package/dist/types_server-CtYRmHdO.d.ts +684 -0
  131. package/dist/types_server-CtYRmHdO.d.ts.map +1 -0
  132. package/dist/types_server-DHRi8bGO.d.ts +684 -0
  133. package/dist/types_server-DHRi8bGO.d.ts.map +1 -0
  134. package/dist/types_server-DRr5yKy1.d.ts +655 -0
  135. package/dist/types_server-DRr5yKy1.d.ts.map +1 -0
  136. package/dist/types_server-DmCbve3w.d.ts +684 -0
  137. package/dist/types_server-DmCbve3w.d.ts.map +1 -0
  138. package/dist/types_server-DoeybfmY.d.ts +684 -0
  139. package/dist/types_server-DoeybfmY.d.ts.map +1 -0
  140. package/dist/types_server-Gs_vSVaS.d.ts +655 -0
  141. package/dist/types_server-Gs_vSVaS.d.ts.map +1 -0
  142. package/dist/types_server-ILUmPFvk.d.ts +614 -0
  143. package/dist/types_server-ILUmPFvk.d.ts.map +1 -0
  144. package/dist/types_server-JK14NE0j.d.ts +684 -0
  145. package/dist/types_server-JK14NE0j.d.ts.map +1 -0
  146. package/dist/types_server-hYv2--1M.d.ts +684 -0
  147. package/dist/types_server-hYv2--1M.d.ts.map +1 -0
  148. package/dist/types_server-kkQJDSjA.d.ts +684 -0
  149. package/dist/types_server-kkQJDSjA.d.ts.map +1 -0
  150. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":["logger","baseLogger","types.RegisterSubscriptionResultSchema","types.RegisterSubscriptionBatchResultSchema","types.UpdateSubscriptionReadSetResultSchema","types.UnregisterSubscriptionResultSchema","types.UnregisterSubscriptionsResultSchema","types.InvalidateWritesetResultSchema","logger","baseLogger","Client","logger","baseLogger","parseMinTs","logger","baseLogger","uuidv4","logger","baseLogger","WebSocketServer"],"sources":["../../../src/server/sub_hash.ts","../../../src/server/one-shot-query.ts","../../../src/server/sub-manager-client.ts","../../../src/jobs/scheduler.ts","../../../src/server/rate-limit.ts","../../../src/server/http-rpc.ts","../../../src/server/supalive-server.ts","../../../src/server/hash.ts","../../../src/server/sub-manager.ts"],"sourcesContent":["// Server-only hashing helpers. Kept off the `helper.ts` module so a browser\n// bundle that only imports `stableStringify` doesn't pull in `hash-wasm`.\n\nimport { sha1 } from \"hash-wasm\";\nimport { stableStringify } from \"../helper\";\n\n/** Compute stable hash using json-stable-stringify and SHA1. */\nexport async function getHashOf(input: unknown): Promise<string> {\n const stableStr = typeof input === \"string\" ? input : stableStringify(input);\n return sha1(stableStr ?? \"\");\n}\n\n/**\n * Default identity literal used when neither a procedure-level\n * `queryIdentity` override nor a per-session user is available.\n */\nexport const ANONYMOUS_IDENTITY = \"anonymous\";\n\n/**\n * Resolve the segmentation key that participates in the subId/cacheKey hash for\n * a query. Shared by every query transport (WS subscribe, WS one-shot call, and\n * HTTP RPC) so identity — and therefore cache segmentation — is computed\n * identically regardless of how the query is invoked. Priority:\n *\n * 1. Procedure-level `queryIdentity` override:\n * - `false` → `ANONYMOUS_IDENTITY` (cache shared across users)\n * - `string` → that literal\n * - `function` → serverCtx + input → string | null | undefined\n * 2. `getUserId(serverCtx)` → falls back to `ANONYMOUS_IDENTITY` when it\n * returns null/undefined or isn't configured.\n */\nexport function resolveQueryIdentity<TContext>(\n procedure: {\n queryIdentity?:\n | false\n | string\n | ((serverCtx: TContext | undefined, input: unknown) => string | null | undefined);\n },\n serverCtx: TContext | undefined,\n input: unknown,\n getUserId?: (ctx: TContext | undefined) => string | null,\n): string {\n const override = procedure.queryIdentity;\n if (override !== undefined) {\n if (override === false) return ANONYMOUS_IDENTITY;\n if (typeof override === \"string\") return override;\n if (typeof override === \"function\") {\n const value = override(serverCtx, input);\n if (value === null || value === undefined) return ANONYMOUS_IDENTITY;\n return value;\n }\n }\n if (!getUserId) return ANONYMOUS_IDENTITY;\n return getUserId(serverCtx) ?? ANONYMOUS_IDENTITY;\n}\n\n/**\n * Compute a subscription id from hash of procedure + input + queryIdentity.\n *\n * `queryIdentity` is the segmentation key for cache/subscriptions. By default\n * it is the authenticated user's id (so two users get distinct subscriptions\n * for the same query), but a procedure may override it to share cache/subs\n * across users (e.g. for a public, user-independent query).\n */\nexport async function generateSubscriptionId(\n procedure: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const inputStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${procedure}_${inputStr}_${queryIdentity}`);\n return `sub_${hash}`;\n}\n\n/**\n * Compute cache key from query name + args + queryIdentity. The identity\n * participates so that user-specific results don't poison each other's\n * cache when a procedure does authorize per-user.\n */\nexport async function generateCacheKey(\n queryName: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const argsStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${queryName}_${argsStr}_${queryIdentity}`);\n return `ck_${hash}`;\n}\n","import type { SupaliveDb } from \"../db/realtime_db\";\nimport type { CacheLayer } from \"../db/cache\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport type { QueryCtx } from \"../router/procedure\";\nimport { stableStringify } from \"../helper\";\nimport { generateCacheKey, resolveQueryIdentity } from \"./sub_hash\";\nimport { evaluateCacheFreshness } from \"./overlap-checker\";\nimport { resolveReadRouting, makeConnRouting } from \"../db/read-routing\";\nimport { logger } from \"../logger\";\n\nexport interface OneShotQueryDeps<TContext> {\n /** Same SupaliveDb the server/subscription path uses. */\n db: SupaliveDb;\n /** Shared query cache (Redis-backed). */\n cache: CacheLayer;\n /** Same identity resolver as subscriptions, so cache keys are segmented\n * identically whether a query arrives over WS subscribe, WS one-shot, or\n * HTTP RPC. */\n getUserId?: (ctx: TContext | undefined) => string | null;\n /** Reads the current in-memory retention watermark — the oldest retained\n * commit-log ts (see {@link RetentionWatermark}). A plain variable read, no\n * per-query round trip. */\n getMinRetainedTs: () => bigint;\n}\n\n/**\n * Execute a one-shot query (WS `call` or HTTP RPC) with the same caching model\n * as a live subscription — but without registering a tracked subscription.\n *\n * Flow (mirrors `handleSubscribe` / worker `register`):\n * 1. Resolve `queryIdentity` → `cacheKey` (identical segmentation to subs).\n * 2. Pick the read connection ONCE (replica when it's caught up to the client's\n * `clientMinTs`, else primary) and use it for BOTH the freshness scan and\n * the row reads, so a cache HIT offloads the primary too — not just a miss.\n * 3. Freshness check via {@link evaluateCacheFreshness} on that connection:\n * cached metadata + the retention watermark + a between-ts commit-log scan.\n * 4. Fresh → return the cached data.\n * Stale/miss → recompute via `queryInternalOn` (on the chosen connection)\n * and write BOTH the data and the metadata (readSet + snapshotTs) back, so\n * a later one-shot or a fresh subscription can reuse it.\n *\n * The retention guard is what makes an untracked one-shot safe: nothing holds\n * the prune watermark down for it, so an entry whose snapshot has fallen below\n * the oldest retained commit log is treated as a miss (its freshness scan would\n * be incomplete).\n *\n * `lastSnapshotTs` is the primary freshness upper bound — the latest snapshot ts\n * as of this call. The caller loads it (via `db.getLatestSnapshotTimestamp()`)\n * and passes it in, exactly like the subscribe path loads it before `register`,\n * so the snapshot read lives at the caller seam rather than being hidden here.\n *\n * `clientMinTs` is the caller's read high-water (the largest commit/snapshot ts\n * it has already observed). It gates replica routing: a query only runs on the\n * replica when the replica has applied at least `clientMinTs` — otherwise reads\n * (and the freshness scan) stay on the primary, preserving read-your-writes and\n * monotonic reads. It is clamped to `lastSnapshotTs` here, so a client can at\n * most force the primary (never a value beyond it). Returns the data plus the\n * `ts` the result reflects, which the caller echoes so the client can advance\n * its high-water.\n */\nexport async function runCachedQuery<TContext>(\n deps: OneShotQueryDeps<TContext>,\n procedure: RegisteredProcedure<TContext>,\n procedureName: string,\n input: unknown,\n serverCtx: TContext | undefined,\n lastSnapshotTs: bigint,\n clientMinTs?: bigint,\n): Promise<{ data: unknown; ts: bigint }> {\n const queryIdentity = resolveQueryIdentity(procedure, serverCtx, input, deps.getUserId);\n const cacheKey = await generateCacheKey(procedureName, stableStringify(input), queryIdentity);\n\n // ── Read routing (chosen ONCE, then used for BOTH freshness and the row\n // reads) ──────────────────────────────────────────────────────────────────\n // The shared router (db/read-routing.ts) clamps the client high-water and\n // picks the replica only when it has applied at least that ts (read-your-writes\n // + monotonic reads), else the primary. A one-shot query DEFAULTS to that\n // choice — including the freshness scan, which runs on the SAME connection at\n // the SAME ts, so a cache HIT offloads the primary too, not just a miss.\n const routing = await resolveReadRouting(deps.db, lastSnapshotTs, clientMinTs ?? 0n);\n const readTs = routing.readTs;\n\n const metadata = await deps.cache.getQueryCacheMetaData(cacheKey);\n const freshness = await evaluateCacheFreshness(routing.readImpl, metadata, readTs, deps.getMinRetainedTs());\n\n if (freshness.fresh) {\n const cached = await deps.cache.getQueryCacheFor(cacheKey);\n // Metadata says fresh, but only trust the data blob when it is actually\n // present AND its version matches the metadata we validated — the data key\n // may have been evicted (shorter TTL) or, under a concurrent write, the two\n // keys may have diverged. Either way, fall through to recompute.\n if (cached && metadata && cached.version === metadata.version) {\n // Mirror the subscription worker's register path (sub-worker.ts): the\n // cache is still valid at a *newer* snapshot, so push the stored horizon\n // forward (preserving data version + readSet). Without this an untracked\n // one-shot re-validates from the same old snapshot on every call, so its\n // between-ts scan window grows without bound between recomputes. Advancing\n // it collapses the next scan to ≈ empty until the next write, and keeps the\n // horizon above the retention watermark. Fire-and-forget: monotonic CAS\n // that won't clobber a fresher recompute, so it adds no latency.\n if (readTs > metadata.lastSnapshotTs) {\n deps.cache\n .advanceQueryCacheMetadata(cacheKey, {\n lastSnapshotTs: readTs,\n version: metadata.version,\n readSet: metadata.readSet,\n })\n .catch((err) => logger.error(err, \"one-shot horizon advance failed\"));\n }\n // The served data reflects its own compute snapshot (metadata horizon) and\n // was just proven valid up to readTs — report the max so the client's\n // high-water never goes backward relative to the bytes it received.\n const servedTs = readTs > metadata.lastSnapshotTs ? readTs : metadata.lastSnapshotTs;\n return { data: cached.data, ts: servedTs };\n }\n }\n\n // Miss / stale → recompute. Reads default to the chosen connection (replica\n // when caught up, else primary); the handler can override before its first\n // read via ctx.usePrimaryConn()/useReplicaConn(). The cache entry is stamped\n // with the FINAL connection's snapshot ts (result.ts), so a later freshness\n // scan uses the correct lower bound.\n const result = await deps.db.queryInternalOn({ impl: routing.readImpl, beginTs: readTs }, async (ctx) => {\n const fn = procedure.fn as (c: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n const qctx: QueryCtx<TContext> = {\n db: ctx,\n serverCtx,\n ...makeConnRouting(ctx, routing),\n };\n return fn(qctx, input);\n });\n\n // version == the compute ts, so the data blob and metadata are written with a\n // matching version and cross-check on read.\n await deps.cache\n .setQueryCacheAndMetadataFor(cacheKey, result.data, {\n lastSnapshotTs: result.ts,\n version: String(result.ts),\n readSet: result.readSet,\n })\n .catch((err) => logger.error(err, \"one-shot query cache write failed\"));\n\n return { data: result.data, ts: result.ts };\n}\n","import { Client } from \"rpc-websockets\";\nimport z, { type ZodType } from \"zod\";\nimport * as types from \"./sub-manager-types\";\nimport type { WriteEntry } from \"../db/types_db\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { supaliveStringify } from \"../helper\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR_CLIENT\" },\n { level: logLevelOf(\"SUB_MANAGER_CLIENT_LOG_LEVEL\") },\n);\n\n/**\n * Callback the app server registers to process invalidations that were\n * deferred during a sub-manager outage. When the sub-manager reconnects and\n * the buffered writeSets are flushed in one batched RPC, the resulting\n * `affected[]` is handed to this callback — which is expected to do the\n * same work as the normal per-mutation `reexecuteAffectedQuery` path.\n */\nexport type AffectedHandler = (\n affected: types.AffectedSubscription[],\n) => Promise<void>;\n\n/**\n * Callback the app server registers to drive recovery on sub-manager\n * reconnect. Iterates `serverSubscriptions`, reads `sl:reg:<subId>` from\n * Redis, and re-registers each known sub. Resolves when complete; only then\n * does the client's buffer flush and gated calls release.\n */\nexport type RecoveryDriver = () => Promise<void>;\n\n/**\n * The transport-agnostic contract the app server uses to talk to the\n * subscription manager. Implemented by:\n *\n * • {@link SubManagerClient} — the WebSocket RPC client, for a sub-manager\n * running in its own process (`subManagerUrl`); and\n * • the object returned by `SubscriptionManager.localLink()` — a direct\n * in-process link when the manager is embedded in the app server (no\n * socket, no port, no serialization).\n *\n * Injecting this into `SupaliveServerConfig.subManager` lets a deployment run\n * combined (one process) now and split the sub-manager out later by switching\n * back to `subManagerUrl`, with no other code changes.\n */\nexport interface SubManagerLink {\n registerSubscription(\n params: types.RegisterSubscriptionParams,\n ): Promise<types.RegisterSubscriptionResult>;\n registerSubscriptionBatch(\n params: types.RegisterSubscriptionParams[],\n ): Promise<types.RegisterSubscriptionResult[]>;\n updateSubscriptionReadSet(\n params: types.UpdateSubscriptionReadSetParams,\n ): Promise<types.UpdateSubscriptionReadSetResult>;\n unregisterSubscription(\n params: types.UnregisterSubscriptionParams,\n ): Promise<types.UnregisterSubscriptionResult>;\n unregisterSubscriptions(\n params: types.UnregisterSubscriptionsParams,\n ): Promise<types.UnregisterSubscriptionsResult>;\n invalidateWriteset(\n params: types.InvalidateWritesetParams,\n ): Promise<types.InvalidateWritesetResult>;\n /**\n * Register recovery for the reconnect path. A no-op for the in-process link,\n * which never disconnects.\n */\n setRecoveryDriver(driver: RecoveryDriver): void;\n /**\n * Register the post-recovery batched-invalidate handler. A no-op for the\n * in-process link.\n */\n setAffectedHandler(handler: AffectedHandler): void;\n}\n\nexport interface SubManagerClientOptions {\n url: string;\n /**\n * Maximum number of buffered WriteEntry records held during an outage.\n * If exceeded, the buffer is dropped — the commit-log replay path during\n * the next register call will catch up any missed invalidations, at the\n * cost of a brief recompute spike. Default 50_000 entries.\n */\n invalidateBufferLimit?: number;\n}\n\ntype ReadyState = \"ready\" | \"busy\";\n\nexport const subscriptionManagerDataPack = {\n encode: (data: unknown) => supaliveStringify(data),\n decode: (data: string) => JSON.parse(data),\n};\n\n\n/**\n * RPC client for the (single) sub-manager process.\n *\n * Design (Convex-inspired):\n * • The sub-manager is in-memory and ephemeral. There is no version\n * protocol, no client-side registration registry, no OUT_OF_SYNC\n * replay logic. All recovery is driven from the app server using\n * state it already has (`serverSubscriptions`) plus registration\n * records persisted to Redis at `sl:reg:<subId>`.\n *\n * • Out-of-order `updateSubscriptionReadSet` calls are filtered server\n * side using `lastSnapshotTs` (monotonic DB commit timestamp).\n *\n * • On rpc-websockets reconnect, the client:\n * 1. Transitions to `busy` (any in-flight `invalidateWriteset`\n * buffers its writeSet).\n * 2. Invokes the app server's `recoveryDriver` to re-register all\n * known subs.\n * 3. Flushes the buffer as one batched `invalidateWriteset` RPC and\n * hands the resulting `affected[]` to `affectedHandler`.\n * 4. Transitions to `ready`.\n *\n * • During an extended outage, the buffer is bounded; if exceeded, it\n * is dropped and we rely on the commit-log replay built into\n * `SubscriptionWorker.register()` to catch each sub up on the next\n * re-register.\n */\nexport class SubManagerClient implements SubManagerLink {\n private client: Client;\n private state: ReadyState = \"busy\";\n private hasConnectedOnce = false;\n\n /** Resolves whenever state transitions to 'ready'. Replaced on each busy→ready cycle. */\n private readyPromise: Promise<void>;\n private resolveReady!: () => void;\n\n /** Writeset entries accumulated while state === 'busy'. Flushed as one RPC after recovery. */\n private bufferedWriteSets: WriteEntry[] = [];\n private readonly bufferLimit: number;\n private bufferDroppedDuringOutage = false;\n\n private recoveryDriver?: RecoveryDriver;\n private affectedHandler?: AffectedHandler;\n\n constructor(urlOrOptions: string | SubManagerClientOptions) {\n const opts = typeof urlOrOptions === \"string\"\n ? { url: urlOrOptions }\n : urlOrOptions;\n\n this.bufferLimit = opts.invalidateBufferLimit ?? 50_000;\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n\n this.client = new Client(opts.url, {\n dataPack: subscriptionManagerDataPack,\n });\n this.client.setAutoReconnect(true);\n this.client.setMaxReconnects(0);\n // Ensure the dataPack is set even if rpc-websockets doesn't pass it through to the Client constructor\n this.client.dataPack = subscriptionManagerDataPack;\n\n this.client.on(\"open\", this.handleOpen.bind(this));\n this.client.on(\"close\", this.handleClose.bind(this));\n this.client.on(\"error\", (err: unknown) => logger.warn(err, \"sub-manager client socket error\"));\n }\n\n /**\n * Wire up the app-server-driven recovery. Must be called before any\n * disconnect/reconnect cycle for buffered writeSets to be flushed and\n * subscriptions re-registered.\n */\n setRecoveryDriver(driver: RecoveryDriver): void {\n this.recoveryDriver = driver;\n }\n\n /**\n * Wire up the handler invoked when batched invalidate results land\n * after recovery. The handler is expected to drive\n * `reexecuteAffectedQuery` for each entry.\n */\n setAffectedHandler(handler: AffectedHandler): void {\n this.affectedHandler = handler;\n }\n\n // ─── RPC methods ────────────────────────────────────────────────────────\n\n async registerSubscription(\n params: types.RegisterSubscriptionParams,\n ): Promise<types.RegisterSubscriptionResult> {\n return this.call(\"registerSubscription\", params, types.RegisterSubscriptionResultSchema);\n }\n\n async registerSubscriptionBatch(\n params: types.RegisterSubscriptionParams[],\n ): Promise<types.RegisterSubscriptionResult[]> {\n return this.call(\"registerSubscriptionBatch\", params, types.RegisterSubscriptionBatchResultSchema);\n }\n\n async updateSubscriptionReadSet(\n params: types.UpdateSubscriptionReadSetParams,\n ): Promise<types.UpdateSubscriptionReadSetResult> {\n return this.call(\"updateSubscriptionReadSet\", params, types.UpdateSubscriptionReadSetResultSchema);\n }\n\n async unregisterSubscription(\n params: types.UnregisterSubscriptionParams,\n ): Promise<types.UnregisterSubscriptionResult> {\n return this.call(\"unregisterSubscription\", params, types.UnregisterSubscriptionResultSchema);\n }\n\n async unregisterSubscriptions(\n params: types.UnregisterSubscriptionsParams,\n ): Promise<types.UnregisterSubscriptionsResult> {\n return this.call(\"unregisterSubscriptions\", params, types.UnregisterSubscriptionsResultSchema);\n }\n\n /**\n * Sends the writeSet to the sub-manager for invalidation, OR buffers it\n * if the client is currently disconnected / recovering. Returns\n * `{ affected: [] }` while buffering — the actual `affected` from the\n * buffered batch is delivered via the registered `affectedHandler`\n * after recovery completes.\n */\n async invalidateWriteset(\n params: types.InvalidateWritesetParams,\n ): Promise<types.InvalidateWritesetResult> {\n if (this.state === \"busy\") {\n this.appendToBuffer(params.writeSet);\n return { affected: [] };\n }\n return this.call(\"invalidateWriteset\", params, types.InvalidateWritesetResultSchema);\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Single point through which RPCs go. Throws on transport / server error.\n * `registerSubscription`, `updateSubscriptionReadSet`, and\n * `unregisterSubscription` are NOT gated on the ready state: during a\n * disconnect they will fail at the socket layer, and during recovery\n * (which itself uses these methods) gating would deadlock.\n */\n private async call<T extends ZodType>(\n procName: string,\n params: unknown,\n resType: T,\n ): Promise<z.infer<T>> {\n const result = await this.client.call(procName, params);\n return resType.parse(result) as z.infer<T>;\n }\n\n private appendToBuffer(writeSet: WriteEntry[]): void {\n if (this.bufferedWriteSets.length + writeSet.length > this.bufferLimit) {\n if (!this.bufferDroppedDuringOutage) {\n logger.warn(\n `Invalidate buffer would exceed ${this.bufferLimit}; dropping buffered entries. ` +\n `Subscriptions will be caught up via commit-log replay on re-register.`,\n );\n this.bufferDroppedDuringOutage = true;\n }\n this.bufferedWriteSets = [];\n return;\n }\n this.bufferedWriteSets.push(...writeSet);\n }\n\n private async flushBuffer(): Promise<void> {\n if (this.bufferedWriteSets.length === 0) return;\n const writeSet = this.bufferedWriteSets;\n this.bufferedWriteSets = [];\n this.bufferDroppedDuringOutage = false;\n\n try {\n const result = await this.call(\n \"invalidateWriteset\",\n { writeSet },\n types.InvalidateWritesetResultSchema,\n );\n if (this.affectedHandler && result.affected.length > 0) {\n await this.affectedHandler(result.affected);\n }\n } catch (err) {\n logger.error(err, \"Failed to flush invalidate buffer after recovery\");\n }\n }\n\n private markBusy(): void {\n if (this.state === \"busy\") return;\n this.state = \"busy\";\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n }\n\n private markReady(): void {\n if (this.state === \"ready\") return;\n this.state = \"ready\";\n this.resolveReady();\n }\n\n private handleClose(): void {\n logger.info(\"sub-manager client: socket closed\");\n this.markBusy();\n }\n\n private handleOpen(): void {\n if (!this.hasConnectedOnce) {\n this.hasConnectedOnce = true;\n logger.info(\"sub-manager client: initial connect\");\n this.markReady();\n return;\n }\n logger.info(\"sub-manager client: reconnected — starting recovery\");\n void this.runRecovery();\n }\n\n private async runRecovery(): Promise<void> {\n // Stay 'busy' for the duration of recovery so concurrent\n // invalidateWriteset calls keep buffering.\n try {\n if (this.recoveryDriver) {\n await this.recoveryDriver();\n } else {\n logger.warn(\"sub-manager client: no recoveryDriver configured — skipping recovery step\");\n }\n await this.flushBuffer();\n } catch (err) {\n logger.error(err, \"Recovery failed; state will be marked ready anyway\");\n } finally {\n logger.info(\"sub-manager client: recovery complete\");\n this.markReady();\n }\n }\n\n /**\n * Test/inspection helpers.\n */\n /** @internal */\n isReady(): boolean { return this.state === \"ready\"; }\n /** @internal */\n bufferSize(): number { return this.bufferedWriteSets.length; }\n}\n","import { Client, Receiver } from \"@upstash/qstash\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\n\nconst logger = baseLogger.child(\n { name: \"SCHEDULER\" },\n { level: logLevelOf(\"SCHEDULER_LOG_LEVEL\") },\n);\n\n// ─── Scheduler Types ─────────────────────────────────────────────────────────\n\n/** A recurring job to keep synced on the scheduler side. */\nexport interface ScheduledJobDef {\n /** Job procedure name (also the last path segment of its endpoint). */\n name: string;\n /** Cron expression, e.g. `\"0 3 * * *\"`. */\n cron: string;\n}\n\n/** Options for a precise, one-shot job run. */\nexport interface EnqueueOptions {\n /** Run after this many seconds from now. */\n delaySeconds?: number;\n /** Run at this absolute time. Overrides `delaySeconds`. */\n runAt?: Date;\n /**\n * Idempotency key. If two enqueues share the same id within the provider's\n * dedup window, only the first is delivered. Use it to make \"schedule the\n * expiry of X\" safe to call more than once.\n */\n dedupeId?: string;\n}\n\n/** Handle returned by {@link JobScheduler.enqueue}; pass its `id` to `cancel`. */\nexport interface ScheduleHandle {\n id: string;\n}\n\n/** The pieces of an inbound webhook the scheduler needs to authenticate it. */\nexport interface IncomingJobRequest {\n /** Job name parsed from the request path. */\n name: string;\n /** Raw (unparsed) request body — signatures are computed over these bytes. */\n rawBody: string;\n /** Request headers, keys lowercased. */\n headers: Record<string, string | undefined>;\n /** Full request URL the webhook was delivered to. */\n url: string;\n}\n\n/**\n * Pluggable scheduler. The server owns exactly one instance and uses it for\n * both directions:\n * • inbound — `verify` authenticates a webhook before the job runs.\n * • outbound — `syncSchedules` (recurring, at startup) and `enqueue`/`cancel`\n * (precise one-shots, at runtime) drive the provider.\n *\n * Both dev and production implementations POST to the same HTTP job endpoint,\n * so the server's dispatch path is exercised identically in every environment.\n */\nexport interface JobScheduler {\n /** Authenticate an inbound webhook. Return false (or throw) to reject. */\n verify(req: IncomingJobRequest): Promise<boolean>;\n\n /**\n * Make the provider's recurring schedules match `jobs` exactly (create,\n * update, and remove stale ones). Called once at server startup.\n * `endpointFor(name)` yields the absolute URL the provider should POST to.\n */\n syncSchedules(\n jobs: ScheduledJobDef[],\n endpointFor: (name: string) => string,\n ): Promise<void>;\n\n /** Enqueue a precise one-shot run of `name` with `body` at `endpoint`. */\n enqueue(\n name: string,\n body: unknown,\n endpoint: string,\n opts?: EnqueueOptions,\n ): Promise<ScheduleHandle>;\n\n /** Cancel a previously enqueued one-shot by its handle id. */\n cancel(handleId: string): Promise<void>;\n}\n\nfunction delayMsFrom(opts?: EnqueueOptions): number {\n if (opts?.runAt) return Math.max(0, opts.runAt.getTime() - Date.now());\n if (opts?.delaySeconds && opts.delaySeconds > 0) return opts.delaySeconds * 1000;\n return 0;\n}\n\n// ─── Dev Scheduler ───────────────────────────────────────────────────────────\n\nexport interface DevSchedulerOptions {\n /**\n * How often, in ms, to fire each recurring job in dev. Dev intentionally\n * ignores exact cron timing (that is the production scheduler's job); it\n * just fires often enough to exercise the handler. Default 60_000.\n */\n recurringIntervalMs?: number;\n /**\n * Override the HTTP client (for tests). Defaults to the global `fetch`.\n */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * In-process scheduler for local development and tests. Recurring jobs fire on\n * a fixed interval; one-shots fire on an exact `setTimeout` (so precise timing\n * is faithfully testable). Every fire POSTs to the real HTTP job endpoint, and\n * `verify` accepts everything — there is no signing in dev.\n */\nexport class DevScheduler implements JobScheduler {\n private readonly intervalMs: number;\n private readonly fetchImpl: typeof fetch;\n private intervals = new Map<string, ReturnType<typeof setInterval>>();\n private timeouts = new Map<string, ReturnType<typeof setTimeout>>();\n\n constructor(opts: DevSchedulerOptions = {}) {\n this.intervalMs = opts.recurringIntervalMs ?? 60_000;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n }\n\n async verify(_req: IncomingJobRequest): Promise<boolean> {\n return true;\n }\n\n async syncSchedules(\n jobs: ScheduledJobDef[],\n endpointFor: (name: string) => string,\n ): Promise<void> {\n // Rebuild from scratch so a re-sync reflects removed jobs.\n for (const timer of this.intervals.values()) clearInterval(timer);\n this.intervals.clear();\n\n for (const job of jobs) {\n const endpoint = endpointFor(job.name);\n const timer = setInterval(() => {\n this.post(endpoint, {}).catch((err) =>\n logger.error(err, `dev recurring job \"${job.name}\" failed`),\n );\n }, this.intervalMs);\n // Don't keep the process alive purely for the dev poller.\n timer.unref?.();\n this.intervals.set(job.name, timer);\n logger.info(\n `dev schedule \"${job.name}\" (cron \"${job.cron}\") → firing every ${this.intervalMs}ms`,\n );\n }\n }\n\n async enqueue(\n name: string,\n body: unknown,\n endpoint: string,\n opts?: EnqueueOptions,\n ): Promise<ScheduleHandle> {\n const id = crypto.randomUUID();\n const timer = setTimeout(() => {\n this.timeouts.delete(id);\n this.post(endpoint, body).catch((err) =>\n logger.error(err, `dev one-shot job \"${name}\" failed`),\n );\n }, delayMsFrom(opts));\n timer.unref?.();\n this.timeouts.set(id, timer);\n return { id };\n }\n\n async cancel(handleId: string): Promise<void> {\n const timer = this.timeouts.get(handleId);\n if (timer) {\n clearTimeout(timer);\n this.timeouts.delete(handleId);\n }\n }\n\n /** Stop every dev timer (call on server shutdown / in test teardown). */\n stop(): void {\n for (const t of this.intervals.values()) clearInterval(t);\n for (const t of this.timeouts.values()) clearTimeout(t);\n this.intervals.clear();\n this.timeouts.clear();\n }\n\n private async post(endpoint: string, body: unknown): Promise<void> {\n const res = await this.fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body ?? {}),\n });\n if (!res.ok) {\n throw new Error(`job endpoint ${endpoint} returned ${res.status}`);\n }\n }\n}\n\n// ─── QStash Scheduler (production) ───────────────────────────────────────────\n\nexport interface QStashSchedulerOptions {\n /** QStash Base url (`QSTASH_BASE_URL`). */\n baseUrl: string;\n /** QStash REST token (`QSTASH_TOKEN`). */\n token: string;\n /** Current signing key for webhook verification (`QSTASH_CURRENT_SIGNING_KEY`). */\n currentSigningKey: string;\n /** Next signing key, for zero-downtime key rotation (`QSTASH_NEXT_SIGNING_KEY`). */\n nextSigningKey: string;\n /* explicitly set qstash to dev mode to use local qstash server */\n devMode: boolean;\n}\n\n/**\n * Production scheduler backed by Upstash QStash. Externalizes the timer so a\n * horizontally-scaled deployment fires each cron tick exactly once (QStash\n * calls one HTTP endpoint), with at-least-once retries and signed webhooks.\n */\nexport class QStashScheduler implements JobScheduler {\n private readonly client: Client;\n private readonly receiver: Receiver;\n\n constructor(opts: QStashSchedulerOptions) {\n this.client = new Client({ baseUrl: opts.baseUrl ,token: opts.token, devMode: opts.devMode });\n this.receiver = new Receiver({\n currentSigningKey: opts.currentSigningKey,\n nextSigningKey: opts.nextSigningKey,\n });\n }\n\n async verify(req: IncomingJobRequest): Promise<boolean> {\n const signature = req.headers[\"upstash-signature\"];\n if (!signature) return false;\n try {\n return await this.receiver.verify({\n signature,\n body: req.rawBody,\n url: req.url,\n });\n } catch (err) {\n logger.warn(err, `signature verification failed for job \"${req.name}\"`);\n return false;\n }\n }\n\n async syncSchedules(\n jobs: ScheduledJobDef[],\n endpointFor: (name: string) => string,\n ): Promise<void> {\n // A deterministic scheduleId per job makes create an idempotent upsert and\n // lets us reconcile: anything QStash still has that we no longer declare is\n // a stale schedule and gets removed.\n const desired = new Map(jobs.map((j) => [scheduleIdFor(j.name), j]));\n\n let existing: Awaited<ReturnType<Client[\"schedules\"][\"list\"]>> = [];\n try {\n existing = await this.client.schedules.list();\n } catch (err) {\n logger.error(err, \"failed to list QStash schedules; skipping reconcile of stale schedules\");\n }\n\n for (const schedule of existing) {\n const id = schedule.scheduleId;\n if (id?.startsWith(SCHEDULE_ID_PREFIX) && !desired.has(id)) {\n try {\n await this.client.schedules.delete(id);\n logger.info(`removed stale QStash schedule ${id}`);\n } catch (err) {\n logger.error(err, `failed to remove stale schedule ${id}`);\n }\n }\n }\n\n for (const [scheduleId, job] of desired) {\n try {\n await this.client.schedules.create({\n scheduleId,\n destination: endpointFor(job.name),\n cron: job.cron,\n });\n logger.info(`synced QStash schedule \"${job.name}\" (${job.cron})`);\n } catch (err) {\n logger.error(err, `failed to sync schedule for job \"${job.name}\"`);\n throw err;\n }\n }\n }\n\n async enqueue(\n name: string,\n body: unknown,\n endpoint: string,\n opts?: EnqueueOptions,\n ): Promise<ScheduleHandle> {\n const res = await this.client.publishJSON({\n url: endpoint,\n body,\n ...(opts?.runAt\n ? { notBefore: Math.floor(opts.runAt.getTime() / 1000) }\n : opts?.delaySeconds\n ? { delay: opts.delaySeconds }\n : {}),\n ...(opts?.dedupeId ? { deduplicationId: opts.dedupeId } : {}),\n });\n const messageId = (res as { messageId?: string }).messageId;\n if (!messageId) throw new Error(`QStash publish for job \"${name}\" returned no messageId`);\n return { id: messageId };\n }\n\n async cancel(handleId: string): Promise<void> {\n await this.client.messages.cancel(handleId);\n }\n}\n\nconst SCHEDULE_ID_PREFIX = \"supalive-job-\";\n\nfunction scheduleIdFor(jobName: string): string {\n return `${SCHEDULE_ID_PREFIX}${jobName}`;\n}\n\n// ─── Job Client (app-facing) ─────────────────────────────────────────────────\n\n/**\n * Thin app-facing handle for scheduling precise one-shots from inside\n * procedures. The server builds one (via `getJobClient()`) that already knows\n * how to turn a job name into its endpoint, so callers just say\n * `jobs.enqueue(\"expireSpendAuth\", { id }, { runAt })`.\n */\nexport class JobClient {\n constructor(\n private readonly scheduler: JobScheduler,\n private readonly endpointFor: (name: string) => string,\n ) {}\n\n enqueue(name: string, body: unknown, opts?: EnqueueOptions): Promise<ScheduleHandle> {\n return this.scheduler.enqueue(name, body, this.endpointFor(name), opts);\n }\n\n cancel(handleId: string): Promise<void> {\n return this.scheduler.cancel(handleId);\n }\n}\n","/**\n * Fixed-window rate limiter. Per-instance and in-memory: each key gets a\n * counter that resets every `windowMs`. Good enough to blunt abuse of the HTTP\n * RPC endpoint (which does a JWT verify + possibly a DB read per call) without\n * the latency of a shared Redis counter. A background sweep evicts expired\n * buckets so memory stays bounded under churny key sets (e.g. many IPs).\n */\nexport interface RateLimitResult {\n allowed: boolean;\n /** Milliseconds until the current window resets (0 when allowed). */\n retryAfterMs: number;\n /** Remaining allowance in the current window (0 when blocked). */\n remaining: number;\n}\n\nexport class FixedWindowRateLimiter {\n private readonly buckets = new Map<string, { count: number; resetAt: number }>();\n private sweepTimer?: ReturnType<typeof setInterval>;\n\n constructor(\n private readonly windowMs: number,\n private readonly max: number,\n ) {\n this.sweepTimer = setInterval(() => this.sweep(), Math.max(windowMs, 1000));\n // Never keep the process alive just for the sweep.\n this.sweepTimer.unref?.();\n }\n\n check(key: string, now: number = Date.now()): RateLimitResult {\n let bucket = this.buckets.get(key);\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + this.windowMs };\n this.buckets.set(key, bucket);\n }\n bucket.count++;\n if (bucket.count > this.max) {\n return { allowed: false, retryAfterMs: bucket.resetAt - now, remaining: 0 };\n }\n return { allowed: true, retryAfterMs: 0, remaining: this.max - bucket.count };\n }\n\n private sweep(now: number = Date.now()): void {\n for (const [key, bucket] of this.buckets) {\n if (bucket.resetAt <= now) this.buckets.delete(key);\n }\n }\n\n /** Stop the background sweep (call on server shutdown / test teardown). */\n stop(): void {\n if (this.sweepTimer) clearInterval(this.sweepTimer);\n this.sweepTimer = undefined;\n }\n}\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport type { ZodType } from \"zod\";\nimport { supaliveStringify } from \"../helper\";\nimport { AuthenticationError } from \"../db/types_client_rpc\";\nimport type { SupaliveDb } from \"../db/realtime_db\";\nimport type { ContextRegistry, RegisteredProcedure } from \"../router/router\";\nimport type { MutationCtx, ActionCtx } from \"../router/procedure\";\nimport type { RpcConfig } from \"../config\";\nimport type { ObjectStorage } from \"../storage\";\nimport type { JobClient } from \"../jobs/scheduler\";\nimport { FixedWindowRateLimiter } from \"./rate-limit\";\nimport { runCachedQuery, type OneShotQueryDeps } from \"./one-shot-query\";\nimport { primaryOnlyConn } from \"../router/procedure\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\n\nconst logger = baseLogger.child(\n { name: \"HTTP_RPC\" },\n { level: logLevelOf(\"SERVER_LOG_LEVEL\") },\n);\n\nexport interface HttpRpcDeps<TContext> {\n /** Lazily fetch the registry (set after `registerRouter`). */\n getRegistry: () => ContextRegistry | undefined;\n /** The server's SupaliveDb — the SAME instance whose after-commit hook drives\n * subscription invalidation, so an HTTP mutation pushes live updates too.\n * Used for mutations/actions; queries go through `oneShotDeps`. */\n db: SupaliveDb;\n /** Optional object storage exposed to ACTION handlers as `ctx.storage`. */\n storage: ObjectStorage;\n /** Optional job client exposed to ACTION handlers as `ctx.scheduler`. */\n scheduler: JobClient;\n /** Shared, pre-built one-shot query cache deps (db + cache + getUserId +\n * getMinRetainedTs). Same object the WS `call` path uses, so HTTP one-shot queries\n * cache/segment identically. */\n oneShotDeps: OneShotQueryDeps<TContext>;\n /** Same auth callback used by the WebSocket path. */\n verifyAuth?: (data: Record<string, unknown>, sessionId: string) => Promise<TContext | null>;\n rpc: RpcConfig<TContext>;\n}\n\n/**\n * Handles the one-shot HTTP RPC transport: a single `POST {path}` endpoint for\n * `query`/`mutation`/`action`. The procedure name travels in the request body\n * (`{ name, input }`), not the URL — so the wire never reveals which procedures\n * exist and a transport-level error (which typically echoes the failing URL)\n * can't leak a procedure name. Subscriptions are not offered here (WS only).\n * Kept independent of the WebSocket server so it can be unit-tested against a\n * plain `http.Server` with a stub db/registry.\n *\n * Request flow: CORS → (OPTIONS preflight) → rate limit (pre-auth) → read body →\n * parse `{ name, input }` → procedure lookup (internal/job rejected) → auth →\n * validate input → run → respond.\n */\nexport class HttpRpcHandler<TContext> {\n private readonly path: string;\n private readonly rpc: RpcConfig<TContext>;\n private readonly rateLimiter?: FixedWindowRateLimiter;\n\n constructor(private readonly deps: HttpRpcDeps<TContext>) {\n this.rpc = deps.rpc;\n this.path = normalizeRpcPath(deps.rpc.path ?? \"/_rpc\");\n if (deps.rpc.rateLimit !== false) {\n const rl = deps.rpc.rateLimit ?? {};\n this.rateLimiter = new FixedWindowRateLimiter(rl.windowMs ?? 60_000, rl.max ?? 120);\n }\n }\n\n /** True when this handler owns the given request path. The procedure name is\n * carried in the body now, so a single exact path is the whole surface. */\n handlesPath(path: string): boolean {\n return path === this.path;\n }\n\n stop(): void {\n this.rateLimiter?.stop();\n }\n\n async handle(req: IncomingMessage, res: ServerResponse, path: string): Promise<void> {\n this.applyCors(req, res);\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n if (req.method !== \"POST\") {\n return this.send(res, 405, { success: false, error: \"method not allowed\", code: \"METHOD_NOT_ALLOWED\" });\n }\n\n // Rate limit BEFORE auth so a flood can't hammer the (expensive) verify.\n // Keyed off the request (IP by default), so it needs no body — do it first.\n if (this.rateLimiter) {\n // `rateLimit` is a config object here (rateLimiter only exists when it\n // isn't `false`), so a `keyBy` override is read straight off it.\n const keyBy = this.rpc.rateLimit ? this.rpc.rateLimit.keyBy : undefined;\n const key = keyBy ? keyBy(req) : clientIp(req);\n const rl = this.rateLimiter.check(key);\n if (!rl.allowed) {\n res.setHeader(\"retry-after\", String(Math.ceil(rl.retryAfterMs / 1000)));\n return this.send(res, 429, { success: false, error: \"rate limit exceeded\", code: \"RATE_LIMITED\" });\n }\n }\n\n // The procedure name and input both live in the body: `{ name, input }`.\n const rawBody = await readBody(req);\n let name: string;\n let rawInput: unknown;\n let minTs: bigint | undefined;\n try {\n const envelope = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};\n if (!envelope || typeof envelope !== \"object\" || typeof envelope.name !== \"string\") {\n throw new Error(\"missing procedure name\");\n }\n name = envelope.name;\n rawInput = envelope.input ?? {};\n // Client read high-water (decimal bigint string), same role as the WS\n // `call` minTs: gates replica routing for read-your-writes / monotonic reads.\n minTs = typeof envelope.minTs === \"string\" ? parseMinTs(envelope.minTs) : undefined;\n } catch {\n // A malformed envelope reveals nothing about the API surface.\n return this.send(res, 400, { success: false, error: \"invalid request\", code: \"BAD_REQUEST\" });\n }\n\n const procedure = this.deps.getRegistry()?.getProcedure(name);\n // Internal procedures and jobs are server-only; subscriptions aren't offered\n // over HTTP. Report NOT_FOUND uniformly (and without the name) so nothing is\n // enumerable from the response.\n if (!procedure || procedure.internal || procedure.type === \"job\") {\n return this.send(res, 404, { success: false, error: \"not found\", code: \"NOT_FOUND\" });\n }\n\n const auth = await this.resolveAuth(req);\n if (!auth.ok) {\n return this.send(res, 401, { success: false, error: auth.message, code: \"UNAUTHENTICATED\" });\n }\n\n let input: unknown;\n try {\n input = (procedure.inputSchema as ZodType).parse(rawInput);\n } catch (err) {\n logger.warn(err, `RPC \"${name}\" received invalid input`);\n return this.send(res, 400, { success: false, error: \"invalid input\", code: \"BAD_REQUEST\" });\n }\n\n try {\n const { data, ts } = await this.runProcedure(procedure, input, auth.ctx, minTs);\n // Echo the observed ts (query snapshot / mutation commitTs) so an HTTP\n // client can advance its own read high-water. Absent for actions.\n return this.send(res, 200, { success: true, data, ts: ts?.toString() });\n } catch (err) {\n logger.error(err, `RPC \"${name}\" failed`);\n return this.send(res, 500, {\n success: false,\n error: err instanceof Error ? err.message : \"execution failed\",\n code: \"EXECUTION_ERROR\",\n });\n }\n }\n\n private async runProcedure(\n procedure: RegisteredProcedure<TContext>,\n input: unknown,\n serverCtx: TContext | undefined,\n minTs: bigint | undefined,\n ): Promise<{ data: unknown; ts?: bigint }> {\n if (procedure.type === \"query\") {\n // Same cache/identity/freshness model as a WS one-shot query — reuses the\n // server's shared one-shot deps. Load the freshness upper bound here, the\n // same way the WS path does before calling runCachedQuery.\n const lastSnapshotTs = await this.deps.db.impl.getLatestSnapshotTimestamp();\n return runCachedQuery(\n this.deps.oneShotDeps,\n procedure,\n procedure.name,\n input,\n serverCtx,\n lastSnapshotTs,\n minTs, \n );\n }\n if (procedure.type === \"mutation\") {\n // Direct (no per-session queue); OCC makes concurrent HTTP mutations safe,\n // and the db's after-commit hook invalidates subscriptions like a WS write.\n const res = await this.deps.db.mutation(async (ctx) => {\n const fn = procedure.fn as (c: MutationCtx<TContext>, i: unknown) => Promise<unknown>;\n return fn({ db: ctx, serverCtx, ...primaryOnlyConn }, input);\n });\n // commitTs seeds the client's read high-water (read-your-writes).\n return { data: res.result, ts: res.commitTs };\n }\n // action — no single snapshot/commit ts to report.\n const fn = procedure.fn as (c: ActionCtx<TContext>, i: unknown) => Promise<unknown>;\n const data = await fn(\n { db: this.deps.db, serverCtx, storage: this.deps.storage, scheduler: this.deps.scheduler },\n input,\n );\n return { data };\n }\n\n private async resolveAuth(\n req: IncomingMessage,\n ): Promise<{ ok: true; ctx: TContext | undefined } | { ok: false; message: string }> {\n const extractor = this.rpc.auth ?? defaultHttpAuth;\n const data = extractor(req);\n // No verifyAuth configured, or no auth data → anonymous (mirrors WS).\n if (!this.deps.verifyAuth || !data) return { ok: true, ctx: undefined };\n try {\n const ctx = await this.deps.verifyAuth(data, `http-${crypto.randomUUID()}`);\n if (!ctx) return { ok: false, message: \"Invalid authentication\" };\n return { ok: true, ctx };\n } catch (err) {\n if (err instanceof AuthenticationError) {\n return { ok: false, message: `Invalid authentication: ${err.message}` };\n }\n return { ok: false, message: \"Invalid authentication\" };\n }\n }\n\n private applyCors(req: IncomingMessage, res: ServerResponse) {\n const cors = this.rpc.cors;\n if (!cors) return;\n const originHeader = req.headers[\"origin\"];\n const origin = Array.isArray(originHeader) ? originHeader[0] : originHeader;\n\n if (cors.origins === \"*\") {\n res.setHeader(\"access-control-allow-origin\", \"*\");\n } else if (origin && cors.origins.includes(origin)) {\n res.setHeader(\"access-control-allow-origin\", origin);\n res.setHeader(\"vary\", \"Origin\");\n } else {\n return; // origin not allowed → emit no CORS headers\n }\n res.setHeader(\"access-control-allow-methods\", \"POST, GET, OPTIONS\");\n res.setHeader(\"access-control-allow-headers\", \"authorization, content-type\");\n res.setHeader(\"access-control-max-age\", \"86400\");\n }\n\n private send(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(supaliveStringify(body));\n }\n}\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\n/** Parse a wire `minTs` (decimal bigint string) to a bigint, or undefined when\n * absent/malformed — a bad value falls back to primary-safe routing. */\nfunction parseMinTs(minTs: string | undefined): bigint | undefined {\n if (minTs === undefined) return undefined;\n try {\n return BigInt(minTs);\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeRpcPath(p: string): string {\n let s = p.startsWith(\"/\") ? p : `/${p}`;\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction defaultHttpAuth(req: IncomingMessage): Record<string, unknown> | undefined {\n const header = req.headers[\"authorization\"];\n const value = Array.isArray(header) ? header[0] : header;\n if (!value) return undefined;\n const match = /^Bearer\\s+(.+)$/i.exec(value);\n return match ? { token: match[1] } : undefined;\n}\n\nfunction clientIp(req: IncomingMessage): string {\n const xff = req.headers[\"x-forwarded-for\"];\n if (xff) {\n const value = Array.isArray(xff) ? xff[0] : xff;\n const first = value.split(\",\")[0]?.trim();\n if (first) return first;\n }\n return req.socket?.remoteAddress ?? \"unknown\";\n}\n\nfunction readBody(req: IncomingMessage, maxBytes = 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > maxBytes) {\n reject(new Error(\"request body too large\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n}\n","import { createServer, IncomingMessage, type ServerResponse, type Server } from \"http\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { ZodType } from \"zod\";\nimport Redis from \"ioredis\";\nimport { retry, handleAll, ExponentialBackoff } from \"cockatiel\";\nimport { stableStringify, supaliveStringify } from \"../helper\";\nimport {\n type QueryCtx,\n type MutationCtx,\n type ActionCtx,\n ContextRegistry,\n getContextRegistry,\n} from \"../router\";\nimport { sleep, SupaliveDb } from \"../db/realtime_db\";\nimport { primaryOnlyConn } from \"../router/procedure\";\nimport type { WriteEntry, CommitTs, LiveResult, MutationResult } from \"../db/types_db\";\nimport { AuthenticationError, ClientMessage, ClientMessageSchema, Context, ServerMessage } from \"../db/types_client_rpc\";\nimport {\n type Session,\n type InstanceId,\n type SubId,\n type PendingMutation,\n RemoteSubscriptionMessageSchema,\n RemoteRecomputeRace,\n SubscriptionEntry,\n RemoteSubscriptionMessage,\n} from \"./types_server\";\nimport { type Router } from \"../router/router\";\nimport {\n generateSubscriptionId,\n generateCacheKey,\n getHashOf,\n resolveQueryIdentity,\n} from \"./sub_hash\";\nimport { runCachedQuery, type OneShotQueryDeps } from \"./one-shot-query\";\nimport { RetentionWatermark, RETENTION_WATERMARK_CHANNEL } from \"./retention-watermark\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport { CacheLayer } from \"../db/cache\";\nimport { SubManagerClient, type SubManagerLink } from \"./sub-manager-client\";\nimport { SupaliveServerConfig } from \"../config\";\nimport { appInstanceId, logger as baseLogger, logLevelOf } from \"../logger\";\nimport { AffectedSubscription, RegistrationRecord } from \"./sub-manager-types\";\nimport { bootstrapDbTypes } from \"../db/init_db\";\nimport { JobClient, type JobScheduler, type EnqueueOptions, type ScheduleHandle } from \"../jobs/scheduler\";\nimport type { JobCtx } from \"../router/procedure\";\nimport type { ObjectStorage } from \"../storage\";\nimport { HttpRpcHandler } from \"./http-rpc\";\n\nconst redisRetryPolicy = retry(handleAll, {\n maxAttempts: 3,\n backoff: new ExponentialBackoff({\n initialDelay: 100,\n maxDelay: 5000,\n exponent: 2,\n }),\n});\n\n/**\n * Global broadcast channel used to signal column-type cache invalidation\n * across every running server instance. Publishers (migration tooling,\n * the publishSchemaReload helper below) send a JSON message of shape\n * `{ type: \"schema:reload\"; tables?: string[]; originInstance?: string }`.\n * Subscribers re-bootstrap from PG (or Redis) so the next batched write\n * sees up-to-date column metadata without a process restart.\n */\nconst SCHEMA_RELOAD_CHANNEL = \"supalive:schema:reload\";\n\n/**\n * Per-socket cap on pending mutations. The Nth+1 mutation from a single\n * client gets rejected with TOO_MANY_CONCURRENT_MUTATIONS rather than\n * being enqueued — this is rate-limit pressure on a misbehaving client,\n * not a hard system limit.\n *\n * Mirrors Convex's OPERATION_QUEUE_BUFFER_SIZE (1000). Mutations drain\n * one-at-a-time per socket, so a healthy client never approaches this.\n */\nconst MAX_MUTATION_QUEUE_BUFFER = 1000;\n\n/**\n * Per-socket cap on in-flight actions. Actions run in parallel (unlike\n * mutations) because they may do long external work (HTTP, queue calls);\n * the cap exists so one socket can't open thousands of outbound\n * connections from inside the server.\n */\nconst MAX_ACTION_CONCURRENCY = 1000;\n\n// ─── Heartbeat constants ─────────────────────────────────────\n//\n// Two independent jobs run on a single per-session interval:\n//\n// 1. Dead-TCP detection: terminate sockets that have gone fully silent\n// (no inbound text/ping/pong) past `CLIENT_TIMEOUT_MS`. Otherwise\n// emit a ws-protocol ping (browser auto-pongs, refreshing `session.lastReceivedAt`).\n// 2. Client-watchdog refresh: if the application channel has been idle\n// for at least `HEARTBEAT_INTERVAL_MS`, send a `{type:\"ping\"}` JSON\n// frame so the client's inactivity watchdog stays fresh. Busy\n// sockets skip this (the `lastSentAt` check makes it idle-only).\n//\n// Both fire on the same `heartbeatTimer` — one `setInterval` per session.\nconst HEARTBEAT_INTERVAL_MS = Number(\n process.env.SUPALIVE_HEARTBEAT_INTERVAL_MS ?? 15_000,\n);\nconst CLIENT_TIMEOUT_MS = 120_000;\n\n/**\n * TTL on the Redis lock that elects the recompute winner when the writer\n * doesn't hold the sub locally. Each (subId, commitTs) gets its own lock\n * key, so this only needs to bound the worst-case recompute duration —\n * if the winner crashes mid-recompute, the lock expires and the next\n * mutation triggers a new race.\n */\nconst RECOMPUTE_LOCK_TTL_SECONDS = Number(\n process.env.SUPALIVE_RECOMPUTE_LOCK_TTL_SECONDS ?? 10,\n);\n\nconst logger = baseLogger.child({\n \"name\": \"SUPALIVE\",\n},\n { level: logLevelOf(\"SERVER_LOG_LEVEL\") },\n);\n\n/**\n * Supalive WebSocket Server\n */\nexport class SupaliveWebSocketServer<TContext extends Context> {\n private config: SupaliveServerConfig<TContext>;\n private instanceId: InstanceId;\n\n private httpServer: Server;\n private wss: WebSocketServer;\n\n private cache: CacheLayer;\n private redisSub: Redis;\n /** App-level pub/sub channels this instance listens on (see config.appPubSub). */\n private readonly appPubSubChannels: Set<string>;\n private db: SupaliveDb;\n private contextRegistry!: ContextRegistry;\n private router?: Router<any, TContext>;\n\n // In-memory retention watermark (oldest retained commit-log ts): loaded from the\n // DB at startup and kept fresh by the sub-manager's Redis broadcast. Read on\n // the one-shot query hot path via `oneShotDeps.getMinRetainedTs` (a plain\n // variable, no per-query round trip).\n private retentionWatermark = new RetentionWatermark();\n // Built once — the one-shot query cache deps are static aside from the live\n // watermark, which `getMinRetainedTs` reads by reference. Shared by the WS `call` path\n // and the HTTP RPC handler.\n private oneShotDeps: OneShotQueryDeps<TContext>;\n\n private scheduler: JobScheduler;\n private jobPath: string;\n private jobClient: JobClient;\n private rpcHandler?: HttpRpcHandler<TContext>;\n /** Optional object storage exposed to every procedure as `ctx.storage`. */\n private storage: ObjectStorage;\n\n private sessions: Map<WebSocket, Session<TContext>> = new Map();\n private serverSubscriptions: Map<SubId, SubscriptionEntry> = new Map();\n private subManagerClient!: SubManagerLink;\n\n private stats = {\n activeConnections: 0,\n totalConnections: 0,\n activeSubscriptions: 0,\n };\n\n constructor(config: SupaliveServerConfig<TContext>) {\n this.config = {\n port: 3000,\n host: \"0.0.0.0\",\n contextName: \"default\",\n sessionTtlSeconds: 3600,\n ...config,\n };\n this.instanceId = appInstanceId;\n\n if (config.subManager) {\n // In-process link: the sub-manager is embedded in this process. Direct\n // method calls, no socket. The recovery/buffer machinery below is inert.\n this.subManagerClient = config.subManager;\n } else if (config.subManagerUrl) {\n this.subManagerClient = new SubManagerClient(config.subManagerUrl);\n } else {\n throw new Error(\n \"either `subManager` (in-process link) or `subManagerUrl` (WebSocket URL) is required\",\n );\n }\n this.cache = config.cacheLayer;\n this.redisSub = config.redisSubClient;\n this.appPubSubChannels = new Set(config.appPubSub?.channels ?? []);\n this.db = config.database;\n this.storage = config.storage;\n\n // Invalidate subscriptions after EVERY committed mutation, whatever its\n // source — client WS call, action, job, or the internal caller (as long as\n // it shares this db instance). Invalidation is instance-independent (it goes\n // through the central sub-manager), so a server-side write reaches every\n // WS node's subscribers. Fire-and-forget: the client's own response never\n // waits on it, and handleMutationCommit swallows/logs its own errors.\n this.db.setOnAfterCommit((writeSet, commitTs) => {\n this.handleMutationCommit(writeSet, commitTs).catch((err) =>\n logger.error(err, \"post-commit invalidation failed\"),\n );\n });\n\n this.scheduler = config.scheduler;\n this.jobPath = normalizeJobPath(config.jobPath ?? \"/_jobs\");\n this.jobClient = new JobClient(this.scheduler, (name) => this.endpointFor(name));\n\n // Static deps for cached one-shot queries (WS `call` + HTTP RPC). Built once;\n // `getMinRetainedTs` reads the live in-memory watermark by reference.\n this.oneShotDeps = {\n db: this.db,\n cache: this.cache,\n getUserId: config.getUserId,\n getMinRetainedTs: () => this.retentionWatermark.get(),\n };\n\n // Opt-in one-shot HTTP RPC transport (query/mutation/action). Shares this\n // server's db + verifyAuth + procedure registry; subscriptions stay WS-only.\n if (config.rpc) {\n this.rpcHandler = new HttpRpcHandler<TContext>({\n getRegistry: () => this.contextRegistry,\n db: this.db,\n oneShotDeps: this.oneShotDeps,\n verifyAuth: config.verifyAuth,\n rpc: config.rpc,\n storage: this.storage,\n scheduler: this.jobClient,\n });\n }\n\n // Wire the sub-manager client's recovery callbacks to drive re-registration\n // from this app server's serverSubscriptions whenever the RPC reconnects,\n // and to process the resulting batched invalidate flush.\n this.subManagerClient.setRecoveryDriver(this.runSubManagerRecovery.bind(this));\n this.subManagerClient.setAffectedHandler(this.runAffectedHandler.bind(this));\n\n // The HTTP server hosts the WS upgrade AND the job webhook endpoint. WS\n // traffic arrives as `upgrade` events (handled by WebSocketServer below),\n // so this plain `request` handler only ever sees the scheduler's POSTs and\n // health checks — the two never overlap.\n this.httpServer = createServer((req, res) => {\n this.handleHttpRequest(req, res).catch((err) => {\n logger.error(err, \"HTTP request handler error\");\n if (!res.headersSent) {\n res.writeHead(500, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"internal error\" }));\n }\n });\n });\n this.wss = new WebSocketServer({\n server: this.httpServer,\n autoPong: true,\n maxPayload: 100 * 1024 * 1024,\n // backlog: //max pending connections queue (default: 511)\n });\n\n this.setupWebSocketHandlers();\n this.subscribeToInstanceChannel();\n }\n\n /**\n * Per-session heartbeat. Fires every `HEARTBEAT_INTERVAL_MS` and does\n * both jobs on the same tick:\n *\n * 1. Inactivity check + ws-protocol ping (dead-TCP detection).\n * 2. App-level `{type:\"ping\"}` if the channel has been idle for at\n * least one heartbeat interval (client-watchdog refresh).\n *\n * The `lastSentAt` gate on (2) preserves the original \"only ping when\n * idle\" semantics: a session producing sub:updates faster than the\n * heartbeat interval never emits a redundant app-ping.\n */\n private startSessionHeartbeat(session: Session<TContext>): void {\n session.heartbeatTimer = setInterval(() => {\n // (1) Dead-TCP detection.\n if (Date.now() - session.lastReceivedAt > CLIENT_TIMEOUT_MS) {\n logger.info(`ws inactivity → terminate session ${session.id}`);\n try { session.ws.terminate(); } catch { /* close handler cleans up */ }\n return;\n }\n try {\n session.ws.ping();\n } catch {\n // socket already broken — close handler will clean up\n }\n\n // (2) Client-watchdog refresh, idle-only.\n if (Date.now() - session.lastSentAt >= HEARTBEAT_INTERVAL_MS) {\n this.sendMessage(session, { type: \"ping\" });\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private clearSessionTimers(session: Session<TContext>): void {\n if (session.heartbeatTimer) {\n clearInterval(session.heartbeatTimer);\n session.heartbeatTimer = null;\n }\n }\n\n registerRouter(router: Router<any, TContext>): void {\n this.router = router;\n this.contextRegistry = getContextRegistry(router.contextName);\n }\n\n async start(): Promise<void> {\n // Seed the in-memory retention watermark from the DB before serving; thereafter\n // the sub-manager's Redis broadcast keeps it fresh. Non-fatal on failure —\n // a 0 watermark is safe (nothing pruned → every cache entry is scannable).\n try {\n await this.retentionWatermark.loadFrom(this.db.impl);\n } catch (err) {\n logger.error(err, \"failed to load initial retention watermark\");\n }\n await new Promise<void>((resolve) => {\n this.httpServer.listen(this.config.port, this.config.host, () => {\n logger.info(`Server: ${this.instanceId} listening on ${this.config.host}:${this.config.port}`);\n resolve();\n });\n });\n await this.syncJobSchedules();\n }\n\n /**\n * Reconcile every registered recurring (cron) job with the scheduler. Safe\n * to call more than once. No-op when no scheduler is configured or the router\n * declares no cron jobs. Runs after the HTTP listener is up so the endpoints\n * the scheduler registers are already reachable.\n */\n private async syncJobSchedules(): Promise<void> {\n if (!this.scheduler || !this.contextRegistry) return;\n const jobs = this.contextRegistry\n .getJobProcedures()\n .filter((p) => !!p.cron)\n .map((p) => ({ name: p.name, cron: p.cron! }));\n if (jobs.length === 0) return;\n try {\n await this.scheduler.syncSchedules(jobs, (name) => this.endpointFor(name));\n logger.info(`Synced ${jobs.length} recurring job schedule(s)`);\n } catch (err) {\n logger.error(err, \"Failed to sync job schedules\");\n }\n }\n\n /** Absolute URL the scheduler POSTs to for a given job name. */\n endpointFor(name: string): string {\n return `${this.config.publicUrl}${this.jobPath}/${name}`;\n }\n\n /**\n * Enqueue a precise, one-shot run of a job (e.g. \"expire this record at\n * exactly `expiresAt`\"). Returns a handle whose `id` can be passed to\n * {@link cancelJob}. Requires a configured scheduler.\n */\n async scheduleJob(name: string, body: unknown, opts?: EnqueueOptions): Promise<ScheduleHandle> {\n if (!this.jobClient) throw new Error(\"No scheduler configured; cannot schedule jobs\");\n return this.jobClient.enqueue(name, body, opts);\n }\n\n /** Cancel a one-shot previously created by {@link scheduleJob}. */\n async cancelJob(handleId: string): Promise<void> {\n if (!this.jobClient) throw new Error(\"No scheduler configured; cannot cancel jobs\");\n return this.jobClient.cancel(handleId);\n }\n\n /**\n * The app-facing {@link JobClient} — hand it to your service layer so\n * procedures can schedule/cancel one-shots without reaching for the server.\n */\n getJobClient(): JobClient {\n return this.jobClient;\n }\n\n async stop(): Promise<void> {\n this.wss.close();\n for (const [ws] of this.sessions) {\n ws.close(1001, \"Server shutting down\");\n }\n this.rpcHandler?.stop();\n this.httpServer.close();\n logger.info(\"Server stopped\");\n }\n\n getStats() {\n return {\n instanceId: this.instanceId,\n activeConnections: this.stats.activeConnections,\n totalConnections: this.stats.totalConnections,\n activeSubscriptions: this.stats.activeSubscriptions,\n };\n }\n\n getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Plain-HTTP entrypoint (non-WS). Serves a liveness probe and the job\n * webhook: `POST {jobPath}/<jobName>`. Every job POST is authenticated by the\n * configured scheduler before the (server-only) job handler runs.\n */\n private async handleHttpRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const rawUrl = req.url ?? \"/\";\n const path = rawUrl.split(\"?\")[0]!;\n\n // Liveness probe — cheap, unauthenticated.\n if (req.method === \"GET\" && (path === \"/_health\")) {\n return sendJson(res, 200, { status: \"ok\", instanceId: this.instanceId });\n }\n\n // One-shot HTTP RPC (query/mutation/action), if enabled.\n if (this.rpcHandler && this.rpcHandler.handlesPath(path)) {\n return this.rpcHandler.handle(req, res, path);\n }\n\n const jobPrefix = `${this.jobPath}/`;\n if (!path.startsWith(jobPrefix)) {\n return sendJson(res, 404, { error: \"not found\" });\n }\n if (req.method !== \"POST\") {\n return sendJson(res, 405, { error: \"method not allowed\" });\n }\n\n const name = decodeURIComponent(path.slice(jobPrefix.length));\n if (!name || name.includes(\"/\")) {\n return sendJson(res, 404, { error: \"unknown job\" });\n }\n\n if (!this.scheduler) {\n return sendJson(res, 503, { error: \"jobs not enabled\" });\n }\n\n const rawBody = await readRequestBody(req);\n\n // Authenticate the webhook BEFORE any lookup or work — a forged POST must\n // not even learn whether a job name exists.\n let verified = false;\n try {\n verified = await this.scheduler.verify({\n name,\n rawBody,\n headers: lowercaseHeaders(req.headers),\n url: this.endpointFor(name),\n });\n } catch (err) {\n logger.warn(err, `job \"${name}\" verification threw`);\n }\n if (!verified) {\n return sendJson(res, 401, { error: \"invalid signature\" });\n }\n\n const procedure = this.contextRegistry?.getProcedure(name);\n if (!procedure || procedure.type !== \"job\") {\n return sendJson(res, 404, { error: \"unknown job\" });\n }\n\n let input: unknown;\n try {\n const parsed = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};\n input = (procedure.inputSchema as ZodType).parse(parsed);\n } catch (err) {\n logger.warn(err, `job \"${name}\" received an invalid payload`);\n return sendJson(res, 400, { error: \"invalid payload\" });\n }\n\n try {\n const serverCtx = this.config.jobContext ? await this.config.jobContext() : undefined;\n const fn = procedure.fn as (ctx: JobCtx<TContext>, input: unknown) => Promise<unknown>;\n const result = await fn({ db: this.db, serverCtx }, input);\n logger.info(`job \"${name}\" completed`);\n return sendJson(res, 200, { ok: true, result });\n } catch (err) {\n // Return 500 so the scheduler retries (jobs must be idempotent).\n logger.error(err, `job \"${name}\" failed`);\n return sendJson(res, 500, { error: \"job failed\" });\n }\n }\n\n private setupWebSocketHandlers(): void {\n this.wss.on(\"connection\", (ws, req) => {\n this.handleConnection(ws, req).catch((err) => {\n logger.error(err, \"Connection handler error\")\n });\n });\n }\n\n private async handleConnection(ws: WebSocket, req: IncomingMessage): Promise<void> {\n this.stats.totalConnections++;\n this.stats.activeConnections++;\n\n const session: Session<TContext> = {\n id: uuidv4(),\n ws,\n serverCtx: undefined,\n subIds: new Set(),\n mutationQueue: [],\n mutationInFlight: false,\n actionInFlight: 0,\n lastReceivedAt: Date.now(),\n lastSentAt: Date.now(),\n heartbeatTimer: null,\n };\n logger.info(`New session: ${session.id}`);\n\n this.sessions.set(ws, session);\n this.sendMessage(session, { type: \"connected\", sessionId: session.id });\n\n // Any inbound frame (text, ws-ping, ws-pong) refreshes the liveness\n // clock. This is the only thing keeping `CLIENT_TIMEOUT_MS` from\n // tripping on a silent-but-live client, since the browser auto-pong\n // is the only thing such a client emits.\n ws.on(\"message\", (data) => {\n session.lastReceivedAt = Date.now();\n this.handleMessage(session, data.toString()).catch((err) => {\n logger.error(err, \"Message handler error\");\n });\n });\n ws.on(\"ping\", () => { session.lastReceivedAt = Date.now(); });\n ws.on(\"pong\", () => { session.lastReceivedAt = Date.now(); });\n\n this.startSessionHeartbeat(session);\n\n ws.on(\"close\", (code) => {\n logger.info(`Connection closed: ${session.id}, code: ${code}`);\n this.clearSessionTimers(session);\n this.handleDisconnect(session);\n this.sessions.delete(ws);\n this.stats.activeConnections--;\n });\n\n ws.on(\"error\", (err) => {\n logger.error(err, \"Websocket error\");\n });\n }\n\n private async handleMessage(session: Session<TContext>, message: string): Promise<void> {\n try {\n const rawMsg = JSON.parse(message);\n const parseResult = ClientMessageSchema.safeParse(rawMsg);\n if (!parseResult.success) {\n this.sendError(session, \"\", \"Invalid message format\", \"INVALID_MESSAGE\");\n return;\n }\n await this.processMessage(session, parseResult.data);\n } catch (err) {\n logger.error(err, \"Message handling error\");\n this.sendError(session, \"\", \"Parse error\", \"PARSE_ERROR\");\n }\n }\n\n private async processMessage(session: Session<TContext>, msg: ClientMessage): Promise<void> {\n switch (msg.type) {\n case \"auth\":\n await this.handleAuth(session, msg.data);\n break;\n case \"call\":\n await this.handleCall(session, msg.id, msg.procedure, msg.input, parseMinTs(msg.minTs));\n break;\n case \"subscribe\":\n await this.handleSubscribe(session, msg.id, msg.procedure, msg.input);\n break;\n case \"unsubscribe\":\n await this.handleUnsubscribe(session, msg.subId);\n break;\n }\n }\n\n private async handleAuth(session: Session<TContext>, data?: Record<string, unknown>): Promise<void> {\n if (!this.config.verifyAuth || !data) {\n this.sendMessage(session, { type: \"auth:success\", userId: \"anonymous\" });\n return;\n }\n\n try {\n const authResult = await this.config.verifyAuth(data, session.id);\n if (!authResult) {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n return;\n }\n\n session.serverCtx = authResult;\n this.sendMessage(session, { type: \"auth:success\", userId: authResult.user.userId });\n } catch (e) {\n if (e instanceof AuthenticationError) {\n this.sendMessage(session, {\n type: \"auth:failed\",\n message: \"Invalid authentication: \" + e.message,\n extra: e.extra,\n });\n } else {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n }\n }\n }\n\n /**\n * Park a mutation on this session's FIFO queue and resolve when it\n * eventually drains. The drainer is fire-and-forget: it picks up any\n * newly enqueued op as long as one drain loop is running on this\n * session. Callers (handleCall) own the response/error for their own\n * op via the returned promise.\n */\n private enqueueMutation(\n session: Session<TContext>,\n procedure: RegisteredProcedure<TContext>,\n validatedInput: unknown,\n ): Promise<MutationResult<unknown>> {\n return new Promise<MutationResult<unknown>>((resolve, reject) => {\n const pending: PendingMutation<TContext> = {\n procedure,\n validatedInput,\n resolve,\n reject,\n };\n session.mutationQueue.push(pending);\n this.drainMutationQueue(session).catch((err) =>\n logger.error(err, \"drainMutationQueue crashed\"),\n );\n });\n }\n\n /**\n * Drain the per-session mutation queue serially. Only one drain loop\n * runs at a time per session — re-entrant calls bail immediately, the\n * loop already in progress will pick up the new op before exiting.\n */\n private async drainMutationQueue(session: Session<TContext>): Promise<void> {\n if (session.mutationInFlight) return;\n session.mutationInFlight = true;\n try {\n while (session.mutationQueue.length > 0) {\n const op = session.mutationQueue.shift()!;\n try {\n const fn = op.procedure.fn as (\n ctx: MutationCtx<TContext>,\n input: unknown,\n ) => Promise<unknown>;\n const result = await this.db.mutation(async (ctx) => {\n return fn({ db: ctx, serverCtx: session.serverCtx, ...primaryOnlyConn }, op.validatedInput);\n });\n op.resolve(result);\n } catch (err) {\n op.reject(err);\n }\n }\n } finally {\n session.mutationInFlight = false;\n }\n }\n\n private async handleCall(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown,\n minTs?: bigint,\n ): Promise<void> {\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type === \"job\") {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found`, \"NOT_FOUND\");\n return;\n }\n\n try {\n const validatedInput = (procedure.inputSchema as ZodType).parse(input);\n\n if (procedure.type === \"query\") {\n // One-shot query: same cache/identity/freshness model as a live\n // subscription (see runCachedQuery), just without tracking a sub.\n // Load the freshness upper bound here, mirroring handleSubscribe which\n // loads lastSnapshotTs before registering.\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const { data, ts } = await runCachedQuery(\n this.oneShotDeps,\n procedure,\n procedureName,\n validatedInput,\n session.serverCtx,\n lastSnapshotTs,\n minTs,\n );\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data,\n // Echo the read snapshot so the client advances its high-water and a\n // later read preserves read-your-writes / monotonic reads.\n ts: ts.toString(),\n });\n } else if (procedure.type === \"mutation\") {\n if (session.mutationQueue.length >= MAX_MUTATION_QUEUE_BUFFER) {\n this.sendError(\n session,\n id,\n `Too many concurrent mutations on this connection (max ${MAX_MUTATION_QUEUE_BUFFER})`,\n \"TOO_MANY_CONCURRENT_MUTATIONS\",\n );\n return;\n }\n\n const mutationResult = await this.enqueueMutation(session, procedure, validatedInput);\n\n // Invalidation is driven by the db's after-commit hook (wired in the\n // constructor) for every mutation source, so we don't invalidate again\n // here — we just return the result to the caller.\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: mutationResult.result,\n // The commitTs seeds the client's read high-water so its next read\n // reads its own write (routed to primary until the replica catches up).\n ts: mutationResult.commitTs.toString(),\n });\n } else if (procedure.type === \"action\") {\n if (session.actionInFlight >= MAX_ACTION_CONCURRENCY) {\n this.sendError(\n session,\n id,\n `Too many concurrent actions on this connection (max ${MAX_ACTION_CONCURRENCY})`,\n \"TOO_MANY_CONCURRENT_ACTIONS\",\n );\n return;\n }\n\n session.actionInFlight++;\n let result: unknown;\n try {\n const fn = procedure.fn as (ctx: ActionCtx<TContext>, input: unknown) => Promise<unknown>;\n result = await fn(\n { db: this.db, serverCtx: session.serverCtx, storage: this.storage, scheduler: this.jobClient },\n validatedInput,\n );\n } finally {\n session.actionInFlight--;\n }\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: result,\n });\n }\n\n\n } catch (err) {\n logger.error(err, `Error executing \"${procedureName}\"`);\n this.sendError(\n session,\n id,\n err instanceof Error ? err.message : \"Execution failed\",\n \"EXECUTION_ERROR\"\n );\n }\n }\n\n private async handleSubscribe(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown\n ): Promise<void> {\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found or not a query`, \"NOT_FOUND\");\n return;\n }\n\n try {\n const validatedInput = procedure.inputSchema.parse(input);\n const queryIdentity = resolveQueryIdentity(procedure, session.serverCtx, validatedInput, this.config.getUserId);\n const inputJsonStable = stableStringify(validatedInput);\n const subId = await generateSubscriptionId(procedureName, inputJsonStable, queryIdentity);\n const cacheKey = await generateCacheKey(procedureName, inputJsonStable, queryIdentity);\n const existingServerSub = this.serverSubscriptions.get(subId);\n\n let data: unknown;\n let dataHash: string;\n let liveResult: LiveResult<unknown> | null = null;\n let servedTs!: bigint;\n\n // track this session subscribed this subscription\n session.subIds.add(subId);\n\n if (existingServerSub) {\n existingServerSub.subscribedTimes += 1;\n\n const cached = await this.cache.getQueryCacheFor(existingServerSub.cacheKey);\n if (cached) {\n data = cached.data;\n servedTs = cached.lastSnapshotTs;\n } else {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);\n data = liveResult.data;\n servedTs = liveResult.ts;\n }\n } else {\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subManagerResult = await this.subManagerClient.registerSubscription({\n subId,\n cacheKey,\n queryName: procedureName,\n args: validatedInput,\n queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs: lastSnapshotTs,\n });\n\n // Serve the cached blob only when the sub-manager says it's fresh AND its\n // version matches the metadata that freshness was validated against — a\n // divergent data/metadata pair (torn read, cross-process write race) is\n // treated as a miss and recomputed rather than served stale.\n let rebuild: boolean = true;\n if (!subManagerResult.recompute) {\n const cached = await this.cache.getQueryCacheFor(subManagerResult.cacheKey);\n if (cached && cached.version === subManagerResult.version) {\n data = cached.data;\n servedTs = cached.lastSnapshotTs;\n rebuild = false;\n }\n }\n\n if (rebuild) {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);\n data = liveResult.data;\n servedTs = liveResult.ts;\n }\n\n this.serverSubscriptions.set(subId, {\n subId,\n cacheKey,\n subscribedTimes: 1,\n });\n this.stats.activeSubscriptions++;\n }\n\n if (liveResult) {\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey,\n subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n // The sub-worker writes the matching metadata version via\n // updateSubscriptionReadSet above (version === String(ts)).\n this.cache.setQueryCacheFor(cacheKey, data, liveResult.ts),\n ]);\n\n // If the sub-manager just told us this subId isn't tracked (we hit\n // a window between its register and a separate restart), our register\n // above should have rebuilt the state — log loudly if not.\n if (!updateResult.tracked) {\n logger.warn(\n `updateSubscriptionReadSet returned tracked:false immediately after register for ${subId} ` +\n `— possible sub-manager restart mid-subscribe`,\n );\n }\n }\n\n dataHash = await getHashOf(data);\n\n // Hand the canonical subId back to the client so it doesn't need to\n // recompute the hash locally (which would require pulling in wasm and\n // wouldn't reflect per-procedure `queryIdentity` overrides).\n this.sendMessage(session, { type: \"response\", id, success: true, subId });\n // Carry the snapshot ts so the client can advance its read high-water\n // (cross-sub monotonic reads): the recompute's ts, or — when served from\n // cache — the entry's own `lastSnapshotTs` (see cachedTs above).\n this.sendMessage(session, {\n type: \"sub:update\",\n subId,\n data,\n dataHash,\n ts: servedTs.toString(),\n });\n\n logger.info(`Subscribed: ${subId} for session ${session.id}`);\n } catch (err) {\n logger.error(err, `Error subscribing to \"${procedureName}\"`);\n this.sendError(session, id, err instanceof Error ? err.message : \"Subscribe failed\", \"SUBSCRIBE_ERROR\");\n }\n }\n\n private async handleUnsubscribe(session: Session<TContext>, subId: string): Promise<void> {\n const serverSub = this.serverSubscriptions.get(subId);\n if (!serverSub) {\n session.subIds.delete(subId);\n return;\n }\n\n serverSub.subscribedTimes -= 1;\n if (serverSub.subscribedTimes <= 0) {\n session.subIds.delete(subId);\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions--;\n\n try {\n await this.subManagerClient.unregisterSubscription({ subId, instanceName: this.instanceId });\n } catch (err) {\n logger.error(err, \"Failed to unregister from sub-manager:\");\n }\n }\n\n logger.info(`Unsubscribed: ${subId} for session ${session.id}, remaining server subs: ${serverSub.subscribedTimes}`);\n }\n\n private async handleDisconnect(session: Session<TContext>): Promise<void> {\n logger.info(`Session disconnected: ${session.id}`);\n\n let unregisterSubs: SubscriptionEntry[] | undefined = undefined;\n for (const subId of session.subIds) {\n const sub = this.serverSubscriptions.get(subId);\n if (!sub) continue;\n\n sub.subscribedTimes -= 1;\n if (sub.subscribedTimes <= 0) {\n if (!unregisterSubs) {\n unregisterSubs = [];\n }\n\n unregisterSubs.push(sub);\n }\n }\n\n if (!unregisterSubs || unregisterSubs.length === 0) return;\n\n try {\n await this.subManagerClient.unregisterSubscriptions(\n unregisterSubs.map((sub) => ({ subId: sub.subId, instanceName: this.instanceId })),\n );\n } catch (err) {\n logger.error(err, \"Failed to unregister from sub-manager:\");\n }\n\n for (const sub of unregisterSubs) {\n // if still not subscribing\n if (sub.subscribedTimes <= 0) {\n this.serverSubscriptions.delete(sub.subId);\n this.stats.activeSubscriptions--;\n }\n }\n\n logger.info(`All Subscription sessions is disconnected: ${session.id}, subs: ${unregisterSubs.length}`);\n }\n\n /**\n * Drives recovery on a sub-manager reconnect. Iterates this app server's\n * `serverSubscriptions`, reads each subId's registration record from\n * Redis (`sl:reg:<subId>`), and re-registers it with the sub-manager.\n *\n * • If the Redis record is missing, another app-server instance fully\n * unregistered the sub during our disconnect. We drop it locally and\n * send `sub:gone` to every session that was holding it.\n * • If the sub-manager reports `recompute: true` (cache-metadata + commit\n * log replay detected staleness), we kick a reexec for that sub.\n *\n * Returns after all subs have been processed. Only then does the\n * SubManagerClient flush its buffered invalidate writeSets.\n */\n private async runSubManagerRecovery(): Promise<void> {\n const snapshot = Array.from(this.serverSubscriptions.values());\n if (snapshot.length === 0) {\n logger.info(\"Sub-manager recovery: no subscriptions to restore\");\n return;\n }\n\n logger.info(`Sub-manager recovery: restoring ${snapshot.length} subscription(s)`);\n\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subIds = snapshot.map((e) => e.subId);\n const recordsMap = await this.cache.fetchRegistrationRecordsBatch(subIds);\n\n const registerParams: Array<{\n entry: typeof snapshot[0];\n record: RegistrationRecord;\n }> = [];\n for (const entry of snapshot) {\n const record = recordsMap.get(entry.subId) ?? null;\n if (!record) {\n this.dropSubscriptionLocally(entry.subId, \"registration not found after recovery\");\n continue;\n }\n registerParams.push({ entry, record });\n }\n\n const recompute: AffectedSubscription[] = [];\n const BATCH_SIZE = 50;\n for (let i = 0; i < registerParams.length; i += BATCH_SIZE) {\n const batch = registerParams.slice(i, i + BATCH_SIZE);\n const params = batch.map(({ entry, record }) => ({\n subId: entry.subId,\n cacheKey: entry.cacheKey,\n queryName: record.queryName,\n args: record.args,\n queryIdentity: record.queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs,\n }));\n try {\n const results = await this.subManagerClient.registerSubscriptionBatch(params);\n for (let j = 0; j < batch.length; j++) {\n const { entry, record } = batch[j]!;\n const result = results[j]!;\n if (result.recompute) {\n recompute.push({\n subId: entry.subId,\n queryName: record.queryName,\n args: record.args,\n cacheKey: entry.cacheKey,\n notifyInstances: [],\n });\n }\n }\n } catch (err) {\n logger.error(err, `Sub-manager recovery: failed to batch register ${i}-${i + batch.length}`);\n }\n }\n\n await Promise.allSettled(recompute.map((a) => this.reexecuteAffectedQuery(a)));\n\n logger.info(`Sub-manager recovery: done (${recompute.length} reexec'd)`);\n }\n\n private async runAffectedHandler(affected: AffectedSubscription[]) {\n await Promise.allSettled(\n affected.map((a) => this.reexecuteAffectedQuery(a)),\n );\n }\n\n /**\n * Removes a subscription from this app server's state and notifies every\n * session that was holding it. Used during recovery when the central\n * registration record is gone, or after a `tracked: false` reply from the\n * sub-manager that we can't reconcile.\n */\n private dropSubscriptionLocally(subId: string, reason: string): void {\n const serverSub = this.serverSubscriptions.get(subId);\n let notified = 0;\n if (serverSub) {\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions = Math.max(0, this.stats.activeSubscriptions - 1);\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) {\n notified++;\n session.subIds.delete(subId);\n this.sendMessage(session, { type: \"sub:gone\", subId, reason });\n }\n }\n }\n\n logger.warn(`Dropped subscription ${subId} locally (${reason}); notified ${notified} session(s)`);\n }\n\n private async handleMutationCommit(writeSet: WriteEntry[], commitTs: CommitTs): Promise<void> {\n if (writeSet.length === 0) return;\n\n const tables = [...new Set(writeSet.map((w) => w.table))];\n logger.debug(`Mutation committed for tables: [${tables.join(\", \")}]`);\n\n try {\n const result = await this.subManagerClient.invalidateWriteset({ writeSet });\n logger.debug({ affected: result }, \"Sub-managers invalidation result\");\n\n if (result?.affected?.length > 0) {\n await Promise.allSettled(\n result.affected.map((affected) => this.dispatchAffected(affected, commitTs)),\n );\n }\n } catch (err) {\n logger.error(err, \"Failed to notify sub-managers\");\n }\n }\n\n /**\n * Route an affected sub to whichever instance can actually run the\n * recompute:\n * • If this instance has a local session for the sub, recompute here\n * (fast path) and broadcast the result to every other instance.\n * • Otherwise broadcast a `recompute-race` to every instance in\n * `notifyInstances`. Each receiver tries to acquire a Redis lock\n * keyed by (subId, commitTs); the winner recomputes and publishes\n * an `update` via the normal broadcast path, losers no-op.\n *\n * If `notifyInstances` is empty (the sub has no live holders), we drop\n * the message — the next subscribe will recompute fresh state.\n */\n private async dispatchAffected(affected: AffectedSubscription, commitTs: CommitTs): Promise<void> {\n if (this.hasLocalSessionFor(affected.subId)) {\n await this.reexecuteAffectedQuery(affected);\n return;\n }\n\n if (affected.notifyInstances.length === 0) {\n logger.debug(\n `dispatchAffected: no live instances hold ${affected.subId} — dropping`,\n );\n return;\n }\n\n await this.publishRecomputeRace(affected, commitTs);\n }\n\n private hasLocalSessionFor(subId: SubId): boolean {\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) return true;\n }\n return false;\n }\n\n private async publishRecomputeRace(\n affected: AffectedSubscription,\n commitTs: CommitTs,\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"recompute-race\",\n subId: affected.subId,\n cacheKey: affected.cacheKey,\n queryName: affected.queryName,\n args: affected.args,\n notifyInstances: affected.notifyInstances,\n commitTs: commitTs.toString(),\n originInstance: this.instanceId,\n });\n\n const publishPromises = affected.notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n logger.error(\n err,\n `Failed to publish recompute-race to ${channel} after many retries`,\n );\n }\n });\n\n await Promise.all(publishPromises);\n }\n\n private async reexecuteAffectedQuery(affected: AffectedSubscription): Promise<void> {\n let foundSession: Session<TContext> | null = null;\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n foundSession = session;\n break;\n }\n }\n\n if (!foundSession) {\n // Should only happen if every local session unsubscribed between\n // dispatchAffected's check and now. dispatchAffected forwards to\n // another instance when no local session exists, so we'd lose the\n // update for everyone in this rare race. Accept the gap.\n logger.debug(`Session not found for subId: ${affected.subId}`);\n return;\n }\n\n try {\n const liveResult = await this.executeLiveQuery(affected.queryName, affected.args, foundSession.serverCtx);\n const dataHash = await getHashOf(liveResult.data);\n\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey: affected.cacheKey,\n subId: affected.subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n this.cache.setQueryCacheFor(affected.cacheKey, liveResult.data, liveResult.ts),\n ]);\n\n // `tracked: false` means the sub-manager has no record of this subId\n // (it restarted before we recovered, or a concurrent unregister wiped\n // it). Drop the sub locally and let clients re-subscribe.\n if (!updateResult.tracked) {\n this.dropSubscriptionLocally(affected.subId, \"no longer tracked by sub-manager\");\n return;\n }\n\n // find any session with same subId and send update\n const ts = liveResult.ts.toString();\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: affected.subId,\n data: liveResult.data,\n dataHash,\n ts,\n });\n }\n }\n\n if (affected.notifyInstances.length > 0) {\n await this.publishSubscriptionUpdate(\n affected.subId,\n liveResult.data,\n dataHash,\n affected.notifyInstances,\n ts,\n );\n }\n\n logger.debug(`Re-executed affected query for subId: ${affected.subId}`);\n } catch (err) {\n logger.error(err, `Failed to re-execute query for subId ${affected.subId}`);\n }\n }\n\n private async executeLiveQuery(\n procedureName: string,\n input: unknown,\n serverCtx?: TContext\n ): Promise<LiveResult<unknown>> {\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n throw new Error(`Procedure not defined: ${procedureName}`);\n }\n\n return this.db.liveQuery(async (ctx) => {\n const fn = procedure.fn as (ctx: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n return fn({ db: ctx, serverCtx, ...primaryOnlyConn }, input);\n });\n }\n\n private sendMessage(session: Session<TContext>, message: ServerMessage): void {\n if (session.ws.readyState !== WebSocket.OPEN) return;\n session.ws.send(supaliveStringify(message));\n // Mark the channel as having had recent outbound traffic. The\n // heartbeat tick uses this to skip the idle-only app-level ping\n // for busy sessions (no point pinging a client that just got a\n // sub:update).\n session.lastSentAt = Date.now();\n }\n\n private sendMessageRaw(session: Session<TContext>, message: string): void {\n if (session.ws.readyState === WebSocket.OPEN) {\n session.ws.send(message);\n }\n }\n\n private sendError(session: Session<TContext>, id: string, error: string, code: string): void {\n this.sendMessage(session, { type: \"response\", id, success: false, error, code });\n }\n\n private async subscribeToInstanceChannel(): Promise<void> {\n const instanceChannel = `sub:instance:${this.instanceId}`;\n const schemaChannel = SCHEMA_RELOAD_CHANNEL;\n const watermarkChannel = RETENTION_WATERMARK_CHANNEL;\n\n // App-level channels (config.appPubSub) ride this SAME subscribe connection —\n // no extra Redis connection per instance.\n const appChannels = [...this.appPubSubChannels];\n await this.redisSub.subscribe(instanceChannel, schemaChannel, watermarkChannel, ...appChannels);\n this.redisSub.on(\"connect\", () => {\n logger.info(\n `Subscribed to Redis channels: ${[instanceChannel, schemaChannel, watermarkChannel, ...appChannels].join(\", \")}`,\n );\n });\n this.redisSub.on(\"message\", (channel: string, message: string) => {\n if (channel === instanceChannel) {\n this.handleRemoteInstanceMessage(message).catch((err) => {\n logger.error(err, \"Error handling remote instance message\");\n });\n } else if (channel === schemaChannel) {\n this.handleSchemaReload().catch((err) => {\n logger.error(err, \"Error handling schema reload\");\n });\n } else if (channel === watermarkChannel) {\n // Sub-manager advertised a new retention watermark after a prune. Adopt it\n // (monotonic — a stale/out-of-order message can't lower it).\n try {\n this.retentionWatermark.set(BigInt(message));\n } catch (err) {\n logger.error(err, `Bad retention watermark message: ${message}`);\n }\n } else if (this.appPubSubChannels.has(channel)) {\n // App-level message (auth revocations, etc.). The handler owns decoding;\n // we just guard against it throwing.\n Promise.resolve(this.config.appPubSub?.onMessage(channel, message)).catch((err) =>\n logger.error(err, `Error in appPubSub handler for channel ${channel}`),\n );\n }\n });\n this.redisSub.on(\"error\", (err: Error) => {\n logger.error(err, `Subscriber error for channels: ${instanceChannel}, ${schemaChannel}`)\n });\n }\n\n /**\n * React to a `schema:reload` broadcast: invalidate the in-memory column\n * type cache for the named tables (or all if omitted) and re-bootstrap\n * from Postgres so subsequent batched writes see the fresh types. Skips\n * messages published by this instance (we already have the new types).\n */\n private async handleSchemaReload(): Promise<void> {\n logger.info(`Schema reload request received in instance ${this.instanceId} — reloading schema...`);\n try {\n await sleep(1000 + (Math.random() * 5000)); // jitter to avoid thundering herd if many instances\n await bootstrapDbTypes(this.db, this.cache, true);\n logger.info(`Schema reload applied in instance ${this.instanceId}`);\n } catch (err) {\n logger.error(err, \"Failed to reload schema\");\n return;\n }\n }\n\n /**\n * Broadcast a schema-reload signal to every server instance (including\n * this one — the handler skips self-originated messages, so calling\n * after a local migration is safe). Pass `tables` to scope the reload;\n * omit to flush everyone's cache.\n */\n async publishSchemaReload(tables?: string[]): Promise<void> {\n const payload = JSON.stringify({\n type: \"schema:reload\",\n tables,\n originInstance: this.instanceId,\n });\n await redisRetryPolicy.execute(() =>\n this.cache.redis.publish(SCHEMA_RELOAD_CHANNEL, payload));\n }\n\n /**\n * Publish an app-level message (see {@link SupaliveServerConfig.appPubSub}) to\n * a Redis channel. Rides the shared command connection and is delivered to\n * every instance subscribed to `channel`, INCLUDING this one — so a handler\n * should tolerate/skip self-originated messages if that matters. The message\n * is an opaque string; the app owns encoding. Uses the standard Redis retry\n * policy; callers typically fire-and-forget.\n */\n async publishAppMessage(channel: string, message: string): Promise<void> {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, message));\n }\n\n private async handleRemoteInstanceMessage(message: string): Promise<void> {\n let parsed: RemoteSubscriptionMessage;\n try {\n parsed = RemoteSubscriptionMessageSchema.parse(JSON.parse(message));\n } catch (err) {\n logger.error(err, \"Failed to parse remote instance message\");\n return;\n }\n\n if (parsed.originInstance === this.instanceId) return;\n\n if (parsed.type === \"update\") {\n for (const [, session] of this.sessions) {\n if (!session.subIds.has(parsed.subId)) continue;\n\n const subEntry = this.serverSubscriptions.get(parsed.subId);\n if (!subEntry) continue;\n\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: parsed.subId,\n data: parsed.data,\n dataHash: parsed.dataHash,\n ts: parsed.ts,\n });\n }\n return;\n }\n\n // type === \"recompute-race\" — writer didn't hold the sub locally and\n // broadcast a race hint to every notifyInstance. The first to acquire\n // the lock recomputes; the rest no-op.\n await this.handleRemoteRecomputeRace(parsed);\n }\n\n private async handleRemoteRecomputeRace(req: RemoteRecomputeRace): Promise<void> {\n if (!this.hasLocalSessionFor(req.subId)) {\n // We're in notifyInstances per sub-manager but the sub has since\n // been dropped here. Another holder may still pick it up — no warn.\n return;\n }\n\n const won = await this.cache.tryAcquireRecomputeLock(\n this.instanceId, req.subId, req.commitTs, RECOMPUTE_LOCK_TTL_SECONDS,\n );\n if (!won) {\n logger.debug(\n `recompute-race: lost lock for ${req.subId}@${req.commitTs}, waiting for winner's update`,\n );\n return;\n }\n\n await this.reexecuteAffectedQuery({\n subId: req.subId,\n queryName: req.queryName,\n args: req.args,\n cacheKey: req.cacheKey,\n notifyInstances: req.notifyInstances,\n });\n }\n\n private async publishSubscriptionUpdate(\n subId: string,\n data: unknown,\n dataHash: string,\n notifyInstances: string[],\n ts?: string,\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"update\",\n subId,\n data,\n dataHash,\n originInstance: this.instanceId,\n ts,\n });\n\n const publishPromises = notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n logger.error(err, `Failed to publish to ${channel} after many retries`);\n }\n });\n\n await Promise.all(publishPromises);\n }\n}\n\n// ─── HTTP helpers (job endpoint) ─────────────────────────────────────────────\n\n/** Parse a wire `minTs` (decimal bigint string) to a bigint, or undefined when\n * absent/malformed — a bad value simply falls back to primary-safe routing. */\nfunction parseMinTs(minTs: string | undefined): bigint | undefined {\n if (minTs === undefined) return undefined;\n try {\n return BigInt(minTs);\n } catch {\n return undefined;\n }\n}\n\n/** Normalize a job path to a leading-slash, no-trailing-slash prefix. */\nfunction normalizeJobPath(p: string): string {\n let s = p.startsWith(\"/\") ? p : `/${p}`;\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const payload = supaliveStringify(body);\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(payload);\n}\n\nfunction lowercaseHeaders(headers: IncomingMessage[\"headers\"]): Record<string, string | undefined> {\n const out: Record<string, string | undefined> = {};\n for (const [k, v] of Object.entries(headers)) {\n out[k.toLowerCase()] = Array.isArray(v) ? v.join(\",\") : v;\n }\n return out;\n}\n\n/** Buffer an incoming request body to a string, capped to guard against abuse. */\nfunction readRequestBody(req: IncomingMessage, maxBytes = 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > maxBytes) {\n reject(new Error(\"request body too large\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n}\n","/**\n * FNV-1a 32-bit hash.\n *\n * Used for routing subscription operations to logical workers inside the\n * sub-manager process. Same input always returns the same 32-bit unsigned\n * integer in both JavaScript and Go, so a future Go port of the sub-manager\n * can use the identical routing without rebalancing keys.\n *\n * Reference: http://www.isthe.com/chongo/tech/comp/fnv/\n */\nexport function fnv1a32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n // h = h * 16777619, expressed as shifts to stay in 32-bit space\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n return h >>> 0;\n}\n","import { Server as WebSocketServer } from \"rpc-websockets\";\nimport { Worker } from \"node:worker_threads\";\nimport { fileURLToPath } from \"node:url\";\nimport { SubscriptionManagerConfig } from \"../config\";\nimport { fnv1a32 } from \"./hash\";\nimport { subscriptionManagerDataPack, type SubManagerLink } from \"./sub-manager-client\";\nimport { SubscriptionWorker } from \"./sub-worker\";\nimport { dispatchWorkerMessage } from \"./sub-worker-dispatch\";\nimport type { Database } from \"../db/db\";\nimport type { CacheLayer } from \"../db/cache\";\nimport { createDatabase, initCacheLayer } from \"../db/init_db\";\n\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport {\n AffectedSubscription,\n InvalidateWritesetResult,\n RegisterSubscriptionBatchResult,\n RegisterSubscriptionParams,\n RegisterSubscriptionResult,\n UnregisterSubscriptionParams,\n UnregisterSubscriptionResult,\n UnregisterSubscriptionsParams,\n UnregisterSubscriptionsResult,\n UpdateSubscriptionReadSetResult,\n} from \"./sub-manager-types\";\nimport { groupByToMap } from \"../helper\";\nimport {\n MsgType,\n type WorkerInitPayload,\n type WorkerRequest,\n type WorkerResponse,\n} from \"./sub-manager-worker-protocol\";\n\n// Re-export SubscriptionWorker so existing test imports\n// (`import { SubscriptionWorker } from \"./subscription-manager\"`) keep working.\nexport { SubscriptionWorker } from \"./sub-worker\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR\" },\n { level: logLevelOf(\"SUBS_MANAGER_SERVER_LOG_LEVEL\") },\n);\n\n// Resolve the entry script next to this compiled file. `import.meta.url` works\n// for both Bun (running TS source) and Node (running emitted JS) — we just\n// swap the extension based on the current module's path.\nfunction resolveWorkerEntry(): { url: URL; isTs: boolean } {\n const here = new URL(import.meta.url);\n const isTs = here.pathname.endsWith(\".ts\");\n const ext = isTs ? \".ts\" : \".js\";\n return {\n url: new URL(`./sub-manager-worker-entry${ext}`, import.meta.url),\n isTs,\n };\n}\n\n// When the entry is a `.ts` file and we're running on Node (not Bun), the\n// worker thread inherits no module loader — Node can strip types but its bare\n// ESM resolver requires explicit file extensions, which our extensionless\n// internal imports don't satisfy. Register tsx as the worker loader so it\n// resolves the same way the parent module does.\nfunction workerExecArgv(isTs: boolean): string[] | undefined {\n if (!isTs) return undefined;\n if (typeof (globalThis as any).Bun !== \"undefined\") return undefined;\n return [\"--import\", \"tsx\"];\n}\n\n// ─── Worker handles ──────────────────────────────────────────────────────────\n\n/**\n * Uniform interface the dispatcher uses to drive a logical worker, regardless\n * of where it runs. {@link ThreadWorkerHandle} proxies to a `worker_threads`\n * Worker (default, parallel across cores); {@link InlineWorkerHandle} calls a\n * {@link SubscriptionWorker} directly on the main event loop (embedded mode —\n * no thread hop, no serialization).\n */\nexport interface WorkerHandle {\n readonly id: number;\n ready(): Promise<void>;\n send<T>(type: MsgType, payload: unknown): Promise<T>;\n shutdown(): Promise<void>;\n}\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (err: Error) => void;\n}\n\nclass ThreadWorkerHandle implements WorkerHandle {\n readonly id: number;\n private worker: Worker;\n private pending = new Map<number, PendingRequest>();\n private nextReqId = 1;\n private readyPromise: Promise<void>;\n\n constructor(id: number, init: WorkerInitPayload) {\n this.id = id;\n const entry = resolveWorkerEntry();\n this.worker = new Worker(fileURLToPath(entry.url), {\n execArgv: workerExecArgv(entry.isTs),\n });\n\n this.worker.on(\"message\", (res: WorkerResponse) => {\n const pending = this.pending.get(res.reqId);\n if (!pending) return;\n\n this.pending.delete(res.reqId);\n if (res.ok) {\n pending.resolve(res.result);\n } else {\n const err = new Error(res.error.message);\n if (res.error.stack) err.stack = res.error.stack;\n pending.reject(err);\n }\n });\n\n this.worker.on(\"error\", (err: unknown) => {\n logger.error(err, `[worker ${this.id}] worker thread errored`);\n const e = err instanceof Error ? err : new Error(String(err));\n for (const [, p] of this.pending) p.reject(e);\n this.pending.clear();\n });\n\n this.readyPromise = this.send<null>(MsgType.Init, init).then(() => { });\n }\n\n ready(): Promise<void> {\n return this.readyPromise;\n }\n\n send<T>(type: MsgType, payload: unknown): Promise<T> {\n const reqId = this.nextReqId++;\n return new Promise<T>((resolve, reject) => {\n this.pending.set(reqId, {\n resolve: resolve as (v: unknown) => void,\n reject,\n });\n const req: WorkerRequest = { type, reqId, payload };\n this.worker.postMessage(req);\n });\n }\n\n async shutdown(): Promise<void> {\n try {\n await this.send<null>(MsgType.Shutdown, null);\n } catch (_) { /* ignore */ }\n await this.worker.terminate();\n }\n}\n\n/**\n * In-process worker handle: holds a live {@link SubscriptionWorker} and drives\n * it via {@link dispatchWorkerMessage} with `trusted: true`. Every `send` is a\n * direct `await worker.method(payload)` on the main event loop — no\n * `postMessage`, no structured-clone. Used when the manager is embedded in the\n * app server. When `ownsDb` is false the db is borrowed from the host server\n * and is NOT closed on shutdown.\n */\nclass InlineWorkerHandle implements WorkerHandle {\n readonly id: number;\n private worker: SubscriptionWorker;\n private db: Database;\n private ownsDb: boolean;\n\n constructor(id: number, worker: SubscriptionWorker, db: Database, ownsDb: boolean) {\n this.id = id;\n this.worker = worker;\n this.db = db;\n this.ownsDb = ownsDb;\n }\n\n ready(): Promise<void> {\n return Promise.resolve();\n }\n\n send<T>(type: MsgType, payload: unknown): Promise<T> {\n return dispatchWorkerMessage(this.worker, this.db, type, payload, { trusted: true }) as Promise<T>;\n }\n\n async shutdown(): Promise<void> {\n if (!this.ownsDb) return;\n try { await this.db.close(); } catch (_) { /* ignore */ }\n }\n}\n\n// ─── Dispatcher ──────────────────────────────────────────────────────────────\n\n/**\n * One process per deployment. Routes each subscription operation to one of N\n * worker threads by hashing subId. Worker threads run in parallel on separate\n * CPU cores; this dispatcher does no Zod parsing — it reads `subId` directly\n * from the decoded payload (the internal channel is trusted) and forwards the\n * raw payload. The worker validates and parses with Zod before executing.\n */\nexport class SubscriptionManager {\n private wss?: WebSocketServer;\n private port?: number;\n private listen: boolean;\n private workers: WorkerHandle[];\n private pruneInterval: ReturnType<typeof setInterval> | null = null;\n private static readonly PRUNE_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000;\n\n constructor(config: SubscriptionManagerConfig) {\n this.listen = config.listen !== false;\n if (this.listen) {\n if (config.port == null) {\n throw new Error(\"SubscriptionManager: `port` is required unless `listen: false`\");\n }\n this.port = config.port;\n this.wss = new WebSocketServer({ port: this.port }, subscriptionManagerDataPack);\n this.wss.dataPack = subscriptionManagerDataPack;\n }\n\n const inline = config.inline === true;\n let numWorkers = resolveWorkerCount(config.workers);\n if (inline && numWorkers !== 1) {\n logger.warn(`inline mode runs on the main event loop; forcing workers=1 (was ${numWorkers})`);\n numWorkers = 1;\n }\n\n if (inline) {\n this.workers = [this.buildInlineWorker(config)];\n } else {\n if (!config.database || !config.upstash) {\n throw new Error(\n \"SubscriptionManager: `database` and `upstash` are required in threaded (non-inline) mode\",\n );\n }\n const maxConnectionsPerWorker = resolveMaxConnectionsPerWorker(config, numWorkers);\n logger.info(`Spawning ${numWorkers} subscription worker${numWorkers === 1 ? \"\" : \"s\"}`);\n this.workers = Array.from({ length: numWorkers }, (_, i) =>\n new ThreadWorkerHandle(i + 1, {\n workerId: i + 1,\n database: config.database!,\n upstash: config.upstash!,\n cacheTTLSeconds: config.cacheTTLSeconds,\n maxConnectionsPerWorker,\n }),\n );\n }\n\n if (this.listen) this.registerMethods();\n }\n\n /**\n * Build the single in-process worker for {@link SubscriptionManagerConfig.inline}\n * mode. Reuses the host server's {@link SubscriptionManagerConfig.db} +\n * {@link SubscriptionManagerConfig.cache} when provided (borrowed — not closed\n * on stop); otherwise opens its own pool/cache from `database`/`upstash`.\n */\n private buildInlineWorker(config: SubscriptionManagerConfig): InlineWorkerHandle {\n let db: Database;\n let cache: CacheLayer;\n let ownsDb: boolean;\n\n if (config.db && config.cache) {\n db = config.db;\n cache = config.cache;\n ownsDb = false;\n logger.info(\"Inline subscription worker: reusing host db + cache\");\n } else {\n if (!config.database || !config.upstash) {\n throw new Error(\n \"SubscriptionManager inline mode needs either { db, cache } to reuse, \" +\n \"or { database, upstash } to build its own\",\n );\n }\n const { cacheLayer } = initCacheLayer({\n upstash: config.upstash,\n cacheTtlSeconds: config.cacheTTLSeconds,\n commandMaxRetries: 5,\n createSubClient: false,\n });\n cache = cacheLayer;\n db = createDatabase(config.database, 1);\n ownsDb = true;\n logger.info(\"Inline subscription worker: built own db + cache\");\n }\n\n const worker = new SubscriptionWorker(1, cache, db);\n return new InlineWorkerHandle(1, worker, db, ownsDb);\n }\n\n async start(): Promise<void> {\n await Promise.all(this.workers.map((w) => w.ready()));\n logger.info(this.listen ? `Listening on port ${this.port}` : \"Started (embedded, in-process)\");\n // Seed the retention watermark before serving so the query-cache freshness\n // check has an accurate watermark immediately (it survives restarts in the\n // metadata table; only pruning moves it afterward). Worker 0 seeds/repairs\n // it from the DB and returns it; fan it out to every worker's in-memory copy\n // (and, via Redis, to app-server instances).\n try {\n const minRetainedTs = await this.workers[0]!.send<bigint>(MsgType.RefreshRetentionWatermark, null);\n await this.broadcastRetentionWatermark(minRetainedTs);\n } catch (err) {\n logger.error(err, \"initial retention watermark seed failed\");\n }\n this.pruneInterval = setInterval(\n () => { this.pruneCommitLogs().catch((err) => logger.error(err, \"commit_logs prune failed\")); },\n SubscriptionManager.PRUNE_INTERVAL_MS,\n );\n if (this.pruneInterval && typeof this.pruneInterval.unref === \"function\") {\n this.pruneInterval.unref();\n }\n }\n\n async stop(): Promise<void> {\n if (this.pruneInterval) {\n clearInterval(this.pruneInterval);\n this.pruneInterval = null;\n }\n this.wss?.close();\n await Promise.all(this.workers.map((w) => w.shutdown()));\n logger.info(\"Stopped\");\n }\n\n /**\n * A direct, in-process {@link SubManagerLink} to this manager, for embedding\n * it in the app server (pass as `SupaliveServerConfig.subManager`). Calls the\n * dispatch handlers directly — no socket, no serialization. The recovery\n * callbacks are no-ops: an in-process link never disconnects, so the app\n * server's reconnect/buffer machinery is inert.\n */\n localLink(): SubManagerLink {\n return {\n registerSubscription: (p) => this.registerSubscription(p),\n registerSubscriptionBatch: (p) => this.registerSubscriptionBatch(p),\n updateSubscriptionReadSet: (p) => this.updateSubscriptionReadSet(p),\n unregisterSubscription: (p) => this.unregisterSubscription(p),\n unregisterSubscriptions: (p) => this.unregisterSubscriptions(p),\n invalidateWriteset: (p) => this.invalidateWriteset(p),\n setRecoveryDriver: () => { /* in-process: never disconnects */ },\n setAffectedHandler: () => { /* in-process: never disconnects */ },\n };\n }\n\n private async pruneCommitLogs(): Promise<void> {\n const minPerWorker = await Promise.all(\n this.workers.map((w) => w.send<bigint | null>(MsgType.MinSubscriptionTs, null)),\n );\n let minRetainedTs: bigint | null = null;\n for (const m of minPerWorker) {\n if (m === null) continue;\n if (minRetainedTs === null || m < minRetainedTs) minRetainedTs = m;\n }\n\n if (minRetainedTs === null) {\n const latest = await this.workers[0]!.send<bigint>(MsgType.LatestSnapshotTs, null);\n if (latest <= 0n) return;\n minRetainedTs = latest;\n }\n\n // Worker 0 advertises the new watermark (persist + Redis publish) then\n // deletes. Fan the same watermark out to the other workers' in-memory copies.\n const deleted = await this.workers[0]!.send<bigint>(MsgType.Prune, minRetainedTs);\n await this.broadcastRetentionWatermark(minRetainedTs);\n if (deleted > 0n) {\n logger.info(`pruned ${deleted} commit_logs older than ts=${minRetainedTs}`);\n }\n }\n\n /** Fan a watermark value out to every worker's in-memory copy. Worker 0 (the\n * pruner/seeder) already has it, but re-sending is harmless — {@link\n * SubscriptionWorker.setRetentionWatermark} is monotonic. */\n private async broadcastRetentionWatermark(ts: bigint): Promise<void> {\n await Promise.all(\n this.workers.map((w) =>\n w.send(MsgType.SetRetentionWatermark, ts).catch((err) =>\n logger.error(err, \"retention watermark broadcast to worker failed\"),\n ),\n ),\n );\n }\n\n workerCount(): number {\n return this.workers.length;\n }\n\n workerIndexFor(subId: string): number {\n return fnv1a32(subId) % this.workers.length;\n }\n\n private workerFor(subId: string): WorkerHandle {\n return this.workers[this.workerIndexFor(subId)]!;\n }\n\n private registerMethods(): void {\n const wss = this.wss;\n if (!wss) return;\n wss.register(\"registerSubscription\", this.registerSubscription.bind(this));\n wss.register(\"registerSubscriptionBatch\", this.registerSubscriptionBatch.bind(this));\n wss.register(\"updateSubscriptionReadSet\", this.updateSubscriptionReadSet.bind(this));\n wss.register(\"invalidateWriteset\", this.invalidateWriteset.bind(this));\n wss.register(\"unregisterSubscription\", this.unregisterSubscription.bind(this));\n wss.register(\"unregisterSubscriptions\", this.unregisterSubscriptions.bind(this));\n }\n\n private readSubId(params: unknown): string {\n return (params as { subId: string }).subId;\n }\n\n private async registerSubscription(params: unknown): Promise<RegisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<RegisterSubscriptionResult>(MsgType.Register, params);\n }\n\n private async registerSubscriptionBatch(params: unknown): Promise<RegisterSubscriptionBatchResult> {\n // Group entries by destination worker, dispatch one batch per worker, then\n // restore the original order.\n const arr = params as RegisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<RegisterSubscriptionResult[]>(\n MsgType.RegisterBatch,\n items.map((t) => t.p),\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: RegisterSubscriptionBatchResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async updateSubscriptionReadSet(params: unknown): Promise<UpdateSubscriptionReadSetResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UpdateSubscriptionReadSetResult>(MsgType.UpdateReadSet, params);\n }\n\n private async unregisterSubscription(params: unknown): Promise<UnregisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UnregisterSubscriptionResult>(MsgType.Unregister, params);\n }\n\n private async unregisterSubscriptions(params: unknown): Promise<UnregisterSubscriptionsResult> {\n const arr = params as UnregisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<UnregisterSubscriptionResult[]>(\n MsgType.Unregisters,\n items.map((t) => t.p) satisfies UnregisterSubscriptionsParams,\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: UnregisterSubscriptionsResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async invalidateWriteset(params: unknown): Promise<InvalidateWritesetResult> {\n // Fan the raw payload out to every worker. Each parses its own copy of the\n // writeSet (cheap relative to the scan) and returns its affected subs.\n const perWorker = await Promise.all(\n this.workers.map((w) => w.send<AffectedSubscription[]>(MsgType.Invalidate, params)),\n );\n return { affected: perWorker.flat() };\n }\n}\n\nfunction resolveWorkerCount(configured?: number): number {\n if (typeof configured === \"number\" && configured > 0) return Math.floor(configured);\n\n const envVal = process.env.SUPALIVE_SUB_MANAGER_WORKERS;\n if (envVal) {\n const n = parseInt(envVal, 10);\n if (Number.isFinite(n) && n > 0) return n;\n }\n return 1;\n}\n\nfunction resolveMaxConnectionsPerWorker(config: SubscriptionManagerConfig, workers: number): number {\n if (config.maxConnectionsPerWorker && config.maxConnectionsPerWorker > 0) {\n return Math.floor(config.maxConnectionsPerWorker);\n }\n const total = config.database?.maxConnections ?? (config.database?.type === \"mysql\" ? 20 : 10);\n return Math.max(1, Math.floor(total / workers));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","names":["defaultLogger","otelContext","logger","baseLogger","types.RegisterSubscriptionResultSchema","types.RegisterSubscriptionBatchResultSchema","types.UpdateSubscriptionReadSetResultSchema","types.UnregisterSubscriptionResultSchema","types.UnregisterSubscriptionsResultSchema","types.InvalidateWritesetResultSchema","logger","baseLogger","Client","parseMinTs","otelContext","baseLogger","uuidv4","otelContext","logger","baseLogger","WebSocketServer"],"sources":["../../../src/server/sub_hash.ts","../../../src/observability.ts","../../../src/server/one-shot-query.ts","../../../src/server/sub-manager-client.ts","../../../src/jobs/scheduler.ts","../../../src/server/rate-limit.ts","../../../src/server/http-rpc.ts","../../../src/server/supalive-server.ts","../../../src/server/hash.ts","../../../src/server/sub-manager.ts"],"sourcesContent":["// Server-only hashing helpers. Kept off the `helper.ts` module so a browser\n// bundle that only imports `stableStringify` doesn't pull in `hash-wasm`.\n\nimport { sha1 } from \"hash-wasm\";\nimport { stableStringify } from \"../helper\";\n\n/** Compute stable hash using json-stable-stringify and SHA1. */\nexport async function getHashOf(input: unknown): Promise<string> {\n const stableStr = typeof input === \"string\" ? input : stableStringify(input);\n return sha1(stableStr ?? \"\");\n}\n\n/**\n * Default identity literal used when neither a procedure-level\n * `queryIdentity` override nor a per-session user is available.\n */\nexport const ANONYMOUS_IDENTITY = \"anonymous\";\n\n/**\n * Resolve the segmentation key that participates in the subId/cacheKey hash for\n * a query. Shared by every query transport (WS subscribe, WS one-shot call, and\n * HTTP RPC) so identity — and therefore cache segmentation — is computed\n * identically regardless of how the query is invoked. Priority:\n *\n * 1. Procedure-level `queryIdentity` override:\n * - `false` → `ANONYMOUS_IDENTITY` (cache shared across users)\n * - `string` → that literal\n * - `function` → serverCtx + input → string | null | undefined\n * 2. `getUserId(serverCtx)` → falls back to `ANONYMOUS_IDENTITY` when it\n * returns null/undefined or isn't configured.\n */\nexport function resolveQueryIdentity<TContext>(\n procedure: {\n queryIdentity?:\n | false\n | string\n | ((serverCtx: TContext | undefined, input: unknown) => string | null | undefined);\n },\n serverCtx: TContext | undefined,\n input: unknown,\n getUserId?: (ctx: TContext | undefined) => string | null,\n): string {\n const override = procedure.queryIdentity;\n if (override !== undefined) {\n if (override === false) return ANONYMOUS_IDENTITY;\n if (typeof override === \"string\") return override;\n if (typeof override === \"function\") {\n const value = override(serverCtx, input);\n if (value === null || value === undefined) return ANONYMOUS_IDENTITY;\n return value;\n }\n }\n if (!getUserId) return ANONYMOUS_IDENTITY;\n return getUserId(serverCtx) ?? ANONYMOUS_IDENTITY;\n}\n\n/**\n * Compute a subscription id from hash of procedure + input + queryIdentity.\n *\n * `queryIdentity` is the segmentation key for cache/subscriptions. By default\n * it is the authenticated user's id (so two users get distinct subscriptions\n * for the same query), but a procedure may override it to share cache/subs\n * across users (e.g. for a public, user-independent query).\n */\nexport async function generateSubscriptionId(\n procedure: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const inputStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${procedure}_${inputStr}_${queryIdentity}`);\n return `sub_${hash}`;\n}\n\n/**\n * Compute cache key from query name + args + queryIdentity. The identity\n * participates so that user-specific results don't poison each other's\n * cache when a procedure does authorize per-user.\n */\nexport async function generateCacheKey(\n queryName: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const argsStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${queryName}_${argsStr}_${queryIdentity}`);\n return `ck_${hash}`;\n}\n","// ─── Observability ────────────────────────────────────────────────────────────\n//\n// One seam for the three signals a running Supalive server emits:\n//\n// • Structured LOGS — one line per operation (via the injectable pino logger).\n// • METRICS — request counter + duration histogram + active\n// connection/subscription up-down counters, through the\n// OpenTelemetry metrics API.\n// • EVENTS — an optional `onEvent` callback fed the same typed\n// record, so an app can drive its own metrics/tracing\n// without Supalive depending on any specific backend.\n//\n// The OpenTelemetry API used here is a NO-OP until the host app registers an SDK\n// (a MeterProvider / TracerProvider). Core therefore carries near-zero overhead\n// and zero vendor lock-in: the app owns the exporter (Prometheus, OTLP, …) and\n// switches backends by config alone. See `packages/backend/src/telemetry.ts`.\n//\n// tracing plugs into the SAME `record()` call site: it starts a span\n// per operation and propagates W3C trace context from the client. Nothing at the\n// call sites needs to change.\n\nimport type { Level, Logger } from \"pino\";\nimport {\n metrics,\n trace,\n context as otelContext,\n propagation,\n SpanKind,\n SpanStatusCode,\n ROOT_CONTEXT,\n isSpanContextValid,\n type Counter,\n type Histogram,\n type UpDownCounter,\n type Tracer,\n type Span,\n type Context,\n} from \"@opentelemetry/api\";\nimport { logger as defaultLogger } from \"./logger\";\nimport type { HandlerLogger } from \"./router/procedure\";\n\n// ─── Injectable root logger ───────────────────────────────────────────────────\n//\n// Modules migrated to observability read their base logger from here rather than\n// from the module-level singleton in `logger.ts`, so an app can swap in its own\n// configured pino instance (transports, redaction, bindings) with\n// `configureRootLogger()` at boot. Un-migrated modules keep using the singleton;\n// migration is incremental.\n\nlet rootLogger: Logger = defaultLogger;\n\n/** Replace the process-wide root logger. Call once at startup, before creating\n * the server, to route Supalive's request/lifecycle logs through your own pino\n * instance. */\nexport function configureRootLogger(logger: Logger): void {\n rootLogger = logger;\n}\n\n/** The current root logger (defaults to the env-configured singleton). */\nexport function getRootLogger(): Logger {\n return rootLogger;\n}\n\n// ─── Operation event ──────────────────────────────────────────────────────────\n\nexport type OperationTransport = \"ws\" | \"http\" | \"internal\";\nexport type OperationKind =\n | \"query\"\n | \"mutation\"\n | \"action\"\n | \"subscribe\"\n | \"unsubscribe\"\n | \"job\"\n | \"connection\";\nexport type OperationOutcome = \"ok\" | \"error\";\n\n/**\n * A single completed operation. Emitted once per WS call / subscribe / HTTP RPC\n * request / job run, and handed to metrics, the log line, and the `onEvent`\n * hook. High-cardinality fields (`reqId`, `userId`, `sessionId`) are logged/\n * forwarded but NEVER used as metric attributes.\n */\nexport interface OperationEvent {\n /** Coarse operation family, e.g. \"rpc\" | \"subscribe\" | \"unsubscribe\" | \"job\" | \"connection\". */\n op: string;\n transport: OperationTransport;\n procedure?: string;\n kind?: OperationKind;\n outcome: OperationOutcome;\n /** Wall-clock duration in milliseconds. */\n durationMs: number;\n /** Error code for a failed op (e.g. NOT_FOUND, EXECUTION_ERROR, RATE_LIMITED). */\n code?: string;\n userId?: string | null;\n sessionId?: string;\n /** Per-request correlation id (WS request id, or a generated HTTP id). */\n reqId?: string;\n /** The thrown error, when the failure was an unexpected exception (→ error level). */\n error?: unknown;\n /** Extra structured fields to include on the log line only. */\n extra?: Record<string, unknown>;\n /** The operation's OTel span, when one was started. `record()` finalizes and\n * ends it (status + attributes + exception). */\n span?: Span;\n}\n\n/** Whether to emit a log line per request. Metrics + `onEvent` fire regardless. */\nexport type RequestLogMode = \"all\" | \"errors\" | \"off\";\n\nexport interface ObservabilityOptions {\n /** Base logger for request/lifecycle lines. Defaults to {@link getRootLogger}. */\n logger?: Logger;\n /** Level override applied to the derived request logger. */\n level?: Level;\n /** Per-request log policy. `\"all\"` logs successes at info + failures; `\"errors\"`\n * logs only failures; `\"off\"` logs neither (metrics/onEvent still fire).\n * Default `\"all\"`. */\n requestLog?: RequestLogMode;\n /** Callback fed every {@link OperationEvent}. Errors thrown by it are swallowed. */\n onEvent?: (e: OperationEvent) => void;\n /** OpenTelemetry meter name. Default `\"@supalive/core\"`. */\n meterName?: string;\n /** OpenTelemetry meter version. Defaults to the core package version. */\n meterVersion?: string;\n}\n\n// Error codes that represent an EXPECTED client-side rejection rather than a\n// server fault. These log at `warn`; anything else (or a thrown exception) logs\n// at `error`.\nconst CLIENT_ERROR_CODES = new Set([\n \"NOT_FOUND\",\n \"BAD_REQUEST\",\n \"INVALID_MESSAGE\",\n \"PARSE_ERROR\",\n \"UNAUTHENTICATED\",\n \"RATE_LIMITED\",\n \"METHOD_NOT_ALLOWED\",\n \"TOO_MANY_CONCURRENT_MUTATIONS\",\n \"TOO_MANY_CONCURRENT_ACTIONS\",\n \"SUBSCRIBE_ERROR\",\n]);\n\n/**\n * The bound observability surface for one server instance: an injectable logger,\n * OTel metric instruments, and the `record()` seam every operation funnels\n * through. Built once in the server constructor and shared with the HTTP RPC\n * handler.\n */\nexport class Observability {\n readonly logger: Logger;\n private readonly requestLog: RequestLogMode;\n private readonly onEvent?: (e: OperationEvent) => void;\n\n private readonly requestCounter: Counter;\n private readonly durationHistogram: Histogram;\n private readonly activeConnections: UpDownCounter;\n private readonly activeSubscriptions: UpDownCounter;\n private readonly tracer: Tracer;\n\n constructor(opts: ObservabilityOptions = {}) {\n const base = opts.logger ?? getRootLogger();\n this.logger = opts.level ? base.child({}, { level: opts.level }) : base;\n this.requestLog = opts.requestLog ?? \"all\";\n this.onEvent = opts.onEvent;\n\n const meterName = opts.meterName ?? \"@supalive/core\";\n const meter = metrics.getMeter(\n meterName,\n opts.meterVersion,\n );\n this.tracer = trace.getTracer(\n meterName,\n opts.meterVersion,\n );\n this.requestCounter = meter.createCounter(\"supalive_requests_total\", {\n description: \"Total Supalive operations (WS calls, subscribes, HTTP RPC, jobs).\",\n });\n this.durationHistogram = meter.createHistogram(\"supalive_request_duration_ms\", {\n description: \"Operation duration in milliseconds.\",\n unit: \"ms\",\n });\n this.activeConnections = meter.createUpDownCounter(\"supalive_active_connections\", {\n description: \"Currently open WebSocket connections.\",\n });\n this.activeSubscriptions = meter.createUpDownCounter(\"supalive_active_subscriptions\", {\n description: \"Currently registered live-query subscriptions on this instance.\",\n });\n }\n\n /** Record one completed operation: metrics + optional onEvent + one log line,\n * and finalize the op's span (status/attributes/exception) when present. */\n record(e: OperationEvent): void {\n // Finalize the span first so trace ids are available to the log line/event.\n const traceId = this.finalizeSpan(e);\n if (traceId) {\n (e.extra ??= {}).traceId = traceId;\n }\n\n // Metrics — bounded-cardinality attributes only.\n const attrs = {\n transport: e.transport,\n procedure: e.procedure ?? \"\",\n kind: e.kind ?? \"\",\n outcome: e.outcome,\n code: e.code ?? \"\",\n };\n this.requestCounter.add(1, attrs);\n this.durationHistogram.record(e.durationMs, attrs);\n\n // App-owned metrics/tracing seam.\n if (this.onEvent) {\n try {\n this.onEvent(e);\n } catch {\n /* an app hook must never break request handling */\n }\n }\n\n // One structured log line.\n if (this.requestLog === \"off\") return;\n if (e.outcome === \"ok\") {\n if (this.requestLog !== \"all\") return;\n this.logger.info(logFields(e), summary(e));\n return;\n }\n // outcome === \"error\"\n const fields = logFields(e);\n const expected = !e.error && e.code != null && CLIENT_ERROR_CODES.has(e.code);\n if (expected) {\n this.logger.warn(fields, summary(e));\n } else {\n this.logger.error({ ...fields, err: e.error }, summary(e));\n }\n }\n\n // ─── Tracing ────────────────────────────────────────────────────────────────\n\n /**\n * Start a SERVER span for an operation, as a child of `parent` (or the active\n * context). No-op — returns a non-recording span — until a TracerProvider is\n * registered by the app, so this costs nothing when tracing is disabled.\n * Returns the span plus a context that has it active, for `context.with(...)`\n * wrapping so nested handler work / `ctx.span` children attach correctly.\n */\n startSpan(\n name: string,\n opts: {\n transport?: OperationTransport;\n procedure?: string;\n kind?: OperationKind;\n parent?: Context;\n } = {},\n ): { span: Span; context: Context } {\n const parent = opts.parent ?? otelContext.active();\n const span = this.tracer.startSpan(\n name,\n {\n kind: SpanKind.SERVER,\n attributes: {\n ...(opts.transport ? { \"supalive.transport\": opts.transport } : {}),\n ...(opts.procedure ? { \"supalive.procedure\": opts.procedure } : {}),\n ...(opts.kind ? { \"supalive.kind\": opts.kind } : {}),\n },\n },\n parent,\n );\n return { span, context: trace.setSpan(parent, span) };\n }\n\n /**\n * Continue a trace from an inbound carrier (HTTP headers, or a WS message's\n * `trace` field). Returns a context to pass as `startSpan({ parent })`. When\n * no propagator is registered / no trace context is present, returns the root\n * context (a fresh trace).\n */\n extractContext(\n carrier: Record<string, string | string[] | undefined>,\n ): Context {\n return propagation.extract(ROOT_CONTEXT, carrier);\n }\n\n /** Set the span's final status/attributes from the event, then end it.\n * Returns the (valid) trace id for log correlation, or undefined. */\n private finalizeSpan(e: OperationEvent): string | undefined {\n const span = e.span;\n if (!span) return undefined;\n try {\n span.setAttribute(\"supalive.outcome\", e.outcome);\n if (e.code) span.setAttribute(\"supalive.code\", e.code);\n if (e.procedure) span.setAttribute(\"supalive.procedure\", e.procedure);\n if (e.kind) span.setAttribute(\"supalive.kind\", e.kind);\n if (e.outcome === \"error\") {\n if (e.error instanceof Error) span.recordException(e.error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: e.code });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n } finally {\n span.end();\n }\n const sc = span.spanContext();\n return isSpanContextValid(sc) ? sc.traceId : undefined;\n }\n\n /** A request-scoped child logger for `ctx.log`, bound with the given fields\n * (reqId, procedure, and trace id when tracing is on). */\n childLogger(fields: Record<string, unknown>): Logger {\n return this.logger.child(fields);\n }\n\n /**\n * A LAZY request-scoped `ctx.log`. Most handlers never log, and pino's\n * `.child()` (bindings merge + serialization) isn't free — so this defers the\n * child creation until the first `debug/info/warn/error` call and reuses it\n * after. When a handler logs nothing, no child logger is ever allocated; the\n * only per-request cost is this small delegator object. Returns the narrow\n * {@link HandlerLogger} surface the handler context exposes.\n */\n lazyChildLogger(fields: Record<string, unknown>): HandlerLogger {\n const base = this.logger;\n let child: Logger | undefined;\n const get = (): Logger => (child ??= base.child(fields));\n return {\n debug: (...args: any[]) => (get().debug as (...a: any[]) => void)(...args),\n info: (...args: any[]) => (get().info as (...a: any[]) => void)(...args),\n warn: (...args: any[]) => (get().warn as (...a: any[]) => void)(...args),\n error: (...args: any[]) => (get().error as (...a: any[]) => void)(...args),\n };\n }\n\n /** The span's trace id, or undefined when tracing is off / the span is\n * non-recording. For binding onto `ctx.log`. */\n traceIdOf(span: Span | undefined): string | undefined {\n if (!span) return undefined;\n const sc = span.spanContext();\n return isSpanContextValid(sc) ? sc.traceId : undefined;\n }\n\n connectionOpened(): void {\n this.activeConnections.add(1);\n }\n connectionClosed(): void {\n this.activeConnections.add(-1);\n }\n subscriptionAdded(n = 1): void {\n this.activeSubscriptions.add(n);\n }\n subscriptionRemoved(n = 1): void {\n this.activeSubscriptions.add(-n);\n }\n}\n\n/** Start marker for an operation; pass to {@link elapsedMs} to get its duration. */\nexport function startTimer(): number {\n return performance.now();\n}\n\nexport function elapsedMs(startedAt: number): number {\n return Math.round((performance.now() - startedAt) * 1000) / 1000;\n}\n\n// ─── Query-execution span ─────────────────────────────────────────────────────\n\nconst coreTracer = trace.getTracer(\"@supalive/core\");\n\n/** What triggered a query execution — a client request/subscribe, or a background\n * reactive recompute (live-query invalidation / sub-manager recovery). */\nexport type QueryTrigger = \"request\" | \"subscribe\" | \"recompute\" | \"recovery\";\n\n/**\n * Run a query handler inside a `query <procedureName>` span, nested under\n * `parentContext` (the operation span) or the active context. The span is handed\n * to `run` so it becomes the query handler's `ctx.span` and the parent of any DB\n * spans. Cache HITs never call this (no execution), so a span appearing means a\n * real recompute — including the otherwise-invisible background reactive path,\n * where `parentContext` is omitted and the span is a root.\n *\n * No-op-cheap when tracing is off: the started span is non-recording, so we skip\n * the `context.with` and just run.\n */\nexport function traceQueryExecution<T>(\n procedureName: string,\n trigger: QueryTrigger,\n parentContext: Context | undefined,\n run: (span: Span) => Promise<T>,\n): Promise<T> {\n const parent = parentContext ?? otelContext.active();\n const span = coreTracer.startSpan(\n `query ${procedureName}`,\n {\n attributes: {\n \"supalive.kind\": \"query\",\n \"supalive.procedure\": procedureName,\n \"supalive.trigger\": trigger,\n },\n },\n parent,\n );\n const exec = async (): Promise<T> => {\n try {\n const r = await run(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return r;\n } catch (err) {\n if (err instanceof Error) span.recordException(err);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw err;\n } finally {\n span.end();\n }\n };\n return span.isRecording()\n ? otelContext.with(trace.setSpan(parent, span), exec)\n : exec();\n}\n\nfunction logFields(e: OperationEvent): Record<string, unknown> {\n const f: Record<string, unknown> = {\n op: e.op,\n transport: e.transport,\n outcome: e.outcome,\n durMs: e.durationMs,\n };\n if (e.procedure) f.procedure = e.procedure;\n if (e.kind) f.kind = e.kind;\n if (e.code) f.code = e.code;\n if (e.reqId) f.reqId = e.reqId;\n if (e.sessionId) f.sessionId = e.sessionId;\n if (e.userId != null) f.userId = e.userId;\n if (e.extra) Object.assign(f, e.extra);\n return f;\n}\n\nfunction summary(e: OperationEvent): string {\n const label = e.procedure ? `${e.kind ?? e.op} ${e.procedure}` : e.op;\n return `${e.transport} ${label} ${e.outcome}`;\n}\n","import type { SupaliveDb } from \"../db/realtime_db\";\nimport type { CacheLayer } from \"../db/cache\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport type { QueryCtx, HandlerObservability } from \"../router/procedure\";\nimport { traceQueryExecution } from \"../observability\";\nimport { traceCacheCall } from \"../db/db-trace\";\nimport { stableStringify } from \"../helper\";\nimport { generateCacheKey, resolveQueryIdentity } from \"./sub_hash\";\nimport { evaluateCacheFreshness } from \"./overlap-checker\";\nimport { resolveReadRouting, makeConnRouting } from \"../db/read-routing\";\nimport { logger } from \"../logger\";\n\nexport interface OneShotQueryDeps<TContext> {\n /** Same SupaliveDb the server/subscription path uses. */\n db: SupaliveDb;\n /** Shared query cache (Redis-backed). */\n cache: CacheLayer;\n /** Same identity resolver as subscriptions, so cache keys are segmented\n * identically whether a query arrives over WS subscribe, WS one-shot, or\n * HTTP RPC. */\n getUserId?: (ctx: TContext | undefined) => string | null;\n /** Reads the current in-memory retention watermark — the oldest retained\n * commit-log ts (see {@link RetentionWatermark}). A plain variable read, no\n * per-query round trip. */\n getMinRetainedTs: () => bigint;\n}\n\n/**\n * Execute a one-shot query (WS `call` or HTTP RPC) with the same caching model\n * as a live subscription — but without registering a tracked subscription.\n *\n * Flow (mirrors `handleSubscribe` / worker `register`):\n * 1. Resolve `queryIdentity` → `cacheKey` (identical segmentation to subs).\n * 2. Pick the read connection ONCE (replica when it's caught up to the client's\n * `clientMinTs`, else primary) and use it for BOTH the freshness scan and\n * the row reads, so a cache HIT offloads the primary too — not just a miss.\n * 3. Freshness check via {@link evaluateCacheFreshness} on that connection:\n * cached metadata + the retention watermark + a between-ts commit-log scan.\n * 4. Fresh → return the cached data.\n * Stale/miss → recompute via `queryInternalOn` (on the chosen connection)\n * and write BOTH the data and the metadata (readSet + snapshotTs) back, so\n * a later one-shot or a fresh subscription can reuse it.\n *\n * The retention guard is what makes an untracked one-shot safe: nothing holds\n * the prune watermark down for it, so an entry whose snapshot has fallen below\n * the oldest retained commit log is treated as a miss (its freshness scan would\n * be incomplete).\n *\n * `lastSnapshotTs` is the primary freshness upper bound — the latest snapshot ts\n * as of this call. The caller loads it (via `db.getLatestSnapshotTimestamp()`)\n * and passes it in, exactly like the subscribe path loads it before `register`,\n * so the snapshot read lives at the caller seam rather than being hidden here.\n *\n * `clientMinTs` is the caller's read high-water (the largest commit/snapshot ts\n * it has already observed). It gates replica routing: a query only runs on the\n * replica when the replica has applied at least `clientMinTs` — otherwise reads\n * (and the freshness scan) stay on the primary, preserving read-your-writes and\n * monotonic reads. It is clamped to `lastSnapshotTs` here, so a client can at\n * most force the primary (never a value beyond it). Returns the data plus the\n * `ts` the result reflects, which the caller echoes so the client can advance\n * its high-water.\n */\nexport async function runCachedQuery<TContext>(\n deps: OneShotQueryDeps<TContext>,\n /** Request-scoped `ctx.log`/`ctx.span`, threaded into the query handler's\n * context (the WS/HTTP call sites supply them). */\n handlerObs: HandlerObservability,\n procedure: RegisteredProcedure<TContext>,\n procedureName: string,\n input: unknown,\n serverCtx: TContext | undefined,\n lastSnapshotTs: bigint,\n clientMinTs?: bigint,\n): Promise<{ data: unknown; ts: bigint }> {\n const queryIdentity = resolveQueryIdentity(procedure, serverCtx, input, deps.getUserId);\n const cacheKey = await generateCacheKey(procedureName, stableStringify(input), queryIdentity);\n\n // ── Read routing (chosen ONCE, then used for BOTH freshness and the row\n // reads) ──────────────────────────────────────────────────────────────────\n // The shared router (db/read-routing.ts) clamps the client high-water and\n // picks the replica only when it has applied at least that ts (read-your-writes\n // + monotonic reads), else the primary. A one-shot query DEFAULTS to that\n // choice — including the freshness scan, which runs on the SAME connection at\n // the SAME ts, so a cache HIT offloads the primary too, not just a miss.\n const routing = await resolveReadRouting(deps.db, lastSnapshotTs, clientMinTs ?? 0n);\n const readTs = routing.readTs;\n\n // Cache freshness (metadata fetch + between-ts commit-log scan) under a\n // `cache.freshness` span, so a trace shows this work even on a HIT — which\n // otherwise produces no DB/query span at all. Zero-cost when tracing is off.\n const { metadata, freshness } = await traceCacheCall(\"freshness\", async () => {\n const metadata = await deps.cache.getQueryCacheMetaData(cacheKey);\n const freshness = await evaluateCacheFreshness(routing.readImpl, metadata, readTs, deps.getMinRetainedTs());\n return { metadata, freshness };\n });\n\n if (freshness.fresh) {\n const cached = await traceCacheCall(\"get\", async (setHit) => {\n const c = await deps.cache.getQueryCacheFor(cacheKey);\n // A HIT is only a real hit when the data blob is present AND its version\n // matches the metadata freshness was validated against (see below).\n setHit(!!(c && metadata && c.version === metadata.version));\n return c;\n });\n // Metadata says fresh, but only trust the data blob when it is actually\n // present AND its version matches the metadata we validated — the data key\n // may have been evicted (shorter TTL) or, under a concurrent write, the two\n // keys may have diverged. Either way, fall through to recompute.\n if (cached && metadata && cached.version === metadata.version) {\n // Mirror the subscription worker's register path (sub-worker.ts): the\n // cache is still valid at a *newer* snapshot, so push the stored horizon\n // forward (preserving data version + readSet). Without this an untracked\n // one-shot re-validates from the same old snapshot on every call, so its\n // between-ts scan window grows without bound between recomputes. Advancing\n // it collapses the next scan to ≈ empty until the next write, and keeps the\n // horizon above the retention watermark. Fire-and-forget: monotonic CAS\n // that won't clobber a fresher recompute, so it adds no latency.\n if (readTs > metadata.lastSnapshotTs) {\n deps.cache\n .advanceQueryCacheMetadata(cacheKey, {\n lastSnapshotTs: readTs,\n version: metadata.version,\n readSet: metadata.readSet,\n })\n .catch((err) => logger.error(err, \"one-shot horizon advance failed\"));\n }\n // The served data reflects its own compute snapshot (metadata horizon) and\n // was just proven valid up to readTs — report the max so the client's\n // high-water never goes backward relative to the bytes it received.\n const servedTs = readTs > metadata.lastSnapshotTs ? readTs : metadata.lastSnapshotTs;\n return { data: cached.data, ts: servedTs };\n }\n }\n\n // Miss / stale → recompute. Reads default to the chosen connection (replica\n // when caught up, else primary); the handler can override before its first\n // read via ctx.usePrimaryConn()/useReplicaConn(). The cache entry is stamped\n // with the FINAL connection's snapshot ts (result.ts), so a later freshness\n // scan uses the correct lower bound.\n const result = await deps.db.queryInternalOn({ impl: routing.readImpl, beginTs: readTs }, async (ctx) => {\n const fn = procedure.fn as (c: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n // Wrap the recompute in a `query <name>` span (child of the operation span\n // via the active context). Only reached on a cache MISS, so a span here\n // marks an actual recompute. The span becomes the handler's `ctx.span`.\n return traceQueryExecution(procedureName, \"request\", undefined, (querySpan) => {\n const qctx: QueryCtx<TContext> = {\n db: ctx,\n serverCtx,\n ...makeConnRouting(ctx, routing),\n log: handlerObs.log,\n span: querySpan,\n };\n return fn(qctx, input);\n });\n });\n\n // version == the compute ts, so the data blob and metadata are written with a\n // matching version and cross-check on read.\n await deps.cache\n .setQueryCacheAndMetadataFor(cacheKey, result.data, {\n lastSnapshotTs: result.ts,\n version: String(result.ts),\n readSet: result.readSet,\n })\n .catch((err) => logger.error(err, \"one-shot query cache write failed\"));\n\n return { data: result.data, ts: result.ts };\n}\n","import { Client } from \"rpc-websockets\";\nimport z, { type ZodType } from \"zod\";\nimport * as types from \"./sub-manager-types\";\nimport type { WriteEntry } from \"../db/types_db\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { supaliveStringify } from \"../helper\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR_CLIENT\" },\n { level: logLevelOf(\"SUB_MANAGER_CLIENT_LOG_LEVEL\") },\n);\n\n/**\n * Callback the app server registers to process invalidations that were\n * deferred during a sub-manager outage. When the sub-manager reconnects and\n * the buffered writeSets are flushed in one batched RPC, the resulting\n * `affected[]` is handed to this callback — which is expected to do the\n * same work as the normal per-mutation `reexecuteAffectedQuery` path.\n */\nexport type AffectedHandler = (\n affected: types.AffectedSubscription[],\n) => Promise<void>;\n\n/**\n * Callback the app server registers to drive recovery on sub-manager\n * reconnect. Iterates `serverSubscriptions`, reads `sl:reg:<subId>` from\n * Redis, and re-registers each known sub. Resolves when complete; only then\n * does the client's buffer flush and gated calls release.\n */\nexport type RecoveryDriver = () => Promise<void>;\n\n/**\n * The transport-agnostic contract the app server uses to talk to the\n * subscription manager. Implemented by:\n *\n * • {@link SubManagerClient} — the WebSocket RPC client, for a sub-manager\n * running in its own process (`subManagerUrl`); and\n * • the object returned by `SubscriptionManager.localLink()` — a direct\n * in-process link when the manager is embedded in the app server (no\n * socket, no port, no serialization).\n *\n * Injecting this into `SupaliveServerConfig.subManager` lets a deployment run\n * combined (one process) now and split the sub-manager out later by switching\n * back to `subManagerUrl`, with no other code changes.\n */\nexport interface SubManagerLink {\n registerSubscription(\n params: types.RegisterSubscriptionParams,\n ): Promise<types.RegisterSubscriptionResult>;\n registerSubscriptionBatch(\n params: types.RegisterSubscriptionParams[],\n ): Promise<types.RegisterSubscriptionResult[]>;\n updateSubscriptionReadSet(\n params: types.UpdateSubscriptionReadSetParams,\n ): Promise<types.UpdateSubscriptionReadSetResult>;\n unregisterSubscription(\n params: types.UnregisterSubscriptionParams,\n ): Promise<types.UnregisterSubscriptionResult>;\n unregisterSubscriptions(\n params: types.UnregisterSubscriptionsParams,\n ): Promise<types.UnregisterSubscriptionsResult>;\n invalidateWriteset(\n params: types.InvalidateWritesetParams,\n ): Promise<types.InvalidateWritesetResult>;\n /**\n * Register recovery for the reconnect path. A no-op for the in-process link,\n * which never disconnects.\n */\n setRecoveryDriver(driver: RecoveryDriver): void;\n /**\n * Register the post-recovery batched-invalidate handler. A no-op for the\n * in-process link.\n */\n setAffectedHandler(handler: AffectedHandler): void;\n}\n\nexport interface SubManagerClientOptions {\n url: string;\n /**\n * Maximum number of buffered WriteEntry records held during an outage.\n * If exceeded, the buffer is dropped — the commit-log replay path during\n * the next register call will catch up any missed invalidations, at the\n * cost of a brief recompute spike. Default 50_000 entries.\n */\n invalidateBufferLimit?: number;\n}\n\ntype ReadyState = \"ready\" | \"busy\";\n\nexport const subscriptionManagerDataPack = {\n encode: (data: unknown) => supaliveStringify(data),\n decode: (data: string) => JSON.parse(data),\n};\n\n\n/**\n * RPC client for the (single) sub-manager process.\n *\n * Design (Convex-inspired):\n * • The sub-manager is in-memory and ephemeral. There is no version\n * protocol, no client-side registration registry, no OUT_OF_SYNC\n * replay logic. All recovery is driven from the app server using\n * state it already has (`serverSubscriptions`) plus registration\n * records persisted to Redis at `sl:reg:<subId>`.\n *\n * • Out-of-order `updateSubscriptionReadSet` calls are filtered server\n * side using `lastSnapshotTs` (monotonic DB commit timestamp).\n *\n * • On rpc-websockets reconnect, the client:\n * 1. Transitions to `busy` (any in-flight `invalidateWriteset`\n * buffers its writeSet).\n * 2. Invokes the app server's `recoveryDriver` to re-register all\n * known subs.\n * 3. Flushes the buffer as one batched `invalidateWriteset` RPC and\n * hands the resulting `affected[]` to `affectedHandler`.\n * 4. Transitions to `ready`.\n *\n * • During an extended outage, the buffer is bounded; if exceeded, it\n * is dropped and we rely on the commit-log replay built into\n * `SubscriptionWorker.register()` to catch each sub up on the next\n * re-register.\n */\nexport class SubManagerClient implements SubManagerLink {\n private client: Client;\n private state: ReadyState = \"busy\";\n private hasConnectedOnce = false;\n\n /** Resolves whenever state transitions to 'ready'. Replaced on each busy→ready cycle. */\n private readyPromise: Promise<void>;\n private resolveReady!: () => void;\n\n /** Writeset entries accumulated while state === 'busy'. Flushed as one RPC after recovery. */\n private bufferedWriteSets: WriteEntry[] = [];\n private readonly bufferLimit: number;\n private bufferDroppedDuringOutage = false;\n\n private recoveryDriver?: RecoveryDriver;\n private affectedHandler?: AffectedHandler;\n\n constructor(urlOrOptions: string | SubManagerClientOptions) {\n const opts = typeof urlOrOptions === \"string\"\n ? { url: urlOrOptions }\n : urlOrOptions;\n\n this.bufferLimit = opts.invalidateBufferLimit ?? 50_000;\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n\n this.client = new Client(opts.url, {\n dataPack: subscriptionManagerDataPack,\n });\n this.client.setAutoReconnect(true);\n this.client.setMaxReconnects(0);\n // Ensure the dataPack is set even if rpc-websockets doesn't pass it through to the Client constructor\n this.client.dataPack = subscriptionManagerDataPack;\n\n this.client.on(\"open\", this.handleOpen.bind(this));\n this.client.on(\"close\", this.handleClose.bind(this));\n this.client.on(\"error\", (err: unknown) => logger.warn(err, \"sub-manager client socket error\"));\n }\n\n /**\n * Wire up the app-server-driven recovery. Must be called before any\n * disconnect/reconnect cycle for buffered writeSets to be flushed and\n * subscriptions re-registered.\n */\n setRecoveryDriver(driver: RecoveryDriver): void {\n this.recoveryDriver = driver;\n }\n\n /**\n * Wire up the handler invoked when batched invalidate results land\n * after recovery. The handler is expected to drive\n * `reexecuteAffectedQuery` for each entry.\n */\n setAffectedHandler(handler: AffectedHandler): void {\n this.affectedHandler = handler;\n }\n\n // ─── RPC methods ────────────────────────────────────────────────────────\n\n async registerSubscription(\n params: types.RegisterSubscriptionParams,\n ): Promise<types.RegisterSubscriptionResult> {\n return this.call(\"registerSubscription\", params, types.RegisterSubscriptionResultSchema);\n }\n\n async registerSubscriptionBatch(\n params: types.RegisterSubscriptionParams[],\n ): Promise<types.RegisterSubscriptionResult[]> {\n return this.call(\"registerSubscriptionBatch\", params, types.RegisterSubscriptionBatchResultSchema);\n }\n\n async updateSubscriptionReadSet(\n params: types.UpdateSubscriptionReadSetParams,\n ): Promise<types.UpdateSubscriptionReadSetResult> {\n return this.call(\"updateSubscriptionReadSet\", params, types.UpdateSubscriptionReadSetResultSchema);\n }\n\n async unregisterSubscription(\n params: types.UnregisterSubscriptionParams,\n ): Promise<types.UnregisterSubscriptionResult> {\n return this.call(\"unregisterSubscription\", params, types.UnregisterSubscriptionResultSchema);\n }\n\n async unregisterSubscriptions(\n params: types.UnregisterSubscriptionsParams,\n ): Promise<types.UnregisterSubscriptionsResult> {\n return this.call(\"unregisterSubscriptions\", params, types.UnregisterSubscriptionsResultSchema);\n }\n\n /**\n * Sends the writeSet to the sub-manager for invalidation, OR buffers it\n * if the client is currently disconnected / recovering. Returns\n * `{ affected: [] }` while buffering — the actual `affected` from the\n * buffered batch is delivered via the registered `affectedHandler`\n * after recovery completes.\n */\n async invalidateWriteset(\n params: types.InvalidateWritesetParams,\n ): Promise<types.InvalidateWritesetResult> {\n if (this.state === \"busy\") {\n this.appendToBuffer(params.writeSet);\n return { affected: [] };\n }\n return this.call(\"invalidateWriteset\", params, types.InvalidateWritesetResultSchema);\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Single point through which RPCs go. Throws on transport / server error.\n * `registerSubscription`, `updateSubscriptionReadSet`, and\n * `unregisterSubscription` are NOT gated on the ready state: during a\n * disconnect they will fail at the socket layer, and during recovery\n * (which itself uses these methods) gating would deadlock.\n */\n private async call<T extends ZodType>(\n procName: string,\n params: unknown,\n resType: T,\n ): Promise<z.infer<T>> {\n const result = await this.client.call(procName, params);\n return resType.parse(result) as z.infer<T>;\n }\n\n private appendToBuffer(writeSet: WriteEntry[]): void {\n if (this.bufferedWriteSets.length + writeSet.length > this.bufferLimit) {\n if (!this.bufferDroppedDuringOutage) {\n logger.warn(\n `Invalidate buffer would exceed ${this.bufferLimit}; dropping buffered entries. ` +\n `Subscriptions will be caught up via commit-log replay on re-register.`,\n );\n this.bufferDroppedDuringOutage = true;\n }\n this.bufferedWriteSets = [];\n return;\n }\n this.bufferedWriteSets.push(...writeSet);\n }\n\n private async flushBuffer(): Promise<void> {\n if (this.bufferedWriteSets.length === 0) return;\n const writeSet = this.bufferedWriteSets;\n this.bufferedWriteSets = [];\n this.bufferDroppedDuringOutage = false;\n\n try {\n const result = await this.call(\n \"invalidateWriteset\",\n { writeSet },\n types.InvalidateWritesetResultSchema,\n );\n if (this.affectedHandler && result.affected.length > 0) {\n await this.affectedHandler(result.affected);\n }\n } catch (err) {\n logger.error(err, \"Failed to flush invalidate buffer after recovery\");\n }\n }\n\n private markBusy(): void {\n if (this.state === \"busy\") return;\n this.state = \"busy\";\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n }\n\n private markReady(): void {\n if (this.state === \"ready\") return;\n this.state = \"ready\";\n this.resolveReady();\n }\n\n private handleClose(): void {\n logger.info(\"sub-manager client: socket closed\");\n this.markBusy();\n }\n\n private handleOpen(): void {\n if (!this.hasConnectedOnce) {\n this.hasConnectedOnce = true;\n logger.info(\"sub-manager client: initial connect\");\n this.markReady();\n return;\n }\n logger.info(\"sub-manager client: reconnected — starting recovery\");\n void this.runRecovery();\n }\n\n private async runRecovery(): Promise<void> {\n // Stay 'busy' for the duration of recovery so concurrent\n // invalidateWriteset calls keep buffering.\n try {\n if (this.recoveryDriver) {\n await this.recoveryDriver();\n } else {\n logger.warn(\"sub-manager client: no recoveryDriver configured — skipping recovery step\");\n }\n await this.flushBuffer();\n } catch (err) {\n logger.error(err, \"Recovery failed; state will be marked ready anyway\");\n } finally {\n logger.info(\"sub-manager client: recovery complete\");\n this.markReady();\n }\n }\n\n /**\n * Test/inspection helpers.\n */\n /** @internal */\n isReady(): boolean { return this.state === \"ready\"; }\n /** @internal */\n bufferSize(): number { return this.bufferedWriteSets.length; }\n}\n","import { Client, Receiver } from \"@upstash/qstash\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\n\nconst logger = baseLogger.child(\n { name: \"SCHEDULER\" },\n { level: logLevelOf(\"SCHEDULER_LOG_LEVEL\") },\n);\n\n// ─── Scheduler Types ─────────────────────────────────────────────────────────\n\n/** A recurring job to keep synced on the scheduler side. */\nexport interface ScheduledJobDef {\n /** Job procedure name (also the last path segment of its endpoint). */\n name: string;\n /** Cron expression, e.g. `\"0 3 * * *\"`. */\n cron: string;\n}\n\n/** Options for a precise, one-shot job run. */\nexport interface EnqueueOptions {\n /** Run after this many seconds from now. */\n delaySeconds?: number;\n /** Run at this absolute time. Overrides `delaySeconds`. */\n runAt?: Date;\n /**\n * Idempotency key. If two enqueues share the same id within the provider's\n * dedup window, only the first is delivered. Use it to make \"schedule the\n * expiry of X\" safe to call more than once.\n */\n dedupeId?: string;\n}\n\n/** Handle returned by {@link JobScheduler.enqueue}; pass its `id` to `cancel`. */\nexport interface ScheduleHandle {\n id: string;\n}\n\n/** The pieces of an inbound webhook the scheduler needs to authenticate it. */\nexport interface IncomingJobRequest {\n /** Job name parsed from the request path. */\n name: string;\n /** Raw (unparsed) request body — signatures are computed over these bytes. */\n rawBody: string;\n /** Request headers, keys lowercased. */\n headers: Record<string, string | undefined>;\n /** Full request URL the webhook was delivered to. */\n url: string;\n}\n\n/**\n * Pluggable scheduler. The server owns exactly one instance and uses it for\n * both directions:\n * • inbound — `verify` authenticates a webhook before the job runs.\n * • outbound — `syncSchedules` (recurring, at startup) and `enqueue`/`cancel`\n * (precise one-shots, at runtime) drive the provider.\n *\n * Both dev and production implementations POST to the same HTTP job endpoint,\n * so the server's dispatch path is exercised identically in every environment.\n */\nexport interface JobScheduler {\n /** Authenticate an inbound webhook. Return false (or throw) to reject. */\n verify(req: IncomingJobRequest): Promise<boolean>;\n\n /**\n * Make the provider's recurring schedules match `jobs` exactly (create,\n * update, and remove stale ones). Called once at server startup.\n * `endpointFor(name)` yields the absolute URL the provider should POST to.\n */\n syncSchedules(\n jobs: ScheduledJobDef[],\n endpointFor: (name: string) => string,\n ): Promise<void>;\n\n /** Enqueue a precise one-shot run of `name` with `body` at `endpoint`. */\n enqueue(\n name: string,\n body: unknown,\n endpoint: string,\n opts?: EnqueueOptions,\n ): Promise<ScheduleHandle>;\n\n /** Cancel a previously enqueued one-shot by its handle id. */\n cancel(handleId: string): Promise<void>;\n}\n\nfunction delayMsFrom(opts?: EnqueueOptions): number {\n if (opts?.runAt) return Math.max(0, opts.runAt.getTime() - Date.now());\n if (opts?.delaySeconds && opts.delaySeconds > 0) return opts.delaySeconds * 1000;\n return 0;\n}\n\n// ─── Dev Scheduler ───────────────────────────────────────────────────────────\n\nexport interface DevSchedulerOptions {\n /**\n * How often, in ms, to fire each recurring job in dev. Dev intentionally\n * ignores exact cron timing (that is the production scheduler's job); it\n * just fires often enough to exercise the handler. Default 60_000.\n */\n recurringIntervalMs?: number;\n /**\n * Override the HTTP client (for tests). Defaults to the global `fetch`.\n */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * In-process scheduler for local development and tests. Recurring jobs fire on\n * a fixed interval; one-shots fire on an exact `setTimeout` (so precise timing\n * is faithfully testable). Every fire POSTs to the real HTTP job endpoint, and\n * `verify` accepts everything — there is no signing in dev.\n */\nexport class DevScheduler implements JobScheduler {\n private readonly intervalMs: number;\n private readonly fetchImpl: typeof fetch;\n private intervals = new Map<string, ReturnType<typeof setInterval>>();\n private timeouts = new Map<string, ReturnType<typeof setTimeout>>();\n\n constructor(opts: DevSchedulerOptions = {}) {\n this.intervalMs = opts.recurringIntervalMs ?? 60_000;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n }\n\n async verify(_req: IncomingJobRequest): Promise<boolean> {\n return true;\n }\n\n async syncSchedules(\n jobs: ScheduledJobDef[],\n endpointFor: (name: string) => string,\n ): Promise<void> {\n // Rebuild from scratch so a re-sync reflects removed jobs.\n for (const timer of this.intervals.values()) clearInterval(timer);\n this.intervals.clear();\n\n for (const job of jobs) {\n const endpoint = endpointFor(job.name);\n const timer = setInterval(() => {\n this.post(endpoint, {}).catch((err) =>\n logger.error(err, `dev recurring job \"${job.name}\" failed`),\n );\n }, this.intervalMs);\n // Don't keep the process alive purely for the dev poller.\n timer.unref?.();\n this.intervals.set(job.name, timer);\n logger.info(\n `dev schedule \"${job.name}\" (cron \"${job.cron}\") → firing every ${this.intervalMs}ms`,\n );\n }\n }\n\n async enqueue(\n name: string,\n body: unknown,\n endpoint: string,\n opts?: EnqueueOptions,\n ): Promise<ScheduleHandle> {\n const id = crypto.randomUUID();\n const timer = setTimeout(() => {\n this.timeouts.delete(id);\n this.post(endpoint, body).catch((err) =>\n logger.error(err, `dev one-shot job \"${name}\" failed`),\n );\n }, delayMsFrom(opts));\n timer.unref?.();\n this.timeouts.set(id, timer);\n return { id };\n }\n\n async cancel(handleId: string): Promise<void> {\n const timer = this.timeouts.get(handleId);\n if (timer) {\n clearTimeout(timer);\n this.timeouts.delete(handleId);\n }\n }\n\n /** Stop every dev timer (call on server shutdown / in test teardown). */\n stop(): void {\n for (const t of this.intervals.values()) clearInterval(t);\n for (const t of this.timeouts.values()) clearTimeout(t);\n this.intervals.clear();\n this.timeouts.clear();\n }\n\n private async post(endpoint: string, body: unknown): Promise<void> {\n const res = await this.fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body ?? {}),\n });\n if (!res.ok) {\n throw new Error(`job endpoint ${endpoint} returned ${res.status}`);\n }\n }\n}\n\n// ─── QStash Scheduler (production) ───────────────────────────────────────────\n\nexport interface QStashSchedulerOptions {\n /** QStash Base url (`QSTASH_BASE_URL`). */\n baseUrl: string;\n /** QStash REST token (`QSTASH_TOKEN`). */\n token: string;\n /** Current signing key for webhook verification (`QSTASH_CURRENT_SIGNING_KEY`). */\n currentSigningKey: string;\n /** Next signing key, for zero-downtime key rotation (`QSTASH_NEXT_SIGNING_KEY`). */\n nextSigningKey: string;\n /* explicitly set qstash to dev mode to use local qstash server */\n devMode: boolean;\n}\n\n/**\n * Production scheduler backed by Upstash QStash. Externalizes the timer so a\n * horizontally-scaled deployment fires each cron tick exactly once (QStash\n * calls one HTTP endpoint), with at-least-once retries and signed webhooks.\n */\nexport class QStashScheduler implements JobScheduler {\n private readonly client: Client;\n private readonly receiver: Receiver;\n\n constructor(opts: QStashSchedulerOptions) {\n this.client = new Client({ baseUrl: opts.baseUrl ,token: opts.token, devMode: opts.devMode });\n this.receiver = new Receiver({\n currentSigningKey: opts.currentSigningKey,\n nextSigningKey: opts.nextSigningKey,\n });\n }\n\n async verify(req: IncomingJobRequest): Promise<boolean> {\n const signature = req.headers[\"upstash-signature\"];\n if (!signature) return false;\n try {\n return await this.receiver.verify({\n signature,\n body: req.rawBody,\n url: req.url,\n });\n } catch (err) {\n logger.warn(err, `signature verification failed for job \"${req.name}\"`);\n return false;\n }\n }\n\n async syncSchedules(\n jobs: ScheduledJobDef[],\n endpointFor: (name: string) => string,\n ): Promise<void> {\n // A deterministic scheduleId per job makes create an idempotent upsert and\n // lets us reconcile: anything QStash still has that we no longer declare is\n // a stale schedule and gets removed.\n const desired = new Map(jobs.map((j) => [scheduleIdFor(j.name), j]));\n\n let existing: Awaited<ReturnType<Client[\"schedules\"][\"list\"]>> = [];\n try {\n existing = await this.client.schedules.list();\n } catch (err) {\n logger.error(err, \"failed to list QStash schedules; skipping reconcile of stale schedules\");\n }\n\n for (const schedule of existing) {\n const id = schedule.scheduleId;\n if (id?.startsWith(SCHEDULE_ID_PREFIX) && !desired.has(id)) {\n try {\n await this.client.schedules.delete(id);\n logger.info(`removed stale QStash schedule ${id}`);\n } catch (err) {\n logger.error(err, `failed to remove stale schedule ${id}`);\n }\n }\n }\n\n for (const [scheduleId, job] of desired) {\n try {\n await this.client.schedules.create({\n scheduleId,\n destination: endpointFor(job.name),\n cron: job.cron,\n });\n logger.info(`synced QStash schedule \"${job.name}\" (${job.cron})`);\n } catch (err) {\n logger.error(err, `failed to sync schedule for job \"${job.name}\"`);\n throw err;\n }\n }\n }\n\n async enqueue(\n name: string,\n body: unknown,\n endpoint: string,\n opts?: EnqueueOptions,\n ): Promise<ScheduleHandle> {\n const res = await this.client.publishJSON({\n url: endpoint,\n body,\n ...(opts?.runAt\n ? { notBefore: Math.floor(opts.runAt.getTime() / 1000) }\n : opts?.delaySeconds\n ? { delay: opts.delaySeconds }\n : {}),\n ...(opts?.dedupeId ? { deduplicationId: opts.dedupeId } : {}),\n });\n const messageId = (res as { messageId?: string }).messageId;\n if (!messageId) throw new Error(`QStash publish for job \"${name}\" returned no messageId`);\n return { id: messageId };\n }\n\n async cancel(handleId: string): Promise<void> {\n await this.client.messages.cancel(handleId);\n }\n}\n\nconst SCHEDULE_ID_PREFIX = \"supalive-job-\";\n\nfunction scheduleIdFor(jobName: string): string {\n return `${SCHEDULE_ID_PREFIX}${jobName}`;\n}\n\n// ─── Job Client (app-facing) ─────────────────────────────────────────────────\n\n/**\n * Thin app-facing handle for scheduling precise one-shots from inside\n * procedures. The server builds one (via `getJobClient()`) that already knows\n * how to turn a job name into its endpoint, so callers just say\n * `jobs.enqueue(\"expireSpendAuth\", { id }, { runAt })`.\n */\nexport class JobClient {\n constructor(\n private readonly scheduler: JobScheduler,\n private readonly endpointFor: (name: string) => string,\n ) {}\n\n enqueue(name: string, body: unknown, opts?: EnqueueOptions): Promise<ScheduleHandle> {\n return this.scheduler.enqueue(name, body, this.endpointFor(name), opts);\n }\n\n cancel(handleId: string): Promise<void> {\n return this.scheduler.cancel(handleId);\n }\n}\n","/**\n * Fixed-window rate limiter. Per-instance and in-memory: each key gets a\n * counter that resets every `windowMs`. Good enough to blunt abuse of the HTTP\n * RPC endpoint (which does a JWT verify + possibly a DB read per call) without\n * the latency of a shared Redis counter. A background sweep evicts expired\n * buckets so memory stays bounded under churny key sets (e.g. many IPs).\n */\nexport interface RateLimitResult {\n allowed: boolean;\n /** Milliseconds until the current window resets (0 when allowed). */\n retryAfterMs: number;\n /** Remaining allowance in the current window (0 when blocked). */\n remaining: number;\n}\n\nexport class FixedWindowRateLimiter {\n private readonly buckets = new Map<string, { count: number; resetAt: number }>();\n private sweepTimer?: ReturnType<typeof setInterval>;\n\n constructor(\n private readonly windowMs: number,\n private readonly max: number,\n ) {\n this.sweepTimer = setInterval(() => this.sweep(), Math.max(windowMs, 1000));\n // Never keep the process alive just for the sweep.\n this.sweepTimer.unref?.();\n }\n\n check(key: string, now: number = Date.now()): RateLimitResult {\n let bucket = this.buckets.get(key);\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + this.windowMs };\n this.buckets.set(key, bucket);\n }\n bucket.count++;\n if (bucket.count > this.max) {\n return { allowed: false, retryAfterMs: bucket.resetAt - now, remaining: 0 };\n }\n return { allowed: true, retryAfterMs: 0, remaining: this.max - bucket.count };\n }\n\n private sweep(now: number = Date.now()): void {\n for (const [key, bucket] of this.buckets) {\n if (bucket.resetAt <= now) this.buckets.delete(key);\n }\n }\n\n /** Stop the background sweep (call on server shutdown / test teardown). */\n stop(): void {\n if (this.sweepTimer) clearInterval(this.sweepTimer);\n this.sweepTimer = undefined;\n }\n}\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport { z, type ZodType } from \"zod\";\nimport { supaliveStringify } from \"../helper\";\nimport { AuthenticationError } from \"../db/types_client_rpc\";\nimport type { SupaliveDb } from \"../db/realtime_db\";\nimport type { ContextRegistry, RegisteredProcedure } from \"../router/router\";\nimport type { MutationCtx, ActionCtx, JobCtx } from \"../router/procedure\";\nimport type { RpcConfig } from \"../config\";\nimport type { ObjectStorage } from \"../storage\";\nimport type { JobClient, JobScheduler } from \"../jobs/scheduler\";\nimport { FixedWindowRateLimiter } from \"./rate-limit\";\nimport { runCachedQuery, type OneShotQueryDeps } from \"./one-shot-query\";\nimport { primaryOnlyConn } from \"../router/procedure\";\nimport {\n Observability,\n startTimer,\n elapsedMs,\n type OperationKind,\n} from \"../observability\";\nimport { context as otelContext, type Span } from \"@opentelemetry/api\";\nimport type { HandlerObservability } from \"../router/procedure\";\n\nexport interface HttpRpcDeps<TContext> {\n /** Lazily fetch the registry (set after `registerRouter`). */\n getRegistry: () => ContextRegistry | undefined;\n /** The server's SupaliveDb — the SAME instance whose after-commit hook drives\n * subscription invalidation, so an HTTP mutation pushes live updates too.\n * Used for mutations/actions; queries go through `oneShotDeps`. */\n db: SupaliveDb;\n /** Optional object storage exposed to ACTION handlers as `ctx.storage`. */\n storage: ObjectStorage;\n /** Optional job client exposed to ACTION handlers as `ctx.scheduler`. */\n scheduler: JobClient;\n /** Shared, pre-built one-shot query cache deps (db + cache + getUserId +\n * getMinRetainedTs). Same object the WS `call` path uses, so HTTP one-shot queries\n * cache/segment identically. */\n oneShotDeps: OneShotQueryDeps<TContext>;\n /** Same auth callback used by the WebSocket path. */\n verifyAuth?: (data: Record<string, unknown>, sessionId: string) => Promise<TContext | null>;\n /** One-shot HTTP RPC config. Always provided (default `{}` when the endpoint is\n * disabled) — \"disabled\" is carried by the explicit {@link rpcEnabled} flag, not\n * by an absent config. */\n rpc: RpcConfig<TContext>;\n /** Whether the one-shot RPC endpoint is enabled. When `false`, the handler still\n * owns the RPC path but {@link HttpRpcHandler.handle} rejects every call\n * explicitly (rather than silently not matching), so a disabled transport is an\n * intentional, observable state — the handler is always constructed for jobs. */\n rpcEnabled: boolean;\n /** Shared observability surface (logs + metrics + onEvent), from the server. */\n obs: Observability;\n /** Resolve a log/segmentation user id from the auth context (same as the WS path). */\n getUserId?: (ctx: TContext | undefined) => string | null;\n\n // ─── Job webhook deps ────────────────────────────────────────────────────\n // The handler also owns the job webhook (`POST {jobPath}/<jobName>`), so the\n // HTTP surface — RPC + jobs — lives in one place. Jobs are independent of the\n // RPC config: they work whenever a scheduler is configured, even if `rpc` is\n // omitted.\n /** The scheduler that authenticates inbound job POSTs (`verify`). Omit to\n * disable jobs (a job POST then returns 503). */\n jobScheduler?: JobScheduler;\n /** Builds the system `serverCtx` for a job run (see `SupaliveServerConfig.jobContext`). */\n jobContext?: () => TContext | Promise<TContext>;\n /** Normalized job path prefix (e.g. `/_jobs`). The webhook is `{jobPath}/<name>`. */\n jobPath: string;\n /** The externally-reachable URL for a job name — passed to `scheduler.verify`\n * so signature schemes that bind the URL match. */\n endpointFor: (name: string) => string;\n}\n\n/**\n * Handles the one-shot HTTP RPC transport: a single `POST {path}` endpoint for\n * `query`/`mutation`/`action`. The procedure name travels in the request body\n * (`{ name, input }`), not the URL — so the wire never reveals which procedures\n * exist and a transport-level error (which typically echoes the failing URL)\n * can't leak a procedure name. Subscriptions are not offered here (WS only).\n * Kept independent of the WebSocket server so it can be unit-tested against a\n * plain `http.Server` with a stub db/registry.\n *\n * Request flow: CORS → (OPTIONS preflight) → rate limit (pre-auth) → read body →\n * parse `{ name, input }` → procedure lookup (internal/job rejected) → auth →\n * validate input → run → respond.\n */\nexport class HttpRpcHandler<TContext> {\n /** The RPC endpoint path (always set; claimed even when disabled so a call is\n * explicitly rejected rather than silently falling through). */\n private readonly path: string;\n private readonly rpc: RpcConfig<TContext>;\n private readonly rpcEnabled: boolean;\n private readonly rateLimiter?: FixedWindowRateLimiter;\n /** Job webhook prefix (`{jobPath}/`) — a job POST is `{jobPath}/<jobName>`. */\n private readonly jobPrefix: string;\n\n constructor(private readonly deps: HttpRpcDeps<TContext>) {\n this.rpc = deps.rpc;\n this.rpcEnabled = deps.rpcEnabled;\n this.path = normalizeRpcPath(deps.rpc.path ?? \"/_rpc\");\n // No rate limiter for a disabled endpoint — it rejects before limiting anyway.\n if (this.rpcEnabled && deps.rpc.rateLimit !== false) {\n const rl = deps.rpc.rateLimit ?? {};\n this.rateLimiter = new FixedWindowRateLimiter(rl.windowMs ?? 60_000, rl.max ?? 120);\n }\n this.jobPrefix = `${deps.jobPath}/`;\n }\n\n /** True when this handler owns the given request path as an RPC call. The\n * procedure name is carried in the body now, so a single exact path is the\n * whole RPC surface. Claims the path even when disabled so {@link handle}\n * rejects the call explicitly. */\n handlesPath(path: string): boolean {\n return path === this.path;\n }\n\n /** True when the path is a job webhook (`{jobPath}/<name>`). */\n handlesJobPath(path: string): boolean {\n return path.startsWith(this.jobPrefix);\n }\n\n stop(): void {\n this.rateLimiter?.stop();\n }\n\n async handle(req: IncomingMessage, res: ServerResponse, path: string): Promise<void> {\n // Endpoint explicitly disabled (server built without an `rpc` config): reject\n // every call with the same opaque NOT_FOUND the enabled path uses for unknown\n // procedures, so a disabled deployment reveals nothing extra about its surface.\n if (!this.rpcEnabled) {\n return this.send(res, 404, { success: false, error: \"not found\", code: \"NOT_FOUND\" });\n }\n\n this.applyCors(req, res);\n\n // Preflight isn't an operation — no measurement.\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const startedAt = startTimer();\n const reqId = crypto.randomUUID().slice(0, 8);\n let procName: string | undefined;\n let kind: OperationKind | undefined;\n let userId: string | null | undefined;\n let span: Span | undefined;\n const record = (\n outcome: \"ok\" | \"error\",\n opts?: { code?: string; error?: unknown },\n ): void => {\n this.deps.obs.record({\n op: \"rpc\",\n transport: \"http\",\n procedure: procName,\n kind,\n outcome,\n durationMs: elapsedMs(startedAt),\n code: opts?.code,\n error: opts?.error,\n reqId,\n userId: userId ?? undefined,\n span,\n });\n };\n\n if (req.method !== \"POST\") {\n record(\"error\", { code: \"METHOD_NOT_ALLOWED\" });\n return this.send(res, 405, { success: false, error: \"method not allowed\", code: \"METHOD_NOT_ALLOWED\" });\n }\n\n // Rate limit BEFORE auth so a flood can't hammer the (expensive) verify.\n // Keyed off the request (IP by default), so it needs no body — do it first.\n if (this.rateLimiter) {\n // `rateLimit` is a config object here (rateLimiter only exists when it\n // isn't `false`), so a `keyBy` override is read straight off it.\n const keyBy = this.rpc.rateLimit ? this.rpc.rateLimit.keyBy : undefined;\n const key = keyBy ? keyBy(req) : clientIp(req);\n const rl = this.rateLimiter.check(key);\n if (!rl.allowed) {\n res.setHeader(\"retry-after\", String(Math.ceil(rl.retryAfterMs / 1000)));\n record(\"error\", { code: \"RATE_LIMITED\" });\n return this.send(res, 429, { success: false, error: \"rate limit exceeded\", code: \"RATE_LIMITED\" });\n }\n }\n\n // The procedure name and input both live in the body: `{ name, input }`.\n const rawBody = await readBody(req);\n let name: string;\n let rawInput: unknown;\n let minTs: bigint | undefined;\n try {\n const envelope = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};\n if (!envelope || typeof envelope !== \"object\" || typeof envelope.name !== \"string\") {\n throw new Error(\"missing procedure name\");\n }\n name = envelope.name;\n rawInput = envelope.input ?? {};\n // Client read high-water (decimal bigint string), same role as the WS\n // `call` minTs: gates replica routing for read-your-writes / monotonic reads.\n minTs = typeof envelope.minTs === \"string\" ? parseMinTs(envelope.minTs) : undefined;\n } catch {\n // A malformed envelope reveals nothing about the API surface.\n record(\"error\", { code: \"BAD_REQUEST\" });\n return this.send(res, 400, { success: false, error: \"invalid request\", code: \"BAD_REQUEST\" });\n }\n procName = name;\n\n const procedure = this.deps.getRegistry()?.getProcedure(name);\n // Internal procedures and jobs are server-only; subscriptions aren't offered\n // over HTTP. Report NOT_FOUND uniformly (and without the name) so nothing is\n // enumerable from the response.\n if (!procedure || procedure.internal || procedure.type === \"job\") {\n record(\"error\", { code: \"NOT_FOUND\" });\n return this.send(res, 404, { success: false, error: \"not found\", code: \"NOT_FOUND\" });\n }\n kind = procedure.type as OperationKind;\n\n const auth = await this.resolveAuth(req);\n if (!auth.ok) {\n record(\"error\", { code: \"UNAUTHENTICATED\" });\n return this.send(res, 401, { success: false, error: auth.message, code: \"UNAUTHENTICATED\" });\n }\n userId = this.deps.getUserId?.(auth.ctx);\n\n let input: unknown;\n try {\n const parseRes = (procedure.inputSchema as ZodType).safeParse(rawInput);\n\n if (!parseRes.success) {\n record(\"error\", { code: \"BAD_REQUEST\" });\n return this.send(res, 400, {\n success: false, error: `Invalid input: ${parseRes.error.issues[0]?.message ?? 'Unknow error'}`,\n code: \"BAD_REQUEST\",\n });\n }\n\n input = parseRes.data;\n } catch (err) {\n record(\"error\", { code: \"BAD_REQUEST\", error: err });\n return this.send(res, 400, { success: false, error: \"invalid input\", code: \"BAD_REQUEST\" });\n }\n\n // Operation span (child of the caller's trace when a `traceparent` header is\n // present) + request-scoped `ctx.log`/`ctx.span`.\n const started = this.deps.obs.startSpan(`rpc.${kind} ${name}`, {\n transport: \"http\",\n procedure: name,\n kind,\n parent: this.deps.obs.extractContext(req.headers),\n });\n span = started.span;\n const traceId = this.deps.obs.traceIdOf(span);\n const handlerObs: HandlerObservability = {\n log: this.deps.obs.lazyChildLogger({\n reqId,\n procedure: name,\n kind,\n ...(userId != null ? { userId } : {}),\n ...(traceId ? { traceId } : {}),\n }),\n span,\n };\n\n try {\n const { data, ts } = await otelContext.with(started.context,\n () => this.runProcedure(procedure, input, auth.ctx, minTs, handlerObs),\n );\n // Echo the observed ts (query snapshot / mutation commitTs) so an HTTP\n // client can advance its own read high-water. Absent for actions.\n record(\"ok\");\n return this.send(res, 200, { success: true, data, ts: ts?.toString() });\n } catch (err) {\n record(\"error\", { code: \"EXECUTION_ERROR\", error: err });\n return this.send(res, 500, {\n success: false,\n error: err instanceof Error ? err.message : \"execution failed\",\n code: \"EXECUTION_ERROR\",\n });\n }\n }\n\n /**\n * Handles a job webhook: `POST {jobPath}/<jobName>`. Kept alongside {@link handle}\n * so the whole HTTP surface (RPC + jobs) lives in one class. Every job POST is\n * authenticated by the configured scheduler BEFORE any lookup or work — a forged\n * POST must not even learn whether a job name exists. Returns 500 on handler\n * failure so the scheduler retries (jobs must be idempotent).\n */\n async handleJob(req: IncomingMessage, res: ServerResponse, path: string): Promise<void> {\n if (req.method !== \"POST\") {\n return this.send(res, 405, { error: \"method not allowed\" });\n }\n\n const name = decodeURIComponent(path.slice(this.jobPrefix.length));\n if (!name || name.includes(\"/\")) {\n return this.send(res, 404, { error: \"unknown job\" });\n }\n\n if (!this.deps.jobScheduler) {\n return this.send(res, 503, { error: \"jobs not enabled\" });\n }\n\n // Start timing BEFORE reading/decoding the body (and the verify + parse), so\n // the recorded duration reflects the whole handling cost — matching the RPC\n // `handle` path, which also starts its timer ahead of `readBody`.\n const startedAt = startTimer();\n const rawBody = await readBody(req);\n\n // Authenticate the webhook BEFORE any lookup or work.\n let verified = false;\n try {\n verified = await this.deps.jobScheduler.verify({\n name,\n rawBody,\n headers: lowercaseHeaders(req.headers),\n url: this.deps.endpointFor(name),\n });\n } catch (err) {\n this.deps.obs.logger.warn(err, `job \"${name}\" verification threw`);\n }\n if (!verified) {\n return this.send(res, 401, { error: \"invalid signature\" });\n }\n\n const procedure = this.deps.getRegistry()?.getProcedure(name);\n if (!procedure || procedure.type !== \"job\") {\n return this.send(res, 404, { error: \"unknown job\" });\n }\n\n let input: unknown;\n try {\n const parsed = rawBody.trim().length > 0 ? JSON.parse(rawBody) : {};\n input = (procedure.inputSchema as ZodType).parse(parsed);\n } catch (err) {\n this.deps.obs.logger.warn(err, `job \"${name}\" received an invalid payload`);\n return this.send(res, 400, { error: \"invalid payload\" });\n }\n\n // Continue the scheduler's trace when it forwards one; start the job span.\n const started = this.deps.obs.startSpan(`job ${name}`, {\n transport: \"http\",\n procedure: name,\n kind: \"job\",\n parent: this.deps.obs.extractContext(req.headers),\n });\n const span = started.span;\n const traceId = this.deps.obs.traceIdOf(span);\n const jobLog = this.deps.obs.lazyChildLogger({\n reqId: span && traceId ? traceId : name,\n procedure: name,\n kind: \"job\",\n ...(traceId ? { traceId } : {}),\n });\n try {\n const serverCtx = this.deps.jobContext ? await this.deps.jobContext() : undefined;\n const fn = procedure.fn as (ctx: JobCtx<TContext>, input: unknown) => Promise<unknown>;\n const result = await otelContext.with(started.context, () =>\n fn({ db: this.deps.db, serverCtx, log: jobLog, span }, input),\n );\n this.deps.obs.record({\n op: \"job\",\n transport: \"http\",\n procedure: name,\n kind: \"job\",\n outcome: \"ok\",\n durationMs: elapsedMs(startedAt),\n span,\n });\n return this.send(res, 200, { ok: true, result });\n } catch (err) {\n // Return 500 so the scheduler retries (jobs must be idempotent).\n this.deps.obs.record({\n op: \"job\",\n transport: \"http\",\n procedure: name,\n kind: \"job\",\n outcome: \"error\",\n durationMs: elapsedMs(startedAt),\n code: \"JOB_FAILED\",\n error: err,\n span,\n });\n return this.send(res, 500, { error: \"job failed\" });\n }\n }\n\n private async runProcedure(\n procedure: RegisteredProcedure<TContext>,\n input: unknown,\n serverCtx: TContext | undefined,\n minTs: bigint | undefined,\n handlerObs: HandlerObservability,\n ): Promise<{ data: unknown; ts?: bigint }> {\n if (procedure.type === \"query\") {\n // Same cache/identity/freshness model as a WS one-shot query — reuses the\n // server's shared one-shot deps. Load the freshness upper bound here, the\n // same way the WS path does before calling runCachedQuery.\n const lastSnapshotTs = await this.deps.db.impl.getLatestSnapshotTimestamp();\n return runCachedQuery(\n this.deps.oneShotDeps,\n handlerObs,\n procedure,\n procedure.name,\n input,\n serverCtx,\n lastSnapshotTs,\n minTs,\n );\n }\n if (procedure.type === \"mutation\") {\n // Direct (no per-session queue); OCC makes concurrent HTTP mutations safe,\n // and the db's after-commit hook invalidates subscriptions like a WS write.\n const res = await this.deps.db.mutation(async (ctx) => {\n const fn = procedure.fn as (c: MutationCtx<TContext>, i: unknown) => Promise<unknown>;\n return fn({ db: ctx, serverCtx, ...primaryOnlyConn, log: handlerObs.log, span: handlerObs.span }, input);\n });\n // commitTs seeds the client's read high-water (read-your-writes).\n return { data: res.result, ts: res.commitTs };\n }\n // action — no single snapshot/commit ts to report.\n const fn = procedure.fn as (c: ActionCtx<TContext>, i: unknown) => Promise<unknown>;\n const data = await fn(\n {\n db: this.deps.db,\n serverCtx,\n storage: this.deps.storage,\n scheduler: this.deps.scheduler,\n log: handlerObs.log,\n span: handlerObs.span,\n },\n input,\n );\n return { data };\n }\n\n private async resolveAuth(\n req: IncomingMessage,\n ): Promise<{ ok: true; ctx: TContext | undefined } | { ok: false; message: string }> {\n const extractor = this.rpc.auth ?? defaultHttpAuth;\n const data = extractor(req);\n // No verifyAuth configured, or no auth data → anonymous (mirrors WS).\n if (!this.deps.verifyAuth || !data) return { ok: true, ctx: undefined };\n try {\n const ctx = await this.deps.verifyAuth(data, `http-${crypto.randomUUID()}`);\n if (!ctx) return { ok: false, message: \"Invalid authentication\" };\n return { ok: true, ctx };\n } catch (err) {\n if (err instanceof AuthenticationError) {\n return { ok: false, message: `Invalid authentication: ${err.message}` };\n }\n return { ok: false, message: \"Invalid authentication\" };\n }\n }\n\n private applyCors(req: IncomingMessage, res: ServerResponse) {\n const cors = this.rpc.cors;\n if (!cors) return;\n const originHeader = req.headers[\"origin\"];\n const origin = Array.isArray(originHeader) ? originHeader[0] : originHeader;\n\n if (cors.origins === \"*\") {\n res.setHeader(\"access-control-allow-origin\", \"*\");\n } else if (origin && cors.origins.includes(origin)) {\n res.setHeader(\"access-control-allow-origin\", origin);\n res.setHeader(\"vary\", \"Origin\");\n } else {\n return; // origin not allowed → emit no CORS headers\n }\n res.setHeader(\"access-control-allow-methods\", \"POST, GET, OPTIONS\");\n res.setHeader(\"access-control-allow-headers\", \"authorization, content-type\");\n res.setHeader(\"access-control-max-age\", \"86400\");\n }\n\n private send(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(supaliveStringify(body));\n }\n}\n\n// ─── helpers ─────────────────────────────────────────────────────────────────\n\n/** Parse a wire `minTs` (decimal bigint string) to a bigint, or undefined when\n * absent/malformed — a bad value falls back to primary-safe routing. */\nfunction parseMinTs(minTs: string | undefined): bigint | undefined {\n if (minTs === undefined) return undefined;\n try {\n return BigInt(minTs);\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeRpcPath(p: string): string {\n let s = p.startsWith(\"/\") ? p : `/${p}`;\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction defaultHttpAuth(req: IncomingMessage): Record<string, unknown> | undefined {\n const header = req.headers[\"authorization\"];\n const value = Array.isArray(header) ? header[0] : header;\n if (!value) return undefined;\n const match = /^Bearer\\s+(.+)$/i.exec(value);\n return match ? { token: match[1] } : undefined;\n}\n\nfunction lowercaseHeaders(headers: IncomingMessage[\"headers\"]): Record<string, string | undefined> {\n const out: Record<string, string | undefined> = {};\n for (const [k, v] of Object.entries(headers)) {\n out[k.toLowerCase()] = Array.isArray(v) ? v.join(\",\") : v;\n }\n return out;\n}\n\nfunction clientIp(req: IncomingMessage): string {\n const xff = req.headers[\"x-forwarded-for\"];\n if (xff) {\n const value = Array.isArray(xff) ? xff[0] : xff;\n const first = value.split(\",\")[0]?.trim();\n if (first) return first;\n }\n return req.socket?.remoteAddress ?? \"unknown\";\n}\n\nfunction readBody(req: IncomingMessage, maxBytes = 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > maxBytes) {\n reject(new Error(\"request body too large\"));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n}\n","import { createServer, IncomingMessage, type ServerResponse, type Server } from \"http\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { ZodType } from \"zod\";\nimport Redis from \"ioredis\";\nimport { retry, handleAll, ExponentialBackoff } from \"cockatiel\";\nimport { stableStringify, supaliveStringify } from \"../helper\";\nimport {\n type QueryCtx,\n type MutationCtx,\n type ActionCtx,\n ContextRegistry,\n getContextRegistry,\n} from \"../router\";\nimport { sleep, SupaliveDb } from \"../db/realtime_db\";\nimport { primaryOnlyConn } from \"../router/procedure\";\nimport type { WriteEntry, CommitTs, LiveResult, MutationResult } from \"../db/types_db\";\nimport { AuthenticationError, ClientMessage, ClientMessageSchema, Context, ServerMessage } from \"../db/types_client_rpc\";\nimport {\n type Session,\n type InstanceId,\n type SubId,\n type PendingMutation,\n RemoteSubscriptionMessageSchema,\n RemoteRecomputeRace,\n SubscriptionEntry,\n RemoteSubscriptionMessage,\n} from \"./types_server\";\nimport { type Router } from \"../router/router\";\nimport {\n generateSubscriptionId,\n generateCacheKey,\n getHashOf,\n resolveQueryIdentity,\n} from \"./sub_hash\";\nimport { runCachedQuery, type OneShotQueryDeps } from \"./one-shot-query\";\nimport { RetentionWatermark, RETENTION_WATERMARK_CHANNEL } from \"./retention-watermark\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport { CacheLayer } from \"../db/cache\";\nimport { SubManagerClient, type SubManagerLink } from \"./sub-manager-client\";\nimport { SupaliveServerConfig } from \"../config\";\nimport { appInstanceId, logger as baseLogger, logLevelOf } from \"../logger\";\nimport {\n Observability,\n traceQueryExecution,\n startTimer,\n elapsedMs,\n type OperationKind,\n type OperationOutcome,\n type QueryTrigger,\n} from \"../observability\";\nimport { context as otelContext, trace, type Span, type Context as OtelContext } from \"@opentelemetry/api\";\nimport type { HandlerObservability, HandlerLogger } from \"../router/procedure\";\nimport { AffectedSubscription, RegistrationRecord } from \"./sub-manager-types\";\nimport { bootstrapDbTypes } from \"../db/init_db\";\nimport { JobClient, type JobScheduler, type EnqueueOptions, type ScheduleHandle } from \"../jobs/scheduler\";\n\nimport type { ObjectStorage } from \"../storage\";\nimport { HttpRpcHandler } from \"./http-rpc\";\n\nconst redisRetryPolicy = retry(handleAll, {\n maxAttempts: 3,\n backoff: new ExponentialBackoff({\n initialDelay: 100,\n maxDelay: 5000,\n exponent: 2,\n }),\n});\n\n/**\n * Global broadcast channel used to signal column-type cache invalidation\n * across every running server instance. Publishers (migration tooling,\n * the publishSchemaReload helper below) send a JSON message of shape\n * `{ type: \"schema:reload\"; tables?: string[]; originInstance?: string }`.\n * Subscribers re-bootstrap from PG (or Redis) so the next batched write\n * sees up-to-date column metadata without a process restart.\n */\nconst SCHEMA_RELOAD_CHANNEL = \"supalive:schema:reload\";\n\n/**\n * Per-socket cap on pending mutations. The Nth+1 mutation from a single\n * client gets rejected with TOO_MANY_CONCURRENT_MUTATIONS rather than\n * being enqueued — this is rate-limit pressure on a misbehaving client,\n * not a hard system limit.\n *\n * Mirrors Convex's OPERATION_QUEUE_BUFFER_SIZE (1000). Mutations drain\n * one-at-a-time per socket, so a healthy client never approaches this.\n */\nconst MAX_MUTATION_QUEUE_BUFFER = 1000;\n\n/**\n * Per-socket cap on in-flight actions. Actions run in parallel (unlike\n * mutations) because they may do long external work (HTTP, queue calls);\n * the cap exists so one socket can't open thousands of outbound\n * connections from inside the server.\n */\nconst MAX_ACTION_CONCURRENCY = 1000;\n\n// ─── Heartbeat constants ─────────────────────────────────────\n//\n// Two independent jobs run on a single per-session interval:\n//\n// 1. Dead-TCP detection: terminate sockets that have gone fully silent\n// (no inbound text/ping/pong) past `CLIENT_TIMEOUT_MS`. Otherwise\n// emit a ws-protocol ping (browser auto-pongs, refreshing `session.lastReceivedAt`).\n// 2. Client-watchdog refresh: if the application channel has been idle\n// for at least `HEARTBEAT_INTERVAL_MS`, send a `{type:\"ping\"}` JSON\n// frame so the client's inactivity watchdog stays fresh. Busy\n// sockets skip this (the `lastSentAt` check makes it idle-only).\n//\n// Both fire on the same `heartbeatTimer` — one `setInterval` per session.\nconst HEARTBEAT_INTERVAL_MS = Number(\n process.env.SUPALIVE_HEARTBEAT_INTERVAL_MS ?? 15_000,\n);\nconst CLIENT_TIMEOUT_MS = 120_000;\n\n/**\n * TTL on the Redis lock that elects the recompute winner when the writer\n * doesn't hold the sub locally. Each (subId, commitTs) gets its own lock\n * key, so this only needs to bound the worst-case recompute duration —\n * if the winner crashes mid-recompute, the lock expires and the next\n * mutation triggers a new race.\n */\nconst RECOMPUTE_LOCK_TTL_SECONDS = Number(\n process.env.SUPALIVE_RECOMPUTE_LOCK_TTL_SECONDS ?? 10,\n);\n\n/**\n * Supalive WebSocket Server\n */\nexport class SupaliveWebSocketServer<TContext extends Context> {\n private config: SupaliveServerConfig<TContext>;\n private instanceId: InstanceId;\n\n private httpServer: Server;\n private wss: WebSocketServer;\n\n private cache: CacheLayer;\n private redisSub: Redis;\n /** App-level pub/sub channels this instance listens on (see config.appPubSub). */\n private readonly appPubSubChannels: Set<string>;\n private db: SupaliveDb;\n private contextRegistry!: ContextRegistry;\n private router?: Router<any, TContext>;\n\n // In-memory retention watermark (oldest retained commit-log ts): loaded from the\n // DB at startup and kept fresh by the sub-manager's Redis broadcast. Read on\n // the one-shot query hot path via `oneShotDeps.getMinRetainedTs` (a plain\n // variable, no per-query round trip).\n private retentionWatermark = new RetentionWatermark();\n // Built once — the one-shot query cache deps are static aside from the live\n // watermark, which `getMinRetainedTs` reads by reference. Shared by the WS `call` path\n // and the HTTP RPC handler.\n private oneShotDeps: OneShotQueryDeps<TContext>;\n\n private scheduler: JobScheduler;\n private jobPath: string;\n private jobClient: JobClient;\n /** Owns the HTTP surface: one-shot RPC (when `config.rpc` is set) AND the job\n * webhook. Always constructed — jobs work even without RPC enabled. */\n private rpcHandler: HttpRpcHandler<TContext>;\n /** Optional object storage exposed to every procedure as `ctx.storage`. */\n private storage: ObjectStorage;\n\n private sessions: Map<WebSocket, Session<TContext>> = new Map();\n private serverSubscriptions: Map<SubId, SubscriptionEntry> = new Map();\n private subManagerClient!: SubManagerLink;\n\n private stats = {\n activeConnections: 0,\n totalConnections: 0,\n activeSubscriptions: 0,\n };\n\n /** Structured logs + OTel metrics + the per-operation `onEvent` seam. */\n private obs: Observability;\n /** The request/lifecycle logger (injectable via `config.observability.logger`). */\n private log;\n\n constructor(config: SupaliveServerConfig<TContext>) {\n this.config = {\n port: 3000,\n host: \"0.0.0.0\",\n contextName: \"default\",\n sessionTtlSeconds: 3600,\n ...config,\n };\n this.instanceId = appInstanceId;\n\n // Build observability first so every downstream wiring (after-commit hook,\n // HTTP handler, connection handlers) can log/measure through it. The derived\n // logger keeps the SUPALIVE child binding so existing log filters still match.\n this.obs = new Observability({\n logger: (config.observability?.logger ?? baseLogger).child(\n { name: \"SUPALIVE\" },\n { level: config.observability?.level ?? logLevelOf(\"SERVER_LOG_LEVEL\") },\n ),\n level: undefined,\n requestLog: config.observability?.requestLog,\n onEvent: config.observability?.onEvent,\n meterName: config.observability?.meterName,\n meterVersion: config.observability?.meterVersion,\n });\n this.log = this.obs.logger;\n\n if (config.subManager) {\n // In-process link: the sub-manager is embedded in this process. Direct\n // method calls, no socket. The recovery/buffer machinery below is inert.\n this.subManagerClient = config.subManager;\n } else if (config.subManagerUrl) {\n this.subManagerClient = new SubManagerClient(config.subManagerUrl);\n } else {\n throw new Error(\n \"either `subManager` (in-process link) or `subManagerUrl` (WebSocket URL) is required\",\n );\n }\n this.cache = config.cacheLayer;\n this.redisSub = config.redisSubClient;\n this.appPubSubChannels = new Set(config.appPubSub?.channels ?? []);\n this.db = config.database;\n this.storage = config.storage;\n\n // Invalidate subscriptions after EVERY committed mutation, whatever its\n // source — client WS call, action, job, or the internal caller (as long as\n // it shares this db instance). Invalidation is instance-independent (it goes\n // through the central sub-manager), so a server-side write reaches every\n // WS node's subscribers. Fire-and-forget: the client's own response never\n // waits on it, and handleMutationCommit swallows/logs its own errors.\n this.db.setOnAfterCommit((writeSet, commitTs) => {\n this.handleMutationCommit(writeSet, commitTs).catch((err) =>\n this.log.error(err, \"post-commit invalidation failed\"),\n );\n });\n\n this.scheduler = config.scheduler;\n this.jobPath = normalizeJobPath(config.jobPath ?? \"/_jobs\");\n this.jobClient = new JobClient(this.scheduler, (name) => this.endpointFor(name));\n\n // Static deps for cached one-shot queries (WS `call` + HTTP RPC). Built once;\n // `getMinRetainedTs` reads the live in-memory watermark by reference.\n this.oneShotDeps = {\n db: this.db,\n cache: this.cache,\n getUserId: config.getUserId,\n getMinRetainedTs: () => this.retentionWatermark.get(),\n };\n\n // HTTP surface handler. Always constructed: it owns BOTH the opt-in one-shot\n // RPC transport (query/mutation/action — enabled only when `config.rpc` is\n // set; subscriptions stay WS-only) AND the job webhook (`{jobPath}/<name>`,\n // authenticated by the scheduler), so all HTTP routing lives in one class.\n // Shares this server's db + verifyAuth + procedure registry + observability.\n this.rpcHandler = new HttpRpcHandler<TContext>({\n getRegistry: () => this.contextRegistry,\n db: this.db,\n oneShotDeps: this.oneShotDeps,\n verifyAuth: config.verifyAuth,\n // RPC on/off is an explicit flag, not \"config present\": when the app omits\n // `rpc`, the endpoint is disabled and the handler rejects calls to it — but\n // it's still constructed so the job webhook works.\n rpc: config.rpc ?? {},\n rpcEnabled: config.rpc !== undefined,\n storage: this.storage,\n scheduler: this.jobClient,\n obs: this.obs,\n getUserId: config.getUserId,\n jobScheduler: this.scheduler,\n jobContext: config.jobContext,\n jobPath: this.jobPath,\n endpointFor: (name) => this.endpointFor(name),\n });\n\n // Wire the sub-manager client's recovery callbacks to drive re-registration\n // from this app server's serverSubscriptions whenever the RPC reconnects,\n // and to process the resulting batched invalidate flush.\n this.subManagerClient.setRecoveryDriver(this.runSubManagerRecovery.bind(this));\n this.subManagerClient.setAffectedHandler(this.runAffectedHandler.bind(this));\n\n // The HTTP server hosts the WS upgrade AND the job webhook endpoint. WS\n // traffic arrives as `upgrade` events (handled by WebSocketServer below),\n // so this plain `request` handler only ever sees the scheduler's POSTs and\n // health checks — the two never overlap.\n this.httpServer = createServer((req, res) => {\n this.handleHttpRequest(req, res).catch((err) => {\n this.log.error(err, \"HTTP request handler error\");\n if (!res.headersSent) {\n res.writeHead(500, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"internal error\" }));\n }\n });\n });\n this.wss = new WebSocketServer({\n server: this.httpServer,\n autoPong: true,\n maxPayload: 100 * 1024 * 1024,\n // backlog: //max pending connections queue (default: 511)\n });\n\n this.setupWebSocketHandlers();\n this.subscribeToInstanceChannel();\n }\n\n /**\n * Per-session heartbeat. Fires every `HEARTBEAT_INTERVAL_MS` and does\n * both jobs on the same tick:\n *\n * 1. Inactivity check + ws-protocol ping (dead-TCP detection).\n * 2. App-level `{type:\"ping\"}` if the channel has been idle for at\n * least one heartbeat interval (client-watchdog refresh).\n *\n * The `lastSentAt` gate on (2) preserves the original \"only ping when\n * idle\" semantics: a session producing sub:updates faster than the\n * heartbeat interval never emits a redundant app-ping.\n */\n private startSessionHeartbeat(session: Session<TContext>): void {\n session.heartbeatTimer = setInterval(() => {\n // (1) Dead-TCP detection.\n if (Date.now() - session.lastReceivedAt > CLIENT_TIMEOUT_MS) {\n this.log.info(`ws inactivity → terminate session ${session.id}`);\n try { session.ws.terminate(); } catch { /* close handler cleans up */ }\n return;\n }\n try {\n session.ws.ping();\n } catch {\n // socket already broken — close handler will clean up\n }\n\n // (2) Client-watchdog refresh, idle-only.\n if (Date.now() - session.lastSentAt >= HEARTBEAT_INTERVAL_MS) {\n this.sendMessage(session, { type: \"ping\" });\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private clearSessionTimers(session: Session<TContext>): void {\n if (session.heartbeatTimer) {\n clearInterval(session.heartbeatTimer);\n session.heartbeatTimer = null;\n }\n }\n\n registerRouter(router: Router<any, TContext>): void {\n this.router = router;\n this.contextRegistry = getContextRegistry(router.contextName);\n }\n\n async start(): Promise<void> {\n // Seed the in-memory retention watermark from the DB before serving; thereafter\n // the sub-manager's Redis broadcast keeps it fresh. Non-fatal on failure —\n // a 0 watermark is safe (nothing pruned → every cache entry is scannable).\n try {\n await this.retentionWatermark.loadFrom(this.db.impl);\n } catch (err) {\n this.log.error(err, \"failed to load initial retention watermark\");\n }\n await new Promise<void>((resolve) => {\n this.httpServer.listen(this.config.port, this.config.host, () => {\n this.log.info(`Server: ${this.instanceId} listening on ${this.config.host}:${this.config.port}`);\n resolve();\n });\n });\n await this.syncJobSchedules();\n }\n\n /**\n * Reconcile every registered recurring (cron) job with the scheduler. Safe\n * to call more than once. No-op when no scheduler is configured or the router\n * declares no cron jobs. Runs after the HTTP listener is up so the endpoints\n * the scheduler registers are already reachable.\n */\n private async syncJobSchedules(): Promise<void> {\n if (!this.scheduler || !this.contextRegistry) return;\n const jobs = this.contextRegistry\n .getJobProcedures()\n .filter((p) => !!p.cron)\n .map((p) => ({ name: p.name, cron: p.cron! }));\n if (jobs.length === 0) return;\n try {\n await this.scheduler.syncSchedules(jobs, (name) => this.endpointFor(name));\n this.log.info(`Synced ${jobs.length} recurring job schedule(s)`);\n } catch (err) {\n this.log.error(err, \"Failed to sync job schedules\");\n }\n }\n\n public getObservability(): Observability {\n return this.obs;\n }\n\n /** Absolute URL the scheduler POSTs to for a given job name. */\n endpointFor(name: string): string {\n return `${this.config.publicUrl}${this.jobPath}/${name}`;\n }\n\n /**\n * Enqueue a precise, one-shot run of a job (e.g. \"expire this record at\n * exactly `expiresAt`\"). Returns a handle whose `id` can be passed to\n * {@link cancelJob}. Requires a configured scheduler.\n */\n async scheduleJob(name: string, body: unknown, opts?: EnqueueOptions): Promise<ScheduleHandle> {\n if (!this.jobClient) throw new Error(\"No scheduler configured; cannot schedule jobs\");\n return this.jobClient.enqueue(name, body, opts);\n }\n\n /** Cancel a one-shot previously created by {@link scheduleJob}. */\n async cancelJob(handleId: string): Promise<void> {\n if (!this.jobClient) throw new Error(\"No scheduler configured; cannot cancel jobs\");\n return this.jobClient.cancel(handleId);\n }\n\n /**\n * The app-facing {@link JobClient} — hand it to your service layer so\n * procedures can schedule/cancel one-shots without reaching for the server.\n */\n getJobClient(): JobClient {\n return this.jobClient;\n }\n\n async stop(): Promise<void> {\n this.wss.close();\n for (const [ws] of this.sessions) {\n ws.close(1001, \"Server shutting down\");\n }\n this.rpcHandler.stop();\n this.httpServer.close();\n this.log.info(\"Server stopped\");\n }\n\n getStats() {\n return {\n instanceId: this.instanceId,\n activeConnections: this.stats.activeConnections,\n totalConnections: this.stats.totalConnections,\n activeSubscriptions: this.stats.activeSubscriptions,\n };\n }\n\n getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Plain-HTTP entrypoint (non-WS). Serves a liveness probe and the job\n * webhook: `POST {jobPath}/<jobName>`. Every job POST is authenticated by the\n * configured scheduler before the (server-only) job handler runs.\n */\n private async handleHttpRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const rawUrl = req.url ?? \"/\";\n const path = rawUrl.split(\"?\")[0]!;\n\n // Liveness probe — cheap, unauthenticated.\n if (req.method === \"GET\" && (path === \"/_health\")) {\n return sendJson(res, 200, { status: \"ok\", instanceId: this.instanceId });\n }\n\n // One-shot HTTP RPC (query/mutation/action) — enabled only when config.rpc\n // is set (handlesPath is false otherwise).\n if (this.rpcHandler.handlesPath(path)) {\n return this.rpcHandler.handle(req, res, path);\n }\n\n // Job webhook (`{jobPath}/<name>`), authenticated by the scheduler. Owned by\n // the same handler as RPC (see HttpRpcHandler.handleJob) so all HTTP routing\n // is in one place.\n if (this.rpcHandler.handlesJobPath(path)) {\n return this.rpcHandler.handleJob(req, res, path);\n }\n\n // App-supplied HTTP routes (e.g. a Prometheus `/metrics` scrape) mounted on\n // this same port — checked after the built-in routes and before the 404, so\n // the app can serve them without standing up a second HTTP server.\n if (this.config.onHttpRequest) {\n const handled = await this.config.onHttpRequest(req, res);\n if (handled) return;\n }\n\n return sendJson(res, 404, { error: \"not found\" });\n }\n\n private setupWebSocketHandlers(): void {\n this.wss.on(\"connection\", (ws, req) => {\n this.handleConnection(ws, req).catch((err) => {\n this.log.error(err, \"Connection handler error\")\n });\n });\n }\n\n private async handleConnection(ws: WebSocket, req: IncomingMessage): Promise<void> {\n this.stats.totalConnections++;\n this.stats.activeConnections++;\n this.obs.connectionOpened();\n\n const connectedAt = startTimer();\n const session: Session<TContext> = {\n id: uuidv4(),\n ws,\n serverCtx: undefined,\n subIds: new Set(),\n mutationQueue: [],\n mutationInFlight: false,\n actionInFlight: 0,\n lastReceivedAt: Date.now(),\n lastSentAt: Date.now(),\n heartbeatTimer: null,\n };\n this.log.info({ sessionId: session.id, activeConnections: this.stats.activeConnections }, `New session: ${session.id}`);\n\n this.sessions.set(ws, session);\n this.sendMessage(session, { type: \"connected\", sessionId: session.id });\n\n // Any inbound frame (text, ws-ping, ws-pong) refreshes the liveness\n // clock. This is the only thing keeping `CLIENT_TIMEOUT_MS` from\n // tripping on a silent-but-live client, since the browser auto-pong\n // is the only thing such a client emits.\n ws.on(\"message\", (data) => {\n session.lastReceivedAt = Date.now();\n this.handleMessage(session, data.toString()).catch((err) => {\n this.log.error(err, \"Message handler error\");\n });\n });\n ws.on(\"ping\", () => { session.lastReceivedAt = Date.now(); });\n ws.on(\"pong\", () => { session.lastReceivedAt = Date.now(); });\n\n this.startSessionHeartbeat(session);\n\n ws.on(\"close\", (code) => {\n this.clearSessionTimers(session);\n this.handleDisconnect(session);\n this.sessions.delete(ws);\n this.stats.activeConnections--;\n this.obs.connectionClosed();\n this.log.info(\n {\n sessionId: session.id,\n code,\n durMs: elapsedMs(connectedAt),\n activeConnections: this.stats.activeConnections,\n },\n `Connection closed: ${session.id}, code: ${code}`,\n );\n });\n\n ws.on(\"error\", (err) => {\n this.log.error(err, \"Websocket error\");\n });\n }\n\n private async handleMessage(session: Session<TContext>, message: string): Promise<void> {\n // Start timing BEFORE decoding the frame (JSON.parse + schema validation), so\n // a call/subscribe's recorded duration includes the decode cost — the same\n // \"measure from arrival to response\" the HTTP paths use.\n const startedAt = startTimer();\n try {\n const rawMsg = JSON.parse(message);\n const parseResult = ClientMessageSchema.safeParse(rawMsg);\n if (!parseResult.success) {\n this.sendError(session, \"\", \"Invalid message format\", \"INVALID_MESSAGE\");\n return;\n }\n await this.processMessage(session, parseResult.data, startedAt);\n } catch (err) {\n this.log.error(err, \"Message handling error\");\n this.sendError(session, \"\", \"Parse error\", \"PARSE_ERROR\");\n }\n }\n\n private async processMessage(\n session: Session<TContext>,\n msg: ClientMessage,\n startedAt: number,\n ): Promise<void> {\n switch (msg.type) {\n case \"auth\":\n await this.handleAuth(session, msg.data);\n break;\n case \"call\":\n await this.handleCall(session, msg.id, msg.procedure, msg.input, startedAt, parseMinTs(msg.minTs), msg.trace);\n break;\n case \"subscribe\":\n await this.handleSubscribe(session, msg.id, msg.procedure, msg.input, startedAt, msg.trace);\n break;\n case \"unsubscribe\":\n await this.handleUnsubscribe(session, msg.subId);\n break;\n }\n }\n\n private async handleAuth(session: Session<TContext>, data?: Record<string, unknown>): Promise<void> {\n if (!this.config.verifyAuth || !data) {\n this.sendMessage(session, { type: \"auth:success\", userId: \"anonymous\" });\n return;\n }\n\n try {\n const authResult = await this.config.verifyAuth(data, session.id);\n if (!authResult) {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n return;\n }\n\n session.serverCtx = authResult;\n this.sendMessage(session, { type: \"auth:success\", userId: authResult.user.userId });\n } catch (e) {\n if (e instanceof AuthenticationError) {\n this.sendMessage(session, {\n type: \"auth:failed\",\n message: \"Invalid authentication: \" + e.message,\n extra: e.extra,\n });\n } else {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n }\n }\n }\n\n /**\n * Park a mutation on this session's FIFO queue and resolve when it\n * eventually drains. The drainer is fire-and-forget: it picks up any\n * newly enqueued op as long as one drain loop is running on this\n * session. Callers (handleCall) own the response/error for their own\n * op via the returned promise.\n */\n private enqueueMutation(\n session: Session<TContext>,\n procedure: RegisteredProcedure<TContext>,\n validatedInput: unknown,\n handlerObs?: HandlerObservability,\n ): Promise<MutationResult<unknown>> {\n return new Promise<MutationResult<unknown>>((resolve, reject) => {\n const pending: PendingMutation<TContext> = {\n procedure,\n validatedInput,\n resolve,\n reject,\n handlerObs,\n };\n session.mutationQueue.push(pending);\n this.drainMutationQueue(session).catch((err) =>\n this.log.error(err, \"drainMutationQueue crashed\"),\n );\n });\n }\n\n /**\n * Drain the per-session mutation queue serially. Only one drain loop\n * runs at a time per session — re-entrant calls bail immediately, the\n * loop already in progress will pick up the new op before exiting.\n */\n private async drainMutationQueue(session: Session<TContext>): Promise<void> {\n if (session.mutationInFlight) return;\n session.mutationInFlight = true;\n try {\n while (session.mutationQueue.length > 0) {\n const op = session.mutationQueue.shift()!;\n try {\n const fn = op.procedure.fn as (\n ctx: MutationCtx<TContext>,\n input: unknown,\n ) => Promise<unknown>;\n const runMutation = () =>\n this.db.mutation(async (ctx) => {\n return fn({\n db: ctx,\n serverCtx: session.serverCtx,\n ...primaryOnlyConn,\n log: op.handlerObs?.log ?? this.obs.lazyChildLogger({ caller: \"internal\", procedure: op.procedure.name }),\n span: op.handlerObs?.span,\n }, op.validatedInput);\n });\n // The drain loop is fire-and-forget, so its ambient OTel context is\n // whichever request first started the loop — not necessarily this op's.\n // Re-establish this mutation's own operation span as active so nested\n // spans (e.g. `db.commit`) parent correctly. Skipped when the span\n // isn't recording (tracing off) — zero overhead.\n const parentSpan = op.handlerObs?.span;\n const result = parentSpan?.isRecording()\n ? await otelContext.with(trace.setSpan(otelContext.active(), parentSpan), runMutation)\n : await runMutation();\n op.resolve(result);\n } catch (err) {\n op.reject(err);\n }\n }\n } finally {\n session.mutationInFlight = false;\n }\n }\n\n private async handleCall(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown,\n startedAt: number,\n minTs?: bigint,\n trace?: string,\n ): Promise<void> {\n const userId = this.config.getUserId?.(session.serverCtx) ?? undefined;\n let span: Span | undefined;\n const record = (\n outcome: OperationOutcome,\n opts?: { kind?: OperationKind; code?: string; error?: unknown },\n ): void => {\n this.obs.record({\n op: \"rpc\",\n transport: \"ws\",\n procedure: procedureName,\n kind: opts?.kind,\n outcome,\n durationMs: elapsedMs(startedAt),\n code: opts?.code,\n error: opts?.error,\n sessionId: session.id,\n reqId: id,\n userId,\n span,\n });\n };\n\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n record(\"error\", { code: \"SERVER_ERROR\" });\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type === \"job\") {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found`, \"NOT_FOUND\");\n record(\"error\", { code: \"NOT_FOUND\" });\n return;\n }\n const kind = procedure.type as OperationKind;\n\n // Start an operation span (child of the client's trace when it sent one),\n // and build the request-scoped `ctx.log`/`ctx.span` handed to the handler.\n const parent = trace ? this.obs.extractContext({ traceparent: trace }) : undefined;\n const started = this.obs.startSpan(`rpc.${kind} ${procedureName}`, {\n transport: \"ws\",\n procedure: procedureName,\n kind,\n parent,\n });\n span = started.span;\n const traceId = this.obs.traceIdOf(span);\n const handlerObs: HandlerObservability = {\n log: this.obs.lazyChildLogger({\n reqId: id,\n sessionId: session.id,\n procedure: procedureName,\n kind,\n ...(userId != null ? { userId } : {}),\n ...(traceId ? { traceId } : {}),\n }),\n span,\n };\n\n // Run the whole operation within the span's context so nested instrumentation\n // and `ctx.span` children attach to it.\n await otelContext.with(started.context, async () => {\n try {\n const validatedInput = (procedure.inputSchema as ZodType).parse(input);\n\n if (procedure.type === \"query\") {\n // One-shot query: same cache/identity/freshness model as a live\n // subscription (see runCachedQuery), just without tracking a sub.\n // Load the freshness upper bound here, mirroring handleSubscribe which\n // loads lastSnapshotTs before registering.\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const { data, ts } = await runCachedQuery(\n this.oneShotDeps,\n handlerObs,\n procedure,\n procedureName,\n validatedInput,\n session.serverCtx,\n lastSnapshotTs,\n minTs,\n );\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data,\n // Echo the read snapshot so the client advances its high-water and a\n // later read preserves read-your-writes / monotonic reads.\n ts: ts.toString(),\n });\n record(\"ok\", { kind });\n } else if (procedure.type === \"mutation\") {\n if (session.mutationQueue.length >= MAX_MUTATION_QUEUE_BUFFER) {\n this.sendError(\n session,\n id,\n `Too many concurrent mutations on this connection (max ${MAX_MUTATION_QUEUE_BUFFER})`,\n \"TOO_MANY_CONCURRENT_MUTATIONS\",\n );\n record(\"error\", { kind, code: \"TOO_MANY_CONCURRENT_MUTATIONS\" });\n return;\n }\n\n const mutationResult = await this.enqueueMutation(session, procedure, validatedInput, handlerObs);\n\n // Invalidation is driven by the db's after-commit hook (wired in the\n // constructor) for every mutation source, so we don't invalidate again\n // here — we just return the result to the caller.\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: mutationResult.result,\n // The commitTs seeds the client's read high-water so its next read\n // reads its own write (routed to primary until the replica catches up).\n ts: mutationResult.commitTs.toString(),\n });\n record(\"ok\", { kind });\n } else if (procedure.type === \"action\") {\n if (session.actionInFlight >= MAX_ACTION_CONCURRENCY) {\n this.sendError(\n session,\n id,\n `Too many concurrent actions on this connection (max ${MAX_ACTION_CONCURRENCY})`,\n \"TOO_MANY_CONCURRENT_ACTIONS\",\n );\n record(\"error\", { kind, code: \"TOO_MANY_CONCURRENT_ACTIONS\" });\n return;\n }\n\n session.actionInFlight++;\n let result: unknown;\n try {\n const fn = procedure.fn as (ctx: ActionCtx<TContext>, input: unknown) => Promise<unknown>;\n result = await fn(\n {\n db: this.db,\n serverCtx: session.serverCtx,\n storage: this.storage,\n scheduler: this.jobClient,\n log: handlerObs.log,\n span: handlerObs.span,\n },\n validatedInput,\n );\n } finally {\n session.actionInFlight--;\n }\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: result,\n });\n record(\"ok\", { kind });\n }\n } catch (err) {\n this.sendError(\n session,\n id,\n err instanceof Error ? err.message : \"Execution failed\",\n \"EXECUTION_ERROR\"\n );\n record(\"error\", { kind, code: \"EXECUTION_ERROR\", error: err });\n }\n });\n }\n\n private async handleSubscribe(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown,\n startedAt: number,\n trace?: string,\n ): Promise<void> {\n const userId = this.config.getUserId?.(session.serverCtx) ?? undefined;\n let span: Span | undefined;\n const record = (\n outcome: OperationOutcome,\n opts?: { code?: string; error?: unknown },\n ): void => {\n this.obs.record({\n op: \"subscribe\",\n transport: \"ws\",\n procedure: procedureName,\n kind: \"subscribe\",\n outcome,\n durationMs: elapsedMs(startedAt),\n code: opts?.code,\n error: opts?.error,\n sessionId: session.id,\n reqId: id,\n userId,\n span,\n });\n };\n\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n record(\"error\", { code: \"SERVER_ERROR\" });\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found or not a query`, \"NOT_FOUND\");\n record(\"error\", { code: \"NOT_FOUND\" });\n return;\n }\n\n // Operation span + request-scoped `ctx.log`/`ctx.span` for the query handler.\n const parent = trace ? this.obs.extractContext({ traceparent: trace }) : undefined;\n const started = this.obs.startSpan(`subscribe ${procedureName}`, {\n transport: \"ws\",\n procedure: procedureName,\n kind: \"subscribe\",\n parent,\n });\n span = started.span;\n const traceId = this.obs.traceIdOf(span);\n const handlerObs: HandlerObservability = {\n log: this.obs.lazyChildLogger({\n reqId: id,\n sessionId: session.id,\n procedure: procedureName,\n kind: \"subscribe\",\n ...(userId != null ? { userId } : {}),\n ...(traceId ? { traceId } : {}),\n }),\n span,\n };\n\n try {\n const validatedInput = procedure.inputSchema.parse(input);\n const queryIdentity = resolveQueryIdentity(procedure, session.serverCtx, validatedInput, this.config.getUserId);\n const inputJsonStable = stableStringify(validatedInput);\n const subId = await generateSubscriptionId(procedureName, inputJsonStable, queryIdentity);\n const cacheKey = await generateCacheKey(procedureName, inputJsonStable, queryIdentity);\n const existingServerSub = this.serverSubscriptions.get(subId);\n\n let data: unknown;\n let dataHash: string;\n let liveResult: LiveResult<unknown> | null = null;\n let servedTs!: bigint;\n\n // track this session subscribed this subscription\n session.subIds.add(subId);\n\n if (existingServerSub) {\n existingServerSub.subscribedTimes += 1;\n\n const cached = await this.cache.getQueryCacheFor(existingServerSub.cacheKey);\n if (cached) {\n data = cached.data;\n servedTs = cached.lastSnapshotTs;\n } else {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx, { log: handlerObs.log, parentContext: started.context, trigger: \"subscribe\" });\n data = liveResult.data;\n servedTs = liveResult.ts;\n }\n } else {\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subManagerResult = await this.subManagerClient.registerSubscription({\n subId,\n cacheKey,\n queryName: procedureName,\n args: validatedInput,\n queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs: lastSnapshotTs,\n });\n\n // Serve the cached blob only when the sub-manager says it's fresh AND its\n // version matches the metadata that freshness was validated against — a\n // divergent data/metadata pair (torn read, cross-process write race) is\n // treated as a miss and recomputed rather than served stale.\n let rebuild: boolean = true;\n if (!subManagerResult.recompute) {\n const cached = await this.cache.getQueryCacheFor(subManagerResult.cacheKey);\n if (cached && cached.version === subManagerResult.version) {\n data = cached.data;\n servedTs = cached.lastSnapshotTs;\n rebuild = false;\n }\n }\n\n if (rebuild) {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx, { log: handlerObs.log, parentContext: started.context, trigger: \"subscribe\" });\n data = liveResult.data;\n servedTs = liveResult.ts;\n }\n\n this.serverSubscriptions.set(subId, {\n subId,\n cacheKey,\n subscribedTimes: 1,\n });\n this.stats.activeSubscriptions++;\n this.obs.subscriptionAdded();\n }\n\n if (liveResult) {\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey,\n subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n // The sub-worker writes the matching metadata version via\n // updateSubscriptionReadSet above (version === String(ts)).\n this.cache.setQueryCacheFor(cacheKey, data, liveResult.ts),\n ]);\n\n // If the sub-manager just told us this subId isn't tracked (we hit\n // a window between its register and a separate restart), our register\n // above should have rebuilt the state — log loudly if not.\n if (!updateResult.tracked) {\n this.log.warn(\n `updateSubscriptionReadSet returned tracked:false immediately after register for ${subId} ` +\n `— possible sub-manager restart mid-subscribe`,\n );\n }\n }\n\n dataHash = await getHashOf(data);\n\n // Hand the canonical subId back to the client so it doesn't need to\n // recompute the hash locally (which would require pulling in wasm and\n // wouldn't reflect per-procedure `queryIdentity` overrides).\n this.sendMessage(session, { type: \"response\", id, success: true, subId });\n // Carry the snapshot ts so the client can advance its read high-water\n // (cross-sub monotonic reads): the recompute's ts, or — when served from\n // cache — the entry's own `lastSnapshotTs` (see cachedTs above).\n this.sendMessage(session, {\n type: \"sub:update\",\n subId,\n data,\n dataHash,\n ts: servedTs.toString(),\n });\n\n record(\"ok\", { code: undefined });\n } catch (err) {\n this.sendError(session, id, err instanceof Error ? err.message : \"Subscribe failed\", \"SUBSCRIBE_ERROR\");\n record(\"error\", { code: \"SUBSCRIBE_ERROR\", error: err });\n }\n }\n\n private async handleUnsubscribe(session: Session<TContext>, subId: string): Promise<void> {\n const serverSub = this.serverSubscriptions.get(subId);\n if (!serverSub) {\n session.subIds.delete(subId);\n return;\n }\n\n serverSub.subscribedTimes -= 1;\n if (serverSub.subscribedTimes <= 0) {\n session.subIds.delete(subId);\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions--;\n this.obs.subscriptionRemoved();\n\n try {\n await this.subManagerClient.unregisterSubscription({ subId, instanceName: this.instanceId });\n } catch (err) {\n this.log.error(err, \"Failed to unregister from sub-manager:\");\n }\n }\n\n this.log.debug(\n { sessionId: session.id, subId, remaining: serverSub.subscribedTimes },\n `Unsubscribed: ${subId} for session ${session.id}`,\n );\n }\n\n private async handleDisconnect(session: Session<TContext>): Promise<void> {\n this.log.info(`Session disconnected: ${session.id}`);\n\n let unregisterSubs: SubscriptionEntry[] | undefined = undefined;\n for (const subId of session.subIds) {\n const sub = this.serverSubscriptions.get(subId);\n if (!sub) continue;\n\n sub.subscribedTimes -= 1;\n if (sub.subscribedTimes <= 0) {\n if (!unregisterSubs) {\n unregisterSubs = [];\n }\n\n unregisterSubs.push(sub);\n }\n }\n\n if (!unregisterSubs || unregisterSubs.length === 0) return;\n\n try {\n await this.subManagerClient.unregisterSubscriptions(\n unregisterSubs.map((sub) => ({ subId: sub.subId, instanceName: this.instanceId })),\n );\n } catch (err) {\n this.log.error(err, \"Failed to unregister from sub-manager:\");\n }\n\n for (const sub of unregisterSubs) {\n // if still not subscribing\n if (sub.subscribedTimes <= 0) {\n this.serverSubscriptions.delete(sub.subId);\n this.stats.activeSubscriptions--;\n this.obs.subscriptionRemoved();\n }\n }\n\n this.log.info(`All Subscription sessions is disconnected: ${session.id}, subs: ${unregisterSubs.length}`);\n }\n\n /**\n * Drives recovery on a sub-manager reconnect. Iterates this app server's\n * `serverSubscriptions`, reads each subId's registration record from\n * Redis (`sl:reg:<subId>`), and re-registers it with the sub-manager.\n *\n * • If the Redis record is missing, another app-server instance fully\n * unregistered the sub during our disconnect. We drop it locally and\n * send `sub:gone` to every session that was holding it.\n * • If the sub-manager reports `recompute: true` (cache-metadata + commit\n * log replay detected staleness), we kick a reexec for that sub.\n *\n * Returns after all subs have been processed. Only then does the\n * SubManagerClient flush its buffered invalidate writeSets.\n */\n private async runSubManagerRecovery(): Promise<void> {\n const snapshot = Array.from(this.serverSubscriptions.values());\n if (snapshot.length === 0) {\n this.log.info(\"Sub-manager recovery: no subscriptions to restore\");\n return;\n }\n\n this.log.info(`Sub-manager recovery: restoring ${snapshot.length} subscription(s)`);\n\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subIds = snapshot.map((e) => e.subId);\n const recordsMap = await this.cache.fetchRegistrationRecordsBatch(subIds);\n\n const registerParams: Array<{\n entry: typeof snapshot[0];\n record: RegistrationRecord;\n }> = [];\n for (const entry of snapshot) {\n const record = recordsMap.get(entry.subId) ?? null;\n if (!record) {\n this.dropSubscriptionLocally(entry.subId, \"registration not found after recovery\");\n continue;\n }\n registerParams.push({ entry, record });\n }\n\n const recompute: AffectedSubscription[] = [];\n const BATCH_SIZE = 50;\n for (let i = 0; i < registerParams.length; i += BATCH_SIZE) {\n const batch = registerParams.slice(i, i + BATCH_SIZE);\n const params = batch.map(({ entry, record }) => ({\n subId: entry.subId,\n cacheKey: entry.cacheKey,\n queryName: record.queryName,\n args: record.args,\n queryIdentity: record.queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs,\n }));\n try {\n const results = await this.subManagerClient.registerSubscriptionBatch(params);\n for (let j = 0; j < batch.length; j++) {\n const { entry, record } = batch[j]!;\n const result = results[j]!;\n if (result.recompute) {\n recompute.push({\n subId: entry.subId,\n queryName: record.queryName,\n args: record.args,\n cacheKey: entry.cacheKey,\n notifyInstances: [],\n });\n }\n }\n } catch (err) {\n this.log.error(err, `Sub-manager recovery: failed to batch register ${i}-${i + batch.length}`);\n }\n }\n\n await Promise.allSettled(recompute.map((a) => this.reexecuteAffectedQuery(a, \"recovery\")));\n\n this.log.info(`Sub-manager recovery: done (${recompute.length} reexec'd)`);\n }\n\n private async runAffectedHandler(affected: AffectedSubscription[]) {\n await Promise.allSettled(\n affected.map((a) => this.reexecuteAffectedQuery(a)),\n );\n }\n\n /**\n * Removes a subscription from this app server's state and notifies every\n * session that was holding it. Used during recovery when the central\n * registration record is gone, or after a `tracked: false` reply from the\n * sub-manager that we can't reconcile.\n */\n private dropSubscriptionLocally(subId: string, reason: string): void {\n const serverSub = this.serverSubscriptions.get(subId);\n let notified = 0;\n if (serverSub) {\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions = Math.max(0, this.stats.activeSubscriptions - 1);\n this.obs.subscriptionRemoved();\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) {\n notified++;\n session.subIds.delete(subId);\n this.sendMessage(session, { type: \"sub:gone\", subId, reason });\n }\n }\n }\n\n this.log.warn(`Dropped subscription ${subId} locally (${reason}); notified ${notified} session(s)`);\n }\n\n private async handleMutationCommit(writeSet: WriteEntry[], commitTs: CommitTs): Promise<void> {\n if (writeSet.length === 0) return;\n\n const tables = [...new Set(writeSet.map((w) => w.table))];\n this.log.debug(`Mutation committed for tables: [${tables.join(\", \")}]`);\n\n try {\n const result = await this.subManagerClient.invalidateWriteset({ writeSet });\n this.log.debug({ affected: result }, \"Sub-managers invalidation result\");\n\n if (result?.affected?.length > 0) {\n await Promise.allSettled(\n result.affected.map((affected) => this.dispatchAffected(affected, commitTs)),\n );\n }\n } catch (err) {\n this.log.error(err, \"Failed to notify sub-managers\");\n }\n }\n\n /**\n * Route an affected sub to whichever instance can actually run the\n * recompute:\n * • If this instance has a local session for the sub, recompute here\n * (fast path) and broadcast the result to every other instance.\n * • Otherwise broadcast a `recompute-race` to every instance in\n * `notifyInstances`. Each receiver tries to acquire a Redis lock\n * keyed by (subId, commitTs); the winner recomputes and publishes\n * an `update` via the normal broadcast path, losers no-op.\n *\n * If `notifyInstances` is empty (the sub has no live holders), we drop\n * the message — the next subscribe will recompute fresh state.\n */\n private async dispatchAffected(affected: AffectedSubscription, commitTs: CommitTs): Promise<void> {\n if (this.hasLocalSessionFor(affected.subId)) {\n await this.reexecuteAffectedQuery(affected);\n return;\n }\n\n if (affected.notifyInstances.length === 0) {\n this.log.debug(\n `dispatchAffected: no live instances hold ${affected.subId} — dropping`,\n );\n return;\n }\n\n await this.publishRecomputeRace(affected, commitTs);\n }\n\n private hasLocalSessionFor(subId: SubId): boolean {\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) return true;\n }\n return false;\n }\n\n private async publishRecomputeRace(\n affected: AffectedSubscription,\n commitTs: CommitTs,\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"recompute-race\",\n subId: affected.subId,\n cacheKey: affected.cacheKey,\n queryName: affected.queryName,\n args: affected.args,\n notifyInstances: affected.notifyInstances,\n commitTs: commitTs.toString(),\n originInstance: this.instanceId,\n });\n\n const publishPromises = affected.notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n this.log.error(\n err,\n `Failed to publish recompute-race to ${channel} after many retries`,\n );\n }\n });\n\n await Promise.all(publishPromises);\n }\n\n private async reexecuteAffectedQuery(\n affected: AffectedSubscription,\n trigger: QueryTrigger = \"recompute\",\n ): Promise<void> {\n let foundSession: Session<TContext> | null = null;\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n foundSession = session;\n break;\n }\n }\n\n if (!foundSession) {\n // Should only happen if every local session unsubscribed between\n // dispatchAffected's check and now. dispatchAffected forwards to\n // another instance when no local session exists, so we'd lose the\n // update for everyone in this rare race. Accept the gap.\n this.log.debug(`Session not found for subId: ${affected.subId}`);\n return;\n }\n\n try {\n const liveResult = await this.executeLiveQuery(affected.queryName, affected.args, foundSession.serverCtx, { trigger });\n const dataHash = await getHashOf(liveResult.data);\n\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey: affected.cacheKey,\n subId: affected.subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n this.cache.setQueryCacheFor(affected.cacheKey, liveResult.data, liveResult.ts),\n ]);\n\n // `tracked: false` means the sub-manager has no record of this subId\n // (it restarted before we recovered, or a concurrent unregister wiped\n // it). Drop the sub locally and let clients re-subscribe.\n if (!updateResult.tracked) {\n this.dropSubscriptionLocally(affected.subId, \"no longer tracked by sub-manager\");\n return;\n }\n\n // find any session with same subId and send update\n const ts = liveResult.ts.toString();\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: affected.subId,\n data: liveResult.data,\n dataHash,\n ts,\n });\n }\n }\n\n if (affected.notifyInstances.length > 0) {\n await this.publishSubscriptionUpdate(\n affected.subId,\n liveResult.data,\n dataHash,\n affected.notifyInstances,\n ts,\n );\n }\n\n this.log.debug(`Re-executed affected query for subId: ${affected.subId}`);\n } catch (err) {\n this.log.error(err, `Failed to re-execute query for subId ${affected.subId}`);\n }\n }\n\n private async executeLiveQuery(\n procedureName: string,\n input: unknown,\n serverCtx?: TContext,\n opts?: {\n /** Request-scoped `ctx.log` (subscribe path); omitted for background recompute. */\n log?: HandlerLogger;\n /** Parent OTel context (the operation span); omitted → the query span is a root. */\n parentContext?: OtelContext;\n /** What triggered this execution. Defaults to `\"recompute\"` (background path). */\n trigger?: QueryTrigger;\n },\n ): Promise<LiveResult<unknown>> {\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n throw new Error(`Procedure not defined: ${procedureName}`);\n }\n\n return this.db.liveQuery(async (ctx) => {\n const fn = procedure.fn as (ctx: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n const callerLog = (): HandlerLogger =>\n this.obs.lazyChildLogger({ caller: \"internal\", procedure: procedureName });\n\n // `query <name>` span around the execution — a child of the operation span\n // for subscribe, or a root span for a background reactive recompute.\n return traceQueryExecution(\n procedureName,\n opts?.trigger ?? \"recompute\",\n opts?.parentContext,\n (querySpan) =>\n fn(\n { db: ctx, serverCtx, ...primaryOnlyConn, log: opts?.log ?? callerLog(), span: querySpan },\n input,\n ),\n );\n });\n }\n\n private sendMessage(session: Session<TContext>, message: ServerMessage): void {\n if (session.ws.readyState !== WebSocket.OPEN) return;\n session.ws.send(supaliveStringify(message));\n // Mark the channel as having had recent outbound traffic. The\n // heartbeat tick uses this to skip the idle-only app-level ping\n // for busy sessions (no point pinging a client that just got a\n // sub:update).\n session.lastSentAt = Date.now();\n }\n\n private sendMessageRaw(session: Session<TContext>, message: string): void {\n if (session.ws.readyState === WebSocket.OPEN) {\n session.ws.send(message);\n }\n }\n\n private sendError(session: Session<TContext>, id: string, error: string, code: string): void {\n this.sendMessage(session, { type: \"response\", id, success: false, error, code });\n }\n\n private async subscribeToInstanceChannel(): Promise<void> {\n const instanceChannel = `sub:instance:${this.instanceId}`;\n const schemaChannel = SCHEMA_RELOAD_CHANNEL;\n const watermarkChannel = RETENTION_WATERMARK_CHANNEL;\n\n // App-level channels (config.appPubSub) ride this SAME subscribe connection —\n // no extra Redis connection per instance.\n const appChannels = [...this.appPubSubChannels];\n await this.redisSub.subscribe(instanceChannel, schemaChannel, watermarkChannel, ...appChannels);\n this.redisSub.on(\"connect\", () => {\n this.log.info(\n `Subscribed to Redis channels: ${[instanceChannel, schemaChannel, watermarkChannel, ...appChannels].join(\", \")}`,\n );\n });\n this.redisSub.on(\"message\", (channel: string, message: string) => {\n if (channel === instanceChannel) {\n this.handleRemoteInstanceMessage(message).catch((err) => {\n this.log.error(err, \"Error handling remote instance message\");\n });\n } else if (channel === schemaChannel) {\n this.handleSchemaReload().catch((err) => {\n this.log.error(err, \"Error handling schema reload\");\n });\n } else if (channel === watermarkChannel) {\n // Sub-manager advertised a new retention watermark after a prune. Adopt it\n // (monotonic — a stale/out-of-order message can't lower it).\n try {\n this.retentionWatermark.set(BigInt(message));\n } catch (err) {\n this.log.error(err, `Bad retention watermark message: ${message}`);\n }\n } else if (this.appPubSubChannels.has(channel)) {\n // App-level message (auth revocations, etc.). The handler owns decoding;\n // we just guard against it throwing.\n Promise.resolve(this.config.appPubSub?.onMessage(channel, message)).catch((err) =>\n this.log.error(err, `Error in appPubSub handler for channel ${channel}`),\n );\n }\n });\n this.redisSub.on(\"error\", (err: Error) => {\n this.log.error(err, `Subscriber error for channels: ${instanceChannel}, ${schemaChannel}`)\n });\n }\n\n /**\n * React to a `schema:reload` broadcast: invalidate the in-memory column\n * type cache for the named tables (or all if omitted) and re-bootstrap\n * from Postgres so subsequent batched writes see the fresh types. Skips\n * messages published by this instance (we already have the new types).\n */\n private async handleSchemaReload(): Promise<void> {\n this.log.info(`Schema reload request received in instance ${this.instanceId} — reloading schema...`);\n try {\n await sleep(1000 + (Math.random() * 5000)); // jitter to avoid thundering herd if many instances\n await bootstrapDbTypes(this.db, this.cache, true);\n this.log.info(`Schema reload applied in instance ${this.instanceId}`);\n } catch (err) {\n this.log.error(err, \"Failed to reload schema\");\n return;\n }\n }\n\n /**\n * Broadcast a schema-reload signal to every server instance (including\n * this one — the handler skips self-originated messages, so calling\n * after a local migration is safe). Pass `tables` to scope the reload;\n * omit to flush everyone's cache.\n */\n async publishSchemaReload(tables?: string[]): Promise<void> {\n const payload = JSON.stringify({\n type: \"schema:reload\",\n tables,\n originInstance: this.instanceId,\n });\n await redisRetryPolicy.execute(() =>\n this.cache.redis.publish(SCHEMA_RELOAD_CHANNEL, payload));\n }\n\n /**\n * Publish an app-level message (see {@link SupaliveServerConfig.appPubSub}) to\n * a Redis channel. Rides the shared command connection and is delivered to\n * every instance subscribed to `channel`, INCLUDING this one — so a handler\n * should tolerate/skip self-originated messages if that matters. The message\n * is an opaque string; the app owns encoding. Uses the standard Redis retry\n * policy; callers typically fire-and-forget.\n */\n async publishAppMessage(channel: string, message: string): Promise<void> {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, message));\n }\n\n private async handleRemoteInstanceMessage(message: string): Promise<void> {\n let parsed: RemoteSubscriptionMessage;\n try {\n parsed = RemoteSubscriptionMessageSchema.parse(JSON.parse(message));\n } catch (err) {\n this.log.error(err, \"Failed to parse remote instance message\");\n return;\n }\n\n if (parsed.originInstance === this.instanceId) return;\n\n if (parsed.type === \"update\") {\n for (const [, session] of this.sessions) {\n if (!session.subIds.has(parsed.subId)) continue;\n\n const subEntry = this.serverSubscriptions.get(parsed.subId);\n if (!subEntry) continue;\n\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: parsed.subId,\n data: parsed.data,\n dataHash: parsed.dataHash,\n ts: parsed.ts,\n });\n }\n return;\n }\n\n // type === \"recompute-race\" — writer didn't hold the sub locally and\n // broadcast a race hint to every notifyInstance. The first to acquire\n // the lock recomputes; the rest no-op.\n await this.handleRemoteRecomputeRace(parsed);\n }\n\n private async handleRemoteRecomputeRace(req: RemoteRecomputeRace): Promise<void> {\n if (!this.hasLocalSessionFor(req.subId)) {\n // We're in notifyInstances per sub-manager but the sub has since\n // been dropped here. Another holder may still pick it up — no warn.\n return;\n }\n\n const won = await this.cache.tryAcquireRecomputeLock(\n this.instanceId, req.subId, req.commitTs, RECOMPUTE_LOCK_TTL_SECONDS,\n );\n if (!won) {\n this.log.debug(\n `recompute-race: lost lock for ${req.subId}@${req.commitTs}, waiting for winner's update`,\n );\n return;\n }\n\n await this.reexecuteAffectedQuery({\n subId: req.subId,\n queryName: req.queryName,\n args: req.args,\n cacheKey: req.cacheKey,\n notifyInstances: req.notifyInstances,\n });\n }\n\n private async publishSubscriptionUpdate(\n subId: string,\n data: unknown,\n dataHash: string,\n notifyInstances: string[],\n ts?: string,\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"update\",\n subId,\n data,\n dataHash,\n originInstance: this.instanceId,\n ts,\n });\n\n const publishPromises = notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n this.log.error(err, `Failed to publish to ${channel} after many retries`);\n }\n });\n\n await Promise.all(publishPromises);\n }\n}\n\n// ─── HTTP helpers (job endpoint) ─────────────────────────────────────────────\n\n/** Parse a wire `minTs` (decimal bigint string) to a bigint, or undefined when\n * absent/malformed — a bad value simply falls back to primary-safe routing. */\nfunction parseMinTs(minTs: string | undefined): bigint | undefined {\n if (minTs === undefined) return undefined;\n try {\n return BigInt(minTs);\n } catch {\n return undefined;\n }\n}\n\n/** Normalize a job path to a leading-slash, no-trailing-slash prefix. */\nfunction normalizeJobPath(p: string): string {\n let s = p.startsWith(\"/\") ? p : `/${p}`;\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const payload = supaliveStringify(body);\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(payload);\n}\n\n\n","/**\n * FNV-1a 32-bit hash.\n *\n * Used for routing subscription operations to logical workers inside the\n * sub-manager process. Same input always returns the same 32-bit unsigned\n * integer in both JavaScript and Go, so a future Go port of the sub-manager\n * can use the identical routing without rebalancing keys.\n *\n * Reference: http://www.isthe.com/chongo/tech/comp/fnv/\n */\nexport function fnv1a32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n // h = h * 16777619, expressed as shifts to stay in 32-bit space\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n return h >>> 0;\n}\n","import { Server as WebSocketServer } from \"rpc-websockets\";\nimport { Worker } from \"node:worker_threads\";\nimport { fileURLToPath } from \"node:url\";\nimport { SubscriptionManagerConfig } from \"../config\";\nimport { fnv1a32 } from \"./hash\";\nimport { subscriptionManagerDataPack, type SubManagerLink } from \"./sub-manager-client\";\nimport { SubscriptionWorker } from \"./sub-worker\";\nimport { dispatchWorkerMessage } from \"./sub-worker-dispatch\";\nimport type { Database } from \"../db/db\";\nimport type { CacheLayer } from \"../db/cache\";\nimport { createDatabase, initCacheLayer } from \"../db/init_db\";\n\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport {\n AffectedSubscription,\n InvalidateWritesetResult,\n RegisterSubscriptionBatchResult,\n RegisterSubscriptionParams,\n RegisterSubscriptionResult,\n UnregisterSubscriptionParams,\n UnregisterSubscriptionResult,\n UnregisterSubscriptionsParams,\n UnregisterSubscriptionsResult,\n UpdateSubscriptionReadSetResult,\n} from \"./sub-manager-types\";\nimport { groupByToMap } from \"../helper\";\nimport {\n MsgType,\n type WorkerInitPayload,\n type WorkerRequest,\n type WorkerResponse,\n} from \"./sub-manager-worker-protocol\";\n\n// Re-export SubscriptionWorker so existing test imports\n// (`import { SubscriptionWorker } from \"./subscription-manager\"`) keep working.\nexport { SubscriptionWorker } from \"./sub-worker\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR\" },\n { level: logLevelOf(\"SUBS_MANAGER_SERVER_LOG_LEVEL\") },\n);\n\n// Resolve the entry script next to this compiled file. `import.meta.url` works\n// for both Bun (running TS source) and Node (running emitted JS) — we just\n// swap the extension based on the current module's path.\nfunction resolveWorkerEntry(): { url: URL; isTs: boolean } {\n const here = new URL(import.meta.url);\n const isTs = here.pathname.endsWith(\".ts\");\n const ext = isTs ? \".ts\" : \".js\";\n return {\n url: new URL(`./sub-manager-worker-entry${ext}`, import.meta.url),\n isTs,\n };\n}\n\n// When the entry is a `.ts` file and we're running on Node (not Bun), the\n// worker thread inherits no module loader — Node can strip types but its bare\n// ESM resolver requires explicit file extensions, which our extensionless\n// internal imports don't satisfy. Register tsx as the worker loader so it\n// resolves the same way the parent module does.\nfunction workerExecArgv(isTs: boolean): string[] | undefined {\n if (!isTs) return undefined;\n if (typeof (globalThis as any).Bun !== \"undefined\") return undefined;\n return [\"--import\", \"tsx\"];\n}\n\n// ─── Worker handles ──────────────────────────────────────────────────────────\n\n/**\n * Uniform interface the dispatcher uses to drive a logical worker, regardless\n * of where it runs. {@link ThreadWorkerHandle} proxies to a `worker_threads`\n * Worker (default, parallel across cores); {@link InlineWorkerHandle} calls a\n * {@link SubscriptionWorker} directly on the main event loop (embedded mode —\n * no thread hop, no serialization).\n */\nexport interface WorkerHandle {\n readonly id: number;\n ready(): Promise<void>;\n send<T>(type: MsgType, payload: unknown): Promise<T>;\n shutdown(): Promise<void>;\n}\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (err: Error) => void;\n}\n\nclass ThreadWorkerHandle implements WorkerHandle {\n readonly id: number;\n private worker: Worker;\n private pending = new Map<number, PendingRequest>();\n private nextReqId = 1;\n private readyPromise: Promise<void>;\n\n constructor(id: number, init: WorkerInitPayload) {\n this.id = id;\n const entry = resolveWorkerEntry();\n this.worker = new Worker(fileURLToPath(entry.url), {\n execArgv: workerExecArgv(entry.isTs),\n });\n\n this.worker.on(\"message\", (res: WorkerResponse) => {\n const pending = this.pending.get(res.reqId);\n if (!pending) return;\n\n this.pending.delete(res.reqId);\n if (res.ok) {\n pending.resolve(res.result);\n } else {\n const err = new Error(res.error.message);\n if (res.error.stack) err.stack = res.error.stack;\n pending.reject(err);\n }\n });\n\n this.worker.on(\"error\", (err: unknown) => {\n logger.error(err, `[worker ${this.id}] worker thread errored`);\n const e = err instanceof Error ? err : new Error(String(err));\n for (const [, p] of this.pending) p.reject(e);\n this.pending.clear();\n });\n\n this.readyPromise = this.send<null>(MsgType.Init, init).then(() => { });\n }\n\n ready(): Promise<void> {\n return this.readyPromise;\n }\n\n send<T>(type: MsgType, payload: unknown): Promise<T> {\n const reqId = this.nextReqId++;\n return new Promise<T>((resolve, reject) => {\n this.pending.set(reqId, {\n resolve: resolve as (v: unknown) => void,\n reject,\n });\n const req: WorkerRequest = { type, reqId, payload };\n this.worker.postMessage(req);\n });\n }\n\n async shutdown(): Promise<void> {\n try {\n await this.send<null>(MsgType.Shutdown, null);\n } catch (_) { /* ignore */ }\n await this.worker.terminate();\n }\n}\n\n/**\n * In-process worker handle: holds a live {@link SubscriptionWorker} and drives\n * it via {@link dispatchWorkerMessage} with `trusted: true`. Every `send` is a\n * direct `await worker.method(payload)` on the main event loop — no\n * `postMessage`, no structured-clone. Used when the manager is embedded in the\n * app server. When `ownsDb` is false the db is borrowed from the host server\n * and is NOT closed on shutdown.\n */\nclass InlineWorkerHandle implements WorkerHandle {\n readonly id: number;\n private worker: SubscriptionWorker;\n private db: Database;\n private ownsDb: boolean;\n\n constructor(id: number, worker: SubscriptionWorker, db: Database, ownsDb: boolean) {\n this.id = id;\n this.worker = worker;\n this.db = db;\n this.ownsDb = ownsDb;\n }\n\n ready(): Promise<void> {\n return Promise.resolve();\n }\n\n send<T>(type: MsgType, payload: unknown): Promise<T> {\n return dispatchWorkerMessage(this.worker, this.db, type, payload, { trusted: true }) as Promise<T>;\n }\n\n async shutdown(): Promise<void> {\n if (!this.ownsDb) return;\n try { await this.db.close(); } catch (_) { /* ignore */ }\n }\n}\n\n// ─── Dispatcher ──────────────────────────────────────────────────────────────\n\n/**\n * One process per deployment. Routes each subscription operation to one of N\n * worker threads by hashing subId. Worker threads run in parallel on separate\n * CPU cores; this dispatcher does no Zod parsing — it reads `subId` directly\n * from the decoded payload (the internal channel is trusted) and forwards the\n * raw payload. The worker validates and parses with Zod before executing.\n */\nexport class SubscriptionManager {\n private wss?: WebSocketServer;\n private port?: number;\n private listen: boolean;\n private workers: WorkerHandle[];\n private pruneInterval: ReturnType<typeof setInterval> | null = null;\n private static readonly PRUNE_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000;\n\n constructor(config: SubscriptionManagerConfig) {\n this.listen = config.listen !== false;\n if (this.listen) {\n if (config.port == null) {\n throw new Error(\"SubscriptionManager: `port` is required unless `listen: false`\");\n }\n this.port = config.port;\n this.wss = new WebSocketServer({ port: this.port }, subscriptionManagerDataPack);\n this.wss.dataPack = subscriptionManagerDataPack;\n }\n\n const inline = config.inline === true;\n let numWorkers = resolveWorkerCount(config.workers);\n if (inline && numWorkers !== 1) {\n logger.warn(`inline mode runs on the main event loop; forcing workers=1 (was ${numWorkers})`);\n numWorkers = 1;\n }\n\n if (inline) {\n this.workers = [this.buildInlineWorker(config)];\n } else {\n if (!config.database || !config.upstash) {\n throw new Error(\n \"SubscriptionManager: `database` and `upstash` are required in threaded (non-inline) mode\",\n );\n }\n const maxConnectionsPerWorker = resolveMaxConnectionsPerWorker(config, numWorkers);\n logger.info(`Spawning ${numWorkers} subscription worker${numWorkers === 1 ? \"\" : \"s\"}`);\n this.workers = Array.from({ length: numWorkers }, (_, i) =>\n new ThreadWorkerHandle(i + 1, {\n workerId: i + 1,\n database: config.database!,\n upstash: config.upstash!,\n cacheTTLSeconds: config.cacheTTLSeconds,\n maxConnectionsPerWorker,\n }),\n );\n }\n\n if (this.listen) this.registerMethods();\n }\n\n /**\n * Build the single in-process worker for {@link SubscriptionManagerConfig.inline}\n * mode. Reuses the host server's {@link SubscriptionManagerConfig.db} +\n * {@link SubscriptionManagerConfig.cache} when provided (borrowed — not closed\n * on stop); otherwise opens its own pool/cache from `database`/`upstash`.\n */\n private buildInlineWorker(config: SubscriptionManagerConfig): InlineWorkerHandle {\n let db: Database;\n let cache: CacheLayer;\n let ownsDb: boolean;\n\n if (config.db && config.cache) {\n db = config.db;\n cache = config.cache;\n ownsDb = false;\n logger.info(\"Inline subscription worker: reusing host db + cache\");\n } else {\n if (!config.database || !config.upstash) {\n throw new Error(\n \"SubscriptionManager inline mode needs either { db, cache } to reuse, \" +\n \"or { database, upstash } to build its own\",\n );\n }\n const { cacheLayer } = initCacheLayer({\n upstash: config.upstash,\n cacheTtlSeconds: config.cacheTTLSeconds,\n commandMaxRetries: 5,\n createSubClient: false,\n });\n cache = cacheLayer;\n db = createDatabase(config.database, 1);\n ownsDb = true;\n logger.info(\"Inline subscription worker: built own db + cache\");\n }\n\n const worker = new SubscriptionWorker(1, cache, db);\n return new InlineWorkerHandle(1, worker, db, ownsDb);\n }\n\n async start(): Promise<void> {\n await Promise.all(this.workers.map((w) => w.ready()));\n logger.info(this.listen ? `Listening on port ${this.port}` : \"Started (embedded, in-process)\");\n // Seed the retention watermark before serving so the query-cache freshness\n // check has an accurate watermark immediately (it survives restarts in the\n // metadata table; only pruning moves it afterward). Worker 0 seeds/repairs\n // it from the DB and returns it; fan it out to every worker's in-memory copy\n // (and, via Redis, to app-server instances).\n try {\n const minRetainedTs = await this.workers[0]!.send<bigint>(MsgType.RefreshRetentionWatermark, null);\n await this.broadcastRetentionWatermark(minRetainedTs);\n } catch (err) {\n logger.error(err, \"initial retention watermark seed failed\");\n }\n this.pruneInterval = setInterval(\n () => { this.pruneCommitLogs().catch((err) => logger.error(err, \"commit_logs prune failed\")); },\n SubscriptionManager.PRUNE_INTERVAL_MS,\n );\n if (this.pruneInterval && typeof this.pruneInterval.unref === \"function\") {\n this.pruneInterval.unref();\n }\n }\n\n async stop(): Promise<void> {\n if (this.pruneInterval) {\n clearInterval(this.pruneInterval);\n this.pruneInterval = null;\n }\n this.wss?.close();\n await Promise.all(this.workers.map((w) => w.shutdown()));\n logger.info(\"Stopped\");\n }\n\n /**\n * A direct, in-process {@link SubManagerLink} to this manager, for embedding\n * it in the app server (pass as `SupaliveServerConfig.subManager`). Calls the\n * dispatch handlers directly — no socket, no serialization. The recovery\n * callbacks are no-ops: an in-process link never disconnects, so the app\n * server's reconnect/buffer machinery is inert.\n */\n localLink(): SubManagerLink {\n return {\n registerSubscription: (p) => this.registerSubscription(p),\n registerSubscriptionBatch: (p) => this.registerSubscriptionBatch(p),\n updateSubscriptionReadSet: (p) => this.updateSubscriptionReadSet(p),\n unregisterSubscription: (p) => this.unregisterSubscription(p),\n unregisterSubscriptions: (p) => this.unregisterSubscriptions(p),\n invalidateWriteset: (p) => this.invalidateWriteset(p),\n setRecoveryDriver: () => { /* in-process: never disconnects */ },\n setAffectedHandler: () => { /* in-process: never disconnects */ },\n };\n }\n\n private async pruneCommitLogs(): Promise<void> {\n const minPerWorker = await Promise.all(\n this.workers.map((w) => w.send<bigint | null>(MsgType.MinSubscriptionTs, null)),\n );\n let minRetainedTs: bigint | null = null;\n for (const m of minPerWorker) {\n if (m === null) continue;\n if (minRetainedTs === null || m < minRetainedTs) minRetainedTs = m;\n }\n\n if (minRetainedTs === null) {\n const latest = await this.workers[0]!.send<bigint>(MsgType.LatestSnapshotTs, null);\n if (latest <= 0n) return;\n minRetainedTs = latest;\n }\n\n // Worker 0 advertises the new watermark (persist + Redis publish) then\n // deletes. Fan the same watermark out to the other workers' in-memory copies.\n const deleted = await this.workers[0]!.send<bigint>(MsgType.Prune, minRetainedTs);\n await this.broadcastRetentionWatermark(minRetainedTs);\n if (deleted > 0n) {\n logger.info(`pruned ${deleted} commit_logs older than ts=${minRetainedTs}`);\n }\n }\n\n /** Fan a watermark value out to every worker's in-memory copy. Worker 0 (the\n * pruner/seeder) already has it, but re-sending is harmless — {@link\n * SubscriptionWorker.setRetentionWatermark} is monotonic. */\n private async broadcastRetentionWatermark(ts: bigint): Promise<void> {\n await Promise.all(\n this.workers.map((w) =>\n w.send(MsgType.SetRetentionWatermark, ts).catch((err) =>\n logger.error(err, \"retention watermark broadcast to worker failed\"),\n ),\n ),\n );\n }\n\n workerCount(): number {\n return this.workers.length;\n }\n\n workerIndexFor(subId: string): number {\n return fnv1a32(subId) % this.workers.length;\n }\n\n private workerFor(subId: string): WorkerHandle {\n return this.workers[this.workerIndexFor(subId)]!;\n }\n\n private registerMethods(): void {\n const wss = this.wss;\n if (!wss) return;\n wss.register(\"registerSubscription\", this.registerSubscription.bind(this));\n wss.register(\"registerSubscriptionBatch\", this.registerSubscriptionBatch.bind(this));\n wss.register(\"updateSubscriptionReadSet\", this.updateSubscriptionReadSet.bind(this));\n wss.register(\"invalidateWriteset\", this.invalidateWriteset.bind(this));\n wss.register(\"unregisterSubscription\", this.unregisterSubscription.bind(this));\n wss.register(\"unregisterSubscriptions\", this.unregisterSubscriptions.bind(this));\n }\n\n private readSubId(params: unknown): string {\n return (params as { subId: string }).subId;\n }\n\n private async registerSubscription(params: unknown): Promise<RegisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<RegisterSubscriptionResult>(MsgType.Register, params);\n }\n\n private async registerSubscriptionBatch(params: unknown): Promise<RegisterSubscriptionBatchResult> {\n // Group entries by destination worker, dispatch one batch per worker, then\n // restore the original order.\n const arr = params as RegisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<RegisterSubscriptionResult[]>(\n MsgType.RegisterBatch,\n items.map((t) => t.p),\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: RegisterSubscriptionBatchResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async updateSubscriptionReadSet(params: unknown): Promise<UpdateSubscriptionReadSetResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UpdateSubscriptionReadSetResult>(MsgType.UpdateReadSet, params);\n }\n\n private async unregisterSubscription(params: unknown): Promise<UnregisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UnregisterSubscriptionResult>(MsgType.Unregister, params);\n }\n\n private async unregisterSubscriptions(params: unknown): Promise<UnregisterSubscriptionsResult> {\n const arr = params as UnregisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<UnregisterSubscriptionResult[]>(\n MsgType.Unregisters,\n items.map((t) => t.p) satisfies UnregisterSubscriptionsParams,\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: UnregisterSubscriptionsResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async invalidateWriteset(params: unknown): Promise<InvalidateWritesetResult> {\n // Fan the raw payload out to every worker. Each parses its own copy of the\n // writeSet (cheap relative to the scan) and returns its affected subs.\n const perWorker = await Promise.all(\n this.workers.map((w) => w.send<AffectedSubscription[]>(MsgType.Invalidate, params)),\n );\n return { affected: perWorker.flat() };\n }\n}\n\nfunction resolveWorkerCount(configured?: number): number {\n if (typeof configured === \"number\" && configured > 0) return Math.floor(configured);\n\n const envVal = process.env.SUPALIVE_SUB_MANAGER_WORKERS;\n if (envVal) {\n const n = parseInt(envVal, 10);\n if (Number.isFinite(n) && n > 0) return n;\n }\n return 1;\n}\n\nfunction resolveMaxConnectionsPerWorker(config: SubscriptionManagerConfig, workers: number): number {\n if (config.maxConnectionsPerWorker && config.maxConnectionsPerWorker > 0) {\n return Math.floor(config.maxConnectionsPerWorker);\n }\n const total = config.database?.maxConnections ?? (config.database?.type === \"mysql\" ? 20 : 10);\n return Math.max(1, Math.floor(total / workers));\n}\n"],"mappings":"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"}