@supalive/core 1.18.0 → 1.20.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 (95) hide show
  1. package/dist/index-BuWa503L.d.ts +2445 -0
  2. package/dist/index-BuWa503L.d.ts.map +1 -0
  3. package/dist/index-CBIjmPUS.d.ts +2444 -0
  4. package/dist/index-CBIjmPUS.d.ts.map +1 -0
  5. package/dist/index-CKo6HvJc.d.ts +2455 -0
  6. package/dist/index-CKo6HvJc.d.ts.map +1 -0
  7. package/dist/index-DShKjpzF.d.ts +2453 -0
  8. package/dist/index-DShKjpzF.d.ts.map +1 -0
  9. package/dist/index-Dcxf_Gm-.d.ts +2454 -0
  10. package/dist/index-Dcxf_Gm-.d.ts.map +1 -0
  11. package/dist/index-DsBAE32T.d.ts +2459 -0
  12. package/dist/index-DsBAE32T.d.ts.map +1 -0
  13. package/dist/logger-DrXccWFZ.js +346 -0
  14. package/dist/logger-DrXccWFZ.js.map +1 -0
  15. package/dist/mysql-B2u0Ye4x.d.ts +114 -0
  16. package/dist/mysql-B2u0Ye4x.d.ts.map +1 -0
  17. package/dist/mysql-BHW-tAqt.d.ts +114 -0
  18. package/dist/mysql-BHW-tAqt.d.ts.map +1 -0
  19. package/dist/mysql-C79_qNQO.d.ts +114 -0
  20. package/dist/mysql-C79_qNQO.d.ts.map +1 -0
  21. package/dist/mysql-C_TTCZ8j.d.ts +114 -0
  22. package/dist/mysql-C_TTCZ8j.d.ts.map +1 -0
  23. package/dist/mysql-CyzJqrvO.d.ts +114 -0
  24. package/dist/mysql-CyzJqrvO.d.ts.map +1 -0
  25. package/dist/mysql-ygH2EMGn.js +622 -0
  26. package/dist/mysql-ygH2EMGn.js.map +1 -0
  27. package/dist/mysql-z9Jefhd6.d.ts +114 -0
  28. package/dist/mysql-z9Jefhd6.d.ts.map +1 -0
  29. package/dist/object-storage-DTVmZq3l.d.ts +110 -0
  30. package/dist/object-storage-DTVmZq3l.d.ts.map +1 -0
  31. package/dist/one-shot-query-9mCboWaB.js +565 -0
  32. package/dist/one-shot-query-9mCboWaB.js.map +1 -0
  33. package/dist/one-shot-query-B9SjfcCD.js +550 -0
  34. package/dist/one-shot-query-B9SjfcCD.js.map +1 -0
  35. package/dist/one-shot-query-BBiv9LGP.js +565 -0
  36. package/dist/one-shot-query-BBiv9LGP.js.map +1 -0
  37. package/dist/one-shot-query-Bxz_N-NE.js +563 -0
  38. package/dist/one-shot-query-Bxz_N-NE.js.map +1 -0
  39. package/dist/one-shot-query-DE9oQU40.js +550 -0
  40. package/dist/one-shot-query-DE9oQU40.js.map +1 -0
  41. package/dist/one-shot-query-adoWqGLs.js +565 -0
  42. package/dist/one-shot-query-adoWqGLs.js.map +1 -0
  43. package/dist/overlap-checker-CCgq_Tpa.js +284 -0
  44. package/dist/overlap-checker-CCgq_Tpa.js.map +1 -0
  45. package/dist/postgres-B2FLN1Kw.d.ts +118 -0
  46. package/dist/postgres-B2FLN1Kw.d.ts.map +1 -0
  47. package/dist/postgres-C7xHWbij.d.ts +118 -0
  48. package/dist/postgres-C7xHWbij.d.ts.map +1 -0
  49. package/dist/postgres-DBMAv_dq.d.ts +118 -0
  50. package/dist/postgres-DBMAv_dq.d.ts.map +1 -0
  51. package/dist/postgres-DBuP9ON_.d.ts +118 -0
  52. package/dist/postgres-DBuP9ON_.d.ts.map +1 -0
  53. package/dist/postgres-DwiUT_A6.js +867 -0
  54. package/dist/postgres-DwiUT_A6.js.map +1 -0
  55. package/dist/postgres-I4sgdBnG.d.ts +118 -0
  56. package/dist/postgres-I4sgdBnG.d.ts.map +1 -0
  57. package/dist/postgres-SM8muZqN.d.ts +118 -0
  58. package/dist/postgres-SM8muZqN.d.ts.map +1 -0
  59. package/dist/router-DP2ThAwh.js.map +1 -1
  60. package/dist/src/client/index.d.ts +1 -1
  61. package/dist/src/client/index.js +47 -50
  62. package/dist/src/client/index.js.map +1 -1
  63. package/dist/src/exports/mysql.d.ts +1 -1
  64. package/dist/src/exports/mysql.js +1 -1
  65. package/dist/src/exports/postgres.d.ts +1 -1
  66. package/dist/src/exports/postgres.js +1 -1
  67. package/dist/src/exports/procedure.d.ts +1 -1
  68. package/dist/src/exports/schema-sql.d.ts +1 -1
  69. package/dist/src/exports/server.d.ts +11 -4
  70. package/dist/src/exports/server.d.ts.map +1 -1
  71. package/dist/src/exports/server.js +31 -410
  72. package/dist/src/exports/server.js.map +1 -1
  73. package/dist/src/exports/storage.d.ts +4 -2
  74. package/dist/src/exports/storage.d.ts.map +1 -1
  75. package/dist/src/exports/storage.js +23 -1
  76. package/dist/src/exports/storage.js.map +1 -1
  77. package/dist/src/exports/sub-manager-worker-entry.js +1 -1
  78. package/dist/src/exports/types.d.ts +2 -2
  79. package/dist/sub-worker-dispatch-BLujn13f.js +792 -0
  80. package/dist/sub-worker-dispatch-BLujn13f.js.map +1 -0
  81. package/dist/types_server-BCst4TfZ.d.ts +703 -0
  82. package/dist/types_server-BCst4TfZ.d.ts.map +1 -0
  83. package/dist/types_server-BYwLkwbC.d.ts +691 -0
  84. package/dist/types_server-BYwLkwbC.d.ts.map +1 -0
  85. package/dist/types_server-Bvrohyc8.d.ts +691 -0
  86. package/dist/types_server-Bvrohyc8.d.ts.map +1 -0
  87. package/dist/types_server-C6oCm9oC.d.ts +691 -0
  88. package/dist/types_server-C6oCm9oC.d.ts.map +1 -0
  89. package/dist/types_server-CHJLfdxb.d.ts +691 -0
  90. package/dist/types_server-CHJLfdxb.d.ts.map +1 -0
  91. package/dist/types_server-DnO_fp-F.d.ts +691 -0
  92. package/dist/types_server-DnO_fp-F.d.ts.map +1 -0
  93. package/dist/types_server-gSgzZHuD.d.ts +703 -0
  94. package/dist/types_server-gSgzZHuD.d.ts.map +1 -0
  95. package/package.json +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/db/query_definition.ts","../../../src/client/sub_key.ts","../../../src/client/ws_client_manager.ts","../../../src/client/client.ts","../../../src/client/caller_client.ts"],"sourcesContent":["import type { DbReader } from \"./context\";\n\n// ─── Core type ────────────────────────────────────────────────────────────────\n\n/**\n * A named, typed query definition produced by defineQuery().\n * Carries the query function at runtime and the name/params/result types\n * at compile time — making it the single source of truth for both the\n * server-side registry and the client-side RPC.\n */\nexport interface QueryDefinition<\n TName extends string,\n TParams,\n TResult,\n> {\n readonly _type: \"QueryDefinition\";\n readonly name: TName;\n readonly fn: (ctx: DbReader, params: TParams) => Promise<TResult>;\n}\n\n// ─── Type helpers ─────────────────────────────────────────────────────────────\n\nexport type AnyQueryDef = QueryDefinition<string, any, any>;\n\n/** Extract the params type from a QueryDefinition. */\nexport type ParamsOf<D extends AnyQueryDef> =\n D extends QueryDefinition<any, infer P, any> ? P : never;\n\n/** Extract the result type from a QueryDefinition. */\nexport type ResultOf<D extends AnyQueryDef> =\n D extends QueryDefinition<any, any, infer R> ? R : never;\n\n/**\n * Convert a readonly tuple of QueryDefinitions into a Record keyed by their\n * name literals. This is what makes ClientRpc fully type-safe: each key in\n * the map carries its own TParams and TResult rather than collapsing to `any`.\n *\n * Example:\n * const defs = [projectBoardQuery, teamMembersQuery] as const;\n * type Map = DefsToMap<typeof defs>;\n * // → { projectBoard: typeof projectBoardQuery; teamMembers: typeof teamMembersQuery }\n */\nexport type DefsToMap<T extends readonly AnyQueryDef[]> = {\n [D in T[number] as D[\"name\"]]: D;\n};\n\n// ─── Global auto-registry ─────────────────────────────────────────────────────\n//\n// defineQuery() registers every definition here at module-load time.\n// The server can call registry.defineAll() to pick up all of them without\n// an explicit defines([...]) call — as long as all query files are imported.\n\nconst _globalDefs = new Map<string, AnyQueryDef>();\n\n/** For test teardown only — clears the global map between suites. */\nexport function _resetGlobalDefs(): void {\n _globalDefs.clear();\n}\n\nexport function _getGlobalDefs(): ReadonlyMap<string, AnyQueryDef> {\n return _globalDefs;\n}\n\n// ─── defineQuery ──────────────────────────────────────────────────────────────\n\n/**\n * Create a named, typed query definition.\n *\n * The definition is automatically registered in a module-level global so\n * the server can call registry.defineAll() instead of listing every query\n * explicitly. On the client, pass the definition to createClientRpc() to\n * get a fully typed subscribe() call.\n *\n * Usage:\n * export const projectBoardQuery = defineQuery(\n * \"projectBoard\",\n * async (ctx, params: { projectId: string }) => {\n * return ctx.query(Tasks)\n * .select()\n * .where(f => f.eq(\"projectId\", params.projectId))\n * .get();\n * }\n * );\n */\nexport function defineQuery<TName extends string, TParams, TResult>(\n name: TName,\n fn: (ctx: DbReader, params: TParams) => Promise<TResult>,\n): QueryDefinition<TName, TParams, TResult> {\n if (_globalDefs.has(name)) {\n throw new Error(\n `[defineQuery] \"${name}\" is already defined. ` +\n `Each query must have a unique name across the whole application.`,\n );\n }\n const def: QueryDefinition<TName, TParams, TResult> = {\n _type: \"QueryDefinition\",\n name,\n fn,\n };\n _globalDefs.set(name, def);\n return def;\n}\n","// Compact, client-side local key for subscription dedup.\n//\n// Lives in its own file so the `hash-wasm` import is reachable only by the\n// transport layer that needs it (`ws_client_manager`) — bundlers that\n// tree-shake `@supalive/core/client` can drop wasm if a consumer never\n// touches subscriptions.\n//\n// The key is local to this client process: the server uses its own canonical\n// subId (computed with `queryIdentity` applied) and ships it back on the\n// subscribe response. We only need a fixed-size handle for in-process dedup\n// and for storing on the Subscription record.\n\nimport { sha1 } from \"hash-wasm\";\nimport { stableStringify } from \"../helper\";\n\n/**\n * Compute a compact local key for a `(procedure, input)` pair.\n *\n * Output shape: `procedureName#16-hex-chars`.\n *\n * - `procedureName#` prefix keeps DevTools / log output legible.\n * - 16 hex chars = 64 bits of SHA-1. Collision probability over N concurrent\n * subscriptions is ~ N²/2⁶⁵ — comfortable up to ~10k subs per session.\n *\n * Async because `hash-wasm` lazily inits the wasm module on first call; the\n * cost is paid once per process. Subsequent calls are effectively free.\n */\nexport async function computeLocalSubKey(procedure: string, input: string): Promise<string> {\n const hex = await sha1(`${procedure}::${input}`);\n return `${procedure}#${hex.slice(0, 16)}`;\n}\n\n/**\n * Trigger the one-time `hash-wasm` init off the critical path so the first\n * real `subscribe` doesn't pay the ~5–10ms wasm-bootstrap cost. Safe to call\n * any number of times; resolves once the module is ready.\n */\nexport function prewarmHasher(): Promise<unknown> {\n return sha1(\"\");\n}\n","// ─── Client WebSocket Manager ─────────────────────────────────────────────────\n//\n// Manages a WebSocket connection to the server and handles:\n// - Connection lifecycle (connect, auth, reconnect)\n// - Connection ID management with localStorage persistence\n// - Request/response matching via request IDs\n// - Subscription management with callbacks\n// - Message encoding/decoding\n// - Queued calls when not connected (FIFO, flushed on reconnect)\n// - Pending in-flight calls are re-queued (not rejected) on transport drop\n// so they re-issue on the next successful connect.\n// - Auto re-subscribe of live queries after reconnect\n// - Passive server-inactivity watchdog: the client never sends a ping; the\n// server emits `{type:\"ping\"}` while idle and we close+reconnect after\n// `serverInactivityMs` of total silence.\n//\n// Subscription identity is server-driven: the client sends `subscribe` with\n// (procedure, input) and the server replies with the canonical `subId` on\n// the response.\n\nimport type {\n ResponseMessage,\n SubscriptionUpdateMessage,\n SubscriptionGoneMessage,\n ClientMessage,\n} from \"../db/types_client_rpc\";\nimport { ServerMessageSchema } from \"../db/types_client_rpc\";\nimport { computeLocalSubKey, prewarmHasher } from \"./sub_key\";\nimport { supaliveStringify } from \"../helper\";\n\n// ─── Typed WebSocket surface ──────────────────────────────────────────────────\n//\n// A minimal interface common to the browser `WebSocket` and the Node `ws`\n// package, so the manager can be fully typed without `any` and run in both\n// environments.\n\ntype WSEventHandler<E> = ((event: E) => void) | null;\n\nexport interface WSLike {\n readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n onopen: WSEventHandler<unknown>;\n onmessage: WSEventHandler<{ data: string | { toString(): string } }>;\n onclose: WSEventHandler<unknown>;\n onerror: WSEventHandler<unknown>;\n}\n\nexport interface WSConstructor {\n new(url: string): WSLike;\n}\n\n// readyState 1 = OPEN in both environments.\nconst WS_OPEN = 1;\n\nexport class RPCError extends Error {\n code?: string;\n constructor(message: string, code?: string) {\n super(message);\n this.name = \"RPCError\";\n this.code = code;\n }\n}\n\n// ─── Logging ────────────────────────────────────────────────────────────────\n\nexport type ClientLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"silent\";\n\n/** Minimal logger sink the client writes lifecycle/diagnostic lines to. Pass a\n * custom implementation via {@link WSClientOptions.logger} to route logs into\n * your app's logger; otherwise a console sink gated by\n * {@link WSClientOptions.logLevel} is used. */\nexport interface ClientLogger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\n/** A structured lifecycle event, for feeding client metrics without parsing logs. */\nexport interface ClientEvent {\n type:\n | \"connecting\"\n | \"connected\"\n | \"disconnected\"\n | \"reconnecting\"\n | \"error\";\n /** Reason/detail (e.g. reconnect trigger, close reason). */\n detail?: string;\n /** Reconnect attempt number, when `type === \"reconnecting\"`. */\n attempt?: number;\n}\n\nconst LEVEL_ORDER: Record<Exclude<ClientLogLevel, \"silent\">, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\n/** Console-backed logger that drops anything below `level`. `\"silent\"` mutes all. */\nexport function createConsoleLogger(level: ClientLogLevel = \"warn\"): ClientLogger {\n const enabled = (l: Exclude<ClientLogLevel, \"silent\">): boolean =>\n level !== \"silent\" && LEVEL_ORDER[l] >= LEVEL_ORDER[level as Exclude<ClientLogLevel, \"silent\">];\n const noop = (): void => {};\n return {\n debug: enabled(\"debug\") ? (m, ...a) => console.debug(`[WSClient] ${m}`, ...a) : noop,\n info: enabled(\"info\") ? (m, ...a) => console.info(`[WSClient] ${m}`, ...a) : noop,\n warn: enabled(\"warn\") ? (m, ...a) => console.warn(`[WSClient] ${m}`, ...a) : noop,\n error: enabled(\"error\") ? (m, ...a) => console.error(`[WSClient] ${m}`, ...a) : noop,\n };\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeartbeatOptions {\n /**\n * Close + reconnect when no server message arrives for this long. The\n * server emits `{type:\"ping\"}` every ~15s of channel idle, so anything\n * larger than ~30s leaves comfortable headroom. Default 60_000.\n */\n serverInactivityMs?: number;\n}\n\nexport interface SendThrottleOptions {\n /** Yield the event loop every N `call()` invocations. 0 disables. Default 100. */\n everyN?: number;\n /** Length of the yield in ms. Default 10. */\n delayMs?: number;\n}\n\nexport interface WSClientOptions {\n /** WebSocket URL (e.g. ws://localhost:3000/ws) */\n url: string;\n /** Auth token to send after connection */\n token?: () => Promise<string | undefined>;\n /** Optional: reconnection timeout in ms (default: 180000 = 3 minutes) */\n reconnectionTimeout?: number;\n /** Reconnection options */\n reconnect?: {\n /** Enable automatic reconnection (default: true) */\n enabled?: boolean;\n /** Max reconnection attempts (default: 5) */\n maxAttempts?: number;\n /** Delay between attempts in ms (default: 1000) */\n delay?: number;\n /** Exponential backoff multiplier (default: 1.5) */\n backoff?: number;\n };\n /** Server-inactivity watchdog. See {@link HeartbeatOptions.serverInactivityMs}. */\n heartbeat?: HeartbeatOptions;\n /**\n * Yields the event loop for `delayMs` ms after every `everyN`\n * `call()` invocations. Useful for keeping React renders / GC tasks\n * scheduled during long synchronous bursts of mutations/actions; the\n * heartbeat does not depend on this (the server drives it).\n * Default `{ everyN: 100, delayMs: 10 }`. Set `everyN: 0` to disable.\n */\n throttle?: SendThrottleOptions;\n /** Called when connection is established */\n onConnect?: () => void;\n /** Called when connection is lost */\n onDisconnect?: () => void;\n /** Called on connection error */\n onError?: (error: Error) => void;\n /**\n * Custom log sink. When set, it receives all lifecycle/diagnostic lines and\n * `logLevel` is ignored (filtering is the sink's responsibility). When unset,\n * a console sink gated by {@link logLevel} is used.\n */\n logger?: ClientLogger;\n /** Level for the default console logger. Default `\"warn\"` (quiet). */\n logLevel?: ClientLogLevel;\n /** Optional structured lifecycle events, for client-side metrics. */\n onEvent?: (event: ClientEvent) => void;\n /**\n * OpenTelemetry-agnostic trace propagation. Return the current W3C\n * `traceparent` for the active client span (e.g. from `@opentelemetry/api`'s\n * propagation) and it is sent on every `call`/`subscribe`; the server\n * continues the trace. Return `undefined` (the default — no hook) to send\n * nothing. Called per outbound request.\n */\n getTraceparent?: () => string | undefined | null;\n}\n\ninterface PendingRequest {\n /** Resolver for non-subscribe responses (call). Receives `message.data`. */\n resolve?: (value: unknown) => void;\n reject: (error: Error) => void;\n /**\n * Resolver for subscribe responses. Receives the server-returned\n * canonical `subId` (typed via the response schema — `string | undefined`).\n * When present, `handleResponse` dispatches to this instead of `resolve`,\n * so subscribe-specific bookkeeping (rekeying the parked `Subscription`\n * record from its `_pending:<reqId>` sentinel to the real subId) stays\n * local to the call site that registered the request.\n */\n subscribeResolve?: (subId: ResponseMessage[\"subId\"]) => void;\n}\n\ninterface SubscribeJoiner {\n resolve: (subId: string) => void;\n reject: (err: Error) => void;\n}\n\ninterface Subscription {\n /** Server-returned canonical id; set after the subscribe response lands. */\n subId: string | null;\n /**\n * Local identity for dedup + reconnect replay. SHA-1 of `procedure +\n * stableStringify(input)`, truncated to 64 bits and prefixed with the\n * procedure name. Fixed-size, opaque to the server. See `sub_key.ts`.\n */\n localKey: string;\n procedure: string;\n input: unknown;\n onUpdate: (data: unknown) => void;\n onGone?: (reason: string | undefined) => void;\n /**\n * Additional `subscribe()` callers that joined while this record was\n * still waiting for its first response (or for a reconnect replay).\n * Resolved by whichever `subscribeResolve` (initial or replay) ultimately\n * lands; rejected on protocol error, cancel, or per-joiner 30s timeout.\n * Event-driven — no polling.\n */\n pendingJoiners?: Set<SubscribeJoiner>;\n}\n\ninterface QueuedCall {\n procedure: string;\n input: unknown;\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n /** Notification fired when the call is finally sent (used by useMutation queued state) */\n onSend?: () => void;\n}\n\ntype ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\ntype AuthState = \"pending\" | \"unauthenticated\" | \"authenticated\" | \"anonymouse\";\n\n/**\n * Public state of the client, surfaced to consumers (React provider, etc.).\n * - \"connecting\" — initial connect or reconnect handshake in flight\n * - \"authenticating\" — connected, waiting for auth result\n * - \"ready\" — connected and auth resolved (authenticated, anonymous, or\n * explicitly failed — i.e. ready to send calls)\n * - \"disconnected\" — currently offline (may auto-retry)\n */\nexport type ClientPublicState =\n | \"disconnected\"\n | \"connecting\"\n | \"authenticating\"\n | \"ready\";\n\nexport class WsClientManager {\n private ws: WSLike | null = null;\n private options: WSClientOptions;\n private lastSuccessfulConnectionTime: number = 0;\n private reconnectionTimeout: number;\n private requestId = 0;\n private pendingRequests = new Map<string, PendingRequest>();\n /**\n * Single source of truth for live subscriptions.\n *\n * Keying:\n * - In-flight subscribe (no subId yet): `_pending:<requestId>`.\n * - Established: the server-returned canonical `subId`.\n *\n * Routing `sub:update` / `sub:gone` is O(1) (`get(subId)`). Subscribe-time\n * dedup scans `values()` for a matching `localKey` (O(N)), which is cheap\n * since subscribes happen on component mounts, not in the hot path.\n */\n private subscriptions = new Map<string, Subscription>();\n private queuedCalls: QueuedCall[] = [];\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private connectionState: ConnectionState = \"disconnected\";\n private authState: AuthState = \"pending\";\n /**\n * Authenticated user id from `auth:success`. Distinct from `queryIdentity`\n * (server-side cache/sub segmentation key); this is just \"who am I logged\n * in as\" for app display purposes. Null while unauthenticated / anonymous.\n */\n private authUserId: string | null = null;\n private explicitlyClosed = false;\n private stateListeners = new Set<(s: ClientPublicState) => void>();\n private lastPublicState: ClientPublicState = \"disconnected\";\n\n // ─── Read high-water (read-your-writes / monotonic reads) ────────────────\n //\n // The largest commit/snapshot ts this client has observed — advanced from\n // every successful `call` response's `ts` (a mutation's commitTs or a query's\n // read snapshot). Sent as `minTs` on every outbound `call` so the server only\n // routes reads to a read replica that has caught up to what we've already\n // seen. Plain integer, no signing: the server clamps it to its own primary\n // latest, so it can at most force the primary (never an error or a value\n // beyond the primary). Never decreases.\n private highWaterTs: bigint = 0n;\n\n // ─── Inactivity watchdog (passive — server drives pings) ─────────────────\n private serverInactivityMs: number;\n private lastServerMessageAt: number = 0;\n private inactivityTimer: ReturnType<typeof setInterval> | null = null;\n\n // ─── Pending connect() promise ──────────────────────────────────────────\n //\n // Tracked on `this` so paths outside the `connect()` closure (the watchdog\n // firing during the connecting window, the OS-level close handler, the\n // reconnect-exhaustion drain) can settle the caller's awaiting promise\n // rather than leaving it suspended. Each setter wraps the underlying\n // resolve/reject with a one-shot guard so we can call freely; subsequent\n // calls are no-ops.\n private pendingConnectResolve: (() => void) | null = null;\n private pendingConnectReject: ((err: Error) => void) | null = null;\n\n // ─── Send throttle ──────────────────────────────────────────────────────\n //\n // The first `throttleEveryN` calls in a quiet window dispatch directly.\n // The N+1th call flips `throttling = true` and any subsequent call is\n // parked on `queuedCalls`. A single async loop drains the queue in\n // batches of up to `throttleEveryN`, sleeping `throttleDelayMs` between\n // batches, and exits as soon as the queue drains in a single batch\n // (burst over → back to direct-dispatch mode).\n private throttleEveryN: number;\n private throttleDelayMs: number;\n private throttleCounter = 0;\n private throttling = false;\n\n // ─── Browser env listeners ──────────────────────────────────────────────\n private visibilityListener: (() => void) | null = null;\n private onlineListener: (() => void) | null = null;\n\n // ─── Logging ────────────────────────────────────────────────────────────\n private readonly log: ClientLogger;\n\n constructor(options: WSClientOptions) {\n this.log = options.logger ?? createConsoleLogger(options.logLevel ?? \"warn\");\n this.options = {\n reconnectionTimeout: 180000, // 3 minutes default\n reconnect: {\n enabled: true,\n maxAttempts: 5,\n delay: 1000,\n backoff: 1.5,\n ...options.reconnect,\n },\n ...options,\n };\n this.reconnectionTimeout = this.options.reconnectionTimeout!;\n this.serverInactivityMs = options.heartbeat?.serverInactivityMs ?? 60_000;\n this.throttleEveryN = options.throttle?.everyN ?? 100;\n this.throttleDelayMs = options.throttle?.delayMs ?? 10;\n this.installEnvironmentListeners();\n // Fire and forget: warm the wasm hash module so the first subscribe\n // isn't billed for the ~5–10ms wasm bootstrap. Swallow rejections —\n // the lazy path inside `computeLocalSubKey` will surface any real\n // wasm failure on the first actual subscribe.\n prewarmHasher().catch(() => { });\n }\n\n /**\n * Subscribe to client state transitions. Returns an unsubscribe fn.\n * The listener is called immediately with the current state.\n */\n onState(listener: (s: ClientPublicState) => void): () => void {\n this.stateListeners.add(listener);\n listener(this.computePublicState());\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n /** Current public state. */\n getPublicState(): ClientPublicState {\n return this.computePublicState();\n }\n\n /**\n * Connect to the WebSocket server.\n * Returns a promise that resolves when connected and authenticated.\n */\n async connect(): Promise<void> {\n this.explicitlyClosed = false;\n if (this.connectionState == \"connected\" || this.connectionState == \"connecting\") {\n return;\n }\n\n this.connectionState = \"connecting\";\n this.emitState();\n this.log.debug(`connecting to ${this.options.url}`);\n this.emitEvent({ type: \"connecting\", detail: this.options.url });\n\n return new Promise<void>(async (resolve, reject) => {\n // One-shot guards. Whichever side fires first nulls both slots so\n // later callers (e.g. handleClose after a successful auth) are no-ops.\n this.pendingConnectResolve = () => {\n this.pendingConnectResolve = null;\n this.pendingConnectReject = null;\n resolve();\n };\n this.pendingConnectReject = (err) => {\n this.pendingConnectResolve = null;\n this.pendingConnectReject = null;\n reject(err);\n };\n\n try {\n const WS = await getWebSocket();\n\n const url = new URL(this.options.url);\n const ws = new WS(url.toString()) as WSLike;\n this.ws = ws;\n\n ws.onopen = () => {\n // Arm the inactivity watchdog the moment the upgrade completes.\n // If the server stalls and never sends `connected`, the watchdog\n // fires at `serverInactivityMs` and forces a reconnect instead\n // of leaving the client wedged in \"connecting\" forever.\n // `handleMessage` keeps the timestamp fresh on every inbound\n // frame, including the `connected` JSON itself.\n this.startInactivityWatchdog();\n };\n\n ws.onmessage = (event) => {\n const data = typeof event.data === \"string\" ? event.data : event.data.toString();\n this.handleMessage(data);\n };\n\n ws.onclose = () => {\n this.handleClose();\n };\n\n ws.onerror = (err) => {\n const error = err instanceof Error ? err : new Error(`WebSocket error: `, {cause: err});\n this.handleError(error);\n this.pendingConnectReject?.(error);\n };\n } catch (err) {\n this.connectionState = \"disconnected\";\n this.emitState();\n this.pendingConnectReject?.(err as Error);\n }\n });\n }\n\n /**\n * Disconnect from the server.\n */\n disconnect(): void {\n this.explicitlyClosed = true;\n this.cleanup();\n const oldWs = this.ws;\n this.ws = null;\n if (oldWs) {\n oldWs.onmessage = null;\n oldWs.onerror = null;\n oldWs.onopen = null;\n oldWs.onclose = null;\n try { oldWs.close(); } catch { /* swallow */ }\n }\n this.removeEnvironmentListeners();\n this.emitState();\n }\n\n /**\n * Check if connected and authenticated.\n */\n isReady(): boolean {\n return this.connectionState == \"connected\" && this.authState != \"pending\";\n }\n\n /**\n * Check if authenticated.\n */\n isAuthenticated(): boolean {\n return this.authState == \"authenticated\";\n }\n\n /**\n * Check if anonymouse.\n */\n isAnonymouse(): boolean {\n return this.authState == \"anonymouse\";\n }\n\n /**\n * The authenticated user's id (from `auth:success`), or null when\n * unauthenticated / anonymous. Useful for \"Logged in as …\" display.\n *\n * Not to be confused with server-side `queryIdentity`, which is the\n * cache/subscription segmentation key (resolved entirely on the server).\n */\n getAuthUserId(): string | null {\n return this.authUserId;\n }\n\n /**\n * Call a procedure (query, mutation, or action).\n *\n * If the client is not ready (disconnected or still authenticating), the\n * call is FIFO-queued and dispatched on the next ready state. The promise\n * still resolves with the eventual server response.\n *\n * Pass `onSend` to be notified when the call actually leaves the queue\n * (used by `useMutation` to flip `state` from \"queued\" → \"loading\").\n */\n async call(\n procedure: string,\n input: unknown,\n options?: { onSend?: () => void },\n ): Promise<unknown> {\n // Not ready — park on the disconnect queue. flushQueuedCalls picks\n // these up when the next session reaches ready.\n if (!this.isReady()) {\n return new Promise((resolve, reject) => {\n this.queuedCalls.push({\n procedure,\n input,\n resolve,\n reject,\n onSend: options?.onSend,\n });\n if (\n !this.explicitlyClosed &&\n this.connectionState === \"disconnected\"\n ) {\n this.connect().catch(() => {/* state event reflects failure */ });\n }\n });\n }\n\n // Throttle disabled — straight dispatch.\n if (this.throttleEveryN <= 0) {\n options?.onSend?.();\n return this.dispatchCall(procedure, input);\n }\n\n // Already throttling — park and let the loop flush us in the next batch.\n if (this.throttling) {\n return new Promise((resolve, reject) => {\n this.queuedCalls.push({\n procedure,\n input,\n resolve,\n reject,\n onSend: options?.onSend,\n });\n });\n }\n\n // Direct dispatch, count toward throttle cap. When we hit the cap,\n // flip into throttling mode and kick off the drain loop.\n this.throttleCounter++;\n if (this.throttleCounter >= this.throttleEveryN) {\n this.throttling = true;\n await this.runThrottleLoop();\n }\n options?.onSend?.();\n return this.dispatchCall(procedure, input);\n }\n\n /**\n * Throttle drain loop. Each iteration: sleep `throttleDelayMs`, then\n * dispatch up to `throttleEveryN` queued items. Exits as soon as the\n * queue drains inside a single batch (burst over, back to direct\n * dispatch on the next `call()`) or the connection goes away\n * (reconnect path will restart us via `flushQueuedCalls`).\n */\n private async runThrottleLoop(): Promise<void> {\n while (this.throttling) {\n await new Promise((r) => setTimeout(r, this.throttleDelayMs));\n\n if (!this.isReady()) {\n this.throttling = false;\n this.throttleCounter = 0;\n return;\n }\n\n this.throttleCounter = 0;\n while (\n this.throttleCounter < this.throttleEveryN &&\n this.queuedCalls.length > 0\n ) {\n const item = this.queuedCalls.shift()!;\n item.onSend?.();\n this.dispatchCall(item.procedure, item.input).then(item.resolve, item.reject);\n this.throttleCounter++;\n }\n\n if (this.queuedCalls.length === 0) {\n // Drained without hitting the cap — release the brake.\n this.throttling = false;\n this.throttleCounter = 0;\n return;\n }\n // Else: hit cap, more pending — loop and wait again.\n }\n }\n\n /**\n * Issue the call against an established connection. Caller must ensure\n * `isReady()` before invoking.\n */\n private dispatchCall(procedure: string, input: unknown): Promise<unknown> {\n const id = this.generateRequestId();\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, { resolve, reject });\n\n this.send({\n type: \"call\",\n id,\n procedure,\n input,\n // Carry our read high-water so the server can safely route to a replica\n // (or fall back to primary) — omitted until we've observed anything.\n minTs: this.highWaterTs > 0n ? this.highWaterTs.toString() : undefined,\n trace: this.traceparent(),\n });\n });\n }\n\n /** Advance the read high-water from a server-returned `ts` (monotonic). */\n private advanceHighWater(ts: string | undefined): void {\n if (ts === undefined) return;\n let parsed: bigint;\n try {\n parsed = BigInt(ts);\n } catch {\n return;\n }\n if (parsed > this.highWaterTs) this.highWaterTs = parsed;\n }\n\n /**\n * Flush queued calls in FIFO order. Called once `isReady()` becomes\n * true. With the throttle enabled, dispatch the first batch\n * synchronously then hand off to the drain loop so a large\n * post-reconnect flush respects backpressure instead of dumping 10k\n * frames in one tick.\n */\n private flushQueuedCalls(): void {\n if (this.queuedCalls.length === 0) return;\n\n if (this.throttleEveryN <= 0) {\n const queue = this.queuedCalls;\n this.queuedCalls = [];\n for (const item of queue) {\n item.onSend?.();\n this.dispatchCall(item.procedure, item.input).then(item.resolve, item.reject);\n }\n return;\n }\n\n this.throttleCounter = 0;\n while (\n this.throttleCounter < this.throttleEveryN &&\n this.queuedCalls.length > 0\n ) {\n const item = this.queuedCalls.shift()!;\n item.onSend?.();\n this.dispatchCall(item.procedure, item.input).then(item.resolve, item.reject);\n this.throttleCounter++;\n }\n\n if (this.queuedCalls.length > 0) {\n this.throttling = true;\n this.runThrottleLoop();\n }\n }\n\n /**\n * Re-send `subscribe` for every existing subscription. The subId for each\n * may legitimately change (e.g. `queryIdentity` was a function of user\n * identity and the user changed during the disconnect), so we re-key the\n * unified `subscriptions` Map: snapshot every record, drop them from the\n * Map, then reinsert under a fresh `_pending:<reqId>` key. Each pending\n * request's `resolve` rekeys the parked record under the new server-issued\n * subId so subsequent `sub:update` frames route to the existing\n * `onUpdate` callback (which the React handle is still holding).\n */\n private resubscribeAll(): void {\n if (this.subscriptions.size === 0) return;\n\n const subs = Array.from(this.subscriptions.values());\n this.subscriptions.clear();\n\n for (const sub of subs) {\n const id = this.generateRequestId();\n const pendingKey = `_pending:${id}`;\n sub.subId = null;\n\n this.subscriptions.set(pendingKey, sub);\n this.pendingRequests.set(id, {\n subscribeResolve: (subId) => {\n const parked = this.subscriptions.get(pendingKey);\n if (!subId || !parked) {\n // Server didn't echo a subId, or the record was torn down\n // mid-replay (e.g. component unmounted). Drop the parked\n // entry so it can't leak.\n this.subscriptions.delete(pendingKey);\n this.rejectJoiners(parked, new RPCError(\"Subscribe response missing subId after replay\", \"PROTOCOL_ERROR\"));\n return;\n }\n // Mutate the SAME Subscription object — the LiveQueryHandleImpl\n // is still holding its `onUpdate` closure indirectly through\n // this record. Replacing the object would orphan the handle.\n parked.subId = subId;\n this.subscriptions.delete(pendingKey);\n this.subscriptions.set(subId, parked);\n // Drain joiners that joined during the parked phase.\n this.resolveJoiners(parked, subId);\n },\n reject: (err) => {\n // Keep the parked record alive on transport-level drops so the\n // next resubscribeAll replays it; purge (and reject joiners)\n // only on protocol-level errors where re-issuing would not help.\n if (!(err instanceof RPCError) || err.code !== \"DISCONNECTED\") {\n const parked = this.subscriptions.get(pendingKey);\n this.subscriptions.delete(pendingKey);\n this.rejectJoiners(parked, err);\n }\n this.log.warn(`Failed to re-subscribe ${sub.procedure}:`, err);\n },\n });\n\n this.send({\n type: \"subscribe\",\n id,\n procedure: sub.procedure,\n input: sub.input,\n trace: this.traceparent(),\n });\n }\n }\n\n /**\n * Subscribe to a live query.\n *\n * The client does not compute the subId locally — it sends the\n * (procedure, input) pair and receives the canonical subId on the\n * subscribe response. This lets the server's per-procedure\n * `queryIdentity` override be authoritative without forcing the client\n * to mirror any of the resolution logic.\n *\n * Returns the server-assigned subId and an unsubscribe function.\n */\n async subscribe(\n procedure: string,\n input: unknown,\n serializedInput: string,\n onUpdate: (data: unknown) => void,\n onGone?: (reason: string | undefined) => void,\n ): Promise<{ subId: string; unsubscribe: () => void }> {\n await this.ensureConnected();\n\n const localKey = await computeLocalSubKey(procedure, serializedInput);\n\n // Manager-level dedup: scan values for an existing subscription with\n // the same logical (procedure, input). Subscribe is a cold path (mounts,\n // not the per-message hot path), so a linear scan is the right shape\n // here in exchange for a single unified Map.\n const existing = this.findSubByLocalKey(localKey);\n if (existing) {\n existing.onUpdate = onUpdate;\n existing.onGone = onGone;\n if (existing.subId) {\n return {\n subId: existing.subId,\n unsubscribe: () => this.unsubscribeByLocalKey(localKey, existing.subId),\n };\n }\n // Subscribe response for the existing record is still in flight.\n // Park an event-driven joiner on the record — the originating\n // subscribeResolve (or the replay's, if the socket dropped first)\n // drains us when the response lands.\n return new Promise((resolve, reject) => {\n const joinerTimeout = setTimeout(() => {\n existing.pendingJoiners?.delete(joiner);\n reject(new RPCError(`Subscribe timeout: ${procedure}`, \"TIMEOUT\"));\n }, 30_000);\n \n const joiner: SubscribeJoiner = {\n resolve: (subId) => {\n clearTimeout(joinerTimeout);\n existing.pendingJoiners?.delete(joiner);\n resolve({\n subId,\n unsubscribe: () => this.unsubscribeByLocalKey(localKey, subId),\n });\n },\n reject: (err) => {\n clearTimeout(joinerTimeout);\n existing.pendingJoiners?.delete(joiner);\n reject(err);\n },\n };\n (existing.pendingJoiners ??= new Set()).add(joiner);\n });\n }\n\n const id = this.generateRequestId();\n const pendingKey = `_pending:${id}`;\n const record: Subscription = {\n subId: null,\n localKey,\n procedure,\n input,\n onUpdate,\n onGone,\n };\n this.subscriptions.set(pendingKey, record);\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n subscribeResolve: (subId) => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n\n const sub = this.subscriptions.get(pendingKey);\n if (!subId || !sub) {\n this.subscriptions.delete(pendingKey);\n const err = new RPCError(\"Subscribe response missing subId\", \"PROTOCOL_ERROR\");\n this.rejectJoiners(sub, err);\n reject(err);\n return;\n }\n\n // Re-key from pending sentinel to real subId, same object.\n sub.subId = subId;\n this.subscriptions.delete(pendingKey);\n this.subscriptions.set(subId, sub);\n resolve({\n subId,\n unsubscribe: () => this.unsubscribeByLocalKey(localKey, subId),\n });\n this.resolveJoiners(sub, subId);\n },\n reject: (err) => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n\n // Keep the record alive on transport-level drops (DISCONNECTED)\n // so `resubscribeAll` replays it after the reconnect handshake.\n // Joiners stay attached to the parked record and are drained by\n // the replay's subscribeResolve. Purge (and reject joiners) only\n // on real protocol-level errors or the 30s timeout.\n if (!(err instanceof RPCError) || err.code !== \"DISCONNECTED\") {\n const sub = this.subscriptions.get(pendingKey);\n this.subscriptions.delete(pendingKey);\n this.rejectJoiners(sub, err);\n }\n reject(err);\n },\n });\n\n this.send({\n type: \"subscribe\",\n id,\n procedure,\n input,\n trace: this.traceparent(),\n });\n\n timeout = setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id);\n const sub = this.subscriptions.get(pendingKey);\n this.subscriptions.delete(pendingKey);\n const err = new RPCError(`Subscribe timeout: ${procedure}`, \"TIMEOUT\");\n this.rejectJoiners(sub, err);\n reject(err);\n }\n }, 30_000);\n });\n }\n\n private findSubByLocalKey(localKey: string): Subscription | undefined {\n for (const sub of this.subscriptions.values()) {\n if (sub.localKey === localKey) return sub;\n }\n return undefined;\n }\n\n /** Resolve every joiner waiting on this Subscription with the canonical subId. */\n private resolveJoiners(sub: Subscription, subId: string): void {\n if (!sub.pendingJoiners) return;\n // Snapshot before iterating — each joiner.resolve() mutates the set.\n const snapshot = Array.from(sub.pendingJoiners);\n for (const j of snapshot) j.resolve(subId);\n }\n\n /** Reject every joiner waiting on this Subscription with the given error. */\n private rejectJoiners(sub: Subscription | undefined, err: Error): void {\n if (!sub?.pendingJoiners) return;\n const snapshot = Array.from(sub.pendingJoiners);\n for (const j of snapshot) j.reject(err);\n }\n\n private unsubscribeByLocalKey(localKey: string, subId: string | null): void {\n const sendUnsubscribe = (subId: string): void => {\n if (this.connectionState === \"connected\") {\n this.send({ type: \"unsubscribe\", subId });\n }\n }\n\n if (subId) {\n // If we have the subId, we can skip straight to the unsubscribe message\n this.subscriptions.delete(subId);\n sendUnsubscribe(subId);\n return;\n }\n\n // We don't know whether the entry is parked under a pending sentinel or\n // a real subId, so iterate. O(N) is fine here: unsubscribe runs at\n // most once per handle teardown.\n for (const [key, sub] of this.subscriptions.entries()) {\n if (sub.localKey !== localKey) continue;\n\n this.subscriptions.delete(key);\n // Cancel any callers still waiting on the in-flight subscribe.\n this.rejectJoiners(sub, new RPCError(\"Subscription cancelled before response\", \"CANCELLED\"));\n sendUnsubscribe(sub.subId!);\n return;\n }\n }\n\n // ─── Internal Handlers ──────────────────────────────────────────────────────\n\n private async handleMessage(dataValue: string): Promise<void> {\n // ANY inbound frame counts as a liveness signal — the bytes arrived,\n // therefore the socket is alive. Done before parse/validate so that\n // even malformed frames still refresh the watchdog (it's the OS\n // delivery we care about, not the JSON correctness).\n this.lastServerMessageAt = Date.now();\n\n // Validate against the full server-message union rather than trusting a\n // naked cast. A protocol-version skew (or a man-in-the-middle proxy\n // that mangles the payload) would otherwise blow up downstream with a\n // runtime TypeError on a missing field; better to drop the message\n // with a clear diagnostic.\n let data: unknown = undefined;\n try {\n data = JSON.parse(dataValue);\n } catch {\n this.log.warn(\"Failed to parse server message as JSON:\", dataValue);\n }\n\n const parsed = ServerMessageSchema.safeParse(data);\n\n if (!parsed.success) {\n this.log.warn(\"Invalid server message:\", parsed.error.issues, data);\n return;\n }\n const message = parsed.data;\n\n switch (message.type) {\n case \"connected\": {\n this.connectionState = \"connected\";\n this.reconnectAttempts = 0;\n // Track successful connection time for reconnection logic\n this.lastSuccessfulConnectionTime = Date.now();\n this.log.info(\"connected\");\n this.emitEvent({ type: \"connected\" });\n // (Inactivity watchdog already started in `ws.onopen`. The top of\n // handleMessage refreshed `lastServerMessageAt` for this very frame.)\n\n // Send auth if token provided\n const token = await this.options.token?.();\n if (token) {\n this.authState = \"pending\";\n this.emitState();\n this.send({ type: \"auth\", data: { token } });\n } else {\n this.authState = \"anonymouse\";\n this.options.onConnect?.();\n this.emitState();\n this.afterReady();\n this.pendingConnectResolve?.();\n }\n break;\n }\n\n case \"auth:success\":\n this.authState = \"authenticated\";\n this.authUserId = message.userId;\n this.options.onConnect?.();\n this.emitState();\n this.afterReady();\n this.pendingConnectResolve?.();\n break;\n case \"auth:failed\":\n // The WebSocket itself is fine — only auth rejected. Resolve the\n // connect promise so the app can decide what to do via\n // `isAuthenticated()` / `getPublicState()`. Avoids flooding the\n // console for anonymous-friendly apps that simply continue without\n // a token.\n this.authState = \"unauthenticated\";\n this.authUserId = null;\n this.options.onConnect?.();\n this.emitState();\n this.afterReady();\n this.pendingConnectResolve?.();\n break;\n\n case \"response\":\n this.handleResponse(message);\n break;\n\n case \"sub:update\":\n this.handleSubscriptionUpdate(message);\n break;\n\n case \"sub:gone\":\n this.handleSubscriptionGone(message);\n break;\n\n case \"ping\":\n // The `lastServerMessageAt` update at the top of `handleMessage`\n // is the only thing this signal exists for. No reply.\n break;\n\n default: {\n // Exhaustive over `ServerMessageSchema`; reaching here means the\n // schema was extended without updating this switch.\n const _exhaustive: never = message;\n this.log.warn(\"Unhandled server message type:\", _exhaustive);\n }\n }\n }\n\n /**\n * Called once the client transitions to a \"ready\" state on a fresh\n * connection. Replays live-query subscriptions and flushes any queued\n * calls in FIFO order.\n */\n private afterReady(): void {\n this.resubscribeAll();\n this.flushQueuedCalls();\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id);\n if (!pending) return;\n\n this.pendingRequests.delete(message.id);\n\n if (message.success) {\n if (pending.subscribeResolve) {\n pending.subscribeResolve(message.subId);\n } else {\n // Fold the observed ts into our high-water BEFORE resolving, so a caller\n // that immediately issues another read already sends the advanced minTs.\n this.advanceHighWater(message.ts);\n pending.resolve?.(message.data);\n }\n } else {\n pending.reject(new RPCError(message.error ?? \"Unknown error\", message.code));\n }\n }\n\n private handleSubscriptionUpdate(message: SubscriptionUpdateMessage): void {\n const sub = this.subscriptions.get(message.subId);\n if (sub) {\n // Advance the read high-water so a later one-shot query can't route to a\n // replica behind what this live view just showed (monotonic reads).\n this.advanceHighWater(message.ts);\n sub.onUpdate(message.data);\n }\n }\n\n /**\n * Handles a `sub:gone` message: the server no longer tracks this\n * subscription (e.g. it was fully unregistered while we were disconnected\n * from the sub-manager). Notify the handle and drop the local entry so a\n * re-subscribe is required to receive further updates.\n */\n private handleSubscriptionGone(message: SubscriptionGoneMessage): void {\n const sub = this.subscriptions.get(message.subId);\n if (!sub) return;\n\n this.subscriptions.delete(message.subId);\n try {\n sub.onGone?.(message.reason);\n } catch (e) {\n this.log.error(\"onGone listener threw:\", e);\n }\n }\n\n private handleClose(): void {\n // Idempotent: `closeAndReconnect()` calls us synchronously to fast-track\n // the reconnect, and then the OS-level `ws.onclose` fires again on the\n // discarded socket. Re-entering would double-fire `onDisconnect` and\n // double-bump `reconnectAttempts`.\n if (this.connectionState === \"disconnected\") return;\n\n this.connectionState = \"disconnected\";\n this.authState = \"pending\";\n this.ws = null;\n this.stopInactivityWatchdog();\n\n this.log.info(\"disconnected\");\n this.emitEvent({ type: \"disconnected\" });\n this.options.onDisconnect?.();\n\n // If a `connect()` promise is still in flight (we never reached\n // auth:success / anonymous-connected before the close), reject it so\n // any `await client.connect()` doesn't hang. Auto-reconnect will\n // still take over via scheduleReconnect below — this only settles\n // the user-facing promise.\n this.pendingConnectReject?.(\n new RPCError(\"Connection closed before ready\", \"DISCONNECTED\")\n );\n\n // Reject every in-flight request. These were already on the wire — the\n // server may have processed a mutation but the response frame was lost\n // when the socket dropped. Re-issuing would risk double-execution\n // (e.g. a transfer applied twice), so we surface DISCONNECTED to the\n // caller; the side effect, if it committed, is reflected via the live\n // query subscription cluster on reconnect.\n //\n // Queued calls (this.queuedCalls) are intentionally preserved and\n // flushed on the next ready state — those were never sent, so\n // re-issuing them is safe and is the user-visible \"I clicked\n // before the socket reconnected\" path. Subscribe pendings are\n // replayed via resubscribeAll in afterReady.\n for (const pending of this.pendingRequests.values()) {\n pending.reject(new RPCError(\"Connection closed\", \"DISCONNECTED\"));\n }\n this.pendingRequests.clear();\n\n this.emitState();\n\n // Attempt reconnection (unless the caller explicitly disconnected)\n if (!this.explicitlyClosed && this.options.reconnect?.enabled) {\n this.scheduleReconnect();\n }\n }\n\n private handleError(err: Error): void {\n this.log.warn(\"connection error:\", err);\n this.emitEvent({ type: \"error\", detail: err.message });\n this.options.onError?.(err);\n }\n\n // ─── Inactivity watchdog ────────────────────────────────────────────────\n //\n // The browser API does not surface ws-protocol pings to JS, and even if\n // it did, asking the JS thread to *send* a periodic ping is fragile\n // during synchronous bursts (a tight 10k mutation loop starves the\n // setInterval long enough to trigger spurious reconnects).\n //\n // The server is in charge of liveness instead: it emits `{type:\"ping\"}`\n // every `HEARTBEAT_INTERVAL_MS` of channel idle (server side). We\n // just track the wall-clock of the most recent inbound message, and if\n // it slips past `serverInactivityMs` we treat the socket as dead.\n\n private startInactivityWatchdog(): void {\n this.stopInactivityWatchdog();\n this.lastServerMessageAt = Date.now();\n // Poll at ~quarter cadence so a fresh-stale boundary fires within\n // serverInactivityMs * 1.25 of the actual silence onset. Minimum 1s.\n const tickMs = Math.max(1_000, Math.floor(this.serverInactivityMs / 4));\n this.inactivityTimer = setInterval(() => {\n if (Date.now() - this.lastServerMessageAt > this.serverInactivityMs) {\n this.closeAndReconnect(\"InactiveServer\");\n }\n }, tickMs);\n }\n\n private stopInactivityWatchdog(): void {\n if (this.inactivityTimer) {\n clearInterval(this.inactivityTimer);\n this.inactivityTimer = null;\n }\n }\n\n /**\n * Close the current WebSocket and schedule a reconnect via `handleClose`.\n *\n * Atomically swaps the old socket's event handlers to no-ops BEFORE\n * `close()` so a frame in flight at the moment of close cannot fire\n * `handleMessage` into a half-cleaned state — that was the source of\n * the \"browser says socket is alive but client says disconnected\" race.\n */\n private closeAndReconnect(reason: string): void {\n if (this.connectionState === \"disconnected\") return;\n\n this.log.warn(`closeAndReconnect: ${reason}`);\n\n const oldWs = this.ws;\n if (oldWs) {\n oldWs.onmessage = null;\n oldWs.onerror = null;\n oldWs.onopen = null;\n // Discard the close handler too — we don't want the OS-level close\n // event to re-enter handleClose; we invoke it synchronously below.\n oldWs.onclose = null;\n try { oldWs.close(4000, reason); } catch { /* swallow */ }\n }\n\n // Synchronous state transition + reconnect scheduling. Same effect\n // as the OS-level onclose path, but doesn't wait on the close\n // handshake (which can stall when the socket is half-open).\n this.handleClose();\n }\n\n private installEnvironmentListeners(): void {\n // visibilitychange → \"visible\": a backgrounded tab that just became\n // visible may have been frozen by the browser. Check staleness now\n // and reconnect immediately if the watchdog is already past due.\n if (\n typeof document !== \"undefined\" &&\n typeof document.addEventListener === \"function\" &&\n !this.visibilityListener\n ) {\n this.visibilityListener = () => {\n if (typeof document === \"undefined\") return;\n if (document.visibilityState !== \"visible\") return;\n if (this.connectionState !== \"connected\") return;\n if (Date.now() - this.lastServerMessageAt > this.serverInactivityMs) {\n this.closeAndReconnect(\"WokeUpStale\");\n }\n };\n document.addEventListener(\"visibilitychange\", this.visibilityListener);\n }\n\n // online: a flip back to online is a strong signal that any current\n // disconnected-state backoff should collapse to \"try right now.\"\n if (\n typeof window !== \"undefined\" &&\n typeof window.addEventListener === \"function\" &&\n !this.onlineListener\n ) {\n this.onlineListener = () => {\n if (this.connectionState !== \"disconnected\") return;\n if (this.explicitlyClosed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.connect().catch(() => { /* state event reflects failure */ });\n };\n window.addEventListener(\"online\", this.onlineListener);\n }\n }\n\n private removeEnvironmentListeners(): void {\n if (this.visibilityListener && typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", this.visibilityListener);\n this.visibilityListener = null;\n }\n if (this.onlineListener && typeof window !== \"undefined\") {\n window.removeEventListener(\"online\", this.onlineListener);\n this.onlineListener = null;\n }\n }\n\n // ─── Reconnection ───────────────────────────────────────────────────────────\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= (this.options.reconnect?.maxAttempts ?? 5)) {\n this.log.error(\"Max reconnection attempts reached\");\n // We're not going to try again — drain any user-facing promises\n // that would otherwise hang forever waiting for a connection that\n // never comes.\n const giveUp = new RPCError(\n \"Max reconnection attempts reached\",\n \"DISCONNECTED\",\n );\n for (const q of this.queuedCalls) q.reject(giveUp);\n this.queuedCalls = [];\n this.pendingConnectReject?.(giveUp);\n return;\n }\n\n const base =\n (this.options.reconnect?.delay ?? 1000) *\n Math.pow(this.options.reconnect?.backoff ?? 1.5, this.reconnectAttempts);\n // ±50% jitter so two tabs that disconnected at the same instant don't\n // wake the server in lockstep on reconnect.\n const delay = base + base * (Math.random() - 0.5);\n\n this.reconnectAttempts++;\n this.log.info(`reconnecting (attempt ${this.reconnectAttempts}) in ${Math.round(delay)}ms`);\n this.emitEvent({ type: \"reconnecting\", attempt: this.reconnectAttempts });\n\n this.reconnectTimer = setTimeout(() => {\n this.connect().catch((err) => {\n this.log.warn(\"Reconnection failed:\", err);\n });\n }, delay);\n }\n\n // ─── State Events ───────────────────────────────────────────────────────────\n\n private computePublicState(): ClientPublicState {\n if (this.connectionState === \"disconnected\") return \"disconnected\";\n if (this.connectionState === \"connecting\") return \"connecting\";\n // connectionState === \"connected\"\n if (this.authState === \"pending\") return \"authenticating\";\n return \"ready\";\n }\n\n private emitState(): void {\n const next = this.computePublicState();\n if (next === this.lastPublicState) return;\n\n this.lastPublicState = next;\n for (const l of this.stateListeners) {\n try { l(next); } catch (e) { this.log.error(\"state listener threw:\", e); }\n }\n }\n\n /** Fire the optional structured lifecycle event; never lets a throwing hook\n * break the client. */\n private emitEvent(event: ClientEvent): void {\n if (!this.options.onEvent) return;\n try {\n this.options.onEvent(event);\n } catch (e) {\n this.log.error(\"onEvent listener threw:\", e);\n }\n }\n\n // ─── Utilities ──────────────────────────────────────────────────────────────\n\n private async ensureConnected(): Promise<void> {\n if (this.connectionState != \"connected\") {\n await this.connect();\n }\n }\n\n private send(message: ClientMessage): void {\n if (this.ws && this.ws.readyState === WS_OPEN) {\n this.ws.send(supaliveStringify(message));\n }\n }\n\n /** Current W3C traceparent from the optional hook, or undefined. */\n private traceparent(): string | undefined {\n return this.options.getTraceparent?.() ?? undefined;\n }\n\n private cleanup(): void {\n this.connectionState = \"disconnected\";\n this.authState = \"pending\";\n this.stopInactivityWatchdog();\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n const teardown = new RPCError(\"Client disconnected\", \"DISCONNECTED\");\n\n for (const pending of this.pendingRequests.values()) {\n pending.reject(teardown);\n }\n this.pendingRequests.clear();\n\n // Explicit disconnect drains the queue with an error — the consumer\n // tore down on purpose, so we shouldn't hold onto their promises.\n for (const q of this.queuedCalls) {\n q.reject(teardown);\n }\n this.queuedCalls = [];\n\n // The throttle loop (if running) wakes from its sleep, sees\n // !isReady, and exits on its own. Reset its flags so the next\n // session starts clean.\n this.throttling = false;\n this.throttleCounter = 0;\n\n // Also settle any in-flight `connect()` promise so a caller that did\n // `await client.connect()` racing with `client.disconnect()` doesn't\n // hang. No-op if already settled.\n this.pendingConnectReject?.(teardown);\n }\n\n private generateRequestId(): string {\n return `${++this.requestId}`;\n }\n}\n\n// ─── Browser/Node WebSocket Handling ──────────────────────────────────────────\n\nlet WebSocketImpl: WSConstructor | null = null;\n\n// Check if we're in a browser environment\nif (typeof globalThis !== \"undefined\" && (globalThis as { WebSocket?: WSConstructor }).WebSocket) {\n WebSocketImpl = (globalThis as unknown as { WebSocket: WSConstructor }).WebSocket;\n}\n\nasync function getWebSocket(): Promise<WSConstructor> {\n if (WebSocketImpl) return WebSocketImpl;\n\n // Dynamic import for Node.js\n const ws = await import(\"ws\");\n return ws.WebSocket as unknown as WSConstructor;\n}\n\nexport { getWebSocket };\n","import type { AnyProcedure } from \"../router/procedure\";\nimport type { Router } from \"../router/router\";\nimport {\n WsClientManager,\n RPCError,\n type WSClientOptions,\n type ClientPublicState,\n} from \"./ws_client_manager\";\nimport { IsInternal } from \"../db/types_client_rpc\";\nimport { InputOf, OutputOf } from \"../router/procedure\";\n\nexport type { ClientPublicState } from \"./ws_client_manager\";\nexport { RPCError } from \"./ws_client_manager\";\n\n// ─── Client Types ────────────────────────────────────────────────────────────\n\nexport interface AuthInfo {\n token: string | null;\n userId: string | null;\n}\n\nexport interface ClientOptions {\n /** WebSocket URL (e.g. ws://localhost:3000/ws) */\n url: string;\n /** Auth Info callback */\n token?: () => Promise<string>;\n /** Optional: reconnection timeout in ms (default: 180000 = 3 minutes) */\n reconnectionTimeout?: number;\n /** Reconnection options */\n reconnect?: WSClientOptions[\"reconnect\"];\n /**\n * Inactivity watchdog. The client never sends a ping itself; the server\n * emits `{type:\"ping\"}` while idle and we close+reconnect if no inbound\n * message arrives within `serverInactivityMs` (default 60s).\n */\n heartbeat?: WSClientOptions[\"heartbeat\"];\n /**\n * Yields the event loop for `delayMs` after every `everyN` `call()`\n * invocations. Useful for keeping React renders / GC tasks scheduled\n * during long synchronous bursts; not load-bearing for the heartbeat.\n * Default `{ everyN: 100, delayMs: 10 }`. Set `everyN: 0` to disable.\n */\n throttle?: WSClientOptions[\"throttle\"];\n\n /** Custom log sink; when set, `logLevel` is ignored. See {@link WSClientOptions.logger}. */\n logger?: WSClientOptions[\"logger\"];\n /** Level for the default console logger. Default `\"warn\"` (quiet). */\n logLevel?: WSClientOptions[\"logLevel\"];\n /** Optional structured lifecycle events, for client-side metrics. */\n onEvent?: WSClientOptions[\"onEvent\"];\n /** OpenTelemetry-agnostic W3C `traceparent` provider; see {@link WSClientOptions.getTraceparent}. */\n getTraceparent?: WSClientOptions[\"getTraceparent\"];\n\n onConnect?: () => void;\n onDisConnect?: () => void;\n}\n\n/**\n * Public state of a live query subscription, exposed via `getState()` and\n * `onState()` on the handle so React (or any UI) can render a consistent\n * snapshot via `useSyncExternalStore`.\n *\n * - \"connecting\" — initial subscribe in flight\n * - \"success\" — subscribed, last server data is in `data`\n * - \"stale\" — was successful, transport dropped; keeping last `data`\n * until the resubscribe completes\n * - \"loading\" — `refetch()` triggered an explicit re-subscribe\n * - \"error\" — subscribe failed or server sent `sub:gone`\n */\nexport type LiveQueryStatus =\n | \"connecting\"\n | \"success\"\n | \"stale\"\n | \"loading\"\n | \"error\";\n\nexport interface LiveQueryState<T> {\n data: T | undefined;\n status: LiveQueryStatus;\n error: Error | null;\n}\n\nexport interface LiveQueryHandle<T> {\n /** Get current cached data (legacy convenience getter). */\n get(): T | undefined;\n /** Snapshot of `{ data, status, error }`. Stable between updates. */\n getState(): LiveQueryState<T>;\n /**\n * Listen for state changes (data, status or error). Returns an\n * unsubscribe function that removes only this listener — the underlying\n * server subscription stays alive as long as any listener is attached.\n *\n * Suitable for `useSyncExternalStore`.\n */\n onState(listener: () => void): () => void;\n /** Listen for data-only changes. Returns listener-removal fn. */\n onData(callback: (data: T) => void): () => void;\n /** Force a re-subscribe round-trip. */\n refetch(): void;\n /** Unsubscribe from the server (drops listeners too). */\n unsubscribe(): void;\n}\n\nexport interface RPCSuccessResponse<TResult> {\n success: true;\n data: TResult;\n}\n\nexport interface RPCErrorResponse {\n success: false;\n error: string;\n code: string;\n}\n\nexport type RPCResponse<TResult> = RPCSuccessResponse<TResult> | RPCErrorResponse;\n\n// ─── Client Implementation ───────────────────────────────────────────────────\n\n/**\n * Create a fully typed RPC client with WebSocket transport.\n */\nexport function createClient<\n AppRouter extends Router<Record<string, AnyProcedure<TContext>>, TContext>,\n TContext = unknown,\n TProcedures extends Record<string, AnyProcedure<TContext>> = AppRouter[\"procedures\"],\n>(options: ClientOptions): ClientFromProcedures<TProcedures, TContext> & WSClientMethods {\n const wsClient = new WsClientManager({\n url: options.url,\n token: options.token,\n reconnectionTimeout: options.reconnectionTimeout,\n reconnect: options.reconnect,\n heartbeat: options.heartbeat,\n throttle: options.throttle,\n logger: options.logger,\n logLevel: options.logLevel,\n onEvent: options.onEvent,\n getTraceparent: options.getTraceparent,\n onConnect: options.onConnect,\n onDisconnect: options.onDisConnect,\n });\n\n // Stable cross-component dedup. Within a single session there is at most\n // one logical user, so `(procedure, stableStringify(input))` uniquely\n // identifies a live query on the client. The server-canonical subId\n // (which may fold in per-procedure `queryIdentity` overrides) is fetched\n // from the subscribe response and stored on the handle for debugging.\n const liveQueryHandles = new Map<string, LiveQueryHandleImpl<unknown>>();\n\n function liveQueryDedupKey(procedure: string, input: string): string {\n return `${procedure}::${input}`;\n }\n\n const cache = new Map();\n\n const client = new Proxy({} as ClientFromProcedures<TProcedures, TContext> & WSClientMethods, {\n get(_target, procedureName) {\n if (typeof procedureName !== \"string\") {\n return undefined;\n }\n\n // Special methods on the client itself\n switch (procedureName) {\n case \"connect\":\n return () => wsClient.connect();\n case \"disconnect\":\n return () => wsClient.disconnect();\n case \"isReady\":\n return () => wsClient.isReady();\n case \"isAuthenticated\":\n return () => wsClient.isAuthenticated();\n case \"isAnonymouse\":\n return () => wsClient.isAnonymouse();\n case \"getAuthUserId\":\n return () => wsClient.getAuthUserId();\n case \"onState\":\n return (listener: (s: ClientPublicState) => void) => wsClient.onState(listener);\n case \"getPublicState\":\n return () => wsClient.getPublicState();\n }\n\n const cachedProp = cache.get(procedureName);\n if (cachedProp) {\n return cachedProp;\n }\n\n const newProp = {\n query: async (input: unknown): Promise<unknown> => {\n return wsClient.call(procedureName, input);\n },\n mutate: async (input: unknown, opts?: { onSend?: () => void }): Promise<unknown> => {\n return wsClient.call(procedureName, input, opts);\n },\n action: async (input: unknown, opts?: { onSend?: () => void }): Promise<unknown> => {\n return wsClient.call(procedureName, input, opts);\n },\n liveQuery: (input: unknown, serializedInput: string): LiveQueryHandle<unknown> => {\n const dedupKey = liveQueryDedupKey(procedureName, serializedInput);\n const existing = liveQueryHandles.get(dedupKey);\n if (existing) return existing;\n\n const handle = new LiveQueryHandleImpl(\n wsClient,\n procedureName,\n input,\n serializedInput,\n () => liveQueryHandles.delete(dedupKey),\n );\n liveQueryHandles.set(dedupKey, handle);\n return handle;\n },\n };\n\n cache.set(procedureName, newProp);\n return newProp;\n },\n });\n\n return client;\n}\n\n// ─── Live Query Handle Implementation ─────────────────────────────────────────\n\n/**\n * Sync external store for one (procedure, input) live query.\n *\n * - Constructor returns immediately; the wasm-hashed server subId is computed\n * in `kickoffSubscribe()` so callers don't have to await.\n * - `onState` / `onData` ref-count their listeners; the last to leave starts\n * a 3 s grace timer before unsubscribing on the wire (so transient mounts\n * in StrictMode don't churn server-side state).\n * - Listens to `wsClient.onState`: any drop flips status to \"stale\", and the\n * manager's own `resubscribeAll()` brings us back without our help.\n * - On `sub:gone`, transitions to \"error\" and auto-retries on next ready.\n */\nclass LiveQueryHandleImpl<T> implements LiveQueryHandle<T> {\n private state: LiveQueryState<T> = {\n data: undefined,\n status: \"connecting\",\n error: null,\n };\n private stateListeners = new Set<() => void>();\n private dataListeners = new Set<(data: T) => void>();\n private unsubscribeServer: (() => void) | null = null;\n private graceTimer: ReturnType<typeof setTimeout> | null = null;\n private subId: string | null = null;\n private wsStateOff: (() => void) | null = null;\n /** Set once the disposer has fired (after the last unsubscribe). */\n private disposed = false;\n /**\n * rAF coalesce buffer for incoming `sub:update` payloads. Bursty\n * mutations (e.g. 2000 spam transfers) can fire `sub:update` faster than\n * the screen can repaint; collapsing to one commit per animation frame\n * caps live-query re-renders at vsync without skipping the latest state.\n * Tab in a background window naturally throttles down to ~1Hz via the\n * browser's own rAF policy.\n */\n private pendingData: { value: T } | null = null;\n private rafHandle: number | null = null;\n\n constructor(\n private wsClient: WsClientManager,\n private procedure: string,\n private input: unknown,\n private serializedInput: string,\n private onDispose: () => void,\n ) {\n // Track transport state so we can flip \"success\" → \"stale\" when the\n // socket drops, without losing the last successful payload.\n this.wsStateOff = wsClient.onState((next) => {\n if (next === \"ready\" && this.state.status === \"stale\") {\n // The manager's resubscribeAll() will reseed us with fresh data.\n } else if (\n this.state.status === \"success\" &&\n (next === \"disconnected\" || next === \"connecting\" || next === \"authenticating\")\n ) {\n this.setState({ status: \"stale\" });\n }\n });\n\n this.kickoffSubscribe();\n }\n\n get(): T | undefined {\n return this.state.data;\n }\n\n getState = (): LiveQueryState<T> => this.state;\n\n onState = (listener: () => void): (() => void) => {\n this.stateListeners.add(listener);\n this.cancelGrace();\n return () => {\n this.stateListeners.delete(listener);\n this.scheduleGraceUnsubscribe();\n };\n };\n\n onData(callback: (data: T) => void): () => void {\n this.dataListeners.add(callback);\n this.cancelGrace();\n\n if (this.state.data !== undefined) {\n callback(this.state.data);\n }\n\n return () => {\n this.dataListeners.delete(callback);\n this.scheduleGraceUnsubscribe();\n };\n }\n\n refetch(): void {\n if (this.disposed) return;\n\n this.setState({ status: \"loading\", error: null });\n void this.kickoffSubscribe();\n }\n\n unsubscribe(): void {\n if (this.disposed) return;\n\n this.disposed = true;\n this.cancelGrace();\n\n if (this.rafHandle !== null && this.rafHandle !== -1 && typeof cancelAnimationFrame === \"function\") {\n cancelAnimationFrame(this.rafHandle);\n }\n this.rafHandle = null;\n this.pendingData = null;\n\n if (this.unsubscribeServer) {\n try {\n this.unsubscribeServer();\n } catch (e) {\n console.error(\"[LiveQuery] unsubscribe threw:\", e);\n }\n this.unsubscribeServer = null;\n }\n\n if (this.wsStateOff) {\n this.wsStateOff();\n this.wsStateOff = null;\n }\n\n this.stateListeners.clear();\n this.dataListeners.clear();\n this.onDispose();\n }\n\n // ─── Internal ────────────────────────────────────────────────────────────\n\n private async kickoffSubscribe(): Promise<void> {\n try {\n const { subId, unsubscribe } = await this.wsClient.subscribe(\n this.procedure,\n this.input,\n this.serializedInput,\n (data) => this.handleData(data as T),\n (reason) => this.handleGone(reason),\n );\n\n if (this.disposed) {\n try { unsubscribe(); } catch { /* swallow */ }\n return;\n }\n\n this.subId = subId;\n this.unsubscribeServer = unsubscribe;\n } catch (err) {\n if (this.disposed) return;\n\n const e = err instanceof Error ? err : new Error(String(err));\n this.setState({ status: \"error\", error: e });\n }\n }\n\n private handleData(data: T): void {\n if (this.disposed) return;\n // Latest snapshot wins — if a commit is already scheduled, the rAF\n // (or microtask fallback) will pick this up.\n this.pendingData = { value: data };\n if (this.rafHandle !== null) return;\n\n if (typeof requestAnimationFrame !== \"function\") {\n // Non-browser (Node tests, SSR). Microtask is the closest analog —\n // still collapses multiple updates within the same task.\n this.rafHandle = -1;\n queueMicrotask(() => {\n this.rafHandle = null;\n this.commitPendingData();\n });\n return;\n }\n\n this.rafHandle = requestAnimationFrame(() => {\n this.rafHandle = null;\n this.commitPendingData();\n });\n }\n\n private commitPendingData(): void {\n if (this.disposed) return;\n const pending = this.pendingData;\n if (!pending) return;\n this.pendingData = null;\n\n this.setState({ data: pending.value, status: \"success\", error: null });\n for (const cb of this.dataListeners) {\n try { cb(pending.value); } catch (e) { console.error(\"[LiveQuery] data listener threw:\", e); }\n }\n }\n\n private handleGone(reason: string | undefined): void {\n if (this.disposed) return;\n this.setState({\n status: \"error\",\n error: new RPCError(reason ?? \"subscription gone\", \"SUB_GONE\"),\n });\n // Auto-retry: clear the server-unsubscribe ref (it's already gone) and\n // re-issue a subscribe. The manager already replays on reconnect; this\n // handles the case where the server dropped us while the socket stayed up.\n this.unsubscribeServer = null;\n this.subId = null;\n this.kickoffSubscribe();\n }\n\n private setState(patch: Partial<LiveQueryState<T>>): void {\n const next: LiveQueryState<T> = { ...this.state, ...patch };\n if (\n next.data === this.state.data &&\n next.status === this.state.status &&\n next.error === this.state.error\n ) {\n return;\n }\n this.state = next;\n for (const l of this.stateListeners) {\n try { l(); } catch (e) { console.error(\"[LiveQuery] state listener threw:\", e); }\n }\n }\n\n private cancelGrace(): void {\n if (this.graceTimer) {\n clearTimeout(this.graceTimer);\n this.graceTimer = null;\n }\n }\n\n private scheduleGraceUnsubscribe(): void {\n if (this.stateListeners.size > 0 || this.dataListeners.size > 0) return;\n\n this.cancelGrace();\n this.graceTimer = setTimeout(() => {\n this.graceTimer = null;\n if (this.stateListeners.size === 0 && this.dataListeners.size === 0) {\n this.unsubscribe();\n }\n }, 3000);\n }\n}\n\n\n/**\n * WebSocket client methods.\n */\nexport interface WSClientMethods {\n /** Connect to the WebSocket server */\n connect(): Promise<void>;\n /** Disconnect from the server */\n disconnect(): void;\n /** Check if connected and authenticated */\n isReady(): boolean;\n /** Check if authenticated */\n isAuthenticated(): boolean;\n /** Check if anonymouse */\n isAnonymouse(): boolean;\n /**\n * The authenticated user id reported by the server on `auth:success`, or\n * null when anonymous/unauthenticated. This is the auth-tier identity,\n * NOT the server-side `queryIdentity` (which is the cache/subscription\n * segmentation key and is not exposed to the client).\n */\n getAuthUserId(): string | null;\n /** Subscribe to client connection state transitions. Returns unsubscribe fn. */\n onState(listener: (s: ClientPublicState) => void): () => void;\n /** Snapshot of the current public state. */\n getPublicState(): ClientPublicState;\n}\n\n/**\n * Client type from procedures.\n * Filters internal procedures and creates appropriate methods.\n * Query procedures get an additional liveQuery() method for real-time subscriptions.\n */\nexport type ClientFromProcedures<\n TProcedures extends Record<string, AnyProcedure<TContext>>,\n TContext = unknown,\n> = {\n [K in keyof TProcedures as IsInternal<TProcedures[K]> extends true ? never : K]:\n TProcedures[K] extends { procedureType: \"query\" }\n ? {\n query: (input: InputOf<TProcedures[K]>) => Promise<OutputOf<TProcedures[K]>>;\n liveQuery: (input: InputOf<TProcedures[K]>) => LiveQueryHandle<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"mutation\" }\n ? { mutate: (input: InputOf<TProcedures[K]>, opts?: CallOptions) => Promise<OutputOf<TProcedures[K]>> }\n : TProcedures[K] extends { procedureType: \"action\" }\n ? { action: (input: InputOf<TProcedures[K]>, opts?: CallOptions) => Promise<OutputOf<TProcedures[K]>> }\n : never;\n };\n\n/**\n * Optional call options forwarded to the transport layer.\n */\nexport interface CallOptions {\n /**\n * Fired the first time the request leaves the queue and is actually\n * sent on the wire. If the call was already sent immediately (client\n * was ready at call time), this is invoked synchronously.\n *\n * Used by `useMutation` to flip `state` from \"queued\" → \"loading\".\n */\n onSend?: () => void;\n}\n","import type { ActionCtx, AnyProcedure, CallerObservability, HandlerLogger, InputOf, JobCtx, MutationCtx, OutputOf, ParentCtx, QueryCtx } from \"../router/procedure\";\nimport { primaryOnlyConn, parentConn } from \"../router/procedure\";\nimport { getContextRegistry, type Router } from \"../router/router\";\nimport type { SupaliveDb } from \"../db/realtime_db\";\nimport { LiveResult } from \"../db/types_db\";\nimport type { ObjectStorage } from \"../storage\";\nimport type { JobClient } from \"../jobs/scheduler\";\nimport { DbReader, TxContext } from \".\";\nimport { resolveReadRouting, makeConnRouting } from \"../db/read-routing\";\n\nexport interface CallerOptions<TContext = unknown> {\n isInternal?: boolean;\n inContext: string;\n}\n\n/**\n * Run a standalone (non-transaction-joining) server-side query with replica\n * routing that DEFAULTS to the primary.\n *\n * Unlike a client one-shot query, the internal caller has no client `minTs`\n * high-water to gate replica staleness — a caller that just performed an\n * internal write and then reads back would silently read a lagging replica. So\n * the safe default here is the PRIMARY; a handler can still opt a heavy\n * reporting read onto the replica with `ctx.useReplicaConn()` (and force back\n * with `ctx.usePrimaryConn()` / `{ readOwnWrite: true }`). The connection knobs\n * use the SAME shared routing rule as the one-shot path ({@link resolveReadRouting}\n * / {@link makeConnRouting}) — the only difference is the DEFAULT connection\n * (primary here, replica-if-caught-up there). With no minTs floor, an explicit\n * `useReplicaConn()` resolves to the replica whenever one is configured. With no\n * replica configured every branch resolves to the primary.\n */\nasync function runRoutedCallerQuery<TContext>(\n db: SupaliveDb,\n serverCtx: TContext | undefined,\n fn: (ctx: QueryCtx<TContext>) => Promise<unknown>,\n log: HandlerLogger,\n): Promise<unknown> {\n const primaryTs = await db.impl.getLatestSnapshotTimestamp();\n // No client high-water on the internal caller → floor 0. The default read\n // still stays on the primary below; the floor only bounds an explicit\n // ctx.useReplicaConn() opt-in.\n const routing = await resolveReadRouting(db, primaryTs, 0n);\n\n const res = await db.queryInternalOn(\n { impl: routing.primaryImpl, beginTs: routing.primaryTs },\n async (dbCtx) => {\n const ctx: QueryCtx<TContext> = {\n db: dbCtx,\n serverCtx,\n log,\n ...makeConnRouting(dbCtx, routing),\n };\n return fn(ctx);\n },\n );\n return res.data;\n}\n\n/**\n * Create a server-side caller that bypasses HTTP/WebSocket.\n * \n * For in-memory calls, input validation is skipped (data is already a JS object).\n * Query procedures also get a liveQuery() method for server-side use.\n */\nexport function createCaller<\n AppRouter extends Router<Record<string, AnyProcedure<TContext>>, TContext>,\n TContext = unknown,\n TProcedures extends Record<string, AnyProcedure<TContext>> = AppRouter[\"procedures\"],\n>(options: CallerOptions<any>): CallerFromProcedures<TProcedures, TContext> {\n const { isInternal = true, inContext } = options;\n const registry = getContextRegistry(inContext ?? \"default\");\n const cache = new Map();\n let db: SupaliveDb;\n // Side-effect services exposed to called ACTION handlers as `ctx.storage` /\n // `ctx.scheduler` (matches the server's action context). Set via `init`.\n let storage: ObjectStorage;\n let scheduler: JobClient;\n // Observability from `init`: standalone internal calls log under the\n // configured logger as a per-procedure child. Required — a caller without a\n // running server (e.g. a CLI seed) passes a console-backed shim.\n let obs: CallerObservability;\n\n return new Proxy({} as CallerFromProcedures<TProcedures, TContext>, {\n get(_target, procedureName) {\n if (typeof procedureName !== \"string\") {\n return undefined;\n }\n\n if (procedureName == \"init\") {\n return async (config: CallerClientInitConfig) => {\n db = config.db;\n storage = config.storage;\n scheduler = config.scheduler;\n obs = config.obs;\n };\n }\n\n // Per-call logger for a standalone internal invocation: a child of the\n // caller's logger bound to this procedure. (Nested runQuery/runMutation\n // inherit the parent handler's own log instead.)\n const callerLog = (): HandlerLogger =>\n obs.lazyChildLogger({ caller: \"internal\", procedure: procedureName });\n\n const procedure = registry.getProcedure(procedureName);\n if (!procedure) {\n throw new Error(`Procedure \"${procedureName}\" not found`);\n }\n\n if (registry.isInternalProcedure(procedureName) && !isInternal) {\n throw new Error(`Procedure \"${procedureName}\" is internal only`);\n }\n\n // Helper to validate input only if it's a string (serialized data)\n // For in-memory calls, skip validation for performance\n const validateInput = (input: unknown): unknown => {\n // Skip validation for non-string inputs (already objects)\n if (typeof input !== \"string\") {\n return input;\n }\n // Only parse if it's a string (e.g., from HTTP body)\n return procedure.inputSchema.parse(input);\n };\n\n const cachedProp = cache.get(procedureName);\n if (cachedProp) {\n return cachedProp;\n }\n\n const newProp = {\n query: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"query\") {\n throw new Error(`Procedure \"${procedureName}\" is not a query`);\n }\n const validatedInput = validateInput(input);\n // Standalone server-side read: defaults to primary, opt into the\n // replica via ctx.useReplicaConn() (see runRoutedCallerQuery).\n return runRoutedCallerQuery(db, serverCtx, (ctx) =>\n (procedure.fn as any)(ctx, validatedInput),\n callerLog(),\n );\n },\n runQuery: async (input: unknown, parentCtx: ParentCtx<TContext>): Promise<unknown> => {\n if (procedure.type !== \"query\") {\n throw new Error(`Procedure \"${procedureName}\" is not a query`);\n }\n const validatedInput = validateInput(input);\n\n // Query/mutation parent: reuse the parent's live reader so the\n // nested query runs at the SAME snapshot (parent beginTs) and its\n // reads accumulate straight into the parent's readSet — that's what\n // makes the parent re-run/conflict on concurrent changes the nested\n // query observed. A DbWriter parent additionally sees its own\n // pending writes (reads-your-writes).\n //\n // Uses `parentConn` (not `primaryOnlyConn`): the connection is\n // inherited from the parent transaction, which may be the PRIMARY\n // or the REPLICA (whatever the parent query was routed to). The\n // routing knobs are inert here because a nested query cannot rebind\n // its parent's connection — see parentConn's doc.\n if (parentCtx.db instanceof TxContext) {\n const ctx: QueryCtx = { db: parentCtx.db as DbReader, serverCtx: parentCtx.serverCtx, log: parentCtx.log, ...parentConn };\n return (procedure.fn as any)(ctx, validatedInput);\n }\n\n // Action parent: no live transaction — `parentCtx.db` is the\n // SupaliveDb, so open a fresh read snapshot for the nested query.\n // Defaults to primary; the handler may opt onto the replica.\n return runRoutedCallerQuery(\n parentCtx.db as SupaliveDb,\n parentCtx.serverCtx,\n (ctx) => (procedure.fn as any)(ctx, validatedInput),\n parentCtx.log,\n );\n },\n mutate: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"mutation\") {\n throw new Error(`Procedure \"${procedureName}\" is not a mutation`);\n }\n const validatedInput = validateInput(input);\n const res = await db.mutation(async (dbCtx) => {\n const ctx: MutationCtx = { db: dbCtx, serverCtx: serverCtx, log: callerLog(), ...primaryOnlyConn };\n return (procedure.fn as any)(ctx, validatedInput);\n });\n return res.result;\n },\n // Runs the mutation as an INDEPENDENT sub-transaction: its own\n // snapshot, its own commit. It inherits the parent's readSet\n // (`appendInternalReadSet`) so the sub-tx's OCC validation also\n // covers what the parent read — if any of those rows changed, the\n // sub-tx conflicts and (by default) throws up into the parent.\n //\n // Two consequences the caller must plan for:\n // 1. It commits on its own the moment it succeeds, so it is NOT\n // rolled back if the parent later fails.\n // 2. When called from a parent mutation, the parent runs under the\n // OCC retry policy — on a parent conflict the whole handler\n // re-executes, so this sub-mutation runs again. Make it\n // idempotent (e.g. check-then-insert) if that matters.\n runMutation: async (input: unknown, parentCtx: ParentCtx<TContext>): Promise<unknown> => {\n if (procedure.type !== \"mutation\") {\n throw new Error(`Procedure \"${procedureName}\" is not a mutation`);\n }\n const validatedInput = validateInput(input);\n const runDb = parentCtx.db instanceof TxContext ? db : parentCtx.db;\n\n const res = await runDb.mutation(async (dbCtx) => {\n const ctx: MutationCtx = { db: dbCtx, serverCtx: parentCtx.serverCtx, log: parentCtx.log, ...primaryOnlyConn };\n if (parentCtx.db instanceof TxContext) {\n ctx.db.appendInternalReadSet(parentCtx.db.internalGetReadSet());\n }\n return (procedure.fn as any)(ctx, validatedInput);\n });\n return res.result;\n },\n action: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"action\") {\n throw new Error(`Procedure \"${procedureName}\" is not an action`);\n }\n const validatedInput = validateInput(input);\n const ctx: ActionCtx = { db, serverCtx: serverCtx, log: callerLog(), storage, scheduler };\n return (procedure.fn as any)(ctx, validatedInput);\n },\n // Run a job procedure in-process (bypassing the HTTP scheduler\n // path). Used by the server's job dispatcher and by tests. Jobs\n // are internal, so this is only reachable from an internal caller.\n run: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"job\") {\n throw new Error(`Procedure \"${procedureName}\" is not a job`);\n }\n const validatedInput = validateInput(input);\n const ctx: JobCtx = { db, serverCtx: serverCtx, log: callerLog() };\n return (procedure.fn as any)(ctx, validatedInput);\n },\n // Query procedures can use liveQuery() for server-side use\n liveQuery: procedure.type === \"query\"\n ? async (input: unknown, serverCtx: TContext) => {\n const validatedInput = validateInput(input);\n return db.liveQuery(async (dbCtx) => {\n const ctx: QueryCtx = { db: dbCtx, serverCtx: serverCtx, log: callerLog(), ...primaryOnlyConn };\n return (procedure.fn as any)(ctx, validatedInput);\n });\n }\n : undefined,\n };\n\n cache.set(procedureName, newProp);\n return newProp;\n },\n });\n}\n\n/**\n * Caller type from procedures (includes internal procedures).\n * Query procedures also have liveQuery() for server-side use.\n */\nexport type CallerFromProcedures<TProcedures extends Record<string, AnyProcedure<TContext>>, TContext = unknown> = {\n [K in keyof TProcedures]: TProcedures[K] extends { procedureType: \"query\" }\n ? {\n query: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>;\n liveQuery: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<LiveResult<OutputOf<TProcedures[K]>>>;\n // in procedure call other procedure\n runQuery: (input: InputOf<TProcedures[K]>, ctx: ParentCtx<TContext>) => Promise<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"mutation\" }\n ? {\n mutate: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>,\n runMutation: (input: InputOf<TProcedures[K]>, ctx: ParentCtx<TContext>) => Promise<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"action\" }\n ? {\n // To call other procedures from an action, use the target procedure's\n // own `.runQuery` / `.runMutation`, passing the action's ctx as the\n // parent (its `db` is a SupaliveDb). An action isn't itself runnable as\n // a nested query/mutation, so no runQuery/runMutation here.\n action: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"job\" }\n ? {\n // Invoke a job in-process. The server's HTTP dispatcher uses this after\n // verifying the scheduler webhook; also handy in tests.\n run: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>;\n }\n : never;\n} & {\n init: (config: CallerClientInitConfig) => Promise<void>;\n};\n\nexport type CallerClientInitConfig = {\n db: SupaliveDb;\n storage: ObjectStorage;\n scheduler: JobClient;\n /**\n * Observability so standalone internal calls log under the configured logger\n * (a per-procedure child). Required — a caller with no running server (e.g. a\n * CLI seed) passes a console-backed shim. Nested runQuery/runMutation always\n * inherit the parent handler's log instead.\n */\n obs: CallerObservability;\n};\n\n/**\n * Caller type from router.\n */\nexport type CallerFromRouter<TRouter extends Router<any, any>> = CallerFromProcedures<TRouter[\"procedures\"]>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/db/query_definition.ts","../../../src/client/sub_key.ts","../../../src/client/ws_client_manager.ts","../../../src/client/client.ts","../../../src/client/caller_client.ts"],"sourcesContent":["import type { DbReader } from \"./context\";\n\n// ─── Core type ────────────────────────────────────────────────────────────────\n\n/**\n * A named, typed query definition produced by defineQuery().\n * Carries the query function at runtime and the name/params/result types\n * at compile time — making it the single source of truth for both the\n * server-side registry and the client-side RPC.\n */\nexport interface QueryDefinition<\n TName extends string,\n TParams,\n TResult,\n> {\n readonly _type: \"QueryDefinition\";\n readonly name: TName;\n readonly fn: (ctx: DbReader, params: TParams) => Promise<TResult>;\n}\n\n// ─── Type helpers ─────────────────────────────────────────────────────────────\n\nexport type AnyQueryDef = QueryDefinition<string, any, any>;\n\n/** Extract the params type from a QueryDefinition. */\nexport type ParamsOf<D extends AnyQueryDef> =\n D extends QueryDefinition<any, infer P, any> ? P : never;\n\n/** Extract the result type from a QueryDefinition. */\nexport type ResultOf<D extends AnyQueryDef> =\n D extends QueryDefinition<any, any, infer R> ? R : never;\n\n/**\n * Convert a readonly tuple of QueryDefinitions into a Record keyed by their\n * name literals. This is what makes ClientRpc fully type-safe: each key in\n * the map carries its own TParams and TResult rather than collapsing to `any`.\n *\n * Example:\n * const defs = [projectBoardQuery, teamMembersQuery] as const;\n * type Map = DefsToMap<typeof defs>;\n * // → { projectBoard: typeof projectBoardQuery; teamMembers: typeof teamMembersQuery }\n */\nexport type DefsToMap<T extends readonly AnyQueryDef[]> = {\n [D in T[number] as D[\"name\"]]: D;\n};\n\n// ─── Global auto-registry ─────────────────────────────────────────────────────\n//\n// defineQuery() registers every definition here at module-load time.\n// The server can call registry.defineAll() to pick up all of them without\n// an explicit defines([...]) call — as long as all query files are imported.\n\nconst _globalDefs = new Map<string, AnyQueryDef>();\n\n/** For test teardown only — clears the global map between suites. */\nexport function _resetGlobalDefs(): void {\n _globalDefs.clear();\n}\n\nexport function _getGlobalDefs(): ReadonlyMap<string, AnyQueryDef> {\n return _globalDefs;\n}\n\n// ─── defineQuery ──────────────────────────────────────────────────────────────\n\n/**\n * Create a named, typed query definition.\n *\n * The definition is automatically registered in a module-level global so\n * the server can call registry.defineAll() instead of listing every query\n * explicitly. On the client, pass the definition to createClientRpc() to\n * get a fully typed subscribe() call.\n *\n * Usage:\n * export const projectBoardQuery = defineQuery(\n * \"projectBoard\",\n * async (ctx, params: { projectId: string }) => {\n * return ctx.query(Tasks)\n * .select()\n * .where(f => f.eq(\"projectId\", params.projectId))\n * .get();\n * }\n * );\n */\nexport function defineQuery<TName extends string, TParams, TResult>(\n name: TName,\n fn: (ctx: DbReader, params: TParams) => Promise<TResult>,\n): QueryDefinition<TName, TParams, TResult> {\n if (_globalDefs.has(name)) {\n throw new Error(\n `[defineQuery] \"${name}\" is already defined. ` +\n `Each query must have a unique name across the whole application.`,\n );\n }\n const def: QueryDefinition<TName, TParams, TResult> = {\n _type: \"QueryDefinition\",\n name,\n fn,\n };\n _globalDefs.set(name, def);\n return def;\n}\n","// Compact, client-side local key for subscription dedup.\n//\n// Lives in its own file so the `hash-wasm` import is reachable only by the\n// transport layer that needs it (`ws_client_manager`) — bundlers that\n// tree-shake `@supalive/core/client` can drop wasm if a consumer never\n// touches subscriptions.\n//\n// The key is local to this client process: the server uses its own canonical\n// subId (computed with `queryIdentity` applied) and ships it back on the\n// subscribe response. We only need a fixed-size handle for in-process dedup\n// and for storing on the Subscription record.\n\nimport { sha1 } from \"hash-wasm\";\nimport { stableStringify } from \"../helper\";\n\n/**\n * Compute a compact local key for a `(procedure, input)` pair.\n *\n * Output shape: `procedureName#16-hex-chars`.\n *\n * - `procedureName#` prefix keeps DevTools / log output legible.\n * - 16 hex chars = 64 bits of SHA-1. Collision probability over N concurrent\n * subscriptions is ~ N²/2⁶⁵ — comfortable up to ~10k subs per session.\n *\n * Async because `hash-wasm` lazily inits the wasm module on first call; the\n * cost is paid once per process. Subsequent calls are effectively free.\n */\nexport async function computeLocalSubKey(procedure: string, input: string): Promise<string> {\n const hex = await sha1(`${procedure}::${input}`);\n return `${procedure}#${hex.slice(0, 16)}`;\n}\n\n/**\n * Trigger the one-time `hash-wasm` init off the critical path so the first\n * real `subscribe` doesn't pay the ~5–10ms wasm-bootstrap cost. Safe to call\n * any number of times; resolves once the module is ready.\n */\nexport function prewarmHasher(): Promise<unknown> {\n return sha1(\"\");\n}\n","// ─── Client WebSocket Manager ─────────────────────────────────────────────────\n//\n// Manages a WebSocket connection to the server and handles:\n// - Connection lifecycle (connect, auth, reconnect)\n// - Connection ID management with localStorage persistence\n// - Request/response matching via request IDs\n// - Subscription management with callbacks\n// - Message encoding/decoding\n// - Queued calls when not connected (FIFO, flushed on reconnect)\n// - Pending in-flight calls are re-queued (not rejected) on transport drop\n// so they re-issue on the next successful connect.\n// - Auto re-subscribe of live queries after reconnect\n// - Passive server-inactivity watchdog: the client never sends a ping; the\n// server emits `{type:\"ping\"}` while idle and we close+reconnect after\n// `serverInactivityMs` of total silence.\n//\n// Subscription identity is server-driven: the client sends `subscribe` with\n// (procedure, input) and the server replies with the canonical `subId` on\n// the response.\n\nimport type {\n ResponseMessage,\n SubscriptionUpdateMessage,\n SubscriptionGoneMessage,\n ClientMessage,\n} from \"../db/types_client_rpc\";\nimport { ServerMessageSchema } from \"../db/types_client_rpc\";\nimport { computeLocalSubKey, prewarmHasher } from \"./sub_key\";\nimport { supaliveStringify } from \"../helper\";\n\n// ─── Typed WebSocket surface ──────────────────────────────────────────────────\n//\n// A minimal interface common to the browser `WebSocket` and the Node `ws`\n// package, so the manager can be fully typed without `any` and run in both\n// environments.\n\ntype WSEventHandler<E> = ((event: E) => void) | null;\n\nexport interface WSLike {\n readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n onopen: WSEventHandler<unknown>;\n onmessage: WSEventHandler<{ data: string | { toString(): string } }>;\n onclose: WSEventHandler<unknown>;\n onerror: WSEventHandler<unknown>;\n}\n\nexport interface WSConstructor {\n new(url: string): WSLike;\n}\n\n// readyState 1 = OPEN in both environments.\nconst WS_OPEN = 1;\n\nexport class RPCError extends Error {\n code?: string;\n constructor(message: string, code?: string) {\n super(message);\n this.name = \"RPCError\";\n this.code = code;\n }\n}\n\n// ─── Logging ────────────────────────────────────────────────────────────────\n\nexport type ClientLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"silent\";\n\n/** Minimal logger sink the client writes lifecycle/diagnostic lines to. Pass a\n * custom implementation via {@link WSClientOptions.logger} to route logs into\n * your app's logger; otherwise a console sink gated by\n * {@link WSClientOptions.logLevel} is used. */\nexport interface ClientLogger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\n/** A structured lifecycle event, for feeding client metrics without parsing logs. */\nexport interface ClientEvent {\n type:\n | \"connecting\"\n | \"connected\"\n | \"disconnected\"\n | \"reconnecting\"\n | \"error\";\n /** Reason/detail (e.g. reconnect trigger, close reason). */\n detail?: string;\n /** Reconnect attempt number, when `type === \"reconnecting\"`. */\n attempt?: number;\n}\n\nconst LEVEL_ORDER: Record<Exclude<ClientLogLevel, \"silent\">, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\n/** Console-backed logger that drops anything below `level`. `\"silent\"` mutes all. */\nexport function createConsoleLogger(level: ClientLogLevel = \"warn\"): ClientLogger {\n const enabled = (l: Exclude<ClientLogLevel, \"silent\">): boolean =>\n level !== \"silent\" && LEVEL_ORDER[l] >= LEVEL_ORDER[level as Exclude<ClientLogLevel, \"silent\">];\n const noop = (): void => {};\n return {\n debug: enabled(\"debug\") ? (m, ...a) => console.debug(`[WSClient] ${m}`, ...a) : noop,\n info: enabled(\"info\") ? (m, ...a) => console.info(`[WSClient] ${m}`, ...a) : noop,\n warn: enabled(\"warn\") ? (m, ...a) => console.warn(`[WSClient] ${m}`, ...a) : noop,\n error: enabled(\"error\") ? (m, ...a) => console.error(`[WSClient] ${m}`, ...a) : noop,\n };\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeartbeatOptions {\n /**\n * Close + reconnect when no server message arrives for this long. The\n * server emits `{type:\"ping\"}` every ~15s of channel idle, so anything\n * larger than ~30s leaves comfortable headroom. Default 60_000.\n */\n serverInactivityMs?: number;\n}\n\nexport interface SendThrottleOptions {\n /** Yield the event loop every N `call()` invocations. 0 disables. Default 100. */\n everyN?: number;\n /** Length of the yield in ms. Default 10. */\n delayMs?: number;\n}\n\nexport interface WSClientOptions {\n /** WebSocket URL (e.g. ws://localhost:3000/ws) */\n url: string;\n /** Auth token to send after connection */\n token?: () => Promise<string | undefined>;\n /** Optional: reconnection timeout in ms (default: 180000 = 3 minutes) */\n reconnectionTimeout?: number;\n /** Reconnection options */\n reconnect?: {\n /** Enable automatic reconnection (default: true) */\n enabled?: boolean;\n /** Max reconnection attempts (default: 5) */\n maxAttempts?: number;\n /** Delay between attempts in ms (default: 1000) */\n delay?: number;\n /** Exponential backoff multiplier (default: 1.5) */\n backoff?: number;\n };\n /** Server-inactivity watchdog. See {@link HeartbeatOptions.serverInactivityMs}. */\n heartbeat?: HeartbeatOptions;\n /**\n * Yields the event loop for `delayMs` ms after every `everyN`\n * `call()` invocations. Useful for keeping React renders / GC tasks\n * scheduled during long synchronous bursts of mutations/actions; the\n * heartbeat does not depend on this (the server drives it).\n * Default `{ everyN: 100, delayMs: 10 }`. Set `everyN: 0` to disable.\n */\n throttle?: SendThrottleOptions;\n /** Called when connection is established */\n onConnect?: () => void;\n /** Called when connection is lost */\n onDisconnect?: () => void;\n /** Called on connection error */\n onError?: (error: Error) => void;\n /**\n * Custom log sink. When set, it receives all lifecycle/diagnostic lines and\n * `logLevel` is ignored (filtering is the sink's responsibility). When unset,\n * a console sink gated by {@link logLevel} is used.\n */\n logger?: ClientLogger;\n /** Level for the default console logger. Default `\"warn\"` (quiet). */\n logLevel?: ClientLogLevel;\n /** Optional structured lifecycle events, for client-side metrics. */\n onEvent?: (event: ClientEvent) => void;\n /**\n * OpenTelemetry-agnostic trace propagation. Return the current W3C\n * `traceparent` for the active client span (e.g. from `@opentelemetry/api`'s\n * propagation) and it is sent on every `call`/`subscribe`; the server\n * continues the trace. Return `undefined` (the default — no hook) to send\n * nothing. Called per outbound request.\n */\n getTraceparent?: () => string | undefined | null;\n}\n\ninterface PendingRequest {\n /** Resolver for non-subscribe responses (call). Receives `message.data`. */\n resolve?: (value: unknown) => void;\n reject: (error: Error) => void;\n /**\n * Resolver for subscribe responses. Receives the server-returned\n * canonical `subId` (typed via the response schema — `string | undefined`).\n * When present, `handleResponse` dispatches to this instead of `resolve`,\n * so subscribe-specific bookkeeping (rekeying the parked `Subscription`\n * record from its `_pending:<reqId>` sentinel to the real subId) stays\n * local to the call site that registered the request.\n */\n subscribeResolve?: (subId: ResponseMessage[\"subId\"]) => void;\n}\n\ninterface SubscribeJoiner {\n resolve: (subId: string) => void;\n reject: (err: Error) => void;\n}\n\ninterface Subscription {\n /** Server-returned canonical id; set after the subscribe response lands. */\n subId: string | null;\n /**\n * Local identity for dedup + reconnect replay. SHA-1 of `procedure +\n * stableStringify(input)`, truncated to 64 bits and prefixed with the\n * procedure name. Fixed-size, opaque to the server. See `sub_key.ts`.\n */\n localKey: string;\n procedure: string;\n input: unknown;\n onUpdate: (data: unknown) => void;\n onGone?: (reason: string | undefined) => void;\n /**\n * Additional `subscribe()` callers that joined while this record was\n * still waiting for its first response (or for a reconnect replay).\n * Resolved by whichever `subscribeResolve` (initial or replay) ultimately\n * lands; rejected on protocol error, cancel, or per-joiner 30s timeout.\n * Event-driven — no polling.\n */\n pendingJoiners?: Set<SubscribeJoiner>;\n}\n\ninterface QueuedCall {\n procedure: string;\n input: unknown;\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n /** Notification fired when the call is finally sent (used by useMutation queued state) */\n onSend?: () => void;\n}\n\ntype ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\ntype AuthState = \"pending\" | \"unauthenticated\" | \"authenticated\" | \"anonymouse\";\n\n/**\n * Public state of the client, surfaced to consumers (React provider, etc.).\n * - \"connecting\" — initial connect or reconnect handshake in flight\n * - \"authenticating\" — connected, waiting for auth result\n * - \"ready\" — connected and auth resolved (authenticated, anonymous, or\n * explicitly failed — i.e. ready to send calls)\n * - \"disconnected\" — currently offline (may auto-retry)\n */\nexport type ClientPublicState =\n | \"disconnected\"\n | \"connecting\"\n | \"authenticating\"\n | \"ready\";\n\nexport class WsClientManager {\n private ws: WSLike | null = null;\n private options: WSClientOptions;\n private lastSuccessfulConnectionTime: number = 0;\n private reconnectionTimeout: number;\n private requestId = 0;\n private pendingRequests = new Map<string, PendingRequest>();\n /**\n * Single source of truth for live subscriptions.\n *\n * Keying:\n * - In-flight subscribe (no subId yet): `_pending:<requestId>`.\n * - Established: the server-returned canonical `subId`.\n *\n * Routing `sub:update` / `sub:gone` is O(1) (`get(subId)`). Subscribe-time\n * dedup scans `values()` for a matching `localKey` (O(N)), which is cheap\n * since subscribes happen on component mounts, not in the hot path.\n */\n private subscriptions = new Map<string, Subscription>();\n private queuedCalls: QueuedCall[] = [];\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private connectionState: ConnectionState = \"disconnected\";\n private authState: AuthState = \"pending\";\n /**\n * Authenticated user id from `auth:success`. Distinct from `queryIdentity`\n * (server-side cache/sub segmentation key); this is just \"who am I logged\n * in as\" for app display purposes. Null while unauthenticated / anonymous.\n */\n private authUserId: string | null = null;\n private explicitlyClosed = false;\n private stateListeners = new Set<(s: ClientPublicState) => void>();\n private lastPublicState: ClientPublicState = \"disconnected\";\n\n // ─── Read high-water (read-your-writes / monotonic reads) ────────────────\n //\n // The largest commit/snapshot ts this client has observed — advanced from\n // every successful `call` response's `ts` (a mutation's commitTs or a query's\n // read snapshot). Sent as `minTs` on every outbound `call` so the server only\n // routes reads to a read replica that has caught up to what we've already\n // seen. Plain integer, no signing: the server clamps it to its own primary\n // latest, so it can at most force the primary (never an error or a value\n // beyond the primary). Never decreases.\n private highWaterTs: bigint = 0n;\n\n // ─── Inactivity watchdog (passive — server drives pings) ─────────────────\n private serverInactivityMs: number;\n private lastServerMessageAt: number = 0;\n private inactivityTimer: ReturnType<typeof setInterval> | null = null;\n\n // ─── Pending connect() promise ──────────────────────────────────────────\n //\n // Tracked on `this` so paths outside the `connect()` closure (the watchdog\n // firing during the connecting window, the OS-level close handler, the\n // reconnect-exhaustion drain) can settle the caller's awaiting promise\n // rather than leaving it suspended. Each setter wraps the underlying\n // resolve/reject with a one-shot guard so we can call freely; subsequent\n // calls are no-ops.\n private pendingConnectResolve: (() => void) | null = null;\n private pendingConnectReject: ((err: Error) => void) | null = null;\n\n // ─── Send throttle ──────────────────────────────────────────────────────\n //\n // The first `throttleEveryN` calls in a quiet window dispatch directly.\n // The N+1th call flips `throttling = true` and any subsequent call is\n // parked on `queuedCalls`. A single async loop drains the queue in\n // batches of up to `throttleEveryN`, sleeping `throttleDelayMs` between\n // batches, and exits as soon as the queue drains in a single batch\n // (burst over → back to direct-dispatch mode).\n private throttleEveryN: number;\n private throttleDelayMs: number;\n private throttleCounter = 0;\n private throttling = false;\n\n // ─── Browser env listeners ──────────────────────────────────────────────\n private visibilityListener: (() => void) | null = null;\n private onlineListener: (() => void) | null = null;\n\n // ─── Logging ────────────────────────────────────────────────────────────\n private readonly log: ClientLogger;\n\n constructor(options: WSClientOptions) {\n this.log = options.logger ?? createConsoleLogger(options.logLevel ?? \"warn\");\n this.options = {\n reconnectionTimeout: 180000, // 3 minutes default\n reconnect: {\n enabled: true,\n maxAttempts: 5,\n delay: 1000,\n backoff: 1.5,\n ...options.reconnect,\n },\n ...options,\n };\n this.reconnectionTimeout = this.options.reconnectionTimeout!;\n this.serverInactivityMs = options.heartbeat?.serverInactivityMs ?? 60_000;\n this.throttleEveryN = options.throttle?.everyN ?? 100;\n this.throttleDelayMs = options.throttle?.delayMs ?? 10;\n this.installEnvironmentListeners();\n // Fire and forget: warm the wasm hash module so the first subscribe\n // isn't billed for the ~5–10ms wasm bootstrap. Swallow rejections —\n // the lazy path inside `computeLocalSubKey` will surface any real\n // wasm failure on the first actual subscribe.\n prewarmHasher().catch(() => { });\n }\n\n /**\n * Subscribe to client state transitions. Returns an unsubscribe fn.\n * The listener is called immediately with the current state.\n */\n onState(listener: (s: ClientPublicState) => void): () => void {\n this.stateListeners.add(listener);\n listener(this.computePublicState());\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n /** Current public state. */\n getPublicState(): ClientPublicState {\n return this.computePublicState();\n }\n\n /**\n * Connect to the WebSocket server.\n * Returns a promise that resolves when connected and authenticated.\n */\n async connect(): Promise<void> {\n this.explicitlyClosed = false;\n if (this.connectionState == \"connected\" || this.connectionState == \"connecting\") {\n return;\n }\n\n this.connectionState = \"connecting\";\n this.emitState();\n this.log.debug(`connecting to ${this.options.url}`);\n this.emitEvent({ type: \"connecting\", detail: this.options.url });\n\n return new Promise<void>(async (resolve, reject) => {\n // One-shot guards. Whichever side fires first nulls both slots so\n // later callers (e.g. handleClose after a successful auth) are no-ops.\n this.pendingConnectResolve = () => {\n this.pendingConnectResolve = null;\n this.pendingConnectReject = null;\n resolve();\n };\n this.pendingConnectReject = (err) => {\n this.pendingConnectResolve = null;\n this.pendingConnectReject = null;\n reject(err);\n };\n\n try {\n const WS = await getWebSocket();\n\n const url = new URL(this.options.url);\n const ws = new WS(url.toString()) as WSLike;\n this.ws = ws;\n\n ws.onopen = () => {\n // Arm the inactivity watchdog the moment the upgrade completes.\n // If the server stalls and never sends `connected`, the watchdog\n // fires at `serverInactivityMs` and forces a reconnect instead\n // of leaving the client wedged in \"connecting\" forever.\n // `handleMessage` keeps the timestamp fresh on every inbound\n // frame, including the `connected` JSON itself.\n this.startInactivityWatchdog();\n };\n\n ws.onmessage = (event) => {\n const data = typeof event.data === \"string\" ? event.data : event.data.toString();\n this.handleMessage(data);\n };\n\n ws.onclose = () => {\n this.handleClose();\n };\n\n ws.onerror = (err) => {\n const error = err instanceof Error ? err : new Error(`WebSocket error: `, {cause: err});\n this.handleError(error);\n this.pendingConnectReject?.(error);\n };\n } catch (err) {\n this.connectionState = \"disconnected\";\n this.emitState();\n this.pendingConnectReject?.(err as Error);\n }\n });\n }\n\n /**\n * Disconnect from the server.\n */\n disconnect(): void {\n this.explicitlyClosed = true;\n this.cleanup();\n const oldWs = this.ws;\n this.ws = null;\n if (oldWs) {\n oldWs.onmessage = null;\n oldWs.onerror = null;\n oldWs.onopen = null;\n oldWs.onclose = null;\n try { oldWs.close(); } catch { /* swallow */ }\n }\n this.removeEnvironmentListeners();\n this.emitState();\n }\n\n /**\n * Check if connected and authenticated.\n */\n isReady(): boolean {\n return this.connectionState == \"connected\" && this.authState != \"pending\";\n }\n\n /**\n * Check if authenticated.\n */\n isAuthenticated(): boolean {\n return this.authState == \"authenticated\";\n }\n\n /**\n * Check if anonymouse.\n */\n isAnonymouse(): boolean {\n return this.authState == \"anonymouse\";\n }\n\n /**\n * The authenticated user's id (from `auth:success`), or null when\n * unauthenticated / anonymous. Useful for \"Logged in as …\" display.\n *\n * Not to be confused with server-side `queryIdentity`, which is the\n * cache/subscription segmentation key (resolved entirely on the server).\n */\n getAuthUserId(): string | null {\n return this.authUserId;\n }\n\n /**\n * Call a procedure (query, mutation, or action).\n *\n * If the client is not ready (disconnected or still authenticating), the\n * call is FIFO-queued and dispatched on the next ready state. The promise\n * still resolves with the eventual server response.\n *\n * Pass `onSend` to be notified when the call actually leaves the queue\n * (used by `useMutation` to flip `state` from \"queued\" → \"loading\").\n */\n async call(\n procedure: string,\n input: unknown,\n options?: { onSend?: () => void },\n ): Promise<unknown> {\n // Not ready — park on the disconnect queue. flushQueuedCalls picks\n // these up when the next session reaches ready.\n if (!this.isReady()) {\n return new Promise((resolve, reject) => {\n this.queuedCalls.push({\n procedure,\n input,\n resolve,\n reject,\n onSend: options?.onSend,\n });\n if (\n !this.explicitlyClosed &&\n this.connectionState === \"disconnected\"\n ) {\n this.connect().catch(() => {/* state event reflects failure */ });\n }\n });\n }\n\n // Throttle disabled — straight dispatch.\n if (this.throttleEveryN <= 0) {\n options?.onSend?.();\n return this.dispatchCall(procedure, input);\n }\n\n // Already throttling — park and let the loop flush us in the next batch.\n if (this.throttling) {\n return new Promise((resolve, reject) => {\n this.queuedCalls.push({\n procedure,\n input,\n resolve,\n reject,\n onSend: options?.onSend,\n });\n });\n }\n\n // Direct dispatch, count toward throttle cap. When we hit the cap,\n // flip into throttling mode and kick off the drain loop.\n this.throttleCounter++;\n if (this.throttleCounter >= this.throttleEveryN) {\n this.throttling = true;\n await this.runThrottleLoop();\n }\n options?.onSend?.();\n return this.dispatchCall(procedure, input);\n }\n\n /**\n * Throttle drain loop. Each iteration: sleep `throttleDelayMs`, then\n * dispatch up to `throttleEveryN` queued items. Exits as soon as the\n * queue drains inside a single batch (burst over, back to direct\n * dispatch on the next `call()`) or the connection goes away\n * (reconnect path will restart us via `flushQueuedCalls`).\n */\n private async runThrottleLoop(): Promise<void> {\n while (this.throttling) {\n await new Promise((r) => setTimeout(r, this.throttleDelayMs));\n\n if (!this.isReady()) {\n this.throttling = false;\n this.throttleCounter = 0;\n return;\n }\n\n this.throttleCounter = 0;\n while (\n this.throttleCounter < this.throttleEveryN &&\n this.queuedCalls.length > 0\n ) {\n const item = this.queuedCalls.shift()!;\n item.onSend?.();\n this.dispatchCall(item.procedure, item.input).then(item.resolve, item.reject);\n this.throttleCounter++;\n }\n\n if (this.queuedCalls.length === 0) {\n // Drained without hitting the cap — release the brake.\n this.throttling = false;\n this.throttleCounter = 0;\n return;\n }\n // Else: hit cap, more pending — loop and wait again.\n }\n }\n\n /**\n * Issue the call against an established connection. Caller must ensure\n * `isReady()` before invoking.\n */\n private dispatchCall(procedure: string, input: unknown): Promise<unknown> {\n const id = this.generateRequestId();\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, { resolve, reject });\n\n this.send({\n type: \"call\",\n id,\n procedure,\n input,\n // Carry our read high-water so the server can safely route to a replica\n // (or fall back to primary) — omitted until we've observed anything.\n minTs: this.highWaterTs > 0n ? this.highWaterTs.toString() : undefined,\n trace: this.traceparent(),\n });\n });\n }\n\n /** Advance the read high-water from a server-returned `ts` (monotonic). */\n private advanceHighWater(ts: string | undefined): void {\n if (ts === undefined) return;\n let parsed: bigint;\n try {\n parsed = BigInt(ts);\n } catch {\n return;\n }\n if (parsed > this.highWaterTs) this.highWaterTs = parsed;\n }\n\n /**\n * Flush queued calls in FIFO order. Called once `isReady()` becomes\n * true. With the throttle enabled, dispatch the first batch\n * synchronously then hand off to the drain loop so a large\n * post-reconnect flush respects backpressure instead of dumping 10k\n * frames in one tick.\n */\n private flushQueuedCalls(): void {\n if (this.queuedCalls.length === 0) return;\n\n if (this.throttleEveryN <= 0) {\n const queue = this.queuedCalls;\n this.queuedCalls = [];\n for (const item of queue) {\n item.onSend?.();\n this.dispatchCall(item.procedure, item.input).then(item.resolve, item.reject);\n }\n return;\n }\n\n this.throttleCounter = 0;\n while (\n this.throttleCounter < this.throttleEveryN &&\n this.queuedCalls.length > 0\n ) {\n const item = this.queuedCalls.shift()!;\n item.onSend?.();\n this.dispatchCall(item.procedure, item.input).then(item.resolve, item.reject);\n this.throttleCounter++;\n }\n\n if (this.queuedCalls.length > 0) {\n this.throttling = true;\n this.runThrottleLoop();\n }\n }\n\n /**\n * Re-send `subscribe` for every existing subscription. The subId for each\n * may legitimately change (e.g. `queryIdentity` was a function of user\n * identity and the user changed during the disconnect), so we re-key the\n * unified `subscriptions` Map: snapshot every record, drop them from the\n * Map, then reinsert under a fresh `_pending:<reqId>` key. Each pending\n * request's `resolve` rekeys the parked record under the new server-issued\n * subId so subsequent `sub:update` frames route to the existing\n * `onUpdate` callback (which the React handle is still holding).\n */\n private resubscribeAll(): void {\n if (this.subscriptions.size === 0) return;\n\n const subs = Array.from(this.subscriptions.values());\n this.subscriptions.clear();\n\n for (const sub of subs) {\n const id = this.generateRequestId();\n const pendingKey = `_pending:${id}`;\n sub.subId = null;\n\n this.subscriptions.set(pendingKey, sub);\n this.pendingRequests.set(id, {\n subscribeResolve: (subId) => {\n const parked = this.subscriptions.get(pendingKey);\n if (!subId || !parked) {\n // Server didn't echo a subId, or the record was torn down\n // mid-replay (e.g. component unmounted). Drop the parked\n // entry so it can't leak.\n this.subscriptions.delete(pendingKey);\n this.rejectJoiners(parked, new RPCError(\"Subscribe response missing subId after replay\", \"PROTOCOL_ERROR\"));\n return;\n }\n // Mutate the SAME Subscription object — the LiveQueryHandleImpl\n // is still holding its `onUpdate` closure indirectly through\n // this record. Replacing the object would orphan the handle.\n parked.subId = subId;\n this.subscriptions.delete(pendingKey);\n this.subscriptions.set(subId, parked);\n // Drain joiners that joined during the parked phase.\n this.resolveJoiners(parked, subId);\n },\n reject: (err) => {\n // Keep the parked record alive on transport-level drops so the\n // next resubscribeAll replays it; purge (and reject joiners)\n // only on protocol-level errors where re-issuing would not help.\n if (!(err instanceof RPCError) || err.code !== \"DISCONNECTED\") {\n const parked = this.subscriptions.get(pendingKey);\n this.subscriptions.delete(pendingKey);\n this.rejectJoiners(parked, err);\n }\n this.log.warn(`Failed to re-subscribe ${sub.procedure}:`, err);\n },\n });\n\n this.send({\n type: \"subscribe\",\n id,\n procedure: sub.procedure,\n input: sub.input,\n trace: this.traceparent(),\n });\n }\n }\n\n /**\n * Subscribe to a live query.\n *\n * The client does not compute the subId locally — it sends the\n * (procedure, input) pair and receives the canonical subId on the\n * subscribe response. This lets the server's per-procedure\n * `queryIdentity` override be authoritative without forcing the client\n * to mirror any of the resolution logic.\n *\n * Returns the server-assigned subId and an unsubscribe function.\n */\n async subscribe(\n procedure: string,\n input: unknown,\n serializedInput: string,\n onUpdate: (data: unknown) => void,\n onGone?: (reason: string | undefined) => void,\n ): Promise<{ subId: string; unsubscribe: () => void }> {\n await this.ensureConnected();\n\n const localKey = await computeLocalSubKey(procedure, serializedInput);\n\n // Manager-level dedup: scan values for an existing subscription with\n // the same logical (procedure, input). Subscribe is a cold path (mounts,\n // not the per-message hot path), so a linear scan is the right shape\n // here in exchange for a single unified Map.\n const existing = this.findSubByLocalKey(localKey);\n if (existing) {\n existing.onUpdate = onUpdate;\n existing.onGone = onGone;\n if (existing.subId) {\n return {\n subId: existing.subId,\n unsubscribe: () => this.unsubscribeByLocalKey(localKey, existing.subId),\n };\n }\n // Subscribe response for the existing record is still in flight.\n // Park an event-driven joiner on the record — the originating\n // subscribeResolve (or the replay's, if the socket dropped first)\n // drains us when the response lands.\n return new Promise((resolve, reject) => {\n const joinerTimeout = setTimeout(() => {\n existing.pendingJoiners?.delete(joiner);\n reject(new RPCError(`Subscribe timeout: ${procedure}`, \"TIMEOUT\"));\n }, 30_000);\n \n const joiner: SubscribeJoiner = {\n resolve: (subId) => {\n clearTimeout(joinerTimeout);\n existing.pendingJoiners?.delete(joiner);\n resolve({\n subId,\n unsubscribe: () => this.unsubscribeByLocalKey(localKey, subId),\n });\n },\n reject: (err) => {\n clearTimeout(joinerTimeout);\n existing.pendingJoiners?.delete(joiner);\n reject(err);\n },\n };\n (existing.pendingJoiners ??= new Set()).add(joiner);\n });\n }\n\n const id = this.generateRequestId();\n const pendingKey = `_pending:${id}`;\n const record: Subscription = {\n subId: null,\n localKey,\n procedure,\n input,\n onUpdate,\n onGone,\n };\n this.subscriptions.set(pendingKey, record);\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n subscribeResolve: (subId) => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n\n const sub = this.subscriptions.get(pendingKey);\n if (!subId || !sub) {\n this.subscriptions.delete(pendingKey);\n const err = new RPCError(\"Subscribe response missing subId\", \"PROTOCOL_ERROR\");\n this.rejectJoiners(sub, err);\n reject(err);\n return;\n }\n\n // Re-key from pending sentinel to real subId, same object.\n sub.subId = subId;\n this.subscriptions.delete(pendingKey);\n this.subscriptions.set(subId, sub);\n resolve({\n subId,\n unsubscribe: () => this.unsubscribeByLocalKey(localKey, subId),\n });\n this.resolveJoiners(sub, subId);\n },\n reject: (err) => {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n\n // Keep the record alive on transport-level drops (DISCONNECTED)\n // so `resubscribeAll` replays it after the reconnect handshake.\n // Joiners stay attached to the parked record and are drained by\n // the replay's subscribeResolve. Purge (and reject joiners) only\n // on real protocol-level errors or the 30s timeout.\n if (!(err instanceof RPCError) || err.code !== \"DISCONNECTED\") {\n const sub = this.subscriptions.get(pendingKey);\n this.subscriptions.delete(pendingKey);\n this.rejectJoiners(sub, err);\n }\n reject(err);\n },\n });\n\n this.send({\n type: \"subscribe\",\n id,\n procedure,\n input,\n trace: this.traceparent(),\n });\n\n timeout = setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id);\n const sub = this.subscriptions.get(pendingKey);\n this.subscriptions.delete(pendingKey);\n const err = new RPCError(`Subscribe timeout: ${procedure}`, \"TIMEOUT\");\n this.rejectJoiners(sub, err);\n reject(err);\n }\n }, 30_000);\n });\n }\n\n private findSubByLocalKey(localKey: string): Subscription | undefined {\n for (const sub of this.subscriptions.values()) {\n if (sub.localKey === localKey) return sub;\n }\n return undefined;\n }\n\n /** Resolve every joiner waiting on this Subscription with the canonical subId. */\n private resolveJoiners(sub: Subscription, subId: string): void {\n if (!sub.pendingJoiners) return;\n // Snapshot before iterating — each joiner.resolve() mutates the set.\n const snapshot = Array.from(sub.pendingJoiners);\n for (const j of snapshot) j.resolve(subId);\n }\n\n /** Reject every joiner waiting on this Subscription with the given error. */\n private rejectJoiners(sub: Subscription | undefined, err: Error): void {\n if (!sub?.pendingJoiners) return;\n const snapshot = Array.from(sub.pendingJoiners);\n for (const j of snapshot) j.reject(err);\n }\n\n private unsubscribeByLocalKey(localKey: string, subId: string | null): void {\n const sendUnsubscribe = (subId: string): void => {\n if (this.connectionState === \"connected\") {\n this.send({ type: \"unsubscribe\", subId });\n }\n }\n\n if (subId) {\n // If we have the subId, we can skip straight to the unsubscribe message\n this.subscriptions.delete(subId);\n sendUnsubscribe(subId);\n return;\n }\n\n // We don't know whether the entry is parked under a pending sentinel or\n // a real subId, so iterate. O(N) is fine here: unsubscribe runs at\n // most once per handle teardown.\n for (const [key, sub] of this.subscriptions.entries()) {\n if (sub.localKey !== localKey) continue;\n\n this.subscriptions.delete(key);\n // Cancel any callers still waiting on the in-flight subscribe.\n this.rejectJoiners(sub, new RPCError(\"Subscription cancelled before response\", \"CANCELLED\"));\n sendUnsubscribe(sub.subId!);\n return;\n }\n }\n\n // ─── Internal Handlers ──────────────────────────────────────────────────────\n\n private async handleMessage(dataValue: string): Promise<void> {\n // ANY inbound frame counts as a liveness signal — the bytes arrived,\n // therefore the socket is alive. Done before parse/validate so that\n // even malformed frames still refresh the watchdog (it's the OS\n // delivery we care about, not the JSON correctness).\n this.lastServerMessageAt = Date.now();\n\n // Validate against the full server-message union rather than trusting a\n // naked cast. A protocol-version skew (or a man-in-the-middle proxy\n // that mangles the payload) would otherwise blow up downstream with a\n // runtime TypeError on a missing field; better to drop the message\n // with a clear diagnostic.\n let data: unknown = undefined;\n try {\n data = JSON.parse(dataValue);\n } catch {\n this.log.warn(\"Failed to parse server message as JSON:\", dataValue);\n }\n\n const parsed = ServerMessageSchema.safeParse(data);\n\n if (!parsed.success) {\n this.log.warn(\"Invalid server message:\", parsed.error.issues, data);\n return;\n }\n const message = parsed.data;\n\n switch (message.type) {\n case \"connected\": {\n this.connectionState = \"connected\";\n this.reconnectAttempts = 0;\n // Track successful connection time for reconnection logic\n this.lastSuccessfulConnectionTime = Date.now();\n this.log.info(\"connected\");\n this.emitEvent({ type: \"connected\" });\n // (Inactivity watchdog already started in `ws.onopen`. The top of\n // handleMessage refreshed `lastServerMessageAt` for this very frame.)\n\n // Send auth if token provided\n const token = await this.options.token?.();\n if (token) {\n this.authState = \"pending\";\n this.emitState();\n this.send({ type: \"auth\", data: { token } });\n } else {\n this.authState = \"anonymouse\";\n this.options.onConnect?.();\n this.emitState();\n this.afterReady();\n this.pendingConnectResolve?.();\n }\n break;\n }\n\n case \"auth:success\":\n this.authState = \"authenticated\";\n this.authUserId = message.userId;\n this.options.onConnect?.();\n this.emitState();\n this.afterReady();\n this.pendingConnectResolve?.();\n break;\n case \"auth:failed\":\n // The WebSocket itself is fine — only auth rejected. Resolve the\n // connect promise so the app can decide what to do via\n // `isAuthenticated()` / `getPublicState()`. Avoids flooding the\n // console for anonymous-friendly apps that simply continue without\n // a token.\n this.authState = \"unauthenticated\";\n this.authUserId = null;\n this.options.onConnect?.();\n this.emitState();\n this.afterReady();\n this.pendingConnectResolve?.();\n break;\n\n case \"response\":\n this.handleResponse(message);\n break;\n\n case \"sub:update\":\n this.handleSubscriptionUpdate(message);\n break;\n\n case \"sub:gone\":\n this.handleSubscriptionGone(message);\n break;\n\n case \"ping\":\n // The `lastServerMessageAt` update at the top of `handleMessage`\n // is the only thing this signal exists for. No reply.\n break;\n\n default: {\n // Exhaustive over `ServerMessageSchema`; reaching here means the\n // schema was extended without updating this switch.\n const _exhaustive: never = message;\n this.log.warn(\"Unhandled server message type:\", _exhaustive);\n }\n }\n }\n\n /**\n * Called once the client transitions to a \"ready\" state on a fresh\n * connection. Replays live-query subscriptions and flushes any queued\n * calls in FIFO order.\n */\n private afterReady(): void {\n this.resubscribeAll();\n this.flushQueuedCalls();\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id);\n if (!pending) return;\n\n this.pendingRequests.delete(message.id);\n\n if (message.success) {\n if (pending.subscribeResolve) {\n pending.subscribeResolve(message.subId);\n } else {\n // Fold the observed ts into our high-water BEFORE resolving, so a caller\n // that immediately issues another read already sends the advanced minTs.\n this.advanceHighWater(message.ts);\n pending.resolve?.(message.data);\n }\n } else {\n pending.reject(new RPCError(message.error ?? \"Unknown error\", message.code));\n }\n }\n\n private handleSubscriptionUpdate(message: SubscriptionUpdateMessage): void {\n const sub = this.subscriptions.get(message.subId);\n if (sub) {\n // Advance the read high-water so a later one-shot query can't route to a\n // replica behind what this live view just showed (monotonic reads).\n this.advanceHighWater(message.ts);\n sub.onUpdate(message.data);\n }\n }\n\n /**\n * Handles a `sub:gone` message: the server no longer tracks this\n * subscription (e.g. it was fully unregistered while we were disconnected\n * from the sub-manager). Notify the handle and drop the local entry so a\n * re-subscribe is required to receive further updates.\n */\n private handleSubscriptionGone(message: SubscriptionGoneMessage): void {\n const sub = this.subscriptions.get(message.subId);\n if (!sub) return;\n\n this.subscriptions.delete(message.subId);\n try {\n sub.onGone?.(message.reason);\n } catch (e) {\n this.log.error(\"onGone listener threw:\", e);\n }\n }\n\n private handleClose(): void {\n // Idempotent: `closeAndReconnect()` calls us synchronously to fast-track\n // the reconnect, and then the OS-level `ws.onclose` fires again on the\n // discarded socket. Re-entering would double-fire `onDisconnect` and\n // double-bump `reconnectAttempts`.\n if (this.connectionState === \"disconnected\") return;\n\n this.connectionState = \"disconnected\";\n this.authState = \"pending\";\n this.ws = null;\n this.stopInactivityWatchdog();\n\n this.log.info(\"disconnected\");\n this.emitEvent({ type: \"disconnected\" });\n this.options.onDisconnect?.();\n\n // If a `connect()` promise is still in flight (we never reached\n // auth:success / anonymous-connected before the close), reject it so\n // any `await client.connect()` doesn't hang. Auto-reconnect will\n // still take over via scheduleReconnect below — this only settles\n // the user-facing promise.\n this.pendingConnectReject?.(\n new RPCError(\"Connection closed before ready\", \"DISCONNECTED\")\n );\n\n // Reject every in-flight request. These were already on the wire — the\n // server may have processed a mutation but the response frame was lost\n // when the socket dropped. Re-issuing would risk double-execution\n // (e.g. a transfer applied twice), so we surface DISCONNECTED to the\n // caller; the side effect, if it committed, is reflected via the live\n // query subscription cluster on reconnect.\n //\n // Queued calls (this.queuedCalls) are intentionally preserved and\n // flushed on the next ready state — those were never sent, so\n // re-issuing them is safe and is the user-visible \"I clicked\n // before the socket reconnected\" path. Subscribe pendings are\n // replayed via resubscribeAll in afterReady.\n for (const pending of this.pendingRequests.values()) {\n pending.reject(new RPCError(\"Connection closed\", \"DISCONNECTED\"));\n }\n this.pendingRequests.clear();\n\n this.emitState();\n\n // Attempt reconnection (unless the caller explicitly disconnected)\n if (!this.explicitlyClosed && this.options.reconnect?.enabled) {\n this.scheduleReconnect();\n }\n }\n\n private handleError(err: Error): void {\n this.log.warn(\"connection error:\", err);\n this.emitEvent({ type: \"error\", detail: err.message });\n this.options.onError?.(err);\n }\n\n // ─── Inactivity watchdog ────────────────────────────────────────────────\n //\n // The browser API does not surface ws-protocol pings to JS, and even if\n // it did, asking the JS thread to *send* a periodic ping is fragile\n // during synchronous bursts (a tight 10k mutation loop starves the\n // setInterval long enough to trigger spurious reconnects).\n //\n // The server is in charge of liveness instead: it emits `{type:\"ping\"}`\n // every `HEARTBEAT_INTERVAL_MS` of channel idle (server side). We\n // just track the wall-clock of the most recent inbound message, and if\n // it slips past `serverInactivityMs` we treat the socket as dead.\n\n private startInactivityWatchdog(): void {\n this.stopInactivityWatchdog();\n this.lastServerMessageAt = Date.now();\n // Poll at ~quarter cadence so a fresh-stale boundary fires within\n // serverInactivityMs * 1.25 of the actual silence onset. Minimum 1s.\n const tickMs = Math.max(1_000, Math.floor(this.serverInactivityMs / 4));\n this.inactivityTimer = setInterval(() => {\n if (Date.now() - this.lastServerMessageAt > this.serverInactivityMs) {\n this.closeAndReconnect(\"InactiveServer\");\n }\n }, tickMs);\n }\n\n private stopInactivityWatchdog(): void {\n if (this.inactivityTimer) {\n clearInterval(this.inactivityTimer);\n this.inactivityTimer = null;\n }\n }\n\n /**\n * Close the current WebSocket and schedule a reconnect via `handleClose`.\n *\n * Atomically swaps the old socket's event handlers to no-ops BEFORE\n * `close()` so a frame in flight at the moment of close cannot fire\n * `handleMessage` into a half-cleaned state — that was the source of\n * the \"browser says socket is alive but client says disconnected\" race.\n */\n private closeAndReconnect(reason: string): void {\n if (this.connectionState === \"disconnected\") return;\n\n this.log.warn(`closeAndReconnect: ${reason}`);\n\n const oldWs = this.ws;\n if (oldWs) {\n oldWs.onmessage = null;\n oldWs.onerror = null;\n oldWs.onopen = null;\n // Discard the close handler too — we don't want the OS-level close\n // event to re-enter handleClose; we invoke it synchronously below.\n oldWs.onclose = null;\n try { oldWs.close(4000, reason); } catch { /* swallow */ }\n }\n\n // Synchronous state transition + reconnect scheduling. Same effect\n // as the OS-level onclose path, but doesn't wait on the close\n // handshake (which can stall when the socket is half-open).\n this.handleClose();\n }\n\n private installEnvironmentListeners(): void {\n // visibilitychange → \"visible\": a backgrounded tab that just became\n // visible may have been frozen by the browser. Check staleness now\n // and reconnect immediately if the watchdog is already past due.\n if (\n typeof document !== \"undefined\" &&\n typeof document.addEventListener === \"function\" &&\n !this.visibilityListener\n ) {\n this.visibilityListener = () => {\n if (typeof document === \"undefined\") return;\n if (document.visibilityState !== \"visible\") return;\n if (this.connectionState !== \"connected\") return;\n if (Date.now() - this.lastServerMessageAt > this.serverInactivityMs) {\n this.closeAndReconnect(\"WokeUpStale\");\n }\n };\n document.addEventListener(\"visibilitychange\", this.visibilityListener);\n }\n\n // online: a flip back to online is a strong signal that any current\n // disconnected-state backoff should collapse to \"try right now.\"\n if (\n typeof window !== \"undefined\" &&\n typeof window.addEventListener === \"function\" &&\n !this.onlineListener\n ) {\n this.onlineListener = () => {\n if (this.connectionState !== \"disconnected\") return;\n if (this.explicitlyClosed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.connect().catch(() => { /* state event reflects failure */ });\n };\n window.addEventListener(\"online\", this.onlineListener);\n }\n }\n\n private removeEnvironmentListeners(): void {\n if (this.visibilityListener && typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", this.visibilityListener);\n this.visibilityListener = null;\n }\n if (this.onlineListener && typeof window !== \"undefined\") {\n window.removeEventListener(\"online\", this.onlineListener);\n this.onlineListener = null;\n }\n }\n\n // ─── Reconnection ───────────────────────────────────────────────────────────\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= (this.options.reconnect?.maxAttempts ?? 5)) {\n this.log.error(\"Max reconnection attempts reached\");\n // We're not going to try again — drain any user-facing promises\n // that would otherwise hang forever waiting for a connection that\n // never comes.\n const giveUp = new RPCError(\n \"Max reconnection attempts reached\",\n \"DISCONNECTED\",\n );\n for (const q of this.queuedCalls) q.reject(giveUp);\n this.queuedCalls = [];\n this.pendingConnectReject?.(giveUp);\n return;\n }\n\n const base =\n (this.options.reconnect?.delay ?? 1000) *\n Math.pow(this.options.reconnect?.backoff ?? 1.5, this.reconnectAttempts);\n // ±50% jitter so two tabs that disconnected at the same instant don't\n // wake the server in lockstep on reconnect.\n const delay = base + base * (Math.random() - 0.5);\n\n this.reconnectAttempts++;\n this.log.info(`reconnecting (attempt ${this.reconnectAttempts}) in ${Math.round(delay)}ms`);\n this.emitEvent({ type: \"reconnecting\", attempt: this.reconnectAttempts });\n\n this.reconnectTimer = setTimeout(() => {\n this.connect().catch((err) => {\n this.log.warn(\"Reconnection failed:\", err);\n });\n }, delay);\n }\n\n // ─── State Events ───────────────────────────────────────────────────────────\n\n private computePublicState(): ClientPublicState {\n if (this.connectionState === \"disconnected\") return \"disconnected\";\n if (this.connectionState === \"connecting\") return \"connecting\";\n // connectionState === \"connected\"\n if (this.authState === \"pending\") return \"authenticating\";\n return \"ready\";\n }\n\n private emitState(): void {\n const next = this.computePublicState();\n if (next === this.lastPublicState) return;\n\n this.lastPublicState = next;\n for (const l of this.stateListeners) {\n try { l(next); } catch (e) { this.log.error(\"state listener threw:\", e); }\n }\n }\n\n /** Fire the optional structured lifecycle event; never lets a throwing hook\n * break the client. */\n private emitEvent(event: ClientEvent): void {\n if (!this.options.onEvent) return;\n try {\n this.options.onEvent(event);\n } catch (e) {\n this.log.error(\"onEvent listener threw:\", e);\n }\n }\n\n // ─── Utilities ──────────────────────────────────────────────────────────────\n\n private async ensureConnected(): Promise<void> {\n if (this.connectionState != \"connected\") {\n await this.connect();\n }\n }\n\n private send(message: ClientMessage): void {\n if (this.ws && this.ws.readyState === WS_OPEN) {\n this.ws.send(supaliveStringify(message));\n }\n }\n\n /** Current W3C traceparent from the optional hook, or undefined. */\n private traceparent(): string | undefined {\n return this.options.getTraceparent?.() ?? undefined;\n }\n\n private cleanup(): void {\n this.connectionState = \"disconnected\";\n this.authState = \"pending\";\n this.stopInactivityWatchdog();\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n const teardown = new RPCError(\"Client disconnected\", \"DISCONNECTED\");\n\n for (const pending of this.pendingRequests.values()) {\n pending.reject(teardown);\n }\n this.pendingRequests.clear();\n\n // Explicit disconnect drains the queue with an error — the consumer\n // tore down on purpose, so we shouldn't hold onto their promises.\n for (const q of this.queuedCalls) {\n q.reject(teardown);\n }\n this.queuedCalls = [];\n\n // The throttle loop (if running) wakes from its sleep, sees\n // !isReady, and exits on its own. Reset its flags so the next\n // session starts clean.\n this.throttling = false;\n this.throttleCounter = 0;\n\n // Also settle any in-flight `connect()` promise so a caller that did\n // `await client.connect()` racing with `client.disconnect()` doesn't\n // hang. No-op if already settled.\n this.pendingConnectReject?.(teardown);\n }\n\n private generateRequestId(): string {\n return `${++this.requestId}`;\n }\n}\n\n// ─── Browser/Node WebSocket Handling ──────────────────────────────────────────\n\nlet WebSocketImpl: WSConstructor | null = null;\n\n// Check if we're in a browser environment\nif (typeof globalThis !== \"undefined\" && (globalThis as { WebSocket?: WSConstructor }).WebSocket) {\n WebSocketImpl = (globalThis as unknown as { WebSocket: WSConstructor }).WebSocket;\n}\n\nasync function getWebSocket(): Promise<WSConstructor> {\n if (WebSocketImpl) return WebSocketImpl;\n\n // Dynamic import for Node.js\n const ws = await import(\"ws\");\n return ws.WebSocket as unknown as WSConstructor;\n}\n\nexport { getWebSocket };\n","import type { AnyProcedure } from \"../router/procedure\";\nimport type { Router } from \"../router/router\";\nimport {\n WsClientManager,\n RPCError,\n type WSClientOptions,\n type ClientPublicState,\n} from \"./ws_client_manager\";\nimport { IsInternal } from \"../db/types_client_rpc\";\nimport { InputOf, OutputOf } from \"../router/procedure\";\n\nexport type { ClientPublicState } from \"./ws_client_manager\";\nexport { RPCError } from \"./ws_client_manager\";\n\n// ─── Client Types ────────────────────────────────────────────────────────────\n\nexport interface AuthInfo {\n token: string | null;\n userId: string | null;\n}\n\nexport interface ClientOptions {\n /** WebSocket URL (e.g. ws://localhost:3000/ws) */\n url: string;\n /** Auth Info callback */\n token?: () => Promise<string>;\n /** Optional: reconnection timeout in ms (default: 180000 = 3 minutes) */\n reconnectionTimeout?: number;\n /** Reconnection options */\n reconnect?: WSClientOptions[\"reconnect\"];\n /**\n * Inactivity watchdog. The client never sends a ping itself; the server\n * emits `{type:\"ping\"}` while idle and we close+reconnect if no inbound\n * message arrives within `serverInactivityMs` (default 60s).\n */\n heartbeat?: WSClientOptions[\"heartbeat\"];\n /**\n * Yields the event loop for `delayMs` after every `everyN` `call()`\n * invocations. Useful for keeping React renders / GC tasks scheduled\n * during long synchronous bursts; not load-bearing for the heartbeat.\n * Default `{ everyN: 100, delayMs: 10 }`. Set `everyN: 0` to disable.\n */\n throttle?: WSClientOptions[\"throttle\"];\n\n /** Custom log sink; when set, `logLevel` is ignored. See {@link WSClientOptions.logger}. */\n logger?: WSClientOptions[\"logger\"];\n /** Level for the default console logger. Default `\"warn\"` (quiet). */\n logLevel?: WSClientOptions[\"logLevel\"];\n /** Optional structured lifecycle events, for client-side metrics. */\n onEvent?: WSClientOptions[\"onEvent\"];\n /** OpenTelemetry-agnostic W3C `traceparent` provider; see {@link WSClientOptions.getTraceparent}. */\n getTraceparent?: WSClientOptions[\"getTraceparent\"];\n\n onConnect?: () => void;\n onDisConnect?: () => void;\n}\n\n/**\n * Public state of a live query subscription, exposed via `getState()` and\n * `onState()` on the handle so React (or any UI) can render a consistent\n * snapshot via `useSyncExternalStore`.\n *\n * - \"connecting\" — initial subscribe in flight\n * - \"success\" — subscribed, last server data is in `data`\n * - \"stale\" — was successful, transport dropped; keeping last `data`\n * until the resubscribe completes\n * - \"loading\" — `refetch()` triggered an explicit re-subscribe\n * - \"error\" — subscribe failed or server sent `sub:gone`\n */\nexport type LiveQueryStatus =\n | \"connecting\"\n | \"success\"\n | \"stale\"\n | \"loading\"\n | \"error\";\n\nexport interface LiveQueryState<T> {\n data: T | undefined;\n status: LiveQueryStatus;\n error: Error | null;\n}\n\nexport interface LiveQueryHandle<T> {\n /** Get current cached data (legacy convenience getter). */\n get(): T | undefined;\n /** Snapshot of `{ data, status, error }`. Stable between updates. */\n getState(): LiveQueryState<T>;\n /**\n * Listen for state changes (data, status or error). Returns an\n * unsubscribe function that removes only this listener — the underlying\n * server subscription stays alive as long as any listener is attached.\n *\n * Suitable for `useSyncExternalStore`.\n */\n onState(listener: () => void): () => void;\n /** Listen for data-only changes. Returns listener-removal fn. */\n onData(callback: (data: T) => void): () => void;\n /** Force a re-subscribe round-trip. */\n refetch(): void;\n /** Unsubscribe from the server (drops listeners too). */\n unsubscribe(): void;\n}\n\nexport interface RPCSuccessResponse<TResult> {\n success: true;\n data: TResult;\n}\n\nexport interface RPCErrorResponse {\n success: false;\n error: string;\n code: string;\n}\n\nexport type RPCResponse<TResult> = RPCSuccessResponse<TResult> | RPCErrorResponse;\n\n// ─── Client Implementation ───────────────────────────────────────────────────\n\n/**\n * Create a fully typed RPC client with WebSocket transport.\n */\nexport function createClient<\n AppRouter extends Router<Record<string, AnyProcedure<TContext>>, TContext>,\n TContext = unknown,\n TProcedures extends Record<string, AnyProcedure<TContext>> = AppRouter[\"procedures\"],\n>(options: ClientOptions): ClientFromProcedures<TProcedures, TContext> & WSClientMethods {\n const wsClient = new WsClientManager({\n url: options.url,\n token: options.token,\n reconnectionTimeout: options.reconnectionTimeout,\n reconnect: options.reconnect,\n heartbeat: options.heartbeat,\n throttle: options.throttle,\n logger: options.logger,\n logLevel: options.logLevel,\n onEvent: options.onEvent,\n getTraceparent: options.getTraceparent,\n onConnect: options.onConnect,\n onDisconnect: options.onDisConnect,\n });\n\n // Stable cross-component dedup. Within a single session there is at most\n // one logical user, so `(procedure, stableStringify(input))` uniquely\n // identifies a live query on the client. The server-canonical subId\n // (which may fold in per-procedure `queryIdentity` overrides) is fetched\n // from the subscribe response and stored on the handle for debugging.\n const liveQueryHandles = new Map<string, LiveQueryHandleImpl<unknown>>();\n\n function liveQueryDedupKey(procedure: string, input: string): string {\n return `${procedure}::${input}`;\n }\n\n const cache = new Map();\n\n const client = new Proxy({} as ClientFromProcedures<TProcedures, TContext> & WSClientMethods, {\n get(_target, procedureName) {\n if (typeof procedureName !== \"string\") {\n return undefined;\n }\n\n // Special methods on the client itself\n switch (procedureName) {\n case \"connect\":\n return () => wsClient.connect();\n case \"disconnect\":\n return () => wsClient.disconnect();\n case \"isReady\":\n return () => wsClient.isReady();\n case \"isAuthenticated\":\n return () => wsClient.isAuthenticated();\n case \"isAnonymouse\":\n return () => wsClient.isAnonymouse();\n case \"getAuthUserId\":\n return () => wsClient.getAuthUserId();\n case \"onState\":\n return (listener: (s: ClientPublicState) => void) => wsClient.onState(listener);\n case \"getPublicState\":\n return () => wsClient.getPublicState();\n }\n\n const cachedProp = cache.get(procedureName);\n if (cachedProp) {\n return cachedProp;\n }\n\n const newProp = {\n query: async (input: unknown): Promise<unknown> => {\n return wsClient.call(procedureName, input);\n },\n mutate: async (input: unknown, opts?: { onSend?: () => void }): Promise<unknown> => {\n return wsClient.call(procedureName, input, opts);\n },\n action: async (input: unknown, opts?: { onSend?: () => void }): Promise<unknown> => {\n return wsClient.call(procedureName, input, opts);\n },\n liveQuery: (input: unknown, serializedInput: string): LiveQueryHandle<unknown> => {\n const dedupKey = liveQueryDedupKey(procedureName, serializedInput);\n const existing = liveQueryHandles.get(dedupKey);\n if (existing) return existing;\n\n const handle = new LiveQueryHandleImpl(\n wsClient,\n procedureName,\n input,\n serializedInput,\n () => liveQueryHandles.delete(dedupKey),\n );\n liveQueryHandles.set(dedupKey, handle);\n return handle;\n },\n };\n\n cache.set(procedureName, newProp);\n return newProp;\n },\n });\n\n return client;\n}\n\n// ─── Live Query Handle Implementation ─────────────────────────────────────────\n\n/**\n * Sync external store for one (procedure, input) live query.\n *\n * - Constructor returns immediately; the wasm-hashed server subId is computed\n * in `kickoffSubscribe()` so callers don't have to await.\n * - `onState` / `onData` ref-count their listeners; the last to leave starts\n * a 3 s grace timer before unsubscribing on the wire (so transient mounts\n * in StrictMode don't churn server-side state).\n * - Listens to `wsClient.onState`: any drop flips status to \"stale\", and the\n * manager's own `resubscribeAll()` brings us back without our help.\n * - On `sub:gone`, transitions to \"error\" and auto-retries on next ready.\n */\nclass LiveQueryHandleImpl<T> implements LiveQueryHandle<T> {\n private state: LiveQueryState<T> = {\n data: undefined,\n status: \"connecting\",\n error: null,\n };\n private stateListeners = new Set<() => void>();\n private dataListeners = new Set<(data: T) => void>();\n private unsubscribeServer: (() => void) | null = null;\n private graceTimer: ReturnType<typeof setTimeout> | null = null;\n private subId: string | null = null;\n private wsStateOff: (() => void) | null = null;\n /** Set once the disposer has fired (after the last unsubscribe). */\n private disposed = false;\n /**\n * rAF coalesce buffer for incoming `sub:update` payloads. Bursty\n * mutations (e.g. 2000 spam transfers) can fire `sub:update` faster than\n * the screen can repaint; collapsing to one commit per animation frame\n * caps live-query re-renders at vsync without skipping the latest state.\n * Tab in a background window naturally throttles down to ~1Hz via the\n * browser's own rAF policy.\n */\n private pendingData: { value: T } | null = null;\n private rafHandle: number | null = null;\n\n constructor(\n private wsClient: WsClientManager,\n private procedure: string,\n private input: unknown,\n private serializedInput: string,\n private onDispose: () => void,\n ) {\n // Track transport state so we can flip \"success\" → \"stale\" when the\n // socket drops, without losing the last successful payload.\n this.wsStateOff = wsClient.onState((next) => {\n if (next === \"ready\" && this.state.status === \"stale\") {\n // The manager's resubscribeAll() will reseed us with fresh data.\n } else if (\n this.state.status === \"success\" &&\n (next === \"disconnected\" || next === \"connecting\" || next === \"authenticating\")\n ) {\n this.setState({ status: \"stale\" });\n }\n });\n\n this.kickoffSubscribe();\n }\n\n get(): T | undefined {\n return this.state.data;\n }\n\n getState = (): LiveQueryState<T> => this.state;\n\n onState = (listener: () => void): (() => void) => {\n this.stateListeners.add(listener);\n this.cancelGrace();\n return () => {\n this.stateListeners.delete(listener);\n this.scheduleGraceUnsubscribe();\n };\n };\n\n onData(callback: (data: T) => void): () => void {\n this.dataListeners.add(callback);\n this.cancelGrace();\n\n if (this.state.data !== undefined) {\n callback(this.state.data);\n }\n\n return () => {\n this.dataListeners.delete(callback);\n this.scheduleGraceUnsubscribe();\n };\n }\n\n refetch(): void {\n if (this.disposed) return;\n\n this.setState({ status: \"loading\", error: null });\n void this.kickoffSubscribe();\n }\n\n unsubscribe(): void {\n if (this.disposed) return;\n\n this.disposed = true;\n this.cancelGrace();\n\n if (this.rafHandle !== null && this.rafHandle !== -1 && typeof cancelAnimationFrame === \"function\") {\n cancelAnimationFrame(this.rafHandle);\n }\n this.rafHandle = null;\n this.pendingData = null;\n\n if (this.unsubscribeServer) {\n try {\n this.unsubscribeServer();\n } catch (e) {\n console.error(\"[LiveQuery] unsubscribe threw:\", e);\n }\n this.unsubscribeServer = null;\n }\n\n if (this.wsStateOff) {\n this.wsStateOff();\n this.wsStateOff = null;\n }\n\n this.stateListeners.clear();\n this.dataListeners.clear();\n this.onDispose();\n }\n\n // ─── Internal ────────────────────────────────────────────────────────────\n\n private async kickoffSubscribe(): Promise<void> {\n try {\n const { subId, unsubscribe } = await this.wsClient.subscribe(\n this.procedure,\n this.input,\n this.serializedInput,\n (data) => this.handleData(data as T),\n (reason) => this.handleGone(reason),\n );\n\n if (this.disposed) {\n try { unsubscribe(); } catch { /* swallow */ }\n return;\n }\n\n this.subId = subId;\n this.unsubscribeServer = unsubscribe;\n } catch (err) {\n if (this.disposed) return;\n\n const e = err instanceof Error ? err : new Error(String(err));\n this.setState({ status: \"error\", error: e });\n }\n }\n\n private handleData(data: T): void {\n if (this.disposed) return;\n // Latest snapshot wins — if a commit is already scheduled, the rAF\n // (or microtask fallback) will pick this up.\n this.pendingData = { value: data };\n if (this.rafHandle !== null) return;\n\n if (typeof requestAnimationFrame !== \"function\") {\n // Non-browser (Node tests, SSR). Microtask is the closest analog —\n // still collapses multiple updates within the same task.\n this.rafHandle = -1;\n queueMicrotask(() => {\n this.rafHandle = null;\n this.commitPendingData();\n });\n return;\n }\n\n this.rafHandle = requestAnimationFrame(() => {\n this.rafHandle = null;\n this.commitPendingData();\n });\n }\n\n private commitPendingData(): void {\n if (this.disposed) return;\n const pending = this.pendingData;\n if (!pending) return;\n this.pendingData = null;\n\n this.setState({ data: pending.value, status: \"success\", error: null });\n for (const cb of this.dataListeners) {\n try { cb(pending.value); } catch (e) { console.error(\"[LiveQuery] data listener threw:\", e); }\n }\n }\n\n private handleGone(reason: string | undefined): void {\n if (this.disposed) return;\n this.setState({\n status: \"error\",\n error: new RPCError(reason ?? \"subscription gone\", \"SUB_GONE\"),\n });\n // Auto-retry: clear the server-unsubscribe ref (it's already gone) and\n // re-issue a subscribe. The manager already replays on reconnect; this\n // handles the case where the server dropped us while the socket stayed up.\n this.unsubscribeServer = null;\n this.subId = null;\n this.kickoffSubscribe();\n }\n\n private setState(patch: Partial<LiveQueryState<T>>): void {\n const next: LiveQueryState<T> = { ...this.state, ...patch };\n if (\n next.data === this.state.data &&\n next.status === this.state.status &&\n next.error === this.state.error\n ) {\n return;\n }\n this.state = next;\n for (const l of this.stateListeners) {\n try { l(); } catch (e) { console.error(\"[LiveQuery] state listener threw:\", e); }\n }\n }\n\n private cancelGrace(): void {\n if (this.graceTimer) {\n clearTimeout(this.graceTimer);\n this.graceTimer = null;\n }\n }\n\n private scheduleGraceUnsubscribe(): void {\n if (this.stateListeners.size > 0 || this.dataListeners.size > 0) return;\n\n this.cancelGrace();\n this.graceTimer = setTimeout(() => {\n this.graceTimer = null;\n if (this.stateListeners.size === 0 && this.dataListeners.size === 0) {\n this.unsubscribe();\n }\n }, 3000);\n }\n}\n\n\n/**\n * WebSocket client methods.\n */\nexport interface WSClientMethods {\n /** Connect to the WebSocket server */\n connect(): Promise<void>;\n /** Disconnect from the server */\n disconnect(): void;\n /** Check if connected and authenticated */\n isReady(): boolean;\n /** Check if authenticated */\n isAuthenticated(): boolean;\n /** Check if anonymouse */\n isAnonymouse(): boolean;\n /**\n * The authenticated user id reported by the server on `auth:success`, or\n * null when anonymous/unauthenticated. This is the auth-tier identity,\n * NOT the server-side `queryIdentity` (which is the cache/subscription\n * segmentation key and is not exposed to the client).\n */\n getAuthUserId(): string | null;\n /** Subscribe to client connection state transitions. Returns unsubscribe fn. */\n onState(listener: (s: ClientPublicState) => void): () => void;\n /** Snapshot of the current public state. */\n getPublicState(): ClientPublicState;\n}\n\n/**\n * Client type from procedures.\n * Filters internal procedures and creates appropriate methods.\n * Query procedures get an additional liveQuery() method for real-time subscriptions.\n */\nexport type ClientFromProcedures<\n TProcedures extends Record<string, AnyProcedure<TContext>>,\n TContext = unknown,\n> = {\n [K in keyof TProcedures as IsInternal<TProcedures[K]> extends true ? never : K]:\n TProcedures[K] extends { procedureType: \"query\" }\n ? {\n query: (input: InputOf<TProcedures[K]>) => Promise<OutputOf<TProcedures[K]>>;\n liveQuery: (input: InputOf<TProcedures[K]>) => LiveQueryHandle<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"mutation\" }\n ? { mutate: (input: InputOf<TProcedures[K]>, opts?: CallOptions) => Promise<OutputOf<TProcedures[K]>> }\n : TProcedures[K] extends { procedureType: \"action\" }\n ? { action: (input: InputOf<TProcedures[K]>, opts?: CallOptions) => Promise<OutputOf<TProcedures[K]>> }\n : never;\n };\n\n/**\n * Optional call options forwarded to the transport layer.\n */\nexport interface CallOptions {\n /**\n * Fired the first time the request leaves the queue and is actually\n * sent on the wire. If the call was already sent immediately (client\n * was ready at call time), this is invoked synchronously.\n *\n * Used by `useMutation` to flip `state` from \"queued\" → \"loading\".\n */\n onSend?: () => void;\n}\n","import type { ActionCtx, AnyProcedure, CallerObservability, HandlerLogger, InputOf, JobCtx, MutationCtx, OutputOf, ParentCtx } from \"../router/procedure\";\nimport { primaryOnlyConn } from \"../router/procedure\";\nimport { getContextRegistry, type Router } from \"../router/router\";\nimport type { SupaliveDb } from \"../db/realtime_db\";\nimport type { CacheLayer } from \"../db/cache\";\nimport { LiveResult } from \"../db/types_db\";\nimport type { ObjectStorage } from \"../storage\";\nimport type { JobClient } from \"../jobs/scheduler\";\nimport { TxContext } from \".\";\nimport type { OneShotQueryDeps } from \"../server/one-shot-query\";\nimport { runCachedQuery, runCachedQueryInParent } from \"../server/one-shot-query\";\n\nexport interface CallerOptions<TContext = unknown> {\n isInternal?: boolean;\n inContext: string;\n}\n\n/**\n * Create a server-side caller that bypasses HTTP/WebSocket.\n * \n * For in-memory calls, input validation is skipped (data is already a JS object).\n * Query procedures also get a liveQuery() method for server-side use.\n *\n * `init` requires the shared Redis-backed query cache, so every read path —\n * `query`, `runQuery`, and `liveQuery` — behaves like a normal client procedure\n * call: it checks cache freshness first and only falls back to executing the\n * query on a miss/stale entry (see runCachedQuery and runCachedQueryInParent).\n * `runQuery` keeps joining the parent snapshot/readSet (or opening a fresh\n * routed read for action/job parents), so nested reads stay consistent with the\n * parent and mutations keep reads-your-writes.\n */\nexport function createCaller<\n AppRouter extends Router<Record<string, AnyProcedure<TContext>>, TContext>,\n TContext = unknown,\n TProcedures extends Record<string, AnyProcedure<TContext>> = AppRouter[\"procedures\"],\n>(options: CallerOptions<any>): CallerFromProcedures<TProcedures, TContext> {\n const { isInternal = true, inContext } = options;\n const registry = getContextRegistry(inContext ?? \"default\");\n const cache = new Map();\n let callerConfig: CallerClientInitConfig<TContext>;\n let db: SupaliveDb;\n // Side-effect services exposed to called ACTION handlers as `ctx.storage` /\n // `ctx.scheduler` (matches the server's action context). Set via `init`.\n let storage: ObjectStorage;\n let scheduler: JobClient;\n // Observability from `init`: standalone internal calls log under the\n // configured logger as a per-procedure child. Required — a caller without a\n // running server (e.g. a CLI seed) passes a console-backed shim.\n let obs: CallerObservability;\n // Query cache (Redis-backed) + freshness deps from `init` — REQUIRED via the\n // config, so every caller read path goes cache-first like a client one-shot\n // (see runCachedQuery / runCachedQueryInParent).\n let cacheLayer: CacheLayer;\n let callerDepsInstance: OneShotQueryDeps<TContext>;\n\n return new Proxy({} as CallerFromProcedures<TProcedures, TContext>, {\n get(_target, procedureName) {\n if (typeof procedureName !== \"string\") {\n return undefined;\n }\n\n if (procedureName == \"init\") {\n return async (config: CallerClientInitConfig<TContext>) => {\n callerConfig = config;\n db = config.db;\n storage = config.storage;\n scheduler = config.scheduler;\n obs = config.obs;\n cacheLayer = config.cache;\n callerDepsInstance = {\n db: config.db,\n cache: cacheLayer,\n getMinRetainedTs: config.getMinRetainedTs,\n getUserId: config.getUserId,\n };\n };\n }\n\n // Per-call logger for a standalone internal invocation: a child of the\n // caller's logger bound to this procedure. (Nested runQuery/runMutation\n // inherit the parent handler's own log instead.)\n const callerLog = (): HandlerLogger =>\n obs.lazyChildLogger({ caller: \"internal\", procedure: procedureName });\n\n const procedure = registry.getProcedure(procedureName);\n if (!procedure) {\n throw new Error(`Procedure \"${procedureName}\" not found`);\n }\n\n if (registry.isInternalProcedure(procedureName) && !isInternal) {\n throw new Error(`Procedure \"${procedureName}\" is internal only`);\n }\n\n // Helper to validate input only if it's a string (serialized data)\n // For in-memory calls, skip validation for performance\n const validateInput = (input: unknown): unknown => {\n // Skip validation for non-string inputs (already objects)\n if (typeof input !== \"string\") {\n return input;\n }\n // Only parse if it's a string (e.g., from HTTP body)\n return procedure.inputSchema.parse(input);\n };\n\n const cachedProp = cache.get(procedureName);\n if (cachedProp) {\n return cachedProp;\n }\n\n const newProp = {\n query: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"query\") {\n throw new Error(`Procedure \"${procedureName}\" is not a query`);\n }\n const validatedInput = validateInput(input);\n // Cache-first standalone read: same identity/freshness model\n // as a client one-shot (see runCachedQuery).\n const lastSnapshotTs = await db.impl.getLatestSnapshotTimestamp();\n const { data } = await runCachedQuery(\n callerDepsInstance,\n { log: callerLog() },\n procedure,\n procedureName,\n validatedInput,\n serverCtx,\n lastSnapshotTs,\n );\n return data;\n },\n runQuery: async (input: unknown, parentCtx: ParentCtx<TContext>): Promise<unknown> => {\n if (procedure.type !== \"query\") {\n throw new Error(`Procedure \"${procedureName}\" is not a query`);\n }\n const validatedInput = validateInput(input);\n\n // Query/mutation parent: reuse the parent's live reader so the\n // nested query runs at the SAME snapshot (parent beginTs) and its\n // reads accumulate straight into the parent's readSet — that's what\n // makes the parent re-run/conflict on concurrent changes the nested\n // query observed. A DbWriter parent additionally sees its own\n // pending writes (reads-your-writes).\n //\n // Cache-first (runCachedQueryInParent) when a cache is configured:\n // on a HIT the cached entry's readSet is folded into the parent; on\n // a MISS the query runs on the parent reader. Mutation parents with\n // pending writes bypass the cache so reads-your-writes holds.\n if (parentCtx.db instanceof TxContext) {\n const parentReader = parentCtx.db;\n return runCachedQueryInParent(\n callerDepsInstance,\n { log: parentCtx.log, span: parentCtx.span },\n procedure,\n procedureName,\n validatedInput,\n parentCtx.serverCtx,\n parentReader,\n );\n }\n\n // Action/job parent: no live transaction — `parentCtx.db` is the\n // SupaliveDb, so open a fresh routed read for the nested query.\n // Cache-first (a client one-shot semantics).\n const parentDb = parentCtx.db as SupaliveDb;\n const lastSnapshotTs = await parentDb.impl.getLatestSnapshotTimestamp();\n const deps = {\n db: parentDb,\n cache: cacheLayer,\n getMinRetainedTs: callerConfig.getMinRetainedTs,\n getUserId: callerConfig.getUserId,\n };\n const obs = { log: parentCtx.log, span: parentCtx.span };\n const { data } = await runCachedQuery(\n deps,\n obs,\n procedure,\n procedureName,\n validatedInput,\n parentCtx.serverCtx,\n lastSnapshotTs,\n );\n return data;\n },\n mutate: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"mutation\") {\n throw new Error(`Procedure \"${procedureName}\" is not a mutation`);\n }\n const validatedInput = validateInput(input);\n const res = await db.mutation(async (dbCtx) => {\n const ctx: MutationCtx = { db: dbCtx, serverCtx: serverCtx, log: callerLog(), ...primaryOnlyConn };\n return (procedure.fn as any)(ctx, validatedInput);\n });\n return res.result;\n },\n // Runs the mutation as an INDEPENDENT sub-transaction: its own\n // snapshot, its own commit. It inherits the parent's readSet\n // (`appendInternalReadSet`) so the sub-tx's OCC validation also\n // covers what the parent read — if any of those rows changed, the\n // sub-tx conflicts and (by default) throws up into the parent.\n //\n // Two consequences the caller must plan for:\n // 1. It commits on its own the moment it succeeds, so it is NOT\n // rolled back if the parent later fails.\n // 2. When called from a parent mutation, the parent runs under the\n // OCC retry policy — on a parent conflict the whole handler\n // re-executes, so this sub-mutation runs again. Make it\n // idempotent (e.g. check-then-insert) if that matters.\n runMutation: async (input: unknown, parentCtx: ParentCtx<TContext>): Promise<unknown> => {\n if (procedure.type !== \"mutation\") {\n throw new Error(`Procedure \"${procedureName}\" is not a mutation`);\n }\n const validatedInput = validateInput(input);\n const runDb = parentCtx.db instanceof TxContext ? db : parentCtx.db;\n\n const res = await runDb.mutation(async (dbCtx) => {\n const ctx: MutationCtx = { db: dbCtx, serverCtx: parentCtx.serverCtx, log: parentCtx.log, span: parentCtx.span, ...primaryOnlyConn };\n if (parentCtx.db instanceof TxContext) {\n ctx.db.appendInternalReadSet(parentCtx.db.internalGetReadSet());\n }\n return (procedure.fn as any)(ctx, validatedInput);\n });\n return res.result;\n },\n action: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"action\") {\n throw new Error(`Procedure \"${procedureName}\" is not an action`);\n }\n const validatedInput = validateInput(input);\n const ctx: ActionCtx = { db, serverCtx: serverCtx, log: callerLog(), storage, scheduler };\n return (procedure.fn as any)(ctx, validatedInput);\n },\n // Run a job procedure in-process (bypassing the HTTP scheduler\n // path). Used by the server's job dispatcher and by tests. Jobs\n // are internal, so this is only reachable from an internal caller.\n run: async (input: unknown, serverCtx: TContext): Promise<unknown> => {\n if (procedure.type !== \"job\") {\n throw new Error(`Procedure \"${procedureName}\" is not a job`);\n }\n const validatedInput = validateInput(input);\n const ctx: JobCtx = { db, storage, scheduler, serverCtx, log: callerLog() };\n return (procedure.fn as any)(ctx, validatedInput);\n },\n // Query procedures can use liveQuery() for server-side use\n liveQuery: procedure.type === \"query\"\n ? async (input: unknown, serverCtx: TContext) => {\n const validatedInput = validateInput(input);\n // Cache-first: runCachedQuery returns {data, ts, readSet} —\n // exactly the LiveResult shape — with the cached readSet on\n // a hit.\n const obs = { log: callerLog() };\n const lastSnapshotTs = await db.impl.getLatestSnapshotTimestamp();\n return runCachedQuery(\n callerDepsInstance,\n obs,\n procedure,\n procedureName,\n validatedInput,\n serverCtx,\n lastSnapshotTs,\n ) as Promise<LiveResult<any>>;\n }\n : undefined,\n };\n\n cache.set(procedureName, newProp);\n return newProp;\n },\n });\n}\n\n/**\n * Caller type from procedures (includes internal procedures).\n * Query procedures also have liveQuery() for server-side use.\n */\nexport type CallerFromProcedures<TProcedures extends Record<string, AnyProcedure<TContext>>, TContext = unknown> = {\n [K in keyof TProcedures]: TProcedures[K] extends { procedureType: \"query\" }\n ? {\n query: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>;\n liveQuery: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<LiveResult<OutputOf<TProcedures[K]>>>;\n // in procedure call other procedure\n runQuery: (input: InputOf<TProcedures[K]>, ctx: ParentCtx<TContext>) => Promise<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"mutation\" }\n ? {\n mutate: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>,\n runMutation: (input: InputOf<TProcedures[K]>, ctx: ParentCtx<TContext>) => Promise<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"action\" }\n ? {\n // To call other procedures from an action, use the target procedure's\n // own `.runQuery` / `.runMutation`, passing the action's ctx as the\n // parent (its `db` is a SupaliveDb). An action isn't itself runnable as\n // a nested query/mutation, so no runQuery/runMutation here.\n action: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>;\n }\n : TProcedures[K] extends { procedureType: \"job\" }\n ? {\n // Invoke a job in-process. The server's HTTP dispatcher uses this after\n // verifying the scheduler webhook; also handy in tests.\n run: (input: InputOf<TProcedures[K]>, serverCtx: TContext) => Promise<OutputOf<TProcedures[K]>>;\n }\n : never;\n} & {\n init: (config: CallerClientInitConfig<TContext>) => Promise<void>;\n};\n\nexport type CallerClientInitConfig<TContext = unknown> = {\n db: SupaliveDb;\n storage: ObjectStorage;\n scheduler: JobClient;\n /**\n * Observability so standalone internal calls log under the configured logger\n * (a per-procedure child). Required — a caller with no running server (e.g. a\n * CLI seed) passes a console-backed shim. Nested runQuery/runMutation always\n * inherit the parent handler's log instead.\n */\n obs: CallerObservability;\n /**\n * Shared query cache (Redis-backed) — REQUIRED. Every caller read path\n * (`query`, `runQuery`, `liveQuery`) goes cache-first with the SAME\n * identity/freshness model as a client one-shot (see runCachedQuery /\n * runCachedQueryInParent) and writes recomputed results back.\n */\n cache: CacheLayer;\n /**\n * Oldest retained commit-log ts (retention watermark) — the guard for the\n * cache-freshness scan. The running server passes the live sub-manager\n * watermark; a CLI seed (no server, nothing pruned) passes `() => 0n`.\n */\n getMinRetainedTs: () => bigint;\n /**\n * Same identity resolver as the server (`SupaliveServerConfig.getUserId`), so\n * internal cache keys segment identically to client WS/HTTP queries — a\n * nested/standalone call shares cache entries with the equivalent client\n * query. Return `null` for anonymous (the cache's anonymous identity\n * segment).\n */\n getUserId: (ctx: TContext | undefined) => string | null;\n};\n\n/**\n * Caller type from router.\n */\nexport type CallerFromRouter<TRouter extends Router<any, any>> = CallerFromProcedures<TRouter[\"procedures\"]>;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { t as MySqlDatabase } from "../../mysql-DcHgRk9f.js";
1
+ import { t as MySqlDatabase } from "../../mysql-C79_qNQO.js";
2
2
  export { MySqlDatabase };
@@ -1,2 +1,2 @@
1
- import { t as MySqlDatabase } from "../../mysql-fTKZ1zaF.js";
1
+ import { t as MySqlDatabase } from "../../mysql-ygH2EMGn.js";
2
2
  export { MySqlDatabase };