@supalive/core 1.20.1 → 1.21.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 (68) hide show
  1. package/dist/ephemeral-BVeIVuYw.js +276 -0
  2. package/dist/ephemeral-BVeIVuYw.js.map +1 -0
  3. package/dist/ephemeral-CkidSG9y.js +295 -0
  4. package/dist/ephemeral-CkidSG9y.js.map +1 -0
  5. package/dist/index-CzW0TkpK.d.ts +2729 -0
  6. package/dist/index-CzW0TkpK.d.ts.map +1 -0
  7. package/dist/index-DB7_pv9P.d.ts +2746 -0
  8. package/dist/index-DB7_pv9P.d.ts.map +1 -0
  9. package/dist/logger-BJNCJkFh.js +463 -0
  10. package/dist/logger-BJNCJkFh.js.map +1 -0
  11. package/dist/logger-By9Pw-X2.js +454 -0
  12. package/dist/logger-By9Pw-X2.js.map +1 -0
  13. package/dist/mysql-Bvc_iWUy.d.ts +114 -0
  14. package/dist/mysql-Bvc_iWUy.d.ts.map +1 -0
  15. package/dist/mysql-CcIuIAt0.js +622 -0
  16. package/dist/mysql-CcIuIAt0.js.map +1 -0
  17. package/dist/mysql-DTK2f5oU.d.ts +114 -0
  18. package/dist/mysql-DTK2f5oU.d.ts.map +1 -0
  19. package/dist/one-shot-query-BNYLU19W.js +573 -0
  20. package/dist/one-shot-query-BNYLU19W.js.map +1 -0
  21. package/dist/one-shot-query-BSK7adwG.js +573 -0
  22. package/dist/one-shot-query-BSK7adwG.js.map +1 -0
  23. package/dist/one-shot-query-DAAHGTcI.js +573 -0
  24. package/dist/one-shot-query-DAAHGTcI.js.map +1 -0
  25. package/dist/overlap-checker-DseE1Lvh.js +300 -0
  26. package/dist/overlap-checker-DseE1Lvh.js.map +1 -0
  27. package/dist/postgres-Bp7SMb9D.d.ts +118 -0
  28. package/dist/postgres-Bp7SMb9D.d.ts.map +1 -0
  29. package/dist/postgres-C5CbPBcp.d.ts +118 -0
  30. package/dist/postgres-C5CbPBcp.d.ts.map +1 -0
  31. package/dist/postgres-CLhoKn_R.js +867 -0
  32. package/dist/postgres-CLhoKn_R.js.map +1 -0
  33. package/dist/realtime_db-CTGOrFuV.js +463 -0
  34. package/dist/realtime_db-CTGOrFuV.js.map +1 -0
  35. package/dist/src/client/index.d.ts +2 -2
  36. package/dist/src/client/index.js +6 -5
  37. package/dist/src/client/index.js.map +1 -1
  38. package/dist/src/exports/mysql.d.ts +1 -1
  39. package/dist/src/exports/mysql.js +1 -1
  40. package/dist/src/exports/postgres.d.ts +1 -1
  41. package/dist/src/exports/postgres.js +1 -1
  42. package/dist/src/exports/procedure.d.ts +2 -2
  43. package/dist/src/exports/procedure.d.ts.map +1 -1
  44. package/dist/src/exports/procedure.js +2 -1
  45. package/dist/src/exports/procedure.js.map +1 -1
  46. package/dist/src/exports/schema-sql.d.ts +1 -1
  47. package/dist/src/exports/server.d.ts +56 -5
  48. package/dist/src/exports/server.d.ts.map +1 -1
  49. package/dist/src/exports/server.js +168 -10
  50. package/dist/src/exports/server.js.map +1 -1
  51. package/dist/src/exports/sub-manager-worker-entry.js +1 -1
  52. package/dist/src/exports/types.d.ts +2 -2
  53. package/dist/src/exports/types.js +2 -2
  54. package/dist/sub-worker-dispatch-C20KTMfg.js +925 -0
  55. package/dist/sub-worker-dispatch-C20KTMfg.js.map +1 -0
  56. package/dist/sub-worker-dispatch-CQCuwZPm.js +925 -0
  57. package/dist/sub-worker-dispatch-CQCuwZPm.js.map +1 -0
  58. package/dist/sub-worker-dispatch-Dwrp3c40.js +925 -0
  59. package/dist/sub-worker-dispatch-Dwrp3c40.js.map +1 -0
  60. package/dist/types_client_rpc-CqUVHTwT.js +163 -0
  61. package/dist/types_client_rpc-CqUVHTwT.js.map +1 -0
  62. package/dist/types_db-DPMeNETt.js +173 -0
  63. package/dist/types_db-DPMeNETt.js.map +1 -0
  64. package/dist/types_server-hiwqDm4G.d.ts +737 -0
  65. package/dist/types_server-hiwqDm4G.d.ts.map +1 -0
  66. package/dist/types_server-xHmZ6baD.d.ts +737 -0
  67. package/dist/types_server-xHmZ6baD.d.ts.map +1 -0
  68. package/package.json +1 -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","parseMinTs","otelContext","baseLogger","uuidv4","otelContext","logger","baseLogger","WebSocketServer"],"sources":["../../../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":["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 /** App build number this server was deployed with (see\n * `SupaliveServerConfig.buildNumber`). When set, it is added as a top-level\n * `build` field to every RPC response envelope (success and error) — the HTTP\n * counterpart of the `build` field stamped on every WebSocket message — so a\n * client detects a newer server consistently across transports. */\n buildNumber?: number;\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 /** App build number added as a top-level `build` field to every response\n * envelope (see {@link HttpRpcDeps.buildNumber}). Undefined = omit. */\n private readonly buildNumber?: number;\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 this.buildNumber = deps.buildNumber;\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(\n {\n db: this.deps.db,\n storage: this.deps.storage,\n scheduler: this.deps.scheduler,\n serverCtx,\n log: jobLog,\n span,\n },\n input,\n ),\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: Record<string, unknown>): void {\n body.build = this.buildNumber;\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 { 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, IORedisClient } 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: IORedisClient;\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 ?? (() => null),\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 // Same build number stamped on WS messages — echoed on RPC response bodies.\n buildNumber: config.buildNumber,\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 /**\n * The server's {@link Observability} — hand it to the internal caller's\n * `init({ obs })` so its standalone calls log as per-procedure children of\n * this server's request logger instead of a no-op.\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 /**\n * The current in-memory retention watermark (oldest retained commit-log ts) —\n * the value the one-shot query cache freshness scan uses as its retention\n * guard. Hand it to the internal caller's `init({ getMinRetainedTs })` so its\n * cache-first reads use the SAME guard as the WS/HTTP query paths.\n */\n getMinRetainedTs(): bigint {\n return this.retentionWatermark.get();\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. When `guardHealthByHeader` is\n // set, requests carrying the guard header get a 404 instead, so the probe\n // stays reachable only from the private network (e.g. off the public edge).\n if (req.method === \"GET\" && (path === \"/_health\")) {\n const guard = this.config.guardHealthByHeader;\n if (guard) {\n const raw = req.headers[guard.name.toLowerCase()];\n const headerValue = Array.isArray(raw) ? raw[0] : raw;\n if (\n headerValue !== undefined &&\n (guard.value === undefined || headerValue === guard.value)\n ) {\n return sendJson(res, 404, { error: \"not found\" });\n }\n }\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 this.subManagerClient.updateSubscriptionReadSet({\n cacheKey,\n subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n });\n\n\n // fire & forget\n this.cache\n .setQueryCacheFor(cacheKey, data, liveResult.ts)\n .catch((err) => this.log.warn(err, \"writing query cache failed\"))\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\n message.build = this.config.buildNumber;\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 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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"}
1
+ {"version":3,"file":"server.js","names":["logger","baseLogger","types.RegisterSubscriptionResultSchema","types.RegisterSubscriptionBatchResultSchema","types.UpdateSubscriptionReadSetResultSchema","types.UnregisterSubscriptionResultSchema","types.UnregisterSubscriptionsResultSchema","types.InvalidateWritesetResultSchema","types.InvalidateEphemeralResultSchema","logger","baseLogger","Client","parseMinTs","otelContext","baseLogger","uuidv4","otelContext","logger","baseLogger","WebSocketServer"],"sources":["../../../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":["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 invalidateEphemeral(\n params: types.InvalidateEphemeralParams,\n ): Promise<types.InvalidateEphemeralResult>;\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 /**\n * Ephemeral keys written while state === 'busy', mapped to the latest version\n * seen for each. A MAP, not a queue like `bufferedWriteSets`: an ephemeral\n * recompute re-reads the current value from Redis, so N writes to one key\n * during an outage collapse into a single post-recovery invalidation with no\n * loss of information. That makes the buffer self-bounding at the number of\n * DISTINCT keys — which is what lets a 60ms position stream survive a long\n * reconnect without a size limit or a drop path.\n */\n private bufferedEphemeralKeys = new Map<string, string>();\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 /**\n * Ephemeral-key counterpart of {@link invalidateWriteset}. Buffers the same\n * way during an outage, but into {@link bufferedEphemeralKeys} — see there for\n * why a map rather than a queue.\n *\n * Unlike the write-set buffer there is no drop path: ephemeral state has no\n * commit log for `register` to replay, so a dropped key would leave its\n * subscribers stale until something else happened to invalidate them.\n * Collapsing per key keeps the buffer small enough that dropping is never\n * needed.\n */\n async invalidateEphemeral(\n params: types.InvalidateEphemeralParams,\n ): Promise<types.InvalidateEphemeralResult> {\n if (this.state === \"busy\") {\n for (const key of params.keys) this.bufferedEphemeralKeys.set(key, params.version);\n return { affected: [] };\n }\n return this.call(\"invalidateEphemeral\", params, types.InvalidateEphemeralResultSchema);\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 await Promise.all([this.flushWriteSetBuffer(), this.flushEphemeralBuffer()]);\n }\n\n private async flushWriteSetBuffer(): 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 /**\n * Replay the outage's ephemeral writes as ONE invalidation. The version sent\n * is the highest buffered — every collapsed write is subsumed by the single\n * recompute this triggers, which reads whatever Redis holds now, so using the\n * newest version keeps the recompute lock keyed to the most recent write\n * rather than an already-superseded one.\n */\n private async flushEphemeralBuffer(): Promise<void> {\n if (this.bufferedEphemeralKeys.size === 0) return;\n const keys = [...this.bufferedEphemeralKeys.keys()];\n const version = [...this.bufferedEphemeralKeys.values()]\n .reduce((a, b) => (BigInt(b) > BigInt(a) ? b : a));\n this.bufferedEphemeralKeys.clear();\n\n try {\n const result = await this.call(\n \"invalidateEphemeral\",\n { keys, version },\n types.InvalidateEphemeralResultSchema,\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 ephemeral 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 /** @internal */\n ephemeralBufferSize(): number { return this.bufferedEphemeralKeys.size; }\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-${process.env.NODE_ENV ?? 'production'}-`;\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 /** App build number this server was deployed with (see\n * `SupaliveServerConfig.buildNumber`). When set, it is added as a top-level\n * `build` field to every RPC response envelope (success and error) — the HTTP\n * counterpart of the `build` field stamped on every WebSocket message — so a\n * client detects a newer server consistently across transports. */\n buildNumber?: number;\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 /** App build number added as a top-level `build` field to every response\n * envelope (see {@link HttpRpcDeps.buildNumber}). Undefined = omit. */\n private readonly buildNumber?: number;\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 this.buildNumber = deps.buildNumber;\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(\n {\n db: this.deps.db,\n storage: this.deps.storage,\n scheduler: this.deps.scheduler,\n serverCtx,\n log: jobLog,\n span,\n },\n input,\n ),\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: Record<string, unknown>): void {\n body.build = this.buildNumber;\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 { 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, IORedisClient } 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: IORedisClient;\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 // The ephemeral twin of the after-commit hook: a write from ANY source\n // (action, job, committed mutation, internal caller) invalidates through the\n // same central sub-manager, so it reaches subscribers on every node.\n this.db.setOnAfterEphemeralWrite((keys, version) => {\n this.handleEphemeralWrite(keys, version).catch((err) =>\n this.log.error(err, \"ephemeral 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 ?? (() => null),\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 // Same build number stamped on WS messages — echoed on RPC response bodies.\n buildNumber: config.buildNumber,\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 /**\n * The server's {@link Observability} — hand it to the internal caller's\n * `init({ obs })` so its standalone calls log as per-procedure children of\n * this server's request logger instead of a no-op.\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 /**\n * The current in-memory retention watermark (oldest retained commit-log ts) —\n * the value the one-shot query cache freshness scan uses as its retention\n * guard. Hand it to the internal caller's `init({ getMinRetainedTs })` so its\n * cache-first reads use the SAME guard as the WS/HTTP query paths.\n */\n getMinRetainedTs(): bigint {\n return this.retentionWatermark.get();\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. When `guardHealthByHeader` is\n // set, requests carrying the guard header get a 404 instead, so the probe\n // stays reachable only from the private network (e.g. off the public edge).\n if (req.method === \"GET\" && (path === \"/_health\")) {\n const guard = this.config.guardHealthByHeader;\n if (guard) {\n const raw = req.headers[guard.name.toLowerCase()];\n const headerValue = Array.isArray(raw) ? raw[0] : raw;\n if (\n headerValue !== undefined &&\n (guard.value === undefined || headerValue === guard.value)\n ) {\n return sendJson(res, 404, { error: \"not found\" });\n }\n }\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 this.subManagerClient.updateSubscriptionReadSet({\n cacheKey,\n subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n ephemeralKeys: liveResult.ephemeralKeys,\n instanceName: this.instanceId,\n });\n\n\n // fire & forget\n this.cache\n .setQueryCacheFor(cacheKey, data, liveResult.ts)\n .catch((err) => this.log.warn(err, \"writing query cache failed\"))\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.toString())),\n );\n }\n } catch (err) {\n this.log.error(err, \"Failed to notify sub-managers\");\n }\n }\n\n /**\n * Invalidate subscriptions that read any of `keys`. The ephemeral twin of\n * {@link handleMutationCommit} (db/ephemeral.ts): same central sub-manager,\n * same dispatch, same recompute — only the matching differs (exact key lookup\n * instead of write-set/read-set overlap).\n *\n * `version` is the store's monotonic write sequence, standing in for a\n * mutation's `commitTs` as the cross-instance recompute-lock discriminator.\n */\n private async handleEphemeralWrite(keys: string[], version: string): Promise<void> {\n if (keys.length === 0) return;\n\n this.log.debug({ keys, version }, \"Ephemeral write committed\");\n\n try {\n const result = await this.subManagerClient.invalidateEphemeral({ keys, version });\n this.log.debug({ affected: result }, \"Ephemeral invalidation result\");\n\n if (result?.affected?.length > 0) {\n await Promise.allSettled(\n result.affected.map((affected) => this.dispatchAffected(affected, version)),\n );\n }\n } catch (err) {\n this.log.error(err, \"Failed to notify sub-managers of ephemeral write\");\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 * `commitTs` identifies the write being reacted to — a mutation's `commitTs`,\n * or an ephemeral write's sequence number(revision). Its only job is that every\n * instance racing over the SAME write derives the same lock key, so any\n * monotonic token works.\n */\n private async dispatchAffected(affected: AffectedSubscription, commitTs: string): Promise<void> {\n if (this.hasLocalSessionFor(affected.subId)) {\n // Local holder: recompute here directly. No lock needed — `commitTs` only\n // matters for the cross-instance race below.\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: string,\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,\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 /**\n * Recomputes currently running, keyed by subId. Holds the in-flight promise\n * plus the newest request that arrived while it was running — see\n * {@link reexecuteAffectedQuery}.\n */\n private recomputesInFlight = new Map<SubId, InFlightRecomputeRequest>();\n\n /**\n * Recompute a subscription and push the result, coalescing concurrent\n * requests for the same subId.\n *\n * A subscription is already shared by every session watching the same\n * (procedure, args, identity), so one write produces one recompute no matter\n * how many clients are attached. What that does NOT cover is RATE: two writes\n * arriving 60ms apart used to start two overlapping recomputes of the same\n * sub, which could finish out of order and leave clients on the older of the\n * two results — `handleSubscriptionUpdate` applies whatever lands last.\n *\n * High-frequency ephemeral state (a position stream) makes that the normal\n * case rather than a rare one, so requests are serialized per subId here:\n * while one runs, later requests collapse into a single follow-up that fires\n * once it finishes. Each recompute reads current state, so the follow-up\n * reflects every write that arrived during the previous run — collapsing\n * loses nothing but redundant work, and the last result published is always\n * the newest.\n */\n private async reexecuteAffectedQuery(\n affected: AffectedSubscription,\n trigger: QueryTrigger = \"recompute\",\n ): Promise<void> {\n const inFlight = this.recomputesInFlight.get(affected.subId);\n if (inFlight) {\n // Overwrite rather than queue: a pending run recomputes from scratch, so\n // only the most recent request's metadata (notifyInstances especially)\n // matters.\n inFlight.pending = { affected, trigger };\n return inFlight.running;\n }\n\n const entry: InFlightRecomputeRequest = { running: Promise.resolve() };\n this.recomputesInFlight.set(affected.subId, entry);\n\n entry.running = (async () => {\n try {\n let next: { affected: AffectedSubscription; trigger: QueryTrigger } | undefined =\n { affected, trigger };\n while (next) {\n await this.runRecompute(next.affected, next.trigger);\n next = entry.pending;\n entry.pending = undefined;\n }\n } finally {\n this.recomputesInFlight.delete(affected.subId);\n }\n })();\n\n return entry.running;\n }\n\n private async runRecompute(\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 ephemeralKeys: liveResult.ephemeralKeys,\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\n message.build = this.config.buildNumber;\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 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\nexport interface InFlightRecomputeRequest {\n running: Promise<void>;\n pending?: { affected: AffectedSubscription; trigger: QueryTrigger };\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 InvalidateEphemeralResult,\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 invalidateEphemeral: (p) => this.invalidateEphemeral(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(\"invalidateEphemeral\", this.invalidateEphemeral.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 /**\n * Ephemeral-key invalidation. Fans out to every worker exactly like\n * {@link invalidateWriteset}: subscriptions are routed by hash(subId), which\n * says nothing about which keys they read, so the holders of a given key can\n * sit in any slice.\n */\n private async invalidateEphemeral(params: unknown): Promise<InvalidateEphemeralResult> {\n const perWorker = await Promise.all(\n this.workers.map((w) => w.send<AffectedSubscription[]>(MsgType.InvalidateEphemeral, 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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"}