@supalive/core 0.1.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index-DouKwYL6.d.ts +1754 -0
- package/dist/index-DouKwYL6.d.ts.map +1 -0
- package/dist/mysql-Bxurg_PH.d.ts +104 -0
- package/dist/mysql-Bxurg_PH.d.ts.map +1 -0
- package/dist/postgres-CfT_aDx3.d.ts +108 -0
- package/dist/postgres-CfT_aDx3.d.ts.map +1 -0
- package/dist/procedure-DX87tyef.js.map +1 -1
- package/dist/router-DlTYWpop.js.map +1 -1
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/exports/codegen.d.ts +38 -0
- package/dist/src/exports/codegen.d.ts.map +1 -0
- package/dist/src/exports/codegen.js +134 -0
- package/dist/src/exports/codegen.js.map +1 -0
- package/dist/src/exports/mysql.d.ts +1 -1
- package/dist/src/exports/postgres.d.ts +1 -1
- package/dist/src/exports/procedure.d.ts +1 -1
- package/dist/src/exports/schema-sql.d.ts +1 -1
- package/dist/src/exports/server.d.ts +4 -4
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +4 -8
- package/dist/src/exports/server.js.map +1 -1
- package/dist/src/exports/subscription-manager-worker-entry.js +1 -2
- package/dist/src/exports/subscription-manager-worker-entry.js.map +1 -1
- package/dist/src/exports/types.d.ts +2 -2
- package/dist/subscription-worker-CqoWp6zB.js +511 -0
- package/dist/subscription-worker-CqoWp6zB.js.map +1 -0
- package/dist/subscription-worker-PtmJWEj1.js +511 -0
- package/dist/subscription-worker-PtmJWEj1.js.map +1 -0
- package/dist/types_server-BW1ys_SK.d.ts +216 -0
- package/dist/types_server-BW1ys_SK.d.ts.map +1 -0
- package/dist/types_server-Dk6o_B7N.js.map +1 -1
- package/dist/types_server-Dpsi0wpG.d.ts +216 -0
- package/dist/types_server-Dpsi0wpG.d.ts.map +1 -0
- package/dist/types_server-DzrIccyO.d.ts +216 -0
- package/dist/types_server-DzrIccyO.d.ts.map +1 -0
- package/package.json +3 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { $ as PublicProcedures, $t as DefineSchemaConfig, Bt as QueryBuilder, Ct as createQueryBuilder, Gt as matchesPredicate, Ht as jsonContains, It as IdAndCommitTs, J as ActionProcedures, Jt as ComputedCodec, Kt as ActionArgs, Lt as JsonContainsOptions, Q as ProcedureNames, Qt as DeclarativeIndex, Rt as JsonHasKeyMultiOptions, St as createMutationBuilder, Ut as jsonPathExists, Vt as buildPredicateSql, Wt as jsonPathExtract, X as ContextRegistry, Xt as ComputedField, Y as AppRouter, Yt as ComputedDataType, Z as MutationProcedures, Zt as ComputedFieldConfig, _n as schemaRegistry, _t as QueryCtx, an as OverrideField, at as ActionConfig, bn as trackSchema, bt as TypeOf, cn as ReturnLiveQuery, ct as ActionProcedure, dn as SchemaColumnMapping, dt as MutationConfig, en as IndexDefinition, et as QueryProcedures, fn as SchemaColumnsOptions, ft as MutationCtx, gn as defineSchema, gt as QueryConfig, hn as defineComputedField, ht as OutputOf, in as MutationArgs, it as router, ln as ReturnQuery, lt as AnyProcedure, mn as StoredSchemaColumnsOptions, mt as MutationProcedure, nn as InsertData, nt as Router, on as Prettify, ot as ActionCtx, pn as SchemaDefinition, pt as MutationFn, qt as ColumnCodec, rn as Model, rt as getContextRegistry, sn as QueryArgs, st as ActionFn, tn as InferSchema, tt as RegisteredProcedure, un as SchemaCodecs, ut as ContextOf, vn as shouldTrackSchema, vt as QueryFn, xt as createActionBuilder, yn as tableNameToId, yt as QueryProcedure, zt as JsonOpOptions } from "../../index-
|
|
1
|
+
import { $ as PublicProcedures, $t as DefineSchemaConfig, Bt as QueryBuilder, Ct as createQueryBuilder, Gt as matchesPredicate, Ht as jsonContains, It as IdAndCommitTs, J as ActionProcedures, Jt as ComputedCodec, Kt as ActionArgs, Lt as JsonContainsOptions, Q as ProcedureNames, Qt as DeclarativeIndex, Rt as JsonHasKeyMultiOptions, St as createMutationBuilder, Ut as jsonPathExists, Vt as buildPredicateSql, Wt as jsonPathExtract, X as ContextRegistry, Xt as ComputedField, Y as AppRouter, Yt as ComputedDataType, Z as MutationProcedures, Zt as ComputedFieldConfig, _n as schemaRegistry, _t as QueryCtx, an as OverrideField, at as ActionConfig, bn as trackSchema, bt as TypeOf, cn as ReturnLiveQuery, ct as ActionProcedure, dn as SchemaColumnMapping, dt as MutationConfig, en as IndexDefinition, et as QueryProcedures, fn as SchemaColumnsOptions, ft as MutationCtx, gn as defineSchema, gt as QueryConfig, hn as defineComputedField, ht as OutputOf, in as MutationArgs, it as router, ln as ReturnQuery, lt as AnyProcedure, mn as StoredSchemaColumnsOptions, mt as MutationProcedure, nn as InsertData, nt as Router, on as Prettify, ot as ActionCtx, pn as SchemaDefinition, pt as MutationFn, qt as ColumnCodec, rn as Model, rt as getContextRegistry, sn as QueryArgs, st as ActionFn, tn as InferSchema, tt as RegisteredProcedure, un as SchemaCodecs, ut as ContextOf, vn as shouldTrackSchema, vt as QueryFn, xt as createActionBuilder, yn as tableNameToId, yt as QueryProcedure, zt as JsonOpOptions } from "../../index-DouKwYL6.js";
|
|
2
2
|
import { n as stableStringify, r as supaliveStringify, t as groupByToMap } from "../../helper-CiacMqje.js";
|
|
3
3
|
export { ActionArgs, type ActionConfig, type ActionCtx, type ActionFn, type ActionProcedure, type ActionProcedures, type AnyProcedure, type AppRouter, ColumnCodec, ComputedCodec, ComputedDataType, ComputedField, ComputedFieldConfig, type ContextOf, ContextRegistry, DeclarativeIndex, DefineSchemaConfig, IdAndCommitTs, IndexDefinition, InferSchema, InsertData, JsonContainsOptions, JsonHasKeyMultiOptions, JsonOpOptions, Model, MutationArgs, type MutationConfig, type MutationCtx, type MutationFn, type MutationProcedure, type MutationProcedures, type OutputOf, OverrideField, Prettify, type ProcedureNames, type PublicProcedures, QueryArgs, QueryBuilder, type QueryConfig, type QueryCtx, type QueryFn, type QueryProcedure, type QueryProcedures, type RegisteredProcedure, ReturnLiveQuery, ReturnQuery, type Router, SchemaCodecs, SchemaColumnMapping, SchemaColumnsOptions, SchemaDefinition, StoredSchemaColumnsOptions, type TypeOf, buildPredicateSql, createActionBuilder, createMutationBuilder, createQueryBuilder, defineComputedField, defineSchema, getContextRegistry, groupByToMap, jsonContains, jsonPathExists, jsonPathExtract, matchesPredicate, router, schemaRegistry, shouldTrackSchema, stableStringify, supaliveStringify, tableNameToId, trackSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _n as schemaRegistry, bn as trackSchema, pn as SchemaDefinition } from "../../index-
|
|
1
|
+
import { _n as schemaRegistry, bn as trackSchema, pn as SchemaDefinition } from "../../index-DouKwYL6.js";
|
|
2
2
|
|
|
3
3
|
//#region src/db/schema-sql.d.ts
|
|
4
4
|
type SqlDialect = "postgres" | "mysql";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { An as CacheLayer, Bn as UnregisterSubscriptionResult, E as Context, Fn as RegisterSubscriptionParams, Gn as UpdateSubscriptionReadSetResult, Hn as UnregisterSubscriptionsResult, In as RegisterSubscriptionParamsSchema, Ln as RegisterSubscriptionResult, Mn as InvalidateWritesetParams, Nn as InvalidateWritesetParamsSchema, Pn as InvalidateWritesetResult, Rn as UnregisterSubscriptionParams, Un as UpdateSubscriptionReadSetParams, Vn as UnregisterSubscriptionsParams, Wn as UpdateSubscriptionReadSetParamsSchema, jn as AffectedSubscription, nt as Router, wt as SupaliveDb, zn as UnregisterSubscriptionParamsSchema } from "../../index-
|
|
2
|
-
import { t as MySqlDatabase } from "../../mysql-
|
|
3
|
-
import { t as PgDatabase } from "../../postgres-
|
|
4
|
-
import { _ as UpstashConfig, f as DatabaseConfig, g as SupaliveServerConfig, h as SubscriptionManagerConfig, u as SubId } from "../../types_server-
|
|
1
|
+
import { An as CacheLayer, Bn as UnregisterSubscriptionResult, E as Context, Fn as RegisterSubscriptionParams, Gn as UpdateSubscriptionReadSetResult, Hn as UnregisterSubscriptionsResult, In as RegisterSubscriptionParamsSchema, Ln as RegisterSubscriptionResult, Mn as InvalidateWritesetParams, Nn as InvalidateWritesetParamsSchema, Pn as InvalidateWritesetResult, Rn as UnregisterSubscriptionParams, Un as UpdateSubscriptionReadSetParams, Vn as UnregisterSubscriptionsParams, Wn as UpdateSubscriptionReadSetParamsSchema, jn as AffectedSubscription, nt as Router, wt as SupaliveDb, zn as UnregisterSubscriptionParamsSchema } from "../../index-DouKwYL6.js";
|
|
2
|
+
import { t as MySqlDatabase } from "../../mysql-Bxurg_PH.js";
|
|
3
|
+
import { t as PgDatabase } from "../../postgres-CfT_aDx3.js";
|
|
4
|
+
import { _ as UpstashConfig, f as DatabaseConfig, g as SupaliveServerConfig, h as SubscriptionManagerConfig, u as SubId } from "../../types_server-BW1ys_SK.js";
|
|
5
5
|
import pino, { Level } from "pino";
|
|
6
6
|
import Redis$1 from "ioredis";
|
|
7
7
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../../../src/server/supalive-server.ts","../../../src/logger.ts","../../../src/server/subscription-manager.ts","../../../src/server/subscription-manager-client.ts","../../../src/db/init_db.ts","../../../src/server/sub_hash.ts"],"mappings":";;;;;;;;;;;cAuHa,uBAAA,kBAAyC,OAAA;EAAA,QAC5C,MAAA;EAAA,QACA,UAAA;EAAA,QAEA,UAAA;EAAA,QACA,GAAA;EAAA,QAEA,KAAA;EAAA,QACA,QAAA;EAAA,QACA,EAAA;EAAA,QACA,eAAA;EAAA,QACA,MAAA;EAAA,QAEA,QAAA;EAAA,QACA,mBAAA;EAAA,QACA,gBAAA;EAAA,QAEA,KAAA;cAMI,MAAA,EAAQ,oBAAA,CAAqB,QAAA;EAvBN;;;;;;;;;;;;EAAA,QAuE3B,qBAAA;EAAA,QAqBA,kBAAA;EAOR,cAAA,CAAe,MAAA,EAAQ,MAAA,MAAY,QAAA;EAK7B,KAAA,IAAS,OAAA;EAST,IAAA,IAAQ,OAAA;EASd,QAAA;;;;;;EASA,aAAA;EAAA,QAIQ,sBAAA;EAAA,QAQM,gBAAA;EAAA,QAiDA,aAAA;EAAA,QAeA,cAAA;EAAA,QAiBA,UAAA;EAtGd;;;;;;;EAAA,QAyIQ,eAAA;EAnEM;;;;;EAAA,QA2FA,kBAAA;EAAA,QAwBA,UAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../../../src/server/supalive-server.ts","../../../src/logger.ts","../../../src/server/subscription-manager.ts","../../../src/server/subscription-manager-client.ts","../../../src/db/init_db.ts","../../../src/server/sub_hash.ts"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { n as stableStringify, r as supaliveStringify, t as groupByToMap } from
|
|
|
3
3
|
import { i as AuthenticationError, o as ClientMessageSchema } from "../../types_client_rpc-CIsjQQGG.js";
|
|
4
4
|
import { n as RemoteSubscriptionMessageSchema } from "../../types_server-Dk6o_B7N.js";
|
|
5
5
|
import { n as sleep } from "../../realtime_db-DOCgqbwi.js";
|
|
6
|
-
import { C as UpdateSubscriptionReadSetParamsSchema, S as UnregisterSubscriptionsResultSchema, _ as RegisterSubscriptionParamsSchema, a as initCacheLayer, b as UnregisterSubscriptionResultSchema, c as appInstanceId, d as logger, f as serializeMapValue, g as RegisterSubscriptionBatchResultSchema, i as createDatabase, l as isProduction, m as InvalidateWritesetResultSchema, n as DB_QUERY_TIMEOUT_MS, o as initDatabase, p as InvalidateWritesetParamsSchema, r as bootstrapDbTypes, s as CacheLayer, u as logLevelOf, v as RegisterSubscriptionResultSchema, w as UpdateSubscriptionReadSetResultSchema, y as UnregisterSubscriptionParamsSchema } from "../../subscription-worker-
|
|
6
|
+
import { C as UpdateSubscriptionReadSetParamsSchema, S as UnregisterSubscriptionsResultSchema, _ as RegisterSubscriptionParamsSchema, a as initCacheLayer, b as UnregisterSubscriptionResultSchema, c as appInstanceId, d as logger, f as serializeMapValue, g as RegisterSubscriptionBatchResultSchema, i as createDatabase, l as isProduction, m as InvalidateWritesetResultSchema, n as DB_QUERY_TIMEOUT_MS, o as initDatabase, p as InvalidateWritesetParamsSchema, r as bootstrapDbTypes, s as CacheLayer, u as logLevelOf, v as RegisterSubscriptionResultSchema, w as UpdateSubscriptionReadSetResultSchema, y as UnregisterSubscriptionParamsSchema } from "../../subscription-worker-CqoWp6zB.js";
|
|
7
7
|
import { createServer } from "http";
|
|
8
8
|
import { WebSocket, WebSocketServer } from "ws";
|
|
9
9
|
import { v4 } from "uuid";
|
|
@@ -399,7 +399,7 @@ var SupaliveWebSocketServer = class {
|
|
|
399
399
|
const session = {
|
|
400
400
|
id: v4(),
|
|
401
401
|
ws,
|
|
402
|
-
serverCtx:
|
|
402
|
+
serverCtx: void 0,
|
|
403
403
|
subIds: /* @__PURE__ */ new Set(),
|
|
404
404
|
mutationQueue: [],
|
|
405
405
|
mutationInFlight: false,
|
|
@@ -584,7 +584,8 @@ var SupaliveWebSocketServer = class {
|
|
|
584
584
|
this.sendMessage(session, {
|
|
585
585
|
type: "response",
|
|
586
586
|
id,
|
|
587
|
-
success: true
|
|
587
|
+
success: true,
|
|
588
|
+
data: mutationResult.result
|
|
588
589
|
});
|
|
589
590
|
await this.handleMutationCommit(mutationResult.writeSet, mutationResult.commitTs);
|
|
590
591
|
} else if (procedure.type === "action") {
|
|
@@ -694,11 +695,6 @@ var SupaliveWebSocketServer = class {
|
|
|
694
695
|
logger$2.info(`Subscribed: ${subId} for session ${session.id}`);
|
|
695
696
|
} catch (err) {
|
|
696
697
|
logger$2.error(err, `Error subscribing to "${procedureName}"`);
|
|
697
|
-
this.sendMessage(session, {
|
|
698
|
-
type: "response",
|
|
699
|
-
id,
|
|
700
|
-
success: false
|
|
701
|
-
});
|
|
702
698
|
this.sendError(session, id, err instanceof Error ? err.message : "Subscribe failed", "SUBSCRIBE_ERROR");
|
|
703
699
|
}
|
|
704
700
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":["logger","baseLogger","types.RegisterSubscriptionResultSchema","types.RegisterSubscriptionBatchResultSchema","types.UpdateSubscriptionReadSetResultSchema","types.UnregisterSubscriptionResultSchema","types.UnregisterSubscriptionsResultSchema","types.InvalidateWritesetResultSchema","logger","baseLogger","uuidv4","logger","baseLogger","WebSocketServer"],"sources":["../../../src/server/sub_hash.ts","../../../src/server/subscription-manager-client.ts","../../../src/server/supalive-server.ts","../../../src/server/hash.ts","../../../src/server/subscription-manager.ts"],"sourcesContent":["// Server-only hashing helpers. Kept off the `helper.ts` module so a browser\n// bundle that only imports `stableStringify` doesn't pull in `hash-wasm`.\n\nimport { sha1 } from \"hash-wasm\";\nimport { stableStringify } from \"../helper\";\n\n/** Compute stable hash using json-stable-stringify and SHA1. */\nexport async function getHashOf(input: unknown): Promise<string> {\n const stableStr = typeof input === \"string\" ? input : stableStringify(input);\n return sha1(stableStr ?? \"\");\n}\n\n/**\n * Default identity literal used when neither a procedure-level\n * `queryIdentity` override nor a per-session user is available.\n */\nexport const ANONYMOUS_IDENTITY = \"anonymous\";\n\n/**\n * Compute a subscription id from hash of procedure + input + queryIdentity.\n *\n * `queryIdentity` is the segmentation key for cache/subscriptions. By default\n * it is the authenticated user's id (so two users get distinct subscriptions\n * for the same query), but a procedure may override it to share cache/subs\n * across users (e.g. for a public, user-independent query).\n */\nexport async function generateSubscriptionId(\n procedure: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const inputStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${procedure}_${inputStr}_${queryIdentity}`);\n return `sub_${hash}`;\n}\n\n/**\n * Compute cache key from query name + args + queryIdentity. The identity\n * participates so that user-specific results don't poison each other's\n * cache when a procedure does authorize per-user.\n */\nexport async function generateCacheKey(\n queryName: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const argsStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${queryName}_${argsStr}_${queryIdentity}`);\n return `ck_${hash}`;\n}\n","import { Client } from \"rpc-websockets\";\nimport z, { type ZodType } from \"zod\";\nimport * as types from \"./subscription-manager-types\";\nimport type { WriteEntry } from \"../db/types_db\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { supaliveStringify } from \"../helper\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR_CLIENT\" },\n { level: logLevelOf(\"SUB_MANAGER_CLIENT_LOG_LEVEL\") },\n);\n\n/**\n * Callback the app server registers to process invalidations that were\n * deferred during a sub-manager outage. When the sub-manager reconnects and\n * the buffered writeSets are flushed in one batched RPC, the resulting\n * `affected[]` is handed to this callback — which is expected to do the\n * same work as the normal per-mutation `reexecuteAffectedQuery` path.\n */\nexport type AffectedHandler = (\n affected: types.AffectedSubscription[],\n) => Promise<void>;\n\n/**\n * Callback the app server registers to drive recovery on sub-manager\n * reconnect. Iterates `serverSubscriptions`, reads `sl:reg:<subId>` from\n * Redis, and re-registers each known sub. Resolves when complete; only then\n * does the client's buffer flush and gated calls release.\n */\nexport type RecoveryDriver = () => Promise<void>;\n\nexport interface SubManagerClientOptions {\n url: string;\n /**\n * Maximum number of buffered WriteEntry records held during an outage.\n * If exceeded, the buffer is dropped — the commit-log replay path during\n * the next register call will catch up any missed invalidations, at the\n * cost of a brief recompute spike. Default 50_000 entries.\n */\n invalidateBufferLimit?: number;\n}\n\ntype ReadyState = \"ready\" | \"busy\";\n\nexport const subscriptionManagerDataPack = {\n encode: (data: unknown) => supaliveStringify(data),\n decode: (data: string) => JSON.parse(data),\n};\n\n\n/**\n * RPC client for the (single) sub-manager process.\n *\n * Design (Convex-inspired):\n * • The sub-manager is in-memory and ephemeral. There is no version\n * protocol, no client-side registration registry, no OUT_OF_SYNC\n * replay logic. All recovery is driven from the app server using\n * state it already has (`serverSubscriptions`) plus registration\n * records persisted to Redis at `sl:reg:<subId>`.\n *\n * • Out-of-order `updateSubscriptionReadSet` calls are filtered server\n * side using `lastSnapshotTs` (monotonic DB commit timestamp).\n *\n * • On rpc-websockets reconnect, the client:\n * 1. Transitions to `busy` (any in-flight `invalidateWriteset`\n * buffers its writeSet).\n * 2. Invokes the app server's `recoveryDriver` to re-register all\n * known subs.\n * 3. Flushes the buffer as one batched `invalidateWriteset` RPC and\n * hands the resulting `affected[]` to `affectedHandler`.\n * 4. Transitions to `ready`.\n *\n * • During an extended outage, the buffer is bounded; if exceeded, it\n * is dropped and we rely on the commit-log replay built into\n * `SubscriptionWorker.register()` to catch each sub up on the next\n * re-register.\n */\nexport class SubManagerClient {\n private client: Client;\n private state: ReadyState = \"busy\";\n private hasConnectedOnce = false;\n\n /** Resolves whenever state transitions to 'ready'. Replaced on each busy→ready cycle. */\n private readyPromise: Promise<void>;\n private resolveReady!: () => void;\n\n /** Writeset entries accumulated while state === 'busy'. Flushed as one RPC after recovery. */\n private bufferedWriteSets: WriteEntry[] = [];\n private readonly bufferLimit: number;\n private bufferDroppedDuringOutage = false;\n\n private recoveryDriver?: RecoveryDriver;\n private affectedHandler?: AffectedHandler;\n\n constructor(urlOrOptions: string | SubManagerClientOptions) {\n const opts = typeof urlOrOptions === \"string\"\n ? { url: urlOrOptions }\n : urlOrOptions;\n\n this.bufferLimit = opts.invalidateBufferLimit ?? 50_000;\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n\n this.client = new Client(opts.url, {\n dataPack: subscriptionManagerDataPack,\n });\n this.client.setAutoReconnect(true);\n this.client.setMaxReconnects(0);\n // Ensure the dataPack is set even if rpc-websockets doesn't pass it through to the Client constructor\n this.client.dataPack = subscriptionManagerDataPack;\n\n this.client.on(\"open\", this.handleOpen.bind(this));\n this.client.on(\"close\", this.handleClose.bind(this));\n this.client.on(\"error\", (err: unknown) => logger.warn(err, \"sub-manager client socket error\"));\n }\n\n /**\n * Wire up the app-server-driven recovery. Must be called before any\n * disconnect/reconnect cycle for buffered writeSets to be flushed and\n * subscriptions re-registered.\n */\n setRecoveryDriver(driver: RecoveryDriver): void {\n this.recoveryDriver = driver;\n }\n\n /**\n * Wire up the handler invoked when batched invalidate results land\n * after recovery. The handler is expected to drive\n * `reexecuteAffectedQuery` for each entry.\n */\n setAffectedHandler(handler: AffectedHandler): void {\n this.affectedHandler = handler;\n }\n\n // ─── RPC methods ────────────────────────────────────────────────────────\n\n async registerSubscription(\n params: types.RegisterSubscriptionParams,\n ): Promise<types.RegisterSubscriptionResult> {\n return this.call(\"registerSubscription\", params, types.RegisterSubscriptionResultSchema);\n }\n\n async registerSubscriptionBatch(\n params: types.RegisterSubscriptionParams[],\n ): Promise<types.RegisterSubscriptionResult[]> {\n return this.call(\"registerSubscriptionBatch\", params, types.RegisterSubscriptionBatchResultSchema);\n }\n\n async updateSubscriptionReadSet(\n params: types.UpdateSubscriptionReadSetParams,\n ): Promise<types.UpdateSubscriptionReadSetResult> {\n return this.call(\"updateSubscriptionReadSet\", params, types.UpdateSubscriptionReadSetResultSchema);\n }\n\n async unregisterSubscription(\n params: types.UnregisterSubscriptionParams,\n ): Promise<types.UnregisterSubscriptionResult> {\n return this.call(\"unregisterSubscription\", params, types.UnregisterSubscriptionResultSchema);\n }\n\n async unregisterSubscriptions(\n params: types.UnregisterSubscriptionsParams,\n ): Promise<types.UnregisterSubscriptionsResult> {\n return this.call(\"unregisterSubscriptions\", params, types.UnregisterSubscriptionsResultSchema);\n }\n\n /**\n * Sends the writeSet to the sub-manager for invalidation, OR buffers it\n * if the client is currently disconnected / recovering. Returns\n * `{ affected: [] }` while buffering — the actual `affected` from the\n * buffered batch is delivered via the registered `affectedHandler`\n * after recovery completes.\n */\n async invalidateWriteset(\n params: types.InvalidateWritesetParams,\n ): Promise<types.InvalidateWritesetResult> {\n if (this.state === \"busy\") {\n this.appendToBuffer(params.writeSet);\n return { affected: [] };\n }\n return this.call(\"invalidateWriteset\", params, types.InvalidateWritesetResultSchema);\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Single point through which RPCs go. Throws on transport / server error.\n * `registerSubscription`, `updateSubscriptionReadSet`, and\n * `unregisterSubscription` are NOT gated on the ready state: during a\n * disconnect they will fail at the socket layer, and during recovery\n * (which itself uses these methods) gating would deadlock.\n */\n private async call<T extends ZodType>(\n procName: string,\n params: unknown,\n resType: T,\n ): Promise<z.infer<T>> {\n const result = await this.client.call(procName, params);\n return resType.parse(result) as z.infer<T>;\n }\n\n private appendToBuffer(writeSet: WriteEntry[]): void {\n if (this.bufferedWriteSets.length + writeSet.length > this.bufferLimit) {\n if (!this.bufferDroppedDuringOutage) {\n logger.warn(\n `Invalidate buffer would exceed ${this.bufferLimit}; dropping buffered entries. ` +\n `Subscriptions will be caught up via commit-log replay on re-register.`,\n );\n this.bufferDroppedDuringOutage = true;\n }\n this.bufferedWriteSets = [];\n return;\n }\n this.bufferedWriteSets.push(...writeSet);\n }\n\n private async flushBuffer(): Promise<void> {\n if (this.bufferedWriteSets.length === 0) return;\n const writeSet = this.bufferedWriteSets;\n this.bufferedWriteSets = [];\n this.bufferDroppedDuringOutage = false;\n\n try {\n const result = await this.call(\n \"invalidateWriteset\",\n { writeSet },\n types.InvalidateWritesetResultSchema,\n );\n if (this.affectedHandler && result.affected.length > 0) {\n await this.affectedHandler(result.affected);\n }\n } catch (err) {\n logger.error(err, \"Failed to flush invalidate buffer after recovery\");\n }\n }\n\n private markBusy(): void {\n if (this.state === \"busy\") return;\n this.state = \"busy\";\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n }\n\n private markReady(): void {\n if (this.state === \"ready\") return;\n this.state = \"ready\";\n this.resolveReady();\n }\n\n private handleClose(): void {\n logger.info(\"sub-manager client: socket closed\");\n this.markBusy();\n }\n\n private handleOpen(): void {\n if (!this.hasConnectedOnce) {\n this.hasConnectedOnce = true;\n logger.info(\"sub-manager client: initial connect\");\n this.markReady();\n return;\n }\n logger.info(\"sub-manager client: reconnected — starting recovery\");\n void this.runRecovery();\n }\n\n private async runRecovery(): Promise<void> {\n // Stay 'busy' for the duration of recovery so concurrent\n // invalidateWriteset calls keep buffering.\n try {\n if (this.recoveryDriver) {\n await this.recoveryDriver();\n } else {\n logger.warn(\"sub-manager client: no recoveryDriver configured — skipping recovery step\");\n }\n await this.flushBuffer();\n } catch (err) {\n logger.error(err, \"Recovery failed; state will be marked ready anyway\");\n } finally {\n logger.info(\"sub-manager client: recovery complete\");\n this.markReady();\n }\n }\n\n /**\n * Test/inspection helpers.\n */\n /** @internal */\n isReady(): boolean { return this.state === \"ready\"; }\n /** @internal */\n bufferSize(): number { return this.bufferedWriteSets.length; }\n}\n","import { createServer, IncomingMessage, type Server } from \"http\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { ZodType } from \"zod\";\nimport Redis from \"ioredis\";\nimport { retry, handleAll, ExponentialBackoff } from \"cockatiel\";\nimport { stableStringify, supaliveStringify } from \"../helper\";\nimport {\n type QueryCtx,\n type MutationCtx,\n type ActionCtx,\n ContextRegistry,\n getContextRegistry,\n} from \"../router\";\nimport { sleep, SupaliveDb } from \"../db/realtime_db\";\nimport type { WriteEntry, CommitTs, LiveResult, MutationResult } from \"../db/types_db\";\nimport { AuthenticationError, ClientMessage, ClientMessageSchema, Context, ServerMessage } from \"../db/types_client_rpc\";\nimport {\n type Session,\n type InstanceId,\n type SubId,\n type PendingMutation,\n RemoteSubscriptionMessageSchema,\n RemoteRecomputeRace,\n SubscriptionEntry,\n RemoteSubscriptionMessage,\n} from \"./types_server\";\nimport { type Router } from \"../router/router\";\nimport {\n ANONYMOUS_IDENTITY,\n generateSubscriptionId,\n generateCacheKey,\n getHashOf,\n} from \"./sub_hash\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport { CacheLayer } from \"../db/cache\";\nimport { SubManagerClient } from \"./subscription-manager-client\";\nimport { SupaliveServerConfig } from \"../config\";\nimport { appInstanceId, logger as baseLogger, logLevelOf } from \"../logger\";\nimport { AffectedSubscription, RegistrationRecord } from \"./subscription-manager-types\";\nimport { bootstrapDbTypes } from \"../db/init_db\";\nimport { exit, stdout } from \"process\";\n\nconst redisRetryPolicy = retry(handleAll, {\n maxAttempts: 3,\n backoff: new ExponentialBackoff({\n initialDelay: 100,\n maxDelay: 5000,\n exponent: 2,\n }),\n});\n\n/**\n * Global broadcast channel used to signal column-type cache invalidation\n * across every running server instance. Publishers (migration tooling,\n * the publishSchemaReload helper below) send a JSON message of shape\n * `{ type: \"schema:reload\"; tables?: string[]; originInstance?: string }`.\n * Subscribers re-bootstrap from PG (or Redis) so the next batched write\n * sees up-to-date column metadata without a process restart.\n */\nconst SCHEMA_RELOAD_CHANNEL = \"supalive:schema:reload\";\n\n/**\n * Per-socket cap on pending mutations. The Nth+1 mutation from a single\n * client gets rejected with TOO_MANY_CONCURRENT_MUTATIONS rather than\n * being enqueued — this is rate-limit pressure on a misbehaving client,\n * not a hard system limit.\n *\n * Mirrors Convex's OPERATION_QUEUE_BUFFER_SIZE (1000). Mutations drain\n * one-at-a-time per socket, so a healthy client never approaches this.\n */\nconst MAX_MUTATION_QUEUE_BUFFER = 1000;\n\n/**\n * Per-socket cap on in-flight actions. Actions run in parallel (unlike\n * mutations) because they may do long external work (HTTP, queue calls);\n * the cap exists so one socket can't open thousands of outbound\n * connections from inside the server.\n */\nconst MAX_ACTION_CONCURRENCY = 1000;\n\n// ─── Heartbeat constants ─────────────────────────────────────\n//\n// Two independent jobs run on a single per-session interval:\n//\n// 1. Dead-TCP detection: terminate sockets that have gone fully silent\n// (no inbound text/ping/pong) past `CLIENT_TIMEOUT_MS`. Otherwise\n// emit a ws-protocol ping (browser auto-pongs, refreshing `session.lastReceivedAt`).\n// 2. Client-watchdog refresh: if the application channel has been idle\n// for at least `HEARTBEAT_INTERVAL_MS`, send a `{type:\"ping\"}` JSON\n// frame so the client's inactivity watchdog stays fresh. Busy\n// sockets skip this (the `lastSentAt` check makes it idle-only).\n//\n// Both fire on the same `heartbeatTimer` — one `setInterval` per session.\nconst HEARTBEAT_INTERVAL_MS = Number(\n process.env.SUPALIVE_HEARTBEAT_INTERVAL_MS ?? 15_000,\n);\nconst CLIENT_TIMEOUT_MS = 120_000;\n\n/**\n * TTL on the Redis lock that elects the recompute winner when the writer\n * doesn't hold the sub locally. Each (subId, commitTs) gets its own lock\n * key, so this only needs to bound the worst-case recompute duration —\n * if the winner crashes mid-recompute, the lock expires and the next\n * mutation triggers a new race.\n */\nconst RECOMPUTE_LOCK_TTL_SECONDS = Number(\n process.env.SUPALIVE_RECOMPUTE_LOCK_TTL_SECONDS ?? 10,\n);\n\nconst logger = baseLogger.child({\n \"name\": \"SUPALIVE\",\n},\n { level: logLevelOf(\"SERVER_LOG_LEVEL\") },\n);\n\n/**\n * Supalive WebSocket Server\n */\nexport class SupaliveWebSocketServer<TContext extends Context> {\n private config: SupaliveServerConfig<TContext>;\n private instanceId: InstanceId;\n\n private httpServer: Server;\n private wss: WebSocketServer;\n\n private cache: CacheLayer;\n private redisSub: Redis;\n private db: SupaliveDb;\n private contextRegistry!: ContextRegistry;\n private router?: Router<any, TContext>;\n\n private sessions: Map<WebSocket, Session<TContext>> = new Map();\n private serverSubscriptions: Map<SubId, SubscriptionEntry> = new Map();\n private subManagerClient!: SubManagerClient;\n\n private stats = {\n activeConnections: 0,\n totalConnections: 0,\n activeSubscriptions: 0,\n };\n\n constructor(config: SupaliveServerConfig<TContext>) {\n this.config = {\n port: 3000,\n host: \"0.0.0.0\",\n contextName: \"default\",\n sessionTtlSeconds: 3600,\n ...config,\n };\n this.instanceId = appInstanceId;\n\n if (!config.subManagerUrl) {\n throw new Error(\"subManagerUrl config is required (WebSocket URL of the sub-manager process)\");\n }\n this.subManagerClient = new SubManagerClient(config.subManagerUrl);\n this.cache = config.cacheLayer;\n this.redisSub = config.redisSubClient;\n this.db = config.database;\n\n // Wire the sub-manager client's recovery callbacks to drive re-registration\n // from this app server's serverSubscriptions whenever the RPC reconnects,\n // and to process the resulting batched invalidate flush.\n this.subManagerClient.setRecoveryDriver(this.runSubManagerRecovery.bind(this));\n this.subManagerClient.setAffectedHandler(this.runAffectedHandler.bind(this));\n\n this.httpServer = createServer();\n this.wss = new WebSocketServer({\n server: this.httpServer,\n autoPong: true,\n maxPayload: 100 * 1024 * 1024,\n // backlog: //max pending connections queue (default: 511)\n });\n\n this.setupWebSocketHandlers();\n this.subscribeToInstanceChannel();\n }\n\n /**\n * Per-session heartbeat. Fires every `HEARTBEAT_INTERVAL_MS` and does\n * both jobs on the same tick:\n *\n * 1. Inactivity check + ws-protocol ping (dead-TCP detection).\n * 2. App-level `{type:\"ping\"}` if the channel has been idle for at\n * least one heartbeat interval (client-watchdog refresh).\n *\n * The `lastSentAt` gate on (2) preserves the original \"only ping when\n * idle\" semantics: a session producing sub:updates faster than the\n * heartbeat interval never emits a redundant app-ping.\n */\n private startSessionHeartbeat(session: Session<TContext>): void {\n session.heartbeatTimer = setInterval(() => {\n // (1) Dead-TCP detection.\n if (Date.now() - session.lastReceivedAt > CLIENT_TIMEOUT_MS) {\n logger.info(`ws inactivity → terminate session ${session.id}`);\n try { session.ws.terminate(); } catch { /* close handler cleans up */ }\n return;\n }\n try {\n session.ws.ping();\n } catch {\n // socket already broken — close handler will clean up\n }\n\n // (2) Client-watchdog refresh, idle-only.\n if (Date.now() - session.lastSentAt >= HEARTBEAT_INTERVAL_MS) {\n this.sendMessage(session, { type: \"ping\" });\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private clearSessionTimers(session: Session<TContext>): void {\n if (session.heartbeatTimer) {\n clearInterval(session.heartbeatTimer);\n session.heartbeatTimer = null;\n }\n }\n\n registerRouter(router: Router<any, TContext>): void {\n this.router = router;\n this.contextRegistry = getContextRegistry(router.contextName);\n }\n\n async start(): Promise<void> {\n return new Promise((resolve) => {\n this.httpServer.listen(this.config.port, this.config.host, () => {\n logger.info(`Server: ${this.instanceId} listening on ${this.config.host}:${this.config.port}`);\n resolve();\n });\n });\n }\n\n async stop(): Promise<void> {\n this.wss.close();\n for (const [ws] of this.sessions) {\n ws.close(1001, \"Server shutting down\");\n }\n this.httpServer.close();\n logger.info(\"Server stopped\");\n }\n\n getStats() {\n return {\n instanceId: this.instanceId,\n activeConnections: this.stats.activeConnections,\n totalConnections: this.stats.totalConnections,\n activeSubscriptions: this.stats.activeSubscriptions,\n };\n }\n\n getInstanceId(): string {\n return this.instanceId;\n }\n\n private setupWebSocketHandlers(): void {\n this.wss.on(\"connection\", (ws, req) => {\n this.handleConnection(ws, req).catch((err) => {\n logger.error(err, \"Connection handler error\")\n });\n });\n }\n\n private async handleConnection(ws: WebSocket, req: IncomingMessage): Promise<void> {\n this.stats.totalConnections++;\n this.stats.activeConnections++;\n\n const session: Session<TContext> = {\n id: uuidv4(),\n ws,\n serverCtx: {} as TContext,\n subIds: new Set(),\n mutationQueue: [],\n mutationInFlight: false,\n actionInFlight: 0,\n lastReceivedAt: Date.now(),\n lastSentAt: Date.now(),\n heartbeatTimer: null,\n };\n logger.info(`New session: ${session.id}`);\n\n this.sessions.set(ws, session);\n this.sendMessage(session, { type: \"connected\", sessionId: session.id });\n\n // Any inbound frame (text, ws-ping, ws-pong) refreshes the liveness\n // clock. This is the only thing keeping `CLIENT_TIMEOUT_MS` from\n // tripping on a silent-but-live client, since the browser auto-pong\n // is the only thing such a client emits.\n ws.on(\"message\", (data) => {\n session.lastReceivedAt = Date.now();\n this.handleMessage(session, data.toString()).catch((err) => {\n logger.error(err, \"Message handler error\");\n });\n });\n ws.on(\"ping\", () => { session.lastReceivedAt = Date.now(); });\n ws.on(\"pong\", () => { session.lastReceivedAt = Date.now(); });\n\n this.startSessionHeartbeat(session);\n\n ws.on(\"close\", (code) => {\n logger.info(`Connection closed: ${session.id}, code: ${code}`);\n this.clearSessionTimers(session);\n this.handleDisconnect(session);\n this.sessions.delete(ws);\n this.stats.activeConnections--;\n });\n\n ws.on(\"error\", (err) => {\n logger.error(err, \"Websocket error\");\n });\n }\n\n private async handleMessage(session: Session<TContext>, message: string): Promise<void> {\n try {\n const rawMsg = JSON.parse(message);\n const parseResult = ClientMessageSchema.safeParse(rawMsg);\n if (!parseResult.success) {\n this.sendError(session, \"\", \"Invalid message format\", \"INVALID_MESSAGE\");\n return;\n }\n await this.processMessage(session, parseResult.data);\n } catch (err) {\n logger.error(err, \"Message handling error\");\n this.sendError(session, \"\", \"Parse error\", \"PARSE_ERROR\");\n }\n }\n\n private async processMessage(session: Session<TContext>, msg: ClientMessage): Promise<void> {\n switch (msg.type) {\n case \"auth\":\n await this.handleAuth(session, msg.data);\n break;\n case \"call\":\n await this.handleCall(session, msg.id, msg.procedure, msg.input);\n break;\n case \"subscribe\":\n await this.handleSubscribe(session, msg.id, msg.procedure, msg.input);\n break;\n case \"unsubscribe\":\n await this.handleUnsubscribe(session, msg.subId);\n break;\n }\n }\n\n private async handleAuth(session: Session<TContext>, data?: Record<string, unknown>): Promise<void> {\n if (!this.config.verifyAuth || !data) {\n this.sendMessage(session, { type: \"auth:success\", userId: \"anonymous\" });\n return;\n }\n\n try {\n const authResult = await this.config.verifyAuth(data, session.id);\n if (!authResult) {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n return;\n }\n\n session.serverCtx = authResult;\n this.sendMessage(session, { type: \"auth:success\", userId: authResult.user.userId });\n } catch (e) {\n if (e instanceof AuthenticationError) {\n this.sendMessage(session, {\n type: \"auth:failed\",\n message: \"Invalid authentication: \" + e.message,\n extra: e.extra,\n });\n } else {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n }\n }\n }\n\n /**\n * Park a mutation on this session's FIFO queue and resolve when it\n * eventually drains. The drainer is fire-and-forget: it picks up any\n * newly enqueued op as long as one drain loop is running on this\n * session. Callers (handleCall) own the response/error for their own\n * op via the returned promise.\n */\n private enqueueMutation(\n session: Session<TContext>,\n procedure: RegisteredProcedure<TContext>,\n validatedInput: unknown,\n ): Promise<MutationResult<unknown>> {\n return new Promise<MutationResult<unknown>>((resolve, reject) => {\n const pending: PendingMutation<TContext> = {\n procedure,\n validatedInput,\n resolve,\n reject,\n };\n session.mutationQueue.push(pending);\n this.drainMutationQueue(session).catch((err) =>\n logger.error(err, \"drainMutationQueue crashed\"),\n );\n });\n }\n\n /**\n * Drain the per-session mutation queue serially. Only one drain loop\n * runs at a time per session — re-entrant calls bail immediately, the\n * loop already in progress will pick up the new op before exiting.\n */\n private async drainMutationQueue(session: Session<TContext>): Promise<void> {\n if (session.mutationInFlight) return;\n session.mutationInFlight = true;\n try {\n while (session.mutationQueue.length > 0) {\n const op = session.mutationQueue.shift()!;\n try {\n const fn = op.procedure.fn as (\n ctx: MutationCtx<TContext>,\n input: unknown,\n ) => Promise<unknown>;\n const result = await this.db.mutation(async (ctx) => {\n return fn({ db: ctx, serverCtx: session.serverCtx }, op.validatedInput);\n });\n op.resolve(result);\n } catch (err) {\n op.reject(err);\n }\n }\n } finally {\n session.mutationInFlight = false;\n }\n }\n\n private async handleCall(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown\n ): Promise<void> {\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure) {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found`, \"NOT_FOUND\");\n return;\n }\n\n try {\n const validatedInput = (procedure.inputSchema as ZodType).parse(input);\n\n if (procedure.type === \"query\") {\n const result = await this.db.query(async (ctx) => {\n const fn = procedure.fn as (ctx: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n return fn({ db: ctx, serverCtx: session.serverCtx }, validatedInput);\n });\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: result,\n });\n } else if (procedure.type === \"mutation\") {\n if (session.mutationQueue.length >= MAX_MUTATION_QUEUE_BUFFER) {\n this.sendError(\n session,\n id,\n `Too many concurrent mutations on this connection (max ${MAX_MUTATION_QUEUE_BUFFER})`,\n \"TOO_MANY_CONCURRENT_MUTATIONS\",\n );\n return;\n }\n\n const mutationResult = await this.enqueueMutation(session, procedure, validatedInput);\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n });\n\n await this.handleMutationCommit(mutationResult.writeSet, mutationResult.commitTs);\n } else if (procedure.type === \"action\") {\n if (session.actionInFlight >= MAX_ACTION_CONCURRENCY) {\n this.sendError(\n session,\n id,\n `Too many concurrent actions on this connection (max ${MAX_ACTION_CONCURRENCY})`,\n \"TOO_MANY_CONCURRENT_ACTIONS\",\n );\n return;\n }\n\n session.actionInFlight++;\n let result: unknown;\n try {\n const fn = procedure.fn as (ctx: ActionCtx<TContext>, input: unknown) => Promise<unknown>;\n result = await fn({ db: this.db, serverCtx: session.serverCtx }, validatedInput);\n } finally {\n session.actionInFlight--;\n }\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: result,\n });\n }\n\n\n } catch (err) {\n logger.error(err, `Error executing \"${procedureName}\"`);\n this.sendError(\n session,\n id,\n err instanceof Error ? err.message : \"Execution failed\",\n \"EXECUTION_ERROR\"\n );\n }\n }\n\n private async handleSubscribe(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown\n ): Promise<void> {\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found or not a query`, \"NOT_FOUND\");\n return;\n }\n\n try {\n const validatedInput = procedure.inputSchema.parse(input);\n const queryIdentity = this.resolveQueryIdentity(procedure, session, validatedInput);\n const inputJsonStable = stableStringify(validatedInput);\n const subId = await generateSubscriptionId(procedureName, inputJsonStable, queryIdentity);\n const cacheKey = await generateCacheKey(procedureName, inputJsonStable, queryIdentity);\n const existingServerSub = this.serverSubscriptions.get(subId);\n\n let data: unknown;\n let dataHash: string;\n let liveResult: LiveResult<unknown> | null = null;\n\n // track this session subscribed this subscription\n session.subIds.add(subId);\n\n if (existingServerSub) {\n existingServerSub.subscribedTimes += 1;\n\n const cached = await this.cache.getQueryCacheFor(existingServerSub.cacheKey);\n if (cached) {\n data = cached.data;\n } else {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);\n data = liveResult.data;\n }\n } else {\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subManagerResult = await this.subManagerClient.registerSubscription({\n subId,\n cacheKey,\n queryName: procedureName,\n args: validatedInput,\n queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs: lastSnapshotTs,\n });\n\n const cached = await this.cache.getQueryCacheFor(subManagerResult.cacheKey);\n if (cached && !subManagerResult.recompute) {\n data = cached.data;\n } else {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);\n data = liveResult.data;\n }\n\n this.serverSubscriptions.set(subId, {\n subId,\n cacheKey,\n subscribedTimes: 1,\n });\n this.stats.activeSubscriptions++;\n }\n\n if (liveResult) {\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey,\n subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n this.cache.setQueryCacheFor(cacheKey, data),\n ]);\n\n // If the sub-manager just told us this subId isn't tracked (we hit\n // a window between its register and a separate restart), our register\n // above should have rebuilt the state — log loudly if not.\n if (!updateResult.tracked) {\n logger.warn(\n `updateSubscriptionReadSet returned tracked:false immediately after register for ${subId} ` +\n `— possible sub-manager restart mid-subscribe`,\n );\n }\n }\n\n dataHash = await getHashOf(data);\n\n // Hand the canonical subId back to the client so it doesn't need to\n // recompute the hash locally (which would require pulling in wasm and\n // wouldn't reflect per-procedure `queryIdentity` overrides).\n this.sendMessage(session, { type: \"response\", id, success: true, subId });\n this.sendMessage(session, { type: \"sub:update\", subId, data, dataHash });\n\n logger.info(`Subscribed: ${subId} for session ${session.id}`);\n } catch (err) {\n logger.error(err, `Error subscribing to \"${procedureName}\"`);\n this.sendMessage(session, { type: \"response\", id, success: false });\n this.sendError(session, id, err instanceof Error ? err.message : \"Subscribe failed\", \"SUBSCRIBE_ERROR\");\n }\n }\n\n private async handleUnsubscribe(session: Session<TContext>, subId: string): Promise<void> {\n const serverSub = this.serverSubscriptions.get(subId);\n if (!serverSub) {\n session.subIds.delete(subId);\n return;\n }\n\n serverSub.subscribedTimes -= 1;\n if (serverSub.subscribedTimes <= 0) {\n session.subIds.delete(subId);\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions--;\n\n try {\n await this.subManagerClient.unregisterSubscription({ subId, instanceName: this.instanceId });\n } catch (err) {\n logger.error(err, \"Failed to unregister from sub-manager:\");\n }\n }\n\n logger.info(`Unsubscribed: ${subId} for session ${session.id}, remaining server subs: ${serverSub.subscribedTimes}`);\n }\n\n private async handleDisconnect(session: Session<TContext>): Promise<void> {\n logger.info(`Session disconnected: ${session.id}`);\n\n let unregisterSubs: SubscriptionEntry[] | undefined = undefined;\n for (const subId of session.subIds) {\n const sub = this.serverSubscriptions.get(subId);\n if (!sub) continue;\n\n sub.subscribedTimes -= 1;\n if (sub.subscribedTimes <= 0) {\n if (!unregisterSubs) {\n unregisterSubs = [];\n }\n\n unregisterSubs.push(sub);\n }\n }\n\n if (!unregisterSubs || unregisterSubs.length === 0) return;\n\n try {\n await this.subManagerClient.unregisterSubscriptions(\n unregisterSubs.map((sub) => ({ subId: sub.subId, instanceName: this.instanceId })),\n );\n } catch (err) {\n logger.error(err, \"Failed to unregister from sub-manager:\");\n }\n\n for (const sub of unregisterSubs) {\n // if still not subscribing\n if (sub.subscribedTimes <= 0) {\n this.serverSubscriptions.delete(sub.subId);\n this.stats.activeSubscriptions--;\n }\n }\n\n logger.info(`All Subscription sessions is disconnected: ${session.id}, subs: ${unregisterSubs.length}`);\n }\n\n /**\n * Drives recovery on a sub-manager reconnect. Iterates this app server's\n * `serverSubscriptions`, reads each subId's registration record from\n * Redis (`sl:reg:<subId>`), and re-registers it with the sub-manager.\n *\n * • If the Redis record is missing, another app-server instance fully\n * unregistered the sub during our disconnect. We drop it locally and\n * send `sub:gone` to every session that was holding it.\n * • If the sub-manager reports `recompute: true` (cache-metadata + commit\n * log replay detected staleness), we kick a reexec for that sub.\n *\n * Returns after all subs have been processed. Only then does the\n * SubManagerClient flush its buffered invalidate writeSets.\n */\n private async runSubManagerRecovery(): Promise<void> {\n const snapshot = Array.from(this.serverSubscriptions.values());\n if (snapshot.length === 0) {\n logger.info(\"Sub-manager recovery: no subscriptions to restore\");\n return;\n }\n\n logger.info(`Sub-manager recovery: restoring ${snapshot.length} subscription(s)`);\n\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subIds = snapshot.map((e) => e.subId);\n const recordsMap = await this.cache.fetchRegistrationRecordsBatch(subIds);\n\n const registerParams: Array<{\n entry: typeof snapshot[0];\n record: RegistrationRecord;\n }> = [];\n for (const entry of snapshot) {\n const record = recordsMap.get(entry.subId) ?? null;\n if (!record) {\n this.dropSubscriptionLocally(entry.subId, \"registration not found after recovery\");\n continue;\n }\n registerParams.push({ entry, record });\n }\n\n const recompute: AffectedSubscription[] = [];\n const BATCH_SIZE = 50;\n for (let i = 0; i < registerParams.length; i += BATCH_SIZE) {\n const batch = registerParams.slice(i, i + BATCH_SIZE);\n const params = batch.map(({ entry, record }) => ({\n subId: entry.subId,\n cacheKey: entry.cacheKey,\n queryName: record.queryName,\n args: record.args,\n queryIdentity: record.queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs,\n }));\n try {\n const results = await this.subManagerClient.registerSubscriptionBatch(params);\n for (let j = 0; j < batch.length; j++) {\n const { entry, record } = batch[j]!;\n const result = results[j]!;\n if (result.recompute) {\n recompute.push({\n subId: entry.subId,\n queryName: record.queryName,\n args: record.args,\n cacheKey: entry.cacheKey,\n notifyInstances: [],\n });\n }\n }\n } catch (err) {\n logger.error(err, `Sub-manager recovery: failed to batch register ${i}-${i + batch.length}`);\n }\n }\n\n await Promise.allSettled(recompute.map((a) => this.reexecuteAffectedQuery(a)));\n\n logger.info(`Sub-manager recovery: done (${recompute.length} reexec'd)`);\n }\n\n private async runAffectedHandler(affected: AffectedSubscription[]) {\n await Promise.allSettled(\n affected.map((a) => this.reexecuteAffectedQuery(a)),\n );\n }\n\n /**\n * Removes a subscription from this app server's state and notifies every\n * session that was holding it. Used during recovery when the central\n * registration record is gone, or after a `tracked: false` reply from the\n * sub-manager that we can't reconcile.\n */\n private dropSubscriptionLocally(subId: string, reason: string): void {\n const serverSub = this.serverSubscriptions.get(subId);\n let notified = 0;\n if (serverSub) {\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions = Math.max(0, this.stats.activeSubscriptions - 1);\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) {\n notified++;\n session.subIds.delete(subId);\n this.sendMessage(session, { type: \"sub:gone\", subId, reason });\n }\n }\n }\n\n logger.warn(`Dropped subscription ${subId} locally (${reason}); notified ${notified} session(s)`);\n }\n\n private async handleMutationCommit(writeSet: WriteEntry[], commitTs: CommitTs): Promise<void> {\n if (writeSet.length === 0) return;\n\n const tables = [...new Set(writeSet.map((w) => w.table))];\n logger.debug(`Mutation committed for tables: [${tables.join(\", \")}]`);\n\n try {\n const result = await this.subManagerClient.invalidateWriteset({ writeSet });\n logger.debug({ affected: result }, \"Sub-managers invalidation result\");\n\n if (result?.affected?.length > 0) {\n await Promise.allSettled(\n result.affected.map((affected) => this.dispatchAffected(affected, commitTs)),\n );\n }\n } catch (err) {\n logger.error(err, \"Failed to notify sub-managers\");\n }\n }\n\n /**\n * Route an affected sub to whichever instance can actually run the\n * recompute:\n * • If this instance has a local session for the sub, recompute here\n * (fast path) and broadcast the result to every other instance.\n * • Otherwise broadcast a `recompute-race` to every instance in\n * `notifyInstances`. Each receiver tries to acquire a Redis lock\n * keyed by (subId, commitTs); the winner recomputes and publishes\n * an `update` via the normal broadcast path, losers no-op.\n *\n * If `notifyInstances` is empty (the sub has no live holders), we drop\n * the message — the next subscribe will recompute fresh state.\n */\n private async dispatchAffected(affected: AffectedSubscription, commitTs: CommitTs): Promise<void> {\n if (this.hasLocalSessionFor(affected.subId)) {\n await this.reexecuteAffectedQuery(affected);\n return;\n }\n\n if (affected.notifyInstances.length === 0) {\n logger.debug(\n `dispatchAffected: no live instances hold ${affected.subId} — dropping`,\n );\n return;\n }\n\n await this.publishRecomputeRace(affected, commitTs);\n }\n\n private hasLocalSessionFor(subId: SubId): boolean {\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) return true;\n }\n return false;\n }\n\n private async publishRecomputeRace(\n affected: AffectedSubscription,\n commitTs: CommitTs,\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"recompute-race\",\n subId: affected.subId,\n cacheKey: affected.cacheKey,\n queryName: affected.queryName,\n args: affected.args,\n notifyInstances: affected.notifyInstances,\n commitTs: commitTs.toString(),\n originInstance: this.instanceId,\n });\n\n const publishPromises = affected.notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n logger.error(\n err,\n `Failed to publish recompute-race to ${channel} after many retries`,\n );\n }\n });\n\n await Promise.all(publishPromises);\n }\n\n private async reexecuteAffectedQuery(affected: AffectedSubscription): Promise<void> {\n let foundSession: Session<TContext> | null = null;\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n foundSession = session;\n break;\n }\n }\n\n if (!foundSession) {\n // Should only happen if every local session unsubscribed between\n // dispatchAffected's check and now. dispatchAffected forwards to\n // another instance when no local session exists, so we'd lose the\n // update for everyone in this rare race. Accept the gap.\n logger.debug(`Session not found for subId: ${affected.subId}`);\n return;\n }\n\n try {\n const liveResult = await this.executeLiveQuery(affected.queryName, affected.args, foundSession.serverCtx);\n const dataHash = await getHashOf(liveResult.data);\n\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey: affected.cacheKey,\n subId: affected.subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n this.cache.setQueryCacheFor(affected.cacheKey, liveResult.data),\n ]);\n\n // `tracked: false` means the sub-manager has no record of this subId\n // (it restarted before we recovered, or a concurrent unregister wiped\n // it). Drop the sub locally and let clients re-subscribe.\n if (!updateResult.tracked) {\n this.dropSubscriptionLocally(affected.subId, \"no longer tracked by sub-manager\");\n return;\n }\n\n // find any session with same subId and send update\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: affected.subId,\n data: liveResult.data,\n dataHash,\n });\n }\n }\n\n if (affected.notifyInstances.length > 0) {\n await this.publishSubscriptionUpdate(\n affected.subId,\n liveResult.data,\n dataHash,\n affected.notifyInstances\n );\n }\n\n logger.debug(`Re-executed affected query for subId: ${affected.subId}`);\n } catch (err) {\n logger.error(err, `Failed to re-execute query for subId ${affected.subId}`);\n }\n }\n\n private async executeLiveQuery(\n procedureName: string,\n input: unknown,\n serverCtx: TContext\n ): Promise<LiveResult<unknown>> {\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n throw new Error(`Procedure not defined: ${procedureName}`);\n }\n\n return this.db.liveQuery(async (ctx) => {\n const fn = procedure.fn as (ctx: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n return fn({ db: ctx, serverCtx }, input);\n });\n }\n\n /**\n * Resolve the segmentation key used in the subId/cacheKey hash for a\n * query subscription. Priority:\n *\n * 1. Procedure-level `queryIdentity` override:\n * - `false` → identity = `ANONYMOUS_IDENTITY` (cache shared across users)\n * - `string` → that literal\n * - `function` → ctx + input → string | null | undefined\n * 2. `config.getUserId(serverCtx)` → falls back to ANONYMOUS_IDENTITY if it\n * returns null/undefined or isn't configured.\n */\n private resolveQueryIdentity(\n procedure: RegisteredProcedure<TContext>,\n session: Session<TContext>,\n input: unknown,\n ): string {\n const override = procedure.queryIdentity;\n if (override !== undefined) {\n if (override === false) return ANONYMOUS_IDENTITY;\n if (typeof override === \"string\") return override;\n if (typeof override === \"function\") {\n const value = override(session.serverCtx, input);\n if (value === null || value === undefined) return ANONYMOUS_IDENTITY;\n return value;\n }\n }\n if (!this.config.getUserId) return ANONYMOUS_IDENTITY;\n return this.config.getUserId(session.serverCtx) ?? ANONYMOUS_IDENTITY;\n }\n\n private sendMessage(session: Session<TContext>, message: ServerMessage): void {\n if (session.ws.readyState !== WebSocket.OPEN) return;\n session.ws.send(supaliveStringify(message));\n // Mark the channel as having had recent outbound traffic. The\n // heartbeat tick uses this to skip the idle-only app-level ping\n // for busy sessions (no point pinging a client that just got a\n // sub:update).\n session.lastSentAt = Date.now();\n }\n\n private sendMessageRaw(session: Session<TContext>, message: string): void {\n if (session.ws.readyState === WebSocket.OPEN) {\n session.ws.send(message);\n }\n }\n\n private sendError(session: Session<TContext>, id: string, error: string, code: string): void {\n this.sendMessage(session, { type: \"response\", id, success: false, error, code });\n }\n\n private async subscribeToInstanceChannel(): Promise<void> {\n const instanceChannel = `sub:instance:${this.instanceId}`;\n const schemaChannel = SCHEMA_RELOAD_CHANNEL;\n\n await this.redisSub.subscribe(instanceChannel, schemaChannel);\n this.redisSub.on(\"connect\", () => {\n logger.info(`Subscribed to Redis channels: ${instanceChannel}, ${schemaChannel}`);\n });\n this.redisSub.on(\"message\", (channel: string, message: string) => {\n if (channel === instanceChannel) {\n this.handleRemoteInstanceMessage(message).catch((err) => {\n logger.error(err, \"Error handling remote instance message\");\n });\n } else if (channel === schemaChannel) {\n this.handleSchemaReload().catch((err) => {\n logger.error(err, \"Error handling schema reload\");\n });\n }\n });\n this.redisSub.on(\"error\", (err: Error) => {\n logger.error(err, `Subscriber error for channels: ${instanceChannel}, ${schemaChannel}`)\n });\n }\n\n /**\n * React to a `schema:reload` broadcast: invalidate the in-memory column\n * type cache for the named tables (or all if omitted) and re-bootstrap\n * from Postgres so subsequent batched writes see the fresh types. Skips\n * messages published by this instance (we already have the new types).\n */\n private async handleSchemaReload(): Promise<void> {\n logger.info(`Schema reload request received in instance ${this.instanceId} — reloading schema...`);\n try {\n await sleep(1000 + (Math.random() * 5000)); // jitter to avoid thundering herd if many instances\n await bootstrapDbTypes(this.db, this.cache, true);\n logger.info(`Schema reload applied in instance ${this.instanceId}`);\n } catch (err) {\n logger.error(err, \"Failed to reload schema\");\n return;\n }\n }\n\n /**\n * Broadcast a schema-reload signal to every server instance (including\n * this one — the handler skips self-originated messages, so calling\n * after a local migration is safe). Pass `tables` to scope the reload;\n * omit to flush everyone's cache.\n */\n async publishSchemaReload(tables?: string[]): Promise<void> {\n const payload = JSON.stringify({\n type: \"schema:reload\",\n tables,\n originInstance: this.instanceId,\n });\n await redisRetryPolicy.execute(() =>\n this.cache.redis.publish(SCHEMA_RELOAD_CHANNEL, payload));\n }\n\n private async handleRemoteInstanceMessage(message: string): Promise<void> {\n let parsed: RemoteSubscriptionMessage;\n try {\n parsed = RemoteSubscriptionMessageSchema.parse(JSON.parse(message));\n } catch (err) {\n logger.error(err, \"Failed to parse remote instance message\");\n return;\n }\n\n if (parsed.originInstance === this.instanceId) return;\n\n if (parsed.type === \"update\") {\n for (const [, session] of this.sessions) {\n if (!session.subIds.has(parsed.subId)) continue;\n\n const subEntry = this.serverSubscriptions.get(parsed.subId);\n if (!subEntry) continue;\n\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: parsed.subId,\n data: parsed.data,\n dataHash: parsed.dataHash,\n });\n }\n return;\n }\n\n // type === \"recompute-race\" — writer didn't hold the sub locally and\n // broadcast a race hint to every notifyInstance. The first to acquire\n // the lock recomputes; the rest no-op.\n await this.handleRemoteRecomputeRace(parsed);\n }\n\n private async handleRemoteRecomputeRace(req: RemoteRecomputeRace): Promise<void> {\n if (!this.hasLocalSessionFor(req.subId)) {\n // We're in notifyInstances per sub-manager but the sub has since\n // been dropped here. Another holder may still pick it up — no warn.\n return;\n }\n\n const won = await this.cache.tryAcquireRecomputeLock(\n this.instanceId, req.subId, req.commitTs, RECOMPUTE_LOCK_TTL_SECONDS,\n );\n if (!won) {\n logger.debug(\n `recompute-race: lost lock for ${req.subId}@${req.commitTs}, waiting for winner's update`,\n );\n return;\n }\n\n await this.reexecuteAffectedQuery({\n subId: req.subId,\n queryName: req.queryName,\n args: req.args,\n cacheKey: req.cacheKey,\n notifyInstances: req.notifyInstances,\n });\n }\n\n private async publishSubscriptionUpdate(\n subId: string,\n data: unknown,\n dataHash: string,\n notifyInstances: string[]\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"update\",\n subId,\n data,\n dataHash,\n originInstance: this.instanceId,\n });\n\n const publishPromises = notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n logger.error(err, `Failed to publish to ${channel} after many retries`);\n }\n });\n\n await Promise.all(publishPromises);\n }\n}\n","/**\n * FNV-1a 32-bit hash.\n *\n * Used for routing subscription operations to logical workers inside the\n * sub-manager process. Same input always returns the same 32-bit unsigned\n * integer in both JavaScript and Go, so a future Go port of the sub-manager\n * can use the identical routing without rebalancing keys.\n *\n * Reference: http://www.isthe.com/chongo/tech/comp/fnv/\n */\nexport function fnv1a32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n // h = h * 16777619, expressed as shifts to stay in 32-bit space\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n return h >>> 0;\n}\n","import { Server as WebSocketServer } from \"rpc-websockets\";\nimport { Worker } from \"node:worker_threads\";\nimport { fileURLToPath } from \"node:url\";\nimport { SubscriptionManagerConfig } from \"../config\";\nimport { fnv1a32 } from \"./hash\";\nimport { subscriptionManagerDataPack } from \"./subscription-manager-client\";\n\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport {\n AffectedSubscription,\n InvalidateWritesetResult,\n RegisterSubscriptionBatchResult,\n RegisterSubscriptionParams,\n RegisterSubscriptionResult,\n UnregisterSubscriptionParams,\n UnregisterSubscriptionResult,\n UnregisterSubscriptionsParams,\n UnregisterSubscriptionsResult,\n UpdateSubscriptionReadSetResult,\n} from \"./subscription-manager-types\";\nimport { groupByToMap } from \"../helper\";\nimport {\n MsgType,\n type WorkerInitPayload,\n type WorkerRequest,\n type WorkerResponse,\n} from \"./subscription-manager-worker-protocol\";\n\n// Re-export SubscriptionWorker so existing test imports\n// (`import { SubscriptionWorker } from \"./subscription-manager\"`) keep working.\nexport { SubscriptionWorker } from \"./subscription-worker\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR\" },\n { level: logLevelOf(\"SUBS_MANAGER_SERVER_LOG_LEVEL\") },\n);\n\n// Resolve the entry script next to this compiled file. `import.meta.url` works\n// for both Bun (running TS source) and Node (running emitted JS) — we just\n// swap the extension based on the current module's path.\nfunction resolveWorkerEntry(): { url: URL; isTs: boolean } {\n const here = new URL(import.meta.url);\n const isTs = here.pathname.endsWith(\".ts\");\n const ext = isTs ? \".ts\" : \".js\";\n return {\n url: new URL(`./subscription-manager-worker-entry${ext}`, import.meta.url),\n isTs,\n };\n}\n\n// When the entry is a `.ts` file and we're running on Node (not Bun), the\n// worker thread inherits no module loader — Node can strip types but its bare\n// ESM resolver requires explicit file extensions, which our extensionless\n// internal imports don't satisfy. Register tsx as the worker loader so it\n// resolves the same way the parent module does.\nfunction workerExecArgv(isTs: boolean): string[] | undefined {\n if (!isTs) return undefined;\n if (typeof (globalThis as any).Bun !== \"undefined\") return undefined;\n return [\"--import\", \"tsx\"];\n}\n\n// ─── Worker-thread proxy ─────────────────────────────────────────────────────\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (err: Error) => void;\n}\n\nclass SubscriptionWorkerHandle {\n readonly id: number;\n private worker: Worker;\n private pending = new Map<number, PendingRequest>();\n private nextReqId = 1;\n private readyPromise: Promise<void>;\n\n constructor(id: number, init: WorkerInitPayload) {\n this.id = id;\n const entry = resolveWorkerEntry();\n this.worker = new Worker(fileURLToPath(entry.url), {\n execArgv: workerExecArgv(entry.isTs),\n });\n\n this.worker.on(\"message\", (res: WorkerResponse) => {\n const pending = this.pending.get(res.reqId);\n if (!pending) return;\n\n this.pending.delete(res.reqId);\n if (res.ok) {\n pending.resolve(res.result);\n } else {\n const err = new Error(res.error.message);\n if (res.error.stack) err.stack = res.error.stack;\n pending.reject(err);\n }\n });\n\n this.worker.on(\"error\", (err: unknown) => {\n logger.error(err, `[worker ${this.id}] worker thread errored`);\n const e = err instanceof Error ? err : new Error(String(err));\n for (const [, p] of this.pending) p.reject(e);\n this.pending.clear();\n });\n\n this.readyPromise = this.send<null>(MsgType.Init, init).then(() => { });\n }\n\n ready(): Promise<void> {\n return this.readyPromise;\n }\n\n send<T>(type: MsgType, payload: unknown): Promise<T> {\n const reqId = this.nextReqId++;\n return new Promise<T>((resolve, reject) => {\n this.pending.set(reqId, {\n resolve: resolve as (v: unknown) => void,\n reject,\n });\n const req: WorkerRequest = { type, reqId, payload };\n this.worker.postMessage(req);\n });\n }\n\n async shutdown(): Promise<void> {\n try {\n await this.send<null>(MsgType.Shutdown, null);\n } catch (_) { /* ignore */ }\n await this.worker.terminate();\n }\n}\n\n// ─── Dispatcher ──────────────────────────────────────────────────────────────\n\n/**\n * One process per deployment. Routes each subscription operation to one of N\n * worker threads by hashing subId. Worker threads run in parallel on separate\n * CPU cores; this dispatcher does no Zod parsing — it reads `subId` directly\n * from the decoded payload (the internal channel is trusted) and forwards the\n * raw payload. The worker validates and parses with Zod before executing.\n */\nexport class SubscriptionManager {\n private wss: WebSocketServer;\n private port: number;\n private workers: SubscriptionWorkerHandle[];\n private pruneInterval: ReturnType<typeof setInterval> | null = null;\n private static readonly PRUNE_INTERVAL_MS = 30 * 60 * 1000;\n\n constructor(config: SubscriptionManagerConfig) {\n this.port = config.port;\n this.wss = new WebSocketServer({ port: this.port }, subscriptionManagerDataPack);\n this.wss.dataPack = subscriptionManagerDataPack;\n\n const numWorkers = resolveWorkerCount(config.workers);\n const maxConnectionsPerWorker = resolveMaxConnectionsPerWorker(config, numWorkers);\n\n logger.info(`Spawning ${numWorkers} subscription worker${numWorkers === 1 ? \"\" : \"s\"}`);\n this.workers = Array.from({ length: numWorkers }, (_, i) =>\n new SubscriptionWorkerHandle(i + 1, {\n workerId: i + 1,\n database: config.database,\n upstash: config.upstash,\n cacheTTLSeconds: config.cacheTTLSeconds,\n maxConnectionsPerWorker,\n }),\n );\n\n this.registerMethods();\n }\n\n async start(): Promise<void> {\n await Promise.all(this.workers.map((w) => w.ready()));\n logger.info(`Listening on port ${this.port}`);\n this.pruneInterval = setInterval(\n () => { this.pruneCommitLogs().catch((err) => logger.error(err, \"commit_logs prune failed\")); },\n SubscriptionManager.PRUNE_INTERVAL_MS,\n );\n if (this.pruneInterval && typeof this.pruneInterval.unref === \"function\") {\n this.pruneInterval.unref();\n }\n }\n\n async stop(): Promise<void> {\n if (this.pruneInterval) {\n clearInterval(this.pruneInterval);\n this.pruneInterval = null;\n }\n this.wss.close();\n await Promise.all(this.workers.map((w) => w.shutdown()));\n logger.info(\"Stopped\");\n }\n\n private async pruneCommitLogs(): Promise<void> {\n const minPerWorker = await Promise.all(\n this.workers.map((w) => w.send<bigint | null>(MsgType.MinSubscriptionTs, null)),\n );\n let floor: bigint | null = null;\n for (const m of minPerWorker) {\n if (m === null) continue;\n if (floor === null || m < floor) floor = m;\n }\n\n if (floor === null) {\n const latest = await this.workers[0]!.send<bigint>(MsgType.LatestSnapshotTs, null);\n if (latest <= 0n) return;\n floor = latest;\n }\n\n const deleted = await this.workers[0]!.send<bigint>(MsgType.Prune, floor);\n if (deleted > 0n) {\n logger.info(`pruned ${deleted} commit_logs older than ts=${floor}`);\n }\n }\n\n workerCount(): number {\n return this.workers.length;\n }\n\n workerIndexFor(subId: string): number {\n return fnv1a32(subId) % this.workers.length;\n }\n\n private workerFor(subId: string): SubscriptionWorkerHandle {\n return this.workers[this.workerIndexFor(subId)]!;\n }\n\n private registerMethods(): void {\n this.wss.register(\"registerSubscription\", this.registerSubscription.bind(this));\n this.wss.register(\"registerSubscriptionBatch\", this.registerSubscriptionBatch.bind(this));\n this.wss.register(\"updateSubscriptionReadSet\", this.updateSubscriptionReadSet.bind(this));\n this.wss.register(\"invalidateWriteset\", this.invalidateWriteset.bind(this));\n this.wss.register(\"unregisterSubscription\", this.unregisterSubscription.bind(this));\n this.wss.register(\"unregisterSubscriptions\", this.unregisterSubscriptions.bind(this));\n }\n\n private readSubId(params: unknown): string {\n return (params as { subId: string }).subId;\n }\n\n private async registerSubscription(params: unknown): Promise<RegisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<RegisterSubscriptionResult>(MsgType.Register, params);\n }\n\n private async registerSubscriptionBatch(params: unknown): Promise<RegisterSubscriptionBatchResult> {\n // Group entries by destination worker, dispatch one batch per worker, then\n // restore the original order.\n const arr = params as RegisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<RegisterSubscriptionResult[]>(\n MsgType.RegisterBatch,\n items.map((t) => t.p),\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: RegisterSubscriptionBatchResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async updateSubscriptionReadSet(params: unknown): Promise<UpdateSubscriptionReadSetResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UpdateSubscriptionReadSetResult>(MsgType.UpdateReadSet, params);\n }\n\n private async unregisterSubscription(params: unknown): Promise<UnregisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UnregisterSubscriptionResult>(MsgType.Unregister, params);\n }\n\n private async unregisterSubscriptions(params: unknown): Promise<UnregisterSubscriptionsResult> {\n const arr = params as UnregisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<UnregisterSubscriptionResult[]>(\n MsgType.Unregisters,\n items.map((t) => t.p) satisfies UnregisterSubscriptionsParams,\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: UnregisterSubscriptionsResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async invalidateWriteset(params: unknown): Promise<InvalidateWritesetResult> {\n // Fan the raw payload out to every worker. Each parses its own copy of the\n // writeSet (cheap relative to the scan) and returns its affected subs.\n const perWorker = await Promise.all(\n this.workers.map((w) => w.send<AffectedSubscription[]>(MsgType.Invalidate, params)),\n );\n return { affected: perWorker.flat() };\n }\n}\n\nfunction resolveWorkerCount(configured?: number): number {\n if (typeof configured === \"number\" && configured > 0) return Math.floor(configured);\n\n const envVal = process.env.SUPALIVE_SUB_MANAGER_WORKERS;\n if (envVal) {\n const n = parseInt(envVal, 10);\n if (Number.isFinite(n) && n > 0) return n;\n }\n return 1;\n}\n\nfunction resolveMaxConnectionsPerWorker(config: SubscriptionManagerConfig, workers: number): number {\n if (config.maxConnectionsPerWorker && config.maxConnectionsPerWorker > 0) {\n return Math.floor(config.maxConnectionsPerWorker);\n }\n const total = config.database.maxConnections ?? (config.database.type === \"mysql\" ? 20 : 10);\n return Math.max(1, Math.floor(total / workers));\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAOA,eAAsB,UAAU,OAAiC;CAE/D,OAAO,MADW,OAAO,UAAU,WAAW,QAAQ,gBAAgB,KAAK,MAClD,EAAE;AAC7B;;;;;AAMA,MAAa,qBAAqB;;;;;;;;;AAUlC,eAAsB,uBACpB,WACA,OACA,eACiB;CAGjB,OAAO,OAAO,MADK,UAAU,GAAG,UAAU,GADzB,OAAO,UAAU,WAAW,QAAQ,gBAAgB,KAAK,EACpB,GAAG,eAAe;AAE1E;;;;;;AAOA,eAAsB,iBACpB,WACA,OACA,eACiB;CAGjB,OAAO,MAAM,MADM,UAAU,GAAG,UAAU,GAD1B,OAAO,UAAU,WAAW,QAAQ,gBAAgB,KAAK,EACpB,GAAG,eAAe;AAEzE;;;AC1CA,MAAMA,WAASC,OAAW,MACxB,EAAE,MAAM,kBAAkB,GAC1B,EAAE,OAAO,WAAW,8BAA8B,EAAE,CACtD;AAkCA,MAAa,8BAA8B;CACzC,SAAS,SAAkB,kBAAkB,IAAI;CACjD,SAAS,SAAiB,KAAK,MAAM,IAAI;AAC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,mBAAb,MAA8B;CAC5B;CACA,QAA4B;CAC5B,mBAA2B;;CAG3B;CACA;;CAGA,oBAA0C,CAAC;CAC3C;CACA,4BAAoC;CAEpC;CACA;CAEA,YAAY,cAAgD;EAC1D,MAAM,OAAO,OAAO,iBAAiB,WACjC,EAAE,KAAK,aAAa,IACpB;EAEJ,KAAK,cAAc,KAAK,yBAAyB;EACjD,KAAK,eAAe,IAAI,SAAe,MAAM;GAAE,KAAK,eAAe;EAAG,CAAC;EAEvE,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,EACjC,UAAU,4BACZ,CAAC;EACD,KAAK,OAAO,iBAAiB,IAAI;EACjC,KAAK,OAAO,iBAAiB,CAAC;EAE9B,KAAK,OAAO,WAAW;EAEvB,KAAK,OAAO,GAAG,QAAQ,KAAK,WAAW,KAAK,IAAI,CAAC;EACjD,KAAK,OAAO,GAAG,SAAS,KAAK,YAAY,KAAK,IAAI,CAAC;EACnD,KAAK,OAAO,GAAG,UAAU,QAAiBD,SAAO,KAAK,KAAK,iCAAiC,CAAC;CAC/F;;;;;;CAOA,kBAAkB,QAA8B;EAC9C,KAAK,iBAAiB;CACxB;;;;;;CAOA,mBAAmB,SAAgC;EACjD,KAAK,kBAAkB;CACzB;CAIA,MAAM,qBACJ,QAC2C;EAC3C,OAAO,KAAK,KAAK,wBAAwB,QAAQE,gCAAsC;CACzF;CAEA,MAAM,0BACJ,QAC6C;EAC7C,OAAO,KAAK,KAAK,6BAA6B,QAAQC,qCAA2C;CACnG;CAEA,MAAM,0BACJ,QACgD;EAChD,OAAO,KAAK,KAAK,6BAA6B,QAAQC,qCAA2C;CACnG;CAEA,MAAM,uBACJ,QAC6C;EAC7C,OAAO,KAAK,KAAK,0BAA0B,QAAQC,kCAAwC;CAC7F;CAEA,MAAM,wBACJ,QAC8C;EAC9C,OAAO,KAAK,KAAK,2BAA2B,QAAQC,mCAAyC;CAC/F;;;;;;;;CASA,MAAM,mBACJ,QACyC;EACzC,IAAI,KAAK,UAAU,QAAQ;GACzB,KAAK,eAAe,OAAO,QAAQ;GACnC,OAAO,EAAE,UAAU,CAAC,EAAE;EACxB;EACA,OAAO,KAAK,KAAK,sBAAsB,QAAQC,8BAAoC;CACrF;;;;;;;;CAWA,MAAc,KACZ,UACA,QACA,SACqB;EACrB,MAAM,SAAS,MAAM,KAAK,OAAO,KAAK,UAAU,MAAM;EACtD,OAAO,QAAQ,MAAM,MAAM;CAC7B;CAEA,eAAuB,UAA8B;EACnD,IAAI,KAAK,kBAAkB,SAAS,SAAS,SAAS,KAAK,aAAa;GACtE,IAAI,CAAC,KAAK,2BAA2B;IACnC,SAAO,KACL,kCAAkC,KAAK,YAAY,mGAErD;IACA,KAAK,4BAA4B;GACnC;GACA,KAAK,oBAAoB,CAAC;GAC1B;EACF;EACA,KAAK,kBAAkB,KAAK,GAAG,QAAQ;CACzC;CAEA,MAAc,cAA6B;EACzC,IAAI,KAAK,kBAAkB,WAAW,GAAG;EACzC,MAAM,WAAW,KAAK;EACtB,KAAK,oBAAoB,CAAC;EAC1B,KAAK,4BAA4B;EAEjC,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,KACxB,sBACA,EAAE,SAAS,GACXA,8BACF;GACA,IAAI,KAAK,mBAAmB,OAAO,SAAS,SAAS,GACnD,MAAM,KAAK,gBAAgB,OAAO,QAAQ;EAE9C,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,kDAAkD;EACtE;CACF;CAEA,WAAyB;EACvB,IAAI,KAAK,UAAU,QAAQ;EAC3B,KAAK,QAAQ;EACb,KAAK,eAAe,IAAI,SAAe,MAAM;GAAE,KAAK,eAAe;EAAG,CAAC;CACzE;CAEA,YAA0B;EACxB,IAAI,KAAK,UAAU,SAAS;EAC5B,KAAK,QAAQ;EACb,KAAK,aAAa;CACpB;CAEA,cAA4B;EAC1B,SAAO,KAAK,mCAAmC;EAC/C,KAAK,SAAS;CAChB;CAEA,aAA2B;EACzB,IAAI,CAAC,KAAK,kBAAkB;GAC1B,KAAK,mBAAmB;GACxB,SAAO,KAAK,qCAAqC;GACjD,KAAK,UAAU;GACf;EACF;EACA,SAAO,KAAK,qDAAqD;EACjE,KAAU,YAAY;CACxB;CAEA,MAAc,cAA6B;EAGzC,IAAI;GACF,IAAI,KAAK,gBACP,MAAM,KAAK,eAAe;QAE1B,SAAO,KAAK,2EAA2E;GAEzF,MAAM,KAAK,YAAY;EACzB,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,oDAAoD;EACxE,UAAU;GACR,SAAO,KAAK,uCAAuC;GACnD,KAAK,UAAU;EACjB;CACF;;;;;CAMA,UAAmB;EAAE,OAAO,KAAK,UAAU;CAAS;;CAEpD,aAAqB;EAAE,OAAO,KAAK,kBAAkB;CAAQ;AAC/D;;;ACrPA,MAAM,mBAAmB,MAAM,WAAW;CACxC,aAAa;CACb,SAAS,IAAI,mBAAmB;EAC9B,cAAc;EACd,UAAU;EACV,UAAU;CACZ,CAAC;AACH,CAAC;;;;;;;;;AAUD,MAAM,wBAAwB;;;;;;;;;;AAW9B,MAAM,4BAA4B;;;;;;;AAQlC,MAAM,yBAAyB;AAe/B,MAAM,wBAAwB,OAC5B,QAAQ,IAAI,kCAAkC,IAChD;AACA,MAAM,oBAAoB;;;;;;;;AAS1B,MAAM,6BAA6B,OACjC,QAAQ,IAAI,uCAAuC,EACrD;AAEA,MAAMC,WAASC,OAAW,MAAM,EAC9B,QAAQ,WACV,GACE,EAAE,OAAO,WAAW,kBAAkB,EAAE,CAC1C;;;;AAKA,IAAa,0BAAb,MAA+D;CAC7D;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA,2BAAsD,IAAI,IAAI;CAC9D,sCAA6D,IAAI,IAAI;CACrE;CAEA,QAAgB;EACd,mBAAmB;EACnB,kBAAkB;EAClB,qBAAqB;CACvB;CAEA,YAAY,QAAwC;EAClD,KAAK,SAAS;GACZ,MAAM;GACN,MAAM;GACN,aAAa;GACb,mBAAmB;GACnB,GAAG;EACL;EACA,KAAK,aAAa;EAElB,IAAI,CAAC,OAAO,eACV,MAAM,IAAI,MAAM,6EAA6E;EAE/F,KAAK,mBAAmB,IAAI,iBAAiB,OAAO,aAAa;EACjE,KAAK,QAAQ,OAAO;EACpB,KAAK,WAAW,OAAO;EACvB,KAAK,KAAK,OAAO;EAKjB,KAAK,iBAAiB,kBAAkB,KAAK,sBAAsB,KAAK,IAAI,CAAC;EAC7E,KAAK,iBAAiB,mBAAmB,KAAK,mBAAmB,KAAK,IAAI,CAAC;EAE3E,KAAK,aAAa,aAAa;EAC/B,KAAK,MAAM,IAAI,gBAAgB;GAC7B,QAAQ,KAAK;GACb,UAAU;GACV,YAAY,MAAM,OAAO;EAE3B,CAAC;EAED,KAAK,uBAAuB;EAC5B,KAAK,2BAA2B;CAClC;;;;;;;;;;;;;CAcA,sBAA8B,SAAkC;EAC9D,QAAQ,iBAAiB,kBAAkB;GAEzC,IAAI,KAAK,IAAI,IAAI,QAAQ,iBAAiB,mBAAmB;IAC3D,SAAO,KAAK,qCAAqC,QAAQ,IAAI;IAC7D,IAAI;KAAE,QAAQ,GAAG,UAAU;IAAG,QAAQ,CAAgC;IACtE;GACF;GACA,IAAI;IACF,QAAQ,GAAG,KAAK;GAClB,QAAQ,CAER;GAGA,IAAI,KAAK,IAAI,IAAI,QAAQ,cAAc,uBACrC,KAAK,YAAY,SAAS,EAAE,MAAM,OAAO,CAAC;EAE9C,GAAG,qBAAqB;CAC1B;CAEA,mBAA2B,SAAkC;EAC3D,IAAI,QAAQ,gBAAgB;GAC1B,cAAc,QAAQ,cAAc;GACpC,QAAQ,iBAAiB;EAC3B;CACF;CAEA,eAAe,QAAqC;EAClD,KAAK,SAAS;EACd,KAAK,kBAAkB,mBAAmB,OAAO,WAAW;CAC9D;CAEA,MAAM,QAAuB;EAC3B,OAAO,IAAI,SAAS,YAAY;GAC9B,KAAK,WAAW,OAAO,KAAK,OAAO,MAAM,KAAK,OAAO,YAAY;IAC/D,SAAO,KAAK,WAAW,KAAK,WAAW,gBAAgB,KAAK,OAAO,KAAK,GAAG,KAAK,OAAO,MAAM;IAC7F,QAAQ;GACV,CAAC;EACH,CAAC;CACH;CAEA,MAAM,OAAsB;EAC1B,KAAK,IAAI,MAAM;EACf,KAAK,MAAM,CAAC,OAAO,KAAK,UACtB,GAAG,MAAM,MAAM,sBAAsB;EAEvC,KAAK,WAAW,MAAM;EACtB,SAAO,KAAK,gBAAgB;CAC9B;CAEA,WAAW;EACT,OAAO;GACL,YAAY,KAAK;GACjB,mBAAmB,KAAK,MAAM;GAC9B,kBAAkB,KAAK,MAAM;GAC7B,qBAAqB,KAAK,MAAM;EAClC;CACF;CAEA,gBAAwB;EACtB,OAAO,KAAK;CACd;CAEA,yBAAuC;EACrC,KAAK,IAAI,GAAG,eAAe,IAAI,QAAQ;GACrC,KAAK,iBAAiB,IAAI,GAAG,CAAC,CAAC,OAAO,QAAQ;IAC5C,SAAO,MAAM,KAAK,0BAA0B;GAC9C,CAAC;EACH,CAAC;CACH;CAEA,MAAc,iBAAiB,IAAe,KAAqC;EACjF,KAAK,MAAM;EACX,KAAK,MAAM;EAEX,MAAM,UAA6B;GACjC,IAAIC,GAAO;GACX;GACA,WAAW,CAAC;GACZ,wBAAQ,IAAI,IAAI;GAChB,eAAe,CAAC;GAChB,kBAAkB;GAClB,gBAAgB;GAChB,gBAAgB,KAAK,IAAI;GACzB,YAAY,KAAK,IAAI;GACrB,gBAAgB;EAClB;EACA,SAAO,KAAK,gBAAgB,QAAQ,IAAI;EAExC,KAAK,SAAS,IAAI,IAAI,OAAO;EAC7B,KAAK,YAAY,SAAS;GAAE,MAAM;GAAa,WAAW,QAAQ;EAAG,CAAC;EAMtE,GAAG,GAAG,YAAY,SAAS;GACzB,QAAQ,iBAAiB,KAAK,IAAI;GAClC,KAAK,cAAc,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,QAAQ;IAC1D,SAAO,MAAM,KAAK,uBAAuB;GAC3C,CAAC;EACH,CAAC;EACD,GAAG,GAAG,cAAc;GAAE,QAAQ,iBAAiB,KAAK,IAAI;EAAG,CAAC;EAC5D,GAAG,GAAG,cAAc;GAAE,QAAQ,iBAAiB,KAAK,IAAI;EAAG,CAAC;EAE5D,KAAK,sBAAsB,OAAO;EAElC,GAAG,GAAG,UAAU,SAAS;GACvB,SAAO,KAAK,sBAAsB,QAAQ,GAAG,UAAU,MAAM;GAC7D,KAAK,mBAAmB,OAAO;GAC/B,KAAK,iBAAiB,OAAO;GAC7B,KAAK,SAAS,OAAO,EAAE;GACvB,KAAK,MAAM;EACb,CAAC;EAED,GAAG,GAAG,UAAU,QAAQ;GACtB,SAAO,MAAM,KAAK,iBAAiB;EACrC,CAAC;CACH;CAEA,MAAc,cAAc,SAA4B,SAAgC;EACtF,IAAI;GACF,MAAM,SAAS,KAAK,MAAM,OAAO;GACjC,MAAM,cAAc,oBAAoB,UAAU,MAAM;GACxD,IAAI,CAAC,YAAY,SAAS;IACxB,KAAK,UAAU,SAAS,IAAI,0BAA0B,iBAAiB;IACvE;GACF;GACA,MAAM,KAAK,eAAe,SAAS,YAAY,IAAI;EACrD,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,wBAAwB;GAC1C,KAAK,UAAU,SAAS,IAAI,eAAe,aAAa;EAC1D;CACF;CAEA,MAAc,eAAe,SAA4B,KAAmC;EAC1F,QAAQ,IAAI,MAAZ;GACE,KAAK;IACH,MAAM,KAAK,WAAW,SAAS,IAAI,IAAI;IACvC;GACF,KAAK;IACH,MAAM,KAAK,WAAW,SAAS,IAAI,IAAI,IAAI,WAAW,IAAI,KAAK;IAC/D;GACF,KAAK;IACH,MAAM,KAAK,gBAAgB,SAAS,IAAI,IAAI,IAAI,WAAW,IAAI,KAAK;IACpE;GACF,KAAK;IACH,MAAM,KAAK,kBAAkB,SAAS,IAAI,KAAK;IAC/C;EACJ;CACF;CAEA,MAAc,WAAW,SAA4B,MAA+C;EAClG,IAAI,CAAC,KAAK,OAAO,cAAc,CAAC,MAAM;GACpC,KAAK,YAAY,SAAS;IAAE,MAAM;IAAgB,QAAQ;GAAY,CAAC;GACvE;EACF;EAEA,IAAI;GACF,MAAM,aAAa,MAAM,KAAK,OAAO,WAAW,MAAM,QAAQ,EAAE;GAChE,IAAI,CAAC,YAAY;IACf,KAAK,YAAY,SAAS;KAAE,MAAM;KAAe,SAAS;IAAyB,CAAC;IACpF;GACF;GAEA,QAAQ,YAAY;GACpB,KAAK,YAAY,SAAS;IAAE,MAAM;IAAgB,QAAQ,WAAW,KAAK;GAAO,CAAC;EACpF,SAAS,GAAG;GACV,IAAI,aAAa,qBACf,KAAK,YAAY,SAAS;IACxB,MAAM;IACN,SAAS,6BAA6B,EAAE;IACxC,OAAO,EAAE;GACX,CAAC;QAED,KAAK,YAAY,SAAS;IAAE,MAAM;IAAe,SAAS;GAAyB,CAAC;EAExF;CACF;;;;;;;;CASA,gBACE,SACA,WACA,gBACkC;EAClC,OAAO,IAAI,SAAkC,SAAS,WAAW;GAC/D,MAAM,UAAqC;IACzC;IACA;IACA;IACA;GACF;GACA,QAAQ,cAAc,KAAK,OAAO;GAClC,KAAK,mBAAmB,OAAO,CAAC,CAAC,OAAO,QACtCF,SAAO,MAAM,KAAK,4BAA4B,CAChD;EACF,CAAC;CACH;;;;;;CAOA,MAAc,mBAAmB,SAA2C;EAC1E,IAAI,QAAQ,kBAAkB;EAC9B,QAAQ,mBAAmB;EAC3B,IAAI;GACF,OAAO,QAAQ,cAAc,SAAS,GAAG;IACvC,MAAM,KAAK,QAAQ,cAAc,MAAM;IACvC,IAAI;KACF,MAAM,KAAK,GAAG,UAAU;KAIxB,MAAM,SAAS,MAAM,KAAK,GAAG,SAAS,OAAO,QAAQ;MACnD,OAAO,GAAG;OAAE,IAAI;OAAK,WAAW,QAAQ;MAAU,GAAG,GAAG,cAAc;KACxE,CAAC;KACD,GAAG,QAAQ,MAAM;IACnB,SAAS,KAAK;KACZ,GAAG,OAAO,GAAG;IACf;GACF;EACF,UAAU;GACR,QAAQ,mBAAmB;EAC7B;CACF;CAEA,MAAc,WACZ,SACA,IACA,eACA,OACe;EACf,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,UAAU,SAAS,IAAI,yBAAyB,cAAc;GACnE;EACF;EAEA,MAAM,YAAY,KAAK,gBAAgB,aAAa,aAAa;EACjE,IAAI,CAAC,WAAW;GACd,KAAK,UAAU,SAAS,IAAI,cAAc,cAAc,cAAc,WAAW;GACjF;EACF;EAEA,IAAI;GACF,MAAM,iBAAkB,UAAU,YAAwB,MAAM,KAAK;GAErE,IAAI,UAAU,SAAS,SAAS;IAC9B,MAAM,SAAS,MAAM,KAAK,GAAG,MAAM,OAAO,QAAQ;KAChD,MAAM,KAAK,UAAU;KACrB,OAAO,GAAG;MAAE,IAAI;MAAK,WAAW,QAAQ;KAAU,GAAG,cAAc;IACrE,CAAC;IAED,KAAK,YAAY,SAAS;KACxB,MAAM;KACF;KACJ,SAAS;KACT,MAAM;IACR,CAAC;GACH,OAAO,IAAI,UAAU,SAAS,YAAY;IACxC,IAAI,QAAQ,cAAc,UAAU,2BAA2B;KAC7D,KAAK,UACH,SACA,IACA,yDAAyD,0BAA0B,IACnF,+BACF;KACA;IACF;IAEA,MAAM,iBAAiB,MAAM,KAAK,gBAAgB,SAAS,WAAW,cAAc;IAEpF,KAAK,YAAY,SAAS;KACxB,MAAM;KACF;KACJ,SAAS;IACX,CAAC;IAED,MAAM,KAAK,qBAAqB,eAAe,UAAU,eAAe,QAAQ;GAClF,OAAO,IAAI,UAAU,SAAS,UAAU;IACtC,IAAI,QAAQ,kBAAkB,wBAAwB;KACpD,KAAK,UACH,SACA,IACA,uDAAuD,uBAAuB,IAC9E,6BACF;KACA;IACF;IAEA,QAAQ;IACR,IAAI;IACJ,IAAI;KACF,MAAM,KAAK,UAAU;KACrB,SAAS,MAAM,GAAG;MAAE,IAAI,KAAK;MAAI,WAAW,QAAQ;KAAU,GAAG,cAAc;IACjF,UAAU;KACR,QAAQ;IACV;IAEA,KAAK,YAAY,SAAS;KACxB,MAAM;KACF;KACJ,SAAS;KACT,MAAM;IACR,CAAC;GACH;EAGF,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,oBAAoB,cAAc,EAAE;GACtD,KAAK,UACH,SACA,IACA,eAAe,QAAQ,IAAI,UAAU,oBACrC,iBACF;EACF;CACF;CAEA,MAAc,gBACZ,SACA,IACA,eACA,OACe;EACf,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,UAAU,SAAS,IAAI,yBAAyB,cAAc;GACnE;EACF;EAEA,MAAM,YAAY,KAAK,gBAAgB,aAAa,aAAa;EACjE,IAAI,CAAC,aAAa,UAAU,SAAS,SAAS;GAC5C,KAAK,UAAU,SAAS,IAAI,cAAc,cAAc,6BAA6B,WAAW;GAChG;EACF;EAEA,IAAI;GACF,MAAM,iBAAiB,UAAU,YAAY,MAAM,KAAK;GACxD,MAAM,gBAAgB,KAAK,qBAAqB,WAAW,SAAS,cAAc;GAClF,MAAM,kBAAkB,gBAAgB,cAAc;GACtD,MAAM,QAAQ,MAAM,uBAAuB,eAAe,iBAAiB,aAAa;GACxF,MAAM,WAAW,MAAM,iBAAiB,eAAe,iBAAiB,aAAa;GACrF,MAAM,oBAAoB,KAAK,oBAAoB,IAAI,KAAK;GAE5D,IAAI;GACJ,IAAI;GACJ,IAAI,aAAyC;GAG7C,QAAQ,OAAO,IAAI,KAAK;GAExB,IAAI,mBAAmB;IACrB,kBAAkB,mBAAmB;IAErC,MAAM,SAAS,MAAM,KAAK,MAAM,iBAAiB,kBAAkB,QAAQ;IAC3E,IAAI,QACF,OAAO,OAAO;SACT;KACL,aAAa,MAAM,KAAK,iBAAiB,eAAe,gBAAgB,QAAQ,SAAS;KACzF,OAAO,WAAW;IACpB;GACF,OAAO;IACL,MAAM,iBAAiB,MAAM,KAAK,GAAG,KAAK,2BAA2B;IACrE,MAAM,mBAAmB,MAAM,KAAK,iBAAiB,qBAAqB;KACxE;KACA;KACA,WAAW;KACX,MAAM;KACN;KACA,cAAc,KAAK;KACH;IAClB,CAAC;IAED,MAAM,SAAS,MAAM,KAAK,MAAM,iBAAiB,iBAAiB,QAAQ;IAC1E,IAAI,UAAU,CAAC,iBAAiB,WAC9B,OAAO,OAAO;SACT;KACL,aAAa,MAAM,KAAK,iBAAiB,eAAe,gBAAgB,QAAQ,SAAS;KACzF,OAAO,WAAW;IACpB;IAEA,KAAK,oBAAoB,IAAI,OAAO;KAClC;KACA;KACA,iBAAiB;IACnB,CAAC;IACD,KAAK,MAAM;GACb;GAEA,IAAI,YAAY;IACd,MAAM,CAAC,gBAAgB,MAAM,QAAQ,IAAI,CACvC,KAAK,iBAAiB,0BAA0B;KAC9C;KACA;KACA,gBAAgB,WAAW;KAC3B,SAAS,WAAW;KACpB,cAAc,KAAK;IACrB,CAAC,GACD,KAAK,MAAM,iBAAiB,UAAU,IAAI,CAC5C,CAAC;IAKD,IAAI,CAAC,aAAa,SAChB,SAAO,KACL,mFAAmF,MAAM,8CAE3F;GAEJ;GAEA,WAAW,MAAM,UAAU,IAAI;GAK/B,KAAK,YAAY,SAAS;IAAE,MAAM;IAAY;IAAI,SAAS;IAAM;GAAM,CAAC;GACxE,KAAK,YAAY,SAAS;IAAE,MAAM;IAAc;IAAO;IAAM;GAAS,CAAC;GAEvE,SAAO,KAAK,eAAe,MAAM,eAAe,QAAQ,IAAI;EAC9D,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,yBAAyB,cAAc,EAAE;GAC3D,KAAK,YAAY,SAAS;IAAE,MAAM;IAAY;IAAI,SAAS;GAAM,CAAC;GAClE,KAAK,UAAU,SAAS,IAAI,eAAe,QAAQ,IAAI,UAAU,oBAAoB,iBAAiB;EACxG;CACF;CAEA,MAAc,kBAAkB,SAA4B,OAA8B;EACxF,MAAM,YAAY,KAAK,oBAAoB,IAAI,KAAK;EACpD,IAAI,CAAC,WAAW;GACd,QAAQ,OAAO,OAAO,KAAK;GAC3B;EACF;EAEA,UAAU,mBAAmB;EAC7B,IAAI,UAAU,mBAAmB,GAAG;GAClC,QAAQ,OAAO,OAAO,KAAK;GAC3B,KAAK,oBAAoB,OAAO,KAAK;GACrC,KAAK,MAAM;GAEX,IAAI;IACF,MAAM,KAAK,iBAAiB,uBAAuB;KAAE;KAAO,cAAc,KAAK;IAAW,CAAC;GAC7F,SAAS,KAAK;IACZ,SAAO,MAAM,KAAK,wCAAwC;GAC5D;EACF;EAEA,SAAO,KAAK,iBAAiB,MAAM,eAAe,QAAQ,GAAG,2BAA2B,UAAU,iBAAiB;CACrH;CAEA,MAAc,iBAAiB,SAA2C;EACxE,SAAO,KAAK,yBAAyB,QAAQ,IAAI;EAEjD,IAAI,iBAAkD,KAAA;EACtD,KAAK,MAAM,SAAS,QAAQ,QAAQ;GAClC,MAAM,MAAM,KAAK,oBAAoB,IAAI,KAAK;GAC9C,IAAI,CAAC,KAAK;GAEV,IAAI,mBAAmB;GACvB,IAAI,IAAI,mBAAmB,GAAG;IAC5B,IAAI,CAAC,gBACH,iBAAiB,CAAC;IAGpB,eAAe,KAAK,GAAG;GACzB;EACF;EAEA,IAAI,CAAC,kBAAkB,eAAe,WAAW,GAAG;EAEpD,IAAI;GACF,MAAM,KAAK,iBAAiB,wBAC1B,eAAe,KAAK,SAAS;IAAE,OAAO,IAAI;IAAO,cAAc,KAAK;GAAW,EAAE,CACnF;EACF,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,wCAAwC;EAC5D;EAEA,KAAK,MAAM,OAAO,gBAEhB,IAAI,IAAI,mBAAmB,GAAG;GAC5B,KAAK,oBAAoB,OAAO,IAAI,KAAK;GACzC,KAAK,MAAM;EACb;EAGF,SAAO,KAAK,8CAA8C,QAAQ,GAAG,UAAU,eAAe,QAAQ;CACxG;;;;;;;;;;;;;;;CAgBA,MAAc,wBAAuC;EACnD,MAAM,WAAW,MAAM,KAAK,KAAK,oBAAoB,OAAO,CAAC;EAC7D,IAAI,SAAS,WAAW,GAAG;GACzB,SAAO,KAAK,mDAAmD;GAC/D;EACF;EAEA,SAAO,KAAK,mCAAmC,SAAS,OAAO,iBAAiB;EAEhF,MAAM,iBAAiB,MAAM,KAAK,GAAG,KAAK,2BAA2B;EACrE,MAAM,SAAS,SAAS,KAAK,MAAM,EAAE,KAAK;EAC1C,MAAM,aAAa,MAAM,KAAK,MAAM,8BAA8B,MAAM;EAExE,MAAM,iBAGD,CAAC;EACN,KAAK,MAAM,SAAS,UAAU;GAC5B,MAAM,SAAS,WAAW,IAAI,MAAM,KAAK,KAAK;GAC9C,IAAI,CAAC,QAAQ;IACX,KAAK,wBAAwB,MAAM,OAAO,uCAAuC;IACjF;GACF;GACA,eAAe,KAAK;IAAE;IAAO;GAAO,CAAC;EACvC;EAEA,MAAM,YAAoC,CAAC;EAC3C,MAAM,aAAa;EACnB,KAAK,IAAI,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK,YAAY;GAC1D,MAAM,QAAQ,eAAe,MAAM,GAAG,IAAI,UAAU;GACpD,MAAM,SAAS,MAAM,KAAK,EAAE,OAAO,cAAc;IAC/C,OAAO,MAAM;IACb,UAAU,MAAM;IAChB,WAAW,OAAO;IAClB,MAAM,OAAO;IACb,eAAe,OAAO;IACtB,cAAc,KAAK;IACnB;GACF,EAAE;GACF,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,iBAAiB,0BAA0B,MAAM;IAC5E,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;KACrC,MAAM,EAAE,OAAO,WAAW,MAAM;KAEhC,IADe,QAAQ,EACb,CAAC,WACT,UAAU,KAAK;MACb,OAAO,MAAM;MACb,WAAW,OAAO;MAClB,MAAM,OAAO;MACb,UAAU,MAAM;MAChB,iBAAiB,CAAC;KACpB,CAAC;IAEL;GACF,SAAS,KAAK;IACZ,SAAO,MAAM,KAAK,kDAAkD,EAAE,GAAG,IAAI,MAAM,QAAQ;GAC7F;EACF;EAEA,MAAM,QAAQ,WAAW,UAAU,KAAK,MAAM,KAAK,uBAAuB,CAAC,CAAC,CAAC;EAE7E,SAAO,KAAK,+BAA+B,UAAU,OAAO,WAAW;CACzE;CAEA,MAAc,mBAAmB,UAAkC;EACjE,MAAM,QAAQ,WACZ,SAAS,KAAK,MAAM,KAAK,uBAAuB,CAAC,CAAC,CACpD;CACF;;;;;;;CAQA,wBAAgC,OAAe,QAAsB;EACnE,MAAM,YAAY,KAAK,oBAAoB,IAAI,KAAK;EACpD,IAAI,WAAW;EACf,IAAI,WAAW;GACb,KAAK,oBAAoB,OAAO,KAAK;GACrC,KAAK,MAAM,sBAAsB,KAAK,IAAI,GAAG,KAAK,MAAM,sBAAsB,CAAC;GAE/E,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,KAAK,GAAG;IAC7B;IACA,QAAQ,OAAO,OAAO,KAAK;IAC3B,KAAK,YAAY,SAAS;KAAE,MAAM;KAAY;KAAO;IAAO,CAAC;GAC/D;EAEJ;EAEA,SAAO,KAAK,wBAAwB,MAAM,YAAY,OAAO,cAAc,SAAS,YAAY;CAClG;CAEA,MAAc,qBAAqB,UAAwB,UAAmC;EAC5F,IAAI,SAAS,WAAW,GAAG;EAE3B,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC;EACxD,SAAO,MAAM,mCAAmC,OAAO,KAAK,IAAI,EAAE,EAAE;EAEpE,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,iBAAiB,mBAAmB,EAAE,SAAS,CAAC;GAC1E,SAAO,MAAM,EAAE,UAAU,OAAO,GAAG,kCAAkC;GAErE,IAAI,QAAQ,UAAU,SAAS,GAC7B,MAAM,QAAQ,WACZ,OAAO,SAAS,KAAK,aAAa,KAAK,iBAAiB,UAAU,QAAQ,CAAC,CAC7E;EAEJ,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,+BAA+B;EACnD;CACF;;;;;;;;;;;;;;CAeA,MAAc,iBAAiB,UAAgC,UAAmC;EAChG,IAAI,KAAK,mBAAmB,SAAS,KAAK,GAAG;GAC3C,MAAM,KAAK,uBAAuB,QAAQ;GAC1C;EACF;EAEA,IAAI,SAAS,gBAAgB,WAAW,GAAG;GACzC,SAAO,MACL,4CAA4C,SAAS,MAAM,YAC7D;GACA;EACF;EAEA,MAAM,KAAK,qBAAqB,UAAU,QAAQ;CACpD;CAEA,mBAA2B,OAAuB;EAChD,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,KAAK,GAAG,OAAO;EAExC,OAAO;CACT;CAEA,MAAc,qBACZ,UACA,UACe;EACf,MAAM,UAAU,kBAAkB;GAChC,MAAM;GACN,OAAO,SAAS;GAChB,UAAU,SAAS;GACnB,WAAW,SAAS;GACpB,MAAM,SAAS;GACf,iBAAiB,SAAS;GAC1B,UAAU,SAAS,SAAS;GAC5B,gBAAgB,KAAK;EACvB,CAAC;EAED,MAAM,kBAAkB,SAAS,gBAAgB,IAAI,OAAO,aAAa;GACvE,IAAI,aAAa,KAAK,YAAY;GAElC,MAAM,UAAU,gBAAgB;GAChC,IAAI;IACF,MAAM,iBAAiB,cAAc,KAAK,MAAM,MAAM,QAAQ,SAAS,OAAO,CAAC;GACjF,SAAS,KAAK;IACZ,SAAO,MACL,KACA,uCAAuC,QAAQ,oBACjD;GACF;EACF,CAAC;EAED,MAAM,QAAQ,IAAI,eAAe;CACnC;CAEA,MAAc,uBAAuB,UAA+C;EAClF,IAAI,eAAyC;EAE7C,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,SAAS,KAAK,GAAG;GACtC,eAAe;GACf;EACF;EAGF,IAAI,CAAC,cAAc;GAKjB,SAAO,MAAM,gCAAgC,SAAS,OAAO;GAC7D;EACF;EAEA,IAAI;GACF,MAAM,aAAa,MAAM,KAAK,iBAAiB,SAAS,WAAW,SAAS,MAAM,aAAa,SAAS;GACxG,MAAM,WAAW,MAAM,UAAU,WAAW,IAAI;GAEhD,MAAM,CAAC,gBAAgB,MAAM,QAAQ,IAAI,CACvC,KAAK,iBAAiB,0BAA0B;IAC9C,UAAU,SAAS;IACnB,OAAO,SAAS;IAChB,gBAAgB,WAAW;IAC3B,SAAS,WAAW;IACpB,cAAc,KAAK;GACrB,CAAC,GACD,KAAK,MAAM,iBAAiB,SAAS,UAAU,WAAW,IAAI,CAChE,CAAC;GAKD,IAAI,CAAC,aAAa,SAAS;IACzB,KAAK,wBAAwB,SAAS,OAAO,kCAAkC;IAC/E;GACF;GAGA,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,SAAS,KAAK,GACnC,KAAK,YAAY,SAAS;IACxB,MAAM;IACN,OAAO,SAAS;IAChB,MAAM,WAAW;IACjB;GACF,CAAC;GAIL,IAAI,SAAS,gBAAgB,SAAS,GACpC,MAAM,KAAK,0BACT,SAAS,OACT,WAAW,MACX,UACA,SAAS,eACX;GAGF,SAAO,MAAM,yCAAyC,SAAS,OAAO;EACxE,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,wCAAwC,SAAS,OAAO;EAC5E;CACF;CAEA,MAAc,iBACZ,eACA,OACA,WAC8B;EAC9B,MAAM,YAAY,KAAK,gBAAgB,aAAa,aAAa;EACjE,IAAI,CAAC,aAAa,UAAU,SAAS,SACnC,MAAM,IAAI,MAAM,0BAA0B,eAAe;EAG3D,OAAO,KAAK,GAAG,UAAU,OAAO,QAAQ;GACtC,MAAM,KAAK,UAAU;GACrB,OAAO,GAAG;IAAE,IAAI;IAAK;GAAU,GAAG,KAAK;EACzC,CAAC;CACH;;;;;;;;;;;;CAaA,qBACE,WACA,SACA,OACQ;EACR,MAAM,WAAW,UAAU;EAC3B,IAAI,aAAa,KAAA,GAAW;GAC1B,IAAI,aAAa,OAAO,OAAO;GAC/B,IAAI,OAAO,aAAa,UAAU,OAAO;GACzC,IAAI,OAAO,aAAa,YAAY;IAClC,MAAM,QAAQ,SAAS,QAAQ,WAAW,KAAK;IAC/C,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;IAClD,OAAO;GACT;EACF;EACA,IAAI,CAAC,KAAK,OAAO,WAAW,OAAO;EACnC,OAAO,KAAK,OAAO,UAAU,QAAQ,SAAS,KAAA;CAChD;CAEA,YAAoB,SAA4B,SAA8B;EAC5E,IAAI,QAAQ,GAAG,eAAe,UAAU,MAAM;EAC9C,QAAQ,GAAG,KAAK,kBAAkB,OAAO,CAAC;EAK1C,QAAQ,aAAa,KAAK,IAAI;CAChC;CAEA,eAAuB,SAA4B,SAAuB;EACxE,IAAI,QAAQ,GAAG,eAAe,UAAU,MACtC,QAAQ,GAAG,KAAK,OAAO;CAE3B;CAEA,UAAkB,SAA4B,IAAY,OAAe,MAAoB;EAC3F,KAAK,YAAY,SAAS;GAAE,MAAM;GAAY;GAAI,SAAS;GAAO;GAAO;EAAK,CAAC;CACjF;CAEA,MAAc,6BAA4C;EACxD,MAAM,kBAAkB,gBAAgB,KAAK;EAC7C,MAAM,gBAAgB;EAEtB,MAAM,KAAK,SAAS,UAAU,iBAAiB,aAAa;EAC5D,KAAK,SAAS,GAAG,iBAAiB;GAChC,SAAO,KAAK,iCAAiC,gBAAgB,IAAI,eAAe;EAClF,CAAC;EACD,KAAK,SAAS,GAAG,YAAY,SAAiB,YAAoB;GAChE,IAAI,YAAY,iBACd,KAAK,4BAA4B,OAAO,CAAC,CAAC,OAAO,QAAQ;IACvD,SAAO,MAAM,KAAK,wCAAwC;GAC5D,CAAC;QACI,IAAI,YAAY,eACrB,KAAK,mBAAmB,CAAC,CAAC,OAAO,QAAQ;IACvC,SAAO,MAAM,KAAK,8BAA8B;GAClD,CAAC;EAEL,CAAC;EACD,KAAK,SAAS,GAAG,UAAU,QAAe;GACxC,SAAO,MAAM,KAAK,kCAAkC,gBAAgB,IAAI,eAAe;EACzF,CAAC;CACH;;;;;;;CAQA,MAAc,qBAAoC;EAChD,SAAO,KAAK,8CAA8C,KAAK,WAAW,uBAAuB;EACjG,IAAI;GACF,MAAM,MAAM,MAAQ,KAAK,OAAO,IAAI,GAAK;GACzC,MAAM,iBAAiB,KAAK,IAAI,KAAK,OAAO,IAAI;GAChD,SAAO,KAAK,qCAAqC,KAAK,YAAY;EACpE,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,yBAAyB;GAC3C;EACF;CACF;;;;;;;CAQA,MAAM,oBAAoB,QAAkC;EAC1D,MAAM,UAAU,KAAK,UAAU;GAC7B,MAAM;GACN;GACA,gBAAgB,KAAK;EACvB,CAAC;EACD,MAAM,iBAAiB,cACrB,KAAK,MAAM,MAAM,QAAQ,uBAAuB,OAAO,CAAC;CAC5D;CAEA,MAAc,4BAA4B,SAAgC;EACxE,IAAI;EACJ,IAAI;GACF,SAAS,gCAAgC,MAAM,KAAK,MAAM,OAAO,CAAC;EACpE,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,yCAAyC;GAC3D;EACF;EAEA,IAAI,OAAO,mBAAmB,KAAK,YAAY;EAE/C,IAAI,OAAO,SAAS,UAAU;GAC5B,KAAK,MAAM,GAAG,YAAY,KAAK,UAAU;IACvC,IAAI,CAAC,QAAQ,OAAO,IAAI,OAAO,KAAK,GAAG;IAGvC,IAAI,CADa,KAAK,oBAAoB,IAAI,OAAO,KACzC,GAAG;IAEf,KAAK,YAAY,SAAS;KACxB,MAAM;KACN,OAAO,OAAO;KACd,MAAM,OAAO;KACb,UAAU,OAAO;IACnB,CAAC;GACH;GACA;EACF;EAKA,MAAM,KAAK,0BAA0B,MAAM;CAC7C;CAEA,MAAc,0BAA0B,KAAyC;EAC/E,IAAI,CAAC,KAAK,mBAAmB,IAAI,KAAK,GAGpC;EAMF,IAAI,CAAC,MAHa,KAAK,MAAM,wBAC3B,KAAK,YAAY,IAAI,OAAO,IAAI,UAAU,0BAC5C,GACU;GACR,SAAO,MACL,iCAAiC,IAAI,MAAM,GAAG,IAAI,SAAS,8BAC7D;GACA;EACF;EAEA,MAAM,KAAK,uBAAuB;GAChC,OAAO,IAAI;GACX,WAAW,IAAI;GACf,MAAM,IAAI;GACV,UAAU,IAAI;GACd,iBAAiB,IAAI;EACvB,CAAC;CACH;CAEA,MAAc,0BACZ,OACA,MACA,UACA,iBACe;EACf,MAAM,UAAU,kBAAkB;GAChC,MAAM;GACN;GACA;GACA;GACA,gBAAgB,KAAK;EACvB,CAAC;EAED,MAAM,kBAAkB,gBAAgB,IAAI,OAAO,aAAa;GAC9D,IAAI,aAAa,KAAK,YAAY;GAElC,MAAM,UAAU,gBAAgB;GAChC,IAAI;IACF,MAAM,iBAAiB,cAAc,KAAK,MAAM,MAAM,QAAQ,SAAS,OAAO,CAAC;GACjF,SAAS,KAAK;IACZ,SAAO,MAAM,KAAK,wBAAwB,QAAQ,oBAAoB;GACxE;EACF,CAAC;EAED,MAAM,QAAQ,IAAI,eAAe;CACnC;AACF;;;;;;;;;;;;;ACpoCA,SAAgB,QAAQ,GAAmB;CACzC,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,KAAK,EAAE,WAAW,CAAC;EAEnB,IAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,SAAU;CACxE;CACA,OAAO,MAAM;AACf;;;ACcA,MAAMG,WAASC,OAAW,MACxB,EAAE,MAAM,WAAW,GACnB,EAAE,OAAO,WAAW,+BAA+B,EAAE,CACvD;AAKA,SAAS,qBAAkD;CAEzD,MAAM,OAAO,IADI,IAAI,OAAO,KAAK,GACjB,CAAC,CAAC,SAAS,SAAS,KAAK;CAEzC,OAAO;EACL,KAAK,IAAI,IAAI,sCAFH,OAAO,QAAQ,SAEiC,OAAO,KAAK,GAAG;EACzE;CACF;AACF;AAOA,SAAS,eAAe,MAAqC;CAC3D,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,IAAI,OAAQ,WAAmB,QAAQ,aAAa,OAAO,KAAA;CAC3D,OAAO,CAAC,YAAY,KAAK;AAC3B;AASA,IAAM,2BAAN,MAA+B;CAC7B;CACA;CACA,0BAAkB,IAAI,IAA4B;CAClD,YAAoB;CACpB;CAEA,YAAY,IAAY,MAAyB;EAC/C,KAAK,KAAK;EACV,MAAM,QAAQ,mBAAmB;EACjC,KAAK,SAAS,IAAI,OAAO,cAAc,MAAM,GAAG,GAAG,EACjD,UAAU,eAAe,MAAM,IAAI,EACrC,CAAC;EAED,KAAK,OAAO,GAAG,YAAY,QAAwB;GACjD,MAAM,UAAU,KAAK,QAAQ,IAAI,IAAI,KAAK;GAC1C,IAAI,CAAC,SAAS;GAEd,KAAK,QAAQ,OAAO,IAAI,KAAK;GAC7B,IAAI,IAAI,IACN,QAAQ,QAAQ,IAAI,MAAM;QACrB;IACL,MAAM,MAAM,IAAI,MAAM,IAAI,MAAM,OAAO;IACvC,IAAI,IAAI,MAAM,OAAO,IAAI,QAAQ,IAAI,MAAM;IAC3C,QAAQ,OAAO,GAAG;GACpB;EACF,CAAC;EAED,KAAK,OAAO,GAAG,UAAU,QAAiB;GACxC,SAAO,MAAM,KAAK,WAAW,KAAK,GAAG,wBAAwB;GAC7D,MAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;GAC5D,KAAK,MAAM,GAAG,MAAM,KAAK,SAAS,EAAE,OAAO,CAAC;GAC5C,KAAK,QAAQ,MAAM;EACrB,CAAC;EAED,KAAK,eAAe,KAAK,KAAA,GAAyB,IAAI,CAAC,CAAC,WAAW,CAAE,CAAC;CACxE;CAEA,QAAuB;EACrB,OAAO,KAAK;CACd;CAEA,KAAQ,MAAe,SAA8B;EACnD,MAAM,QAAQ,KAAK;EACnB,OAAO,IAAI,SAAY,SAAS,WAAW;GACzC,KAAK,QAAQ,IAAI,OAAO;IACb;IACT;GACF,CAAC;GACD,MAAM,MAAqB;IAAE;IAAM;IAAO;GAAQ;GAClD,KAAK,OAAO,YAAY,GAAG;EAC7B,CAAC;CACH;CAEA,MAAM,WAA0B;EAC9B,IAAI;GACF,MAAM,KAAK,KAAA,IAA6B,IAAI;EAC9C,SAAS,GAAG,CAAe;EAC3B,MAAM,KAAK,OAAO,UAAU;CAC9B;AACF;;;;;;;;AAWA,IAAa,sBAAb,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA,gBAA+D;CAC/D,OAAwB,oBAAoB,OAAU;CAEtD,YAAY,QAAmC;EAC7C,KAAK,OAAO,OAAO;EACnB,KAAK,MAAM,IAAIC,OAAgB,EAAE,MAAM,KAAK,KAAK,GAAG,2BAA2B;EAC/E,KAAK,IAAI,WAAW;EAEpB,MAAM,aAAa,mBAAmB,OAAO,OAAO;EACpD,MAAM,0BAA0B,+BAA+B,QAAQ,UAAU;EAEjF,SAAO,KAAK,YAAY,WAAW,sBAAsB,eAAe,IAAI,KAAK,KAAK;EACtF,KAAK,UAAU,MAAM,KAAK,EAAE,QAAQ,WAAW,IAAI,GAAG,MACpD,IAAI,yBAAyB,IAAI,GAAG;GAClC,UAAU,IAAI;GACd,UAAU,OAAO;GACjB,SAAS,OAAO;GAChB,iBAAiB,OAAO;GACxB;EACF,CAAC,CACH;EAEA,KAAK,gBAAgB;CACvB;CAEA,MAAM,QAAuB;EAC3B,MAAM,QAAQ,IAAI,KAAK,QAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC;EACpD,SAAO,KAAK,qBAAqB,KAAK,MAAM;EAC5C,KAAK,gBAAgB,kBACb;GAAE,KAAK,gBAAgB,CAAC,CAAC,OAAO,QAAQF,SAAO,MAAM,KAAK,0BAA0B,CAAC;EAAG,GAC9F,oBAAoB,iBACtB;EACA,IAAI,KAAK,iBAAiB,OAAO,KAAK,cAAc,UAAU,YAC5D,KAAK,cAAc,MAAM;CAE7B;CAEA,MAAM,OAAsB;EAC1B,IAAI,KAAK,eAAe;GACtB,cAAc,KAAK,aAAa;GAChC,KAAK,gBAAgB;EACvB;EACA,KAAK,IAAI,MAAM;EACf,MAAM,QAAQ,IAAI,KAAK,QAAQ,KAAK,MAAM,EAAE,SAAS,CAAC,CAAC;EACvD,SAAO,KAAK,SAAS;CACvB;CAEA,MAAc,kBAAiC;EAC7C,MAAM,eAAe,MAAM,QAAQ,IACjC,KAAK,QAAQ,KAAK,MAAM,EAAE,KAAA,GAA+C,IAAI,CAAC,CAChF;EACA,IAAI,QAAuB;EAC3B,KAAK,MAAM,KAAK,cAAc;GAC5B,IAAI,MAAM,MAAM;GAChB,IAAI,UAAU,QAAQ,IAAI,OAAO,QAAQ;EAC3C;EAEA,IAAI,UAAU,MAAM;GAClB,MAAM,SAAS,MAAM,KAAK,QAAQ,EAAE,CAAE,KAAA,GAAuC,IAAI;GACjF,IAAI,UAAU,IAAI;GAClB,QAAQ;EACV;EAEA,MAAM,UAAU,MAAM,KAAK,QAAQ,EAAE,CAAE,KAAA,IAA4B,KAAK;EACxE,IAAI,UAAU,IACZ,SAAO,KAAK,UAAU,QAAQ,6BAA6B,OAAO;CAEtE;CAEA,cAAsB;EACpB,OAAO,KAAK,QAAQ;CACtB;CAEA,eAAe,OAAuB;EACpC,OAAO,QAAQ,KAAK,IAAI,KAAK,QAAQ;CACvC;CAEA,UAAkB,OAAyC;EACzD,OAAO,KAAK,QAAQ,KAAK,eAAe,KAAK;CAC/C;CAEA,kBAAgC;EAC9B,KAAK,IAAI,SAAS,wBAAwB,KAAK,qBAAqB,KAAK,IAAI,CAAC;EAC9E,KAAK,IAAI,SAAS,6BAA6B,KAAK,0BAA0B,KAAK,IAAI,CAAC;EACxF,KAAK,IAAI,SAAS,6BAA6B,KAAK,0BAA0B,KAAK,IAAI,CAAC;EACxF,KAAK,IAAI,SAAS,sBAAsB,KAAK,mBAAmB,KAAK,IAAI,CAAC;EAC1E,KAAK,IAAI,SAAS,0BAA0B,KAAK,uBAAuB,KAAK,IAAI,CAAC;EAClF,KAAK,IAAI,SAAS,2BAA2B,KAAK,wBAAwB,KAAK,IAAI,CAAC;CACtF;CAEA,UAAkB,QAAyB;EACzC,OAAQ,OAA6B;CACvC;CAEA,MAAc,qBAAqB,QAAsD;EACvF,MAAM,QAAQ,KAAK,UAAU,MAAM;EACnC,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAA,GAAmD,MAAM;CACxF;CAEA,MAAc,0BAA0B,QAA2D;EAGjG,MAAM,MAAM;EAEZ,MAAM,WAAW,aADF,IAAI,KAAK,GAAG,WAAW;GAAE;GAAO;GAAG,QAAQ,KAAK,UAAU,EAAE,KAAK;EAAE,EAC/C,IAAI,MAAM,EAAE,MAAM;EAErD,MAAM,UAAU,MAAM,QAAQ,IAC5B,CAAC,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,QAAQ,WAAW;GACrD,MAAM,UAAU,MAAM,OAAO,KAAA,GAE3B,MAAM,KAAK,MAAM,EAAE,CAAC,CACtB;GACA,OAAO,MAAM,KAAK,GAAG,OAAO;IAAE,OAAO,EAAE;IAAO,QAAQ,QAAQ;GAAI,EAAE;EACtE,CAAC,CACH;EAEA,MAAM,SAA0C,IAAI,MAAM,IAAI,MAAM;EACpE,KAAK,MAAM,EAAE,OAAO,QAAQ,OAAO,QAAQ,KAAK,GAC9C,OAAO,SAAS;EAElB,OAAO;CACT;CAEA,MAAc,0BAA0B,QAA2D;EACjG,MAAM,QAAQ,KAAK,UAAU,MAAM;EACnC,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAA,GAA6D,MAAM;CAClG;CAEA,MAAc,uBAAuB,QAAwD;EAC3F,MAAM,QAAQ,KAAK,UAAU,MAAM;EACnC,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAA,GAAuD,MAAM;CAC5F;CAEA,MAAc,wBAAwB,QAAyD;EAC7F,MAAM,MAAM;EAEZ,MAAM,WAAW,aADF,IAAI,KAAK,GAAG,WAAW;GAAE;GAAO;GAAG,QAAQ,KAAK,UAAU,EAAE,KAAK;EAAE,EAC/C,IAAI,MAAM,EAAE,MAAM;EAErD,MAAM,UAAU,MAAM,QAAQ,IAC5B,CAAC,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,QAAQ,WAAW;GACrD,MAAM,UAAU,MAAM,OAAO,KAAA,GAE3B,MAAM,KAAK,MAAM,EAAE,CAAC,CACtB;GACA,OAAO,MAAM,KAAK,GAAG,OAAO;IAAE,OAAO,EAAE;IAAO,QAAQ,QAAQ;GAAI,EAAE;EACtE,CAAC,CACH;EAEA,MAAM,SAAwC,IAAI,MAAM,IAAI,MAAM;EAClE,KAAK,MAAM,EAAE,OAAO,QAAQ,OAAO,QAAQ,KAAK,GAC9C,OAAO,SAAS;EAElB,OAAO;CACT;CAEA,MAAc,mBAAmB,QAAoD;EAMnF,OAAO,EAAE,WAAU,MAHK,QAAQ,IAC9B,KAAK,QAAQ,KAAK,MAAM,EAAE,KAAA,GAAiD,MAAM,CAAC,CACpF,EAAA,CAC6B,KAAK,EAAE;CACtC;AACF;AAEA,SAAS,mBAAmB,YAA6B;CACvD,IAAI,OAAO,eAAe,YAAY,aAAa,GAAG,OAAO,KAAK,MAAM,UAAU;CAElF,MAAM,SAAS,QAAQ,IAAI;CAC3B,IAAI,QAAQ;EACV,MAAM,IAAI,SAAS,QAAQ,EAAE;EAC7B,IAAI,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG,OAAO;CAC1C;CACA,OAAO;AACT;AAEA,SAAS,+BAA+B,QAAmC,SAAyB;CAClG,IAAI,OAAO,2BAA2B,OAAO,0BAA0B,GACrE,OAAO,KAAK,MAAM,OAAO,uBAAuB;CAElD,MAAM,QAAQ,OAAO,SAAS,mBAAmB,OAAO,SAAS,SAAS,UAAU,KAAK;CACzF,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC;AAChD"}
|
|
1
|
+
{"version":3,"file":"server.js","names":["logger","baseLogger","types.RegisterSubscriptionResultSchema","types.RegisterSubscriptionBatchResultSchema","types.UpdateSubscriptionReadSetResultSchema","types.UnregisterSubscriptionResultSchema","types.UnregisterSubscriptionsResultSchema","types.InvalidateWritesetResultSchema","logger","baseLogger","uuidv4","logger","baseLogger","WebSocketServer"],"sources":["../../../src/server/sub_hash.ts","../../../src/server/subscription-manager-client.ts","../../../src/server/supalive-server.ts","../../../src/server/hash.ts","../../../src/server/subscription-manager.ts"],"sourcesContent":["// Server-only hashing helpers. Kept off the `helper.ts` module so a browser\n// bundle that only imports `stableStringify` doesn't pull in `hash-wasm`.\n\nimport { sha1 } from \"hash-wasm\";\nimport { stableStringify } from \"../helper\";\n\n/** Compute stable hash using json-stable-stringify and SHA1. */\nexport async function getHashOf(input: unknown): Promise<string> {\n const stableStr = typeof input === \"string\" ? input : stableStringify(input);\n return sha1(stableStr ?? \"\");\n}\n\n/**\n * Default identity literal used when neither a procedure-level\n * `queryIdentity` override nor a per-session user is available.\n */\nexport const ANONYMOUS_IDENTITY = \"anonymous\";\n\n/**\n * Compute a subscription id from hash of procedure + input + queryIdentity.\n *\n * `queryIdentity` is the segmentation key for cache/subscriptions. By default\n * it is the authenticated user's id (so two users get distinct subscriptions\n * for the same query), but a procedure may override it to share cache/subs\n * across users (e.g. for a public, user-independent query).\n */\nexport async function generateSubscriptionId(\n procedure: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const inputStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${procedure}_${inputStr}_${queryIdentity}`);\n return `sub_${hash}`;\n}\n\n/**\n * Compute cache key from query name + args + queryIdentity. The identity\n * participates so that user-specific results don't poison each other's\n * cache when a procedure does authorize per-user.\n */\nexport async function generateCacheKey(\n queryName: string,\n input: unknown | string,\n queryIdentity: string,\n): Promise<string> {\n const argsStr = typeof input === 'string' ? input : stableStringify(input);\n const hash = await getHashOf(`${queryName}_${argsStr}_${queryIdentity}`);\n return `ck_${hash}`;\n}\n","import { Client } from \"rpc-websockets\";\nimport z, { type ZodType } from \"zod\";\nimport * as types from \"./subscription-manager-types\";\nimport type { WriteEntry } from \"../db/types_db\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { supaliveStringify } from \"../helper\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR_CLIENT\" },\n { level: logLevelOf(\"SUB_MANAGER_CLIENT_LOG_LEVEL\") },\n);\n\n/**\n * Callback the app server registers to process invalidations that were\n * deferred during a sub-manager outage. When the sub-manager reconnects and\n * the buffered writeSets are flushed in one batched RPC, the resulting\n * `affected[]` is handed to this callback — which is expected to do the\n * same work as the normal per-mutation `reexecuteAffectedQuery` path.\n */\nexport type AffectedHandler = (\n affected: types.AffectedSubscription[],\n) => Promise<void>;\n\n/**\n * Callback the app server registers to drive recovery on sub-manager\n * reconnect. Iterates `serverSubscriptions`, reads `sl:reg:<subId>` from\n * Redis, and re-registers each known sub. Resolves when complete; only then\n * does the client's buffer flush and gated calls release.\n */\nexport type RecoveryDriver = () => Promise<void>;\n\nexport interface SubManagerClientOptions {\n url: string;\n /**\n * Maximum number of buffered WriteEntry records held during an outage.\n * If exceeded, the buffer is dropped — the commit-log replay path during\n * the next register call will catch up any missed invalidations, at the\n * cost of a brief recompute spike. Default 50_000 entries.\n */\n invalidateBufferLimit?: number;\n}\n\ntype ReadyState = \"ready\" | \"busy\";\n\nexport const subscriptionManagerDataPack = {\n encode: (data: unknown) => supaliveStringify(data),\n decode: (data: string) => JSON.parse(data),\n};\n\n\n/**\n * RPC client for the (single) sub-manager process.\n *\n * Design (Convex-inspired):\n * • The sub-manager is in-memory and ephemeral. There is no version\n * protocol, no client-side registration registry, no OUT_OF_SYNC\n * replay logic. All recovery is driven from the app server using\n * state it already has (`serverSubscriptions`) plus registration\n * records persisted to Redis at `sl:reg:<subId>`.\n *\n * • Out-of-order `updateSubscriptionReadSet` calls are filtered server\n * side using `lastSnapshotTs` (monotonic DB commit timestamp).\n *\n * • On rpc-websockets reconnect, the client:\n * 1. Transitions to `busy` (any in-flight `invalidateWriteset`\n * buffers its writeSet).\n * 2. Invokes the app server's `recoveryDriver` to re-register all\n * known subs.\n * 3. Flushes the buffer as one batched `invalidateWriteset` RPC and\n * hands the resulting `affected[]` to `affectedHandler`.\n * 4. Transitions to `ready`.\n *\n * • During an extended outage, the buffer is bounded; if exceeded, it\n * is dropped and we rely on the commit-log replay built into\n * `SubscriptionWorker.register()` to catch each sub up on the next\n * re-register.\n */\nexport class SubManagerClient {\n private client: Client;\n private state: ReadyState = \"busy\";\n private hasConnectedOnce = false;\n\n /** Resolves whenever state transitions to 'ready'. Replaced on each busy→ready cycle. */\n private readyPromise: Promise<void>;\n private resolveReady!: () => void;\n\n /** Writeset entries accumulated while state === 'busy'. Flushed as one RPC after recovery. */\n private bufferedWriteSets: WriteEntry[] = [];\n private readonly bufferLimit: number;\n private bufferDroppedDuringOutage = false;\n\n private recoveryDriver?: RecoveryDriver;\n private affectedHandler?: AffectedHandler;\n\n constructor(urlOrOptions: string | SubManagerClientOptions) {\n const opts = typeof urlOrOptions === \"string\"\n ? { url: urlOrOptions }\n : urlOrOptions;\n\n this.bufferLimit = opts.invalidateBufferLimit ?? 50_000;\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n\n this.client = new Client(opts.url, {\n dataPack: subscriptionManagerDataPack,\n });\n this.client.setAutoReconnect(true);\n this.client.setMaxReconnects(0);\n // Ensure the dataPack is set even if rpc-websockets doesn't pass it through to the Client constructor\n this.client.dataPack = subscriptionManagerDataPack;\n\n this.client.on(\"open\", this.handleOpen.bind(this));\n this.client.on(\"close\", this.handleClose.bind(this));\n this.client.on(\"error\", (err: unknown) => logger.warn(err, \"sub-manager client socket error\"));\n }\n\n /**\n * Wire up the app-server-driven recovery. Must be called before any\n * disconnect/reconnect cycle for buffered writeSets to be flushed and\n * subscriptions re-registered.\n */\n setRecoveryDriver(driver: RecoveryDriver): void {\n this.recoveryDriver = driver;\n }\n\n /**\n * Wire up the handler invoked when batched invalidate results land\n * after recovery. The handler is expected to drive\n * `reexecuteAffectedQuery` for each entry.\n */\n setAffectedHandler(handler: AffectedHandler): void {\n this.affectedHandler = handler;\n }\n\n // ─── RPC methods ────────────────────────────────────────────────────────\n\n async registerSubscription(\n params: types.RegisterSubscriptionParams,\n ): Promise<types.RegisterSubscriptionResult> {\n return this.call(\"registerSubscription\", params, types.RegisterSubscriptionResultSchema);\n }\n\n async registerSubscriptionBatch(\n params: types.RegisterSubscriptionParams[],\n ): Promise<types.RegisterSubscriptionResult[]> {\n return this.call(\"registerSubscriptionBatch\", params, types.RegisterSubscriptionBatchResultSchema);\n }\n\n async updateSubscriptionReadSet(\n params: types.UpdateSubscriptionReadSetParams,\n ): Promise<types.UpdateSubscriptionReadSetResult> {\n return this.call(\"updateSubscriptionReadSet\", params, types.UpdateSubscriptionReadSetResultSchema);\n }\n\n async unregisterSubscription(\n params: types.UnregisterSubscriptionParams,\n ): Promise<types.UnregisterSubscriptionResult> {\n return this.call(\"unregisterSubscription\", params, types.UnregisterSubscriptionResultSchema);\n }\n\n async unregisterSubscriptions(\n params: types.UnregisterSubscriptionsParams,\n ): Promise<types.UnregisterSubscriptionsResult> {\n return this.call(\"unregisterSubscriptions\", params, types.UnregisterSubscriptionsResultSchema);\n }\n\n /**\n * Sends the writeSet to the sub-manager for invalidation, OR buffers it\n * if the client is currently disconnected / recovering. Returns\n * `{ affected: [] }` while buffering — the actual `affected` from the\n * buffered batch is delivered via the registered `affectedHandler`\n * after recovery completes.\n */\n async invalidateWriteset(\n params: types.InvalidateWritesetParams,\n ): Promise<types.InvalidateWritesetResult> {\n if (this.state === \"busy\") {\n this.appendToBuffer(params.writeSet);\n return { affected: [] };\n }\n return this.call(\"invalidateWriteset\", params, types.InvalidateWritesetResultSchema);\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Single point through which RPCs go. Throws on transport / server error.\n * `registerSubscription`, `updateSubscriptionReadSet`, and\n * `unregisterSubscription` are NOT gated on the ready state: during a\n * disconnect they will fail at the socket layer, and during recovery\n * (which itself uses these methods) gating would deadlock.\n */\n private async call<T extends ZodType>(\n procName: string,\n params: unknown,\n resType: T,\n ): Promise<z.infer<T>> {\n const result = await this.client.call(procName, params);\n return resType.parse(result) as z.infer<T>;\n }\n\n private appendToBuffer(writeSet: WriteEntry[]): void {\n if (this.bufferedWriteSets.length + writeSet.length > this.bufferLimit) {\n if (!this.bufferDroppedDuringOutage) {\n logger.warn(\n `Invalidate buffer would exceed ${this.bufferLimit}; dropping buffered entries. ` +\n `Subscriptions will be caught up via commit-log replay on re-register.`,\n );\n this.bufferDroppedDuringOutage = true;\n }\n this.bufferedWriteSets = [];\n return;\n }\n this.bufferedWriteSets.push(...writeSet);\n }\n\n private async flushBuffer(): Promise<void> {\n if (this.bufferedWriteSets.length === 0) return;\n const writeSet = this.bufferedWriteSets;\n this.bufferedWriteSets = [];\n this.bufferDroppedDuringOutage = false;\n\n try {\n const result = await this.call(\n \"invalidateWriteset\",\n { writeSet },\n types.InvalidateWritesetResultSchema,\n );\n if (this.affectedHandler && result.affected.length > 0) {\n await this.affectedHandler(result.affected);\n }\n } catch (err) {\n logger.error(err, \"Failed to flush invalidate buffer after recovery\");\n }\n }\n\n private markBusy(): void {\n if (this.state === \"busy\") return;\n this.state = \"busy\";\n this.readyPromise = new Promise<void>((r) => { this.resolveReady = r; });\n }\n\n private markReady(): void {\n if (this.state === \"ready\") return;\n this.state = \"ready\";\n this.resolveReady();\n }\n\n private handleClose(): void {\n logger.info(\"sub-manager client: socket closed\");\n this.markBusy();\n }\n\n private handleOpen(): void {\n if (!this.hasConnectedOnce) {\n this.hasConnectedOnce = true;\n logger.info(\"sub-manager client: initial connect\");\n this.markReady();\n return;\n }\n logger.info(\"sub-manager client: reconnected — starting recovery\");\n void this.runRecovery();\n }\n\n private async runRecovery(): Promise<void> {\n // Stay 'busy' for the duration of recovery so concurrent\n // invalidateWriteset calls keep buffering.\n try {\n if (this.recoveryDriver) {\n await this.recoveryDriver();\n } else {\n logger.warn(\"sub-manager client: no recoveryDriver configured — skipping recovery step\");\n }\n await this.flushBuffer();\n } catch (err) {\n logger.error(err, \"Recovery failed; state will be marked ready anyway\");\n } finally {\n logger.info(\"sub-manager client: recovery complete\");\n this.markReady();\n }\n }\n\n /**\n * Test/inspection helpers.\n */\n /** @internal */\n isReady(): boolean { return this.state === \"ready\"; }\n /** @internal */\n bufferSize(): number { return this.bufferedWriteSets.length; }\n}\n","import { createServer, IncomingMessage, type Server } from \"http\";\nimport { WebSocketServer, WebSocket } from \"ws\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { ZodType } from \"zod\";\nimport Redis from \"ioredis\";\nimport { retry, handleAll, ExponentialBackoff } from \"cockatiel\";\nimport { stableStringify, supaliveStringify } from \"../helper\";\nimport {\n type QueryCtx,\n type MutationCtx,\n type ActionCtx,\n ContextRegistry,\n getContextRegistry,\n} from \"../router\";\nimport { sleep, SupaliveDb } from \"../db/realtime_db\";\nimport type { WriteEntry, CommitTs, LiveResult, MutationResult } from \"../db/types_db\";\nimport { AuthenticationError, ClientMessage, ClientMessageSchema, Context, ServerMessage } from \"../db/types_client_rpc\";\nimport {\n type Session,\n type InstanceId,\n type SubId,\n type PendingMutation,\n RemoteSubscriptionMessageSchema,\n RemoteRecomputeRace,\n SubscriptionEntry,\n RemoteSubscriptionMessage,\n} from \"./types_server\";\nimport { type Router } from \"../router/router\";\nimport {\n ANONYMOUS_IDENTITY,\n generateSubscriptionId,\n generateCacheKey,\n getHashOf,\n} from \"./sub_hash\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport { CacheLayer } from \"../db/cache\";\nimport { SubManagerClient } from \"./subscription-manager-client\";\nimport { SupaliveServerConfig } from \"../config\";\nimport { appInstanceId, logger as baseLogger, logLevelOf } from \"../logger\";\nimport { AffectedSubscription, RegistrationRecord } from \"./subscription-manager-types\";\nimport { bootstrapDbTypes } from \"../db/init_db\";\nimport { exit, stdout } from \"process\";\n\nconst redisRetryPolicy = retry(handleAll, {\n maxAttempts: 3,\n backoff: new ExponentialBackoff({\n initialDelay: 100,\n maxDelay: 5000,\n exponent: 2,\n }),\n});\n\n/**\n * Global broadcast channel used to signal column-type cache invalidation\n * across every running server instance. Publishers (migration tooling,\n * the publishSchemaReload helper below) send a JSON message of shape\n * `{ type: \"schema:reload\"; tables?: string[]; originInstance?: string }`.\n * Subscribers re-bootstrap from PG (or Redis) so the next batched write\n * sees up-to-date column metadata without a process restart.\n */\nconst SCHEMA_RELOAD_CHANNEL = \"supalive:schema:reload\";\n\n/**\n * Per-socket cap on pending mutations. The Nth+1 mutation from a single\n * client gets rejected with TOO_MANY_CONCURRENT_MUTATIONS rather than\n * being enqueued — this is rate-limit pressure on a misbehaving client,\n * not a hard system limit.\n *\n * Mirrors Convex's OPERATION_QUEUE_BUFFER_SIZE (1000). Mutations drain\n * one-at-a-time per socket, so a healthy client never approaches this.\n */\nconst MAX_MUTATION_QUEUE_BUFFER = 1000;\n\n/**\n * Per-socket cap on in-flight actions. Actions run in parallel (unlike\n * mutations) because they may do long external work (HTTP, queue calls);\n * the cap exists so one socket can't open thousands of outbound\n * connections from inside the server.\n */\nconst MAX_ACTION_CONCURRENCY = 1000;\n\n// ─── Heartbeat constants ─────────────────────────────────────\n//\n// Two independent jobs run on a single per-session interval:\n//\n// 1. Dead-TCP detection: terminate sockets that have gone fully silent\n// (no inbound text/ping/pong) past `CLIENT_TIMEOUT_MS`. Otherwise\n// emit a ws-protocol ping (browser auto-pongs, refreshing `session.lastReceivedAt`).\n// 2. Client-watchdog refresh: if the application channel has been idle\n// for at least `HEARTBEAT_INTERVAL_MS`, send a `{type:\"ping\"}` JSON\n// frame so the client's inactivity watchdog stays fresh. Busy\n// sockets skip this (the `lastSentAt` check makes it idle-only).\n//\n// Both fire on the same `heartbeatTimer` — one `setInterval` per session.\nconst HEARTBEAT_INTERVAL_MS = Number(\n process.env.SUPALIVE_HEARTBEAT_INTERVAL_MS ?? 15_000,\n);\nconst CLIENT_TIMEOUT_MS = 120_000;\n\n/**\n * TTL on the Redis lock that elects the recompute winner when the writer\n * doesn't hold the sub locally. Each (subId, commitTs) gets its own lock\n * key, so this only needs to bound the worst-case recompute duration —\n * if the winner crashes mid-recompute, the lock expires and the next\n * mutation triggers a new race.\n */\nconst RECOMPUTE_LOCK_TTL_SECONDS = Number(\n process.env.SUPALIVE_RECOMPUTE_LOCK_TTL_SECONDS ?? 10,\n);\n\nconst logger = baseLogger.child({\n \"name\": \"SUPALIVE\",\n},\n { level: logLevelOf(\"SERVER_LOG_LEVEL\") },\n);\n\n/**\n * Supalive WebSocket Server\n */\nexport class SupaliveWebSocketServer<TContext extends Context> {\n private config: SupaliveServerConfig<TContext>;\n private instanceId: InstanceId;\n\n private httpServer: Server;\n private wss: WebSocketServer;\n\n private cache: CacheLayer;\n private redisSub: Redis;\n private db: SupaliveDb;\n private contextRegistry!: ContextRegistry;\n private router?: Router<any, TContext>;\n\n private sessions: Map<WebSocket, Session<TContext>> = new Map();\n private serverSubscriptions: Map<SubId, SubscriptionEntry> = new Map();\n private subManagerClient!: SubManagerClient;\n\n private stats = {\n activeConnections: 0,\n totalConnections: 0,\n activeSubscriptions: 0,\n };\n\n constructor(config: SupaliveServerConfig<TContext>) {\n this.config = {\n port: 3000,\n host: \"0.0.0.0\",\n contextName: \"default\",\n sessionTtlSeconds: 3600,\n ...config,\n };\n this.instanceId = appInstanceId;\n\n if (!config.subManagerUrl) {\n throw new Error(\"subManagerUrl config is required (WebSocket URL of the sub-manager process)\");\n }\n this.subManagerClient = new SubManagerClient(config.subManagerUrl);\n this.cache = config.cacheLayer;\n this.redisSub = config.redisSubClient;\n this.db = config.database;\n\n // Wire the sub-manager client's recovery callbacks to drive re-registration\n // from this app server's serverSubscriptions whenever the RPC reconnects,\n // and to process the resulting batched invalidate flush.\n this.subManagerClient.setRecoveryDriver(this.runSubManagerRecovery.bind(this));\n this.subManagerClient.setAffectedHandler(this.runAffectedHandler.bind(this));\n\n this.httpServer = createServer();\n this.wss = new WebSocketServer({\n server: this.httpServer,\n autoPong: true,\n maxPayload: 100 * 1024 * 1024,\n // backlog: //max pending connections queue (default: 511)\n });\n\n this.setupWebSocketHandlers();\n this.subscribeToInstanceChannel();\n }\n\n /**\n * Per-session heartbeat. Fires every `HEARTBEAT_INTERVAL_MS` and does\n * both jobs on the same tick:\n *\n * 1. Inactivity check + ws-protocol ping (dead-TCP detection).\n * 2. App-level `{type:\"ping\"}` if the channel has been idle for at\n * least one heartbeat interval (client-watchdog refresh).\n *\n * The `lastSentAt` gate on (2) preserves the original \"only ping when\n * idle\" semantics: a session producing sub:updates faster than the\n * heartbeat interval never emits a redundant app-ping.\n */\n private startSessionHeartbeat(session: Session<TContext>): void {\n session.heartbeatTimer = setInterval(() => {\n // (1) Dead-TCP detection.\n if (Date.now() - session.lastReceivedAt > CLIENT_TIMEOUT_MS) {\n logger.info(`ws inactivity → terminate session ${session.id}`);\n try { session.ws.terminate(); } catch { /* close handler cleans up */ }\n return;\n }\n try {\n session.ws.ping();\n } catch {\n // socket already broken — close handler will clean up\n }\n\n // (2) Client-watchdog refresh, idle-only.\n if (Date.now() - session.lastSentAt >= HEARTBEAT_INTERVAL_MS) {\n this.sendMessage(session, { type: \"ping\" });\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private clearSessionTimers(session: Session<TContext>): void {\n if (session.heartbeatTimer) {\n clearInterval(session.heartbeatTimer);\n session.heartbeatTimer = null;\n }\n }\n\n registerRouter(router: Router<any, TContext>): void {\n this.router = router;\n this.contextRegistry = getContextRegistry(router.contextName);\n }\n\n async start(): Promise<void> {\n return new Promise((resolve) => {\n this.httpServer.listen(this.config.port, this.config.host, () => {\n logger.info(`Server: ${this.instanceId} listening on ${this.config.host}:${this.config.port}`);\n resolve();\n });\n });\n }\n\n async stop(): Promise<void> {\n this.wss.close();\n for (const [ws] of this.sessions) {\n ws.close(1001, \"Server shutting down\");\n }\n this.httpServer.close();\n logger.info(\"Server stopped\");\n }\n\n getStats() {\n return {\n instanceId: this.instanceId,\n activeConnections: this.stats.activeConnections,\n totalConnections: this.stats.totalConnections,\n activeSubscriptions: this.stats.activeSubscriptions,\n };\n }\n\n getInstanceId(): string {\n return this.instanceId;\n }\n\n private setupWebSocketHandlers(): void {\n this.wss.on(\"connection\", (ws, req) => {\n this.handleConnection(ws, req).catch((err) => {\n logger.error(err, \"Connection handler error\")\n });\n });\n }\n\n private async handleConnection(ws: WebSocket, req: IncomingMessage): Promise<void> {\n this.stats.totalConnections++;\n this.stats.activeConnections++;\n\n const session: Session<TContext> = {\n id: uuidv4(),\n ws,\n serverCtx: undefined,\n subIds: new Set(),\n mutationQueue: [],\n mutationInFlight: false,\n actionInFlight: 0,\n lastReceivedAt: Date.now(),\n lastSentAt: Date.now(),\n heartbeatTimer: null,\n };\n logger.info(`New session: ${session.id}`);\n\n this.sessions.set(ws, session);\n this.sendMessage(session, { type: \"connected\", sessionId: session.id });\n\n // Any inbound frame (text, ws-ping, ws-pong) refreshes the liveness\n // clock. This is the only thing keeping `CLIENT_TIMEOUT_MS` from\n // tripping on a silent-but-live client, since the browser auto-pong\n // is the only thing such a client emits.\n ws.on(\"message\", (data) => {\n session.lastReceivedAt = Date.now();\n this.handleMessage(session, data.toString()).catch((err) => {\n logger.error(err, \"Message handler error\");\n });\n });\n ws.on(\"ping\", () => { session.lastReceivedAt = Date.now(); });\n ws.on(\"pong\", () => { session.lastReceivedAt = Date.now(); });\n\n this.startSessionHeartbeat(session);\n\n ws.on(\"close\", (code) => {\n logger.info(`Connection closed: ${session.id}, code: ${code}`);\n this.clearSessionTimers(session);\n this.handleDisconnect(session);\n this.sessions.delete(ws);\n this.stats.activeConnections--;\n });\n\n ws.on(\"error\", (err) => {\n logger.error(err, \"Websocket error\");\n });\n }\n\n private async handleMessage(session: Session<TContext>, message: string): Promise<void> {\n try {\n const rawMsg = JSON.parse(message);\n const parseResult = ClientMessageSchema.safeParse(rawMsg);\n if (!parseResult.success) {\n this.sendError(session, \"\", \"Invalid message format\", \"INVALID_MESSAGE\");\n return;\n }\n await this.processMessage(session, parseResult.data);\n } catch (err) {\n logger.error(err, \"Message handling error\");\n this.sendError(session, \"\", \"Parse error\", \"PARSE_ERROR\");\n }\n }\n\n private async processMessage(session: Session<TContext>, msg: ClientMessage): Promise<void> {\n switch (msg.type) {\n case \"auth\":\n await this.handleAuth(session, msg.data);\n break;\n case \"call\":\n await this.handleCall(session, msg.id, msg.procedure, msg.input);\n break;\n case \"subscribe\":\n await this.handleSubscribe(session, msg.id, msg.procedure, msg.input);\n break;\n case \"unsubscribe\":\n await this.handleUnsubscribe(session, msg.subId);\n break;\n }\n }\n\n private async handleAuth(session: Session<TContext>, data?: Record<string, unknown>): Promise<void> {\n if (!this.config.verifyAuth || !data) {\n this.sendMessage(session, { type: \"auth:success\", userId: \"anonymous\" });\n return;\n }\n\n try {\n const authResult = await this.config.verifyAuth(data, session.id);\n if (!authResult) {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n return;\n }\n\n session.serverCtx = authResult;\n this.sendMessage(session, { type: \"auth:success\", userId: authResult.user.userId });\n } catch (e) {\n if (e instanceof AuthenticationError) {\n this.sendMessage(session, {\n type: \"auth:failed\",\n message: \"Invalid authentication: \" + e.message,\n extra: e.extra,\n });\n } else {\n this.sendMessage(session, { type: \"auth:failed\", message: \"Invalid authentication\" });\n }\n }\n }\n\n /**\n * Park a mutation on this session's FIFO queue and resolve when it\n * eventually drains. The drainer is fire-and-forget: it picks up any\n * newly enqueued op as long as one drain loop is running on this\n * session. Callers (handleCall) own the response/error for their own\n * op via the returned promise.\n */\n private enqueueMutation(\n session: Session<TContext>,\n procedure: RegisteredProcedure<TContext>,\n validatedInput: unknown,\n ): Promise<MutationResult<unknown>> {\n return new Promise<MutationResult<unknown>>((resolve, reject) => {\n const pending: PendingMutation<TContext> = {\n procedure,\n validatedInput,\n resolve,\n reject,\n };\n session.mutationQueue.push(pending);\n this.drainMutationQueue(session).catch((err) =>\n logger.error(err, \"drainMutationQueue crashed\"),\n );\n });\n }\n\n /**\n * Drain the per-session mutation queue serially. Only one drain loop\n * runs at a time per session — re-entrant calls bail immediately, the\n * loop already in progress will pick up the new op before exiting.\n */\n private async drainMutationQueue(session: Session<TContext>): Promise<void> {\n if (session.mutationInFlight) return;\n session.mutationInFlight = true;\n try {\n while (session.mutationQueue.length > 0) {\n const op = session.mutationQueue.shift()!;\n try {\n const fn = op.procedure.fn as (\n ctx: MutationCtx<TContext>,\n input: unknown,\n ) => Promise<unknown>;\n const result = await this.db.mutation(async (ctx) => {\n return fn({ db: ctx, serverCtx: session.serverCtx }, op.validatedInput);\n });\n op.resolve(result);\n } catch (err) {\n op.reject(err);\n }\n }\n } finally {\n session.mutationInFlight = false;\n }\n }\n\n private async handleCall(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown\n ): Promise<void> {\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure) {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found`, \"NOT_FOUND\");\n return;\n }\n\n try {\n const validatedInput = (procedure.inputSchema as ZodType).parse(input);\n\n if (procedure.type === \"query\") {\n const result = await this.db.query(async (ctx) => {\n const fn = procedure.fn as (ctx: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n return fn({ db: ctx, serverCtx: session.serverCtx }, validatedInput);\n });\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: result,\n });\n } else if (procedure.type === \"mutation\") {\n if (session.mutationQueue.length >= MAX_MUTATION_QUEUE_BUFFER) {\n this.sendError(\n session,\n id,\n `Too many concurrent mutations on this connection (max ${MAX_MUTATION_QUEUE_BUFFER})`,\n \"TOO_MANY_CONCURRENT_MUTATIONS\",\n );\n return;\n }\n\n const mutationResult = await this.enqueueMutation(session, procedure, validatedInput);\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: mutationResult.result,\n });\n\n await this.handleMutationCommit(mutationResult.writeSet, mutationResult.commitTs);\n } else if (procedure.type === \"action\") {\n if (session.actionInFlight >= MAX_ACTION_CONCURRENCY) {\n this.sendError(\n session,\n id,\n `Too many concurrent actions on this connection (max ${MAX_ACTION_CONCURRENCY})`,\n \"TOO_MANY_CONCURRENT_ACTIONS\",\n );\n return;\n }\n\n session.actionInFlight++;\n let result: unknown;\n try {\n const fn = procedure.fn as (ctx: ActionCtx<TContext>, input: unknown) => Promise<unknown>;\n result = await fn({ db: this.db, serverCtx: session.serverCtx }, validatedInput);\n } finally {\n session.actionInFlight--;\n }\n\n this.sendMessage(session, {\n type: \"response\",\n id: id,\n success: true,\n data: result,\n });\n }\n\n\n } catch (err) {\n logger.error(err, `Error executing \"${procedureName}\"`);\n this.sendError(\n session,\n id,\n err instanceof Error ? err.message : \"Execution failed\",\n \"EXECUTION_ERROR\"\n );\n }\n }\n\n private async handleSubscribe(\n session: Session<TContext>,\n id: string,\n procedureName: string,\n input: unknown\n ): Promise<void> {\n if (!this.router) {\n this.sendError(session, id, \"Router not registered\", \"SERVER_ERROR\");\n return;\n }\n\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n this.sendError(session, id, `Procedure \"${procedureName}\" not found or not a query`, \"NOT_FOUND\");\n return;\n }\n\n try {\n const validatedInput = procedure.inputSchema.parse(input);\n const queryIdentity = this.resolveQueryIdentity(procedure, session, validatedInput);\n const inputJsonStable = stableStringify(validatedInput);\n const subId = await generateSubscriptionId(procedureName, inputJsonStable, queryIdentity);\n const cacheKey = await generateCacheKey(procedureName, inputJsonStable, queryIdentity);\n const existingServerSub = this.serverSubscriptions.get(subId);\n\n let data: unknown;\n let dataHash: string;\n let liveResult: LiveResult<unknown> | null = null;\n\n // track this session subscribed this subscription\n session.subIds.add(subId);\n\n if (existingServerSub) {\n existingServerSub.subscribedTimes += 1;\n\n const cached = await this.cache.getQueryCacheFor(existingServerSub.cacheKey);\n if (cached) {\n data = cached.data;\n } else {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);\n data = liveResult.data;\n }\n } else {\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subManagerResult = await this.subManagerClient.registerSubscription({\n subId,\n cacheKey,\n queryName: procedureName,\n args: validatedInput,\n queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs: lastSnapshotTs,\n });\n\n const cached = await this.cache.getQueryCacheFor(subManagerResult.cacheKey);\n if (cached && !subManagerResult.recompute) {\n data = cached.data;\n } else {\n liveResult = await this.executeLiveQuery(procedureName, validatedInput, session.serverCtx);\n data = liveResult.data;\n }\n\n this.serverSubscriptions.set(subId, {\n subId,\n cacheKey,\n subscribedTimes: 1,\n });\n this.stats.activeSubscriptions++;\n }\n\n if (liveResult) {\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey,\n subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n this.cache.setQueryCacheFor(cacheKey, data),\n ]);\n\n // If the sub-manager just told us this subId isn't tracked (we hit\n // a window between its register and a separate restart), our register\n // above should have rebuilt the state — log loudly if not.\n if (!updateResult.tracked) {\n logger.warn(\n `updateSubscriptionReadSet returned tracked:false immediately after register for ${subId} ` +\n `— possible sub-manager restart mid-subscribe`,\n );\n }\n }\n\n dataHash = await getHashOf(data);\n\n // Hand the canonical subId back to the client so it doesn't need to\n // recompute the hash locally (which would require pulling in wasm and\n // wouldn't reflect per-procedure `queryIdentity` overrides).\n this.sendMessage(session, { type: \"response\", id, success: true, subId });\n this.sendMessage(session, { type: \"sub:update\", subId, data, dataHash });\n\n logger.info(`Subscribed: ${subId} for session ${session.id}`);\n } catch (err) {\n logger.error(err, `Error subscribing to \"${procedureName}\"`);\n this.sendError(session, id, err instanceof Error ? err.message : \"Subscribe failed\", \"SUBSCRIBE_ERROR\");\n }\n }\n\n private async handleUnsubscribe(session: Session<TContext>, subId: string): Promise<void> {\n const serverSub = this.serverSubscriptions.get(subId);\n if (!serverSub) {\n session.subIds.delete(subId);\n return;\n }\n\n serverSub.subscribedTimes -= 1;\n if (serverSub.subscribedTimes <= 0) {\n session.subIds.delete(subId);\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions--;\n\n try {\n await this.subManagerClient.unregisterSubscription({ subId, instanceName: this.instanceId });\n } catch (err) {\n logger.error(err, \"Failed to unregister from sub-manager:\");\n }\n }\n\n logger.info(`Unsubscribed: ${subId} for session ${session.id}, remaining server subs: ${serverSub.subscribedTimes}`);\n }\n\n private async handleDisconnect(session: Session<TContext>): Promise<void> {\n logger.info(`Session disconnected: ${session.id}`);\n\n let unregisterSubs: SubscriptionEntry[] | undefined = undefined;\n for (const subId of session.subIds) {\n const sub = this.serverSubscriptions.get(subId);\n if (!sub) continue;\n\n sub.subscribedTimes -= 1;\n if (sub.subscribedTimes <= 0) {\n if (!unregisterSubs) {\n unregisterSubs = [];\n }\n\n unregisterSubs.push(sub);\n }\n }\n\n if (!unregisterSubs || unregisterSubs.length === 0) return;\n\n try {\n await this.subManagerClient.unregisterSubscriptions(\n unregisterSubs.map((sub) => ({ subId: sub.subId, instanceName: this.instanceId })),\n );\n } catch (err) {\n logger.error(err, \"Failed to unregister from sub-manager:\");\n }\n\n for (const sub of unregisterSubs) {\n // if still not subscribing\n if (sub.subscribedTimes <= 0) {\n this.serverSubscriptions.delete(sub.subId);\n this.stats.activeSubscriptions--;\n }\n }\n\n logger.info(`All Subscription sessions is disconnected: ${session.id}, subs: ${unregisterSubs.length}`);\n }\n\n /**\n * Drives recovery on a sub-manager reconnect. Iterates this app server's\n * `serverSubscriptions`, reads each subId's registration record from\n * Redis (`sl:reg:<subId>`), and re-registers it with the sub-manager.\n *\n * • If the Redis record is missing, another app-server instance fully\n * unregistered the sub during our disconnect. We drop it locally and\n * send `sub:gone` to every session that was holding it.\n * • If the sub-manager reports `recompute: true` (cache-metadata + commit\n * log replay detected staleness), we kick a reexec for that sub.\n *\n * Returns after all subs have been processed. Only then does the\n * SubManagerClient flush its buffered invalidate writeSets.\n */\n private async runSubManagerRecovery(): Promise<void> {\n const snapshot = Array.from(this.serverSubscriptions.values());\n if (snapshot.length === 0) {\n logger.info(\"Sub-manager recovery: no subscriptions to restore\");\n return;\n }\n\n logger.info(`Sub-manager recovery: restoring ${snapshot.length} subscription(s)`);\n\n const lastSnapshotTs = await this.db.impl.getLatestSnapshotTimestamp();\n const subIds = snapshot.map((e) => e.subId);\n const recordsMap = await this.cache.fetchRegistrationRecordsBatch(subIds);\n\n const registerParams: Array<{\n entry: typeof snapshot[0];\n record: RegistrationRecord;\n }> = [];\n for (const entry of snapshot) {\n const record = recordsMap.get(entry.subId) ?? null;\n if (!record) {\n this.dropSubscriptionLocally(entry.subId, \"registration not found after recovery\");\n continue;\n }\n registerParams.push({ entry, record });\n }\n\n const recompute: AffectedSubscription[] = [];\n const BATCH_SIZE = 50;\n for (let i = 0; i < registerParams.length; i += BATCH_SIZE) {\n const batch = registerParams.slice(i, i + BATCH_SIZE);\n const params = batch.map(({ entry, record }) => ({\n subId: entry.subId,\n cacheKey: entry.cacheKey,\n queryName: record.queryName,\n args: record.args,\n queryIdentity: record.queryIdentity,\n instanceName: this.instanceId,\n lastSnapshotTs,\n }));\n try {\n const results = await this.subManagerClient.registerSubscriptionBatch(params);\n for (let j = 0; j < batch.length; j++) {\n const { entry, record } = batch[j]!;\n const result = results[j]!;\n if (result.recompute) {\n recompute.push({\n subId: entry.subId,\n queryName: record.queryName,\n args: record.args,\n cacheKey: entry.cacheKey,\n notifyInstances: [],\n });\n }\n }\n } catch (err) {\n logger.error(err, `Sub-manager recovery: failed to batch register ${i}-${i + batch.length}`);\n }\n }\n\n await Promise.allSettled(recompute.map((a) => this.reexecuteAffectedQuery(a)));\n\n logger.info(`Sub-manager recovery: done (${recompute.length} reexec'd)`);\n }\n\n private async runAffectedHandler(affected: AffectedSubscription[]) {\n await Promise.allSettled(\n affected.map((a) => this.reexecuteAffectedQuery(a)),\n );\n }\n\n /**\n * Removes a subscription from this app server's state and notifies every\n * session that was holding it. Used during recovery when the central\n * registration record is gone, or after a `tracked: false` reply from the\n * sub-manager that we can't reconcile.\n */\n private dropSubscriptionLocally(subId: string, reason: string): void {\n const serverSub = this.serverSubscriptions.get(subId);\n let notified = 0;\n if (serverSub) {\n this.serverSubscriptions.delete(subId);\n this.stats.activeSubscriptions = Math.max(0, this.stats.activeSubscriptions - 1);\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) {\n notified++;\n session.subIds.delete(subId);\n this.sendMessage(session, { type: \"sub:gone\", subId, reason });\n }\n }\n }\n\n logger.warn(`Dropped subscription ${subId} locally (${reason}); notified ${notified} session(s)`);\n }\n\n private async handleMutationCommit(writeSet: WriteEntry[], commitTs: CommitTs): Promise<void> {\n if (writeSet.length === 0) return;\n\n const tables = [...new Set(writeSet.map((w) => w.table))];\n logger.debug(`Mutation committed for tables: [${tables.join(\", \")}]`);\n\n try {\n const result = await this.subManagerClient.invalidateWriteset({ writeSet });\n logger.debug({ affected: result }, \"Sub-managers invalidation result\");\n\n if (result?.affected?.length > 0) {\n await Promise.allSettled(\n result.affected.map((affected) => this.dispatchAffected(affected, commitTs)),\n );\n }\n } catch (err) {\n logger.error(err, \"Failed to notify sub-managers\");\n }\n }\n\n /**\n * Route an affected sub to whichever instance can actually run the\n * recompute:\n * • If this instance has a local session for the sub, recompute here\n * (fast path) and broadcast the result to every other instance.\n * • Otherwise broadcast a `recompute-race` to every instance in\n * `notifyInstances`. Each receiver tries to acquire a Redis lock\n * keyed by (subId, commitTs); the winner recomputes and publishes\n * an `update` via the normal broadcast path, losers no-op.\n *\n * If `notifyInstances` is empty (the sub has no live holders), we drop\n * the message — the next subscribe will recompute fresh state.\n */\n private async dispatchAffected(affected: AffectedSubscription, commitTs: CommitTs): Promise<void> {\n if (this.hasLocalSessionFor(affected.subId)) {\n await this.reexecuteAffectedQuery(affected);\n return;\n }\n\n if (affected.notifyInstances.length === 0) {\n logger.debug(\n `dispatchAffected: no live instances hold ${affected.subId} — dropping`,\n );\n return;\n }\n\n await this.publishRecomputeRace(affected, commitTs);\n }\n\n private hasLocalSessionFor(subId: SubId): boolean {\n for (const [, session] of this.sessions) {\n if (session.subIds.has(subId)) return true;\n }\n return false;\n }\n\n private async publishRecomputeRace(\n affected: AffectedSubscription,\n commitTs: CommitTs,\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"recompute-race\",\n subId: affected.subId,\n cacheKey: affected.cacheKey,\n queryName: affected.queryName,\n args: affected.args,\n notifyInstances: affected.notifyInstances,\n commitTs: commitTs.toString(),\n originInstance: this.instanceId,\n });\n\n const publishPromises = affected.notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n logger.error(\n err,\n `Failed to publish recompute-race to ${channel} after many retries`,\n );\n }\n });\n\n await Promise.all(publishPromises);\n }\n\n private async reexecuteAffectedQuery(affected: AffectedSubscription): Promise<void> {\n let foundSession: Session<TContext> | null = null;\n\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n foundSession = session;\n break;\n }\n }\n\n if (!foundSession) {\n // Should only happen if every local session unsubscribed between\n // dispatchAffected's check and now. dispatchAffected forwards to\n // another instance when no local session exists, so we'd lose the\n // update for everyone in this rare race. Accept the gap.\n logger.debug(`Session not found for subId: ${affected.subId}`);\n return;\n }\n\n try {\n const liveResult = await this.executeLiveQuery(affected.queryName, affected.args, foundSession.serverCtx);\n const dataHash = await getHashOf(liveResult.data);\n\n const [updateResult] = await Promise.all([\n this.subManagerClient.updateSubscriptionReadSet({\n cacheKey: affected.cacheKey,\n subId: affected.subId,\n lastSnapshotTs: liveResult.ts,\n readSet: liveResult.readSet,\n instanceName: this.instanceId,\n }),\n this.cache.setQueryCacheFor(affected.cacheKey, liveResult.data),\n ]);\n\n // `tracked: false` means the sub-manager has no record of this subId\n // (it restarted before we recovered, or a concurrent unregister wiped\n // it). Drop the sub locally and let clients re-subscribe.\n if (!updateResult.tracked) {\n this.dropSubscriptionLocally(affected.subId, \"no longer tracked by sub-manager\");\n return;\n }\n\n // find any session with same subId and send update\n for (const [, session] of this.sessions) {\n if (session.subIds.has(affected.subId)) {\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: affected.subId,\n data: liveResult.data,\n dataHash,\n });\n }\n }\n\n if (affected.notifyInstances.length > 0) {\n await this.publishSubscriptionUpdate(\n affected.subId,\n liveResult.data,\n dataHash,\n affected.notifyInstances\n );\n }\n\n logger.debug(`Re-executed affected query for subId: ${affected.subId}`);\n } catch (err) {\n logger.error(err, `Failed to re-execute query for subId ${affected.subId}`);\n }\n }\n\n private async executeLiveQuery(\n procedureName: string,\n input: unknown,\n serverCtx?: TContext\n ): Promise<LiveResult<unknown>> {\n const procedure = this.contextRegistry.getProcedure(procedureName);\n if (!procedure || procedure.type !== \"query\") {\n throw new Error(`Procedure not defined: ${procedureName}`);\n }\n\n return this.db.liveQuery(async (ctx) => {\n const fn = procedure.fn as (ctx: QueryCtx<TContext>, input: unknown) => Promise<unknown>;\n return fn({ db: ctx, serverCtx }, input);\n });\n }\n\n /**\n * Resolve the segmentation key used in the subId/cacheKey hash for a\n * query subscription. Priority:\n *\n * 1. Procedure-level `queryIdentity` override:\n * - `false` → identity = `ANONYMOUS_IDENTITY` (cache shared across users)\n * - `string` → that literal\n * - `function` → ctx + input → string | null | undefined\n * 2. `config.getUserId(serverCtx)` → falls back to ANONYMOUS_IDENTITY if it\n * returns null/undefined or isn't configured.\n */\n private resolveQueryIdentity(\n procedure: RegisteredProcedure<TContext>,\n session: Session<TContext>,\n input: unknown,\n ): string {\n const override = procedure.queryIdentity;\n if (override !== undefined) {\n if (override === false) return ANONYMOUS_IDENTITY;\n if (typeof override === \"string\") return override;\n if (typeof override === \"function\") {\n const value = override(session.serverCtx, input);\n if (value === null || value === undefined) return ANONYMOUS_IDENTITY;\n return value;\n }\n }\n if (!this.config.getUserId) return ANONYMOUS_IDENTITY;\n return this.config.getUserId(session.serverCtx) ?? ANONYMOUS_IDENTITY;\n }\n\n private sendMessage(session: Session<TContext>, message: ServerMessage): void {\n if (session.ws.readyState !== WebSocket.OPEN) return;\n session.ws.send(supaliveStringify(message));\n // Mark the channel as having had recent outbound traffic. The\n // heartbeat tick uses this to skip the idle-only app-level ping\n // for busy sessions (no point pinging a client that just got a\n // sub:update).\n session.lastSentAt = Date.now();\n }\n\n private sendMessageRaw(session: Session<TContext>, message: string): void {\n if (session.ws.readyState === WebSocket.OPEN) {\n session.ws.send(message);\n }\n }\n\n private sendError(session: Session<TContext>, id: string, error: string, code: string): void {\n this.sendMessage(session, { type: \"response\", id, success: false, error, code });\n }\n\n private async subscribeToInstanceChannel(): Promise<void> {\n const instanceChannel = `sub:instance:${this.instanceId}`;\n const schemaChannel = SCHEMA_RELOAD_CHANNEL;\n\n await this.redisSub.subscribe(instanceChannel, schemaChannel);\n this.redisSub.on(\"connect\", () => {\n logger.info(`Subscribed to Redis channels: ${instanceChannel}, ${schemaChannel}`);\n });\n this.redisSub.on(\"message\", (channel: string, message: string) => {\n if (channel === instanceChannel) {\n this.handleRemoteInstanceMessage(message).catch((err) => {\n logger.error(err, \"Error handling remote instance message\");\n });\n } else if (channel === schemaChannel) {\n this.handleSchemaReload().catch((err) => {\n logger.error(err, \"Error handling schema reload\");\n });\n }\n });\n this.redisSub.on(\"error\", (err: Error) => {\n logger.error(err, `Subscriber error for channels: ${instanceChannel}, ${schemaChannel}`)\n });\n }\n\n /**\n * React to a `schema:reload` broadcast: invalidate the in-memory column\n * type cache for the named tables (or all if omitted) and re-bootstrap\n * from Postgres so subsequent batched writes see the fresh types. Skips\n * messages published by this instance (we already have the new types).\n */\n private async handleSchemaReload(): Promise<void> {\n logger.info(`Schema reload request received in instance ${this.instanceId} — reloading schema...`);\n try {\n await sleep(1000 + (Math.random() * 5000)); // jitter to avoid thundering herd if many instances\n await bootstrapDbTypes(this.db, this.cache, true);\n logger.info(`Schema reload applied in instance ${this.instanceId}`);\n } catch (err) {\n logger.error(err, \"Failed to reload schema\");\n return;\n }\n }\n\n /**\n * Broadcast a schema-reload signal to every server instance (including\n * this one — the handler skips self-originated messages, so calling\n * after a local migration is safe). Pass `tables` to scope the reload;\n * omit to flush everyone's cache.\n */\n async publishSchemaReload(tables?: string[]): Promise<void> {\n const payload = JSON.stringify({\n type: \"schema:reload\",\n tables,\n originInstance: this.instanceId,\n });\n await redisRetryPolicy.execute(() =>\n this.cache.redis.publish(SCHEMA_RELOAD_CHANNEL, payload));\n }\n\n private async handleRemoteInstanceMessage(message: string): Promise<void> {\n let parsed: RemoteSubscriptionMessage;\n try {\n parsed = RemoteSubscriptionMessageSchema.parse(JSON.parse(message));\n } catch (err) {\n logger.error(err, \"Failed to parse remote instance message\");\n return;\n }\n\n if (parsed.originInstance === this.instanceId) return;\n\n if (parsed.type === \"update\") {\n for (const [, session] of this.sessions) {\n if (!session.subIds.has(parsed.subId)) continue;\n\n const subEntry = this.serverSubscriptions.get(parsed.subId);\n if (!subEntry) continue;\n\n this.sendMessage(session, {\n type: \"sub:update\",\n subId: parsed.subId,\n data: parsed.data,\n dataHash: parsed.dataHash,\n });\n }\n return;\n }\n\n // type === \"recompute-race\" — writer didn't hold the sub locally and\n // broadcast a race hint to every notifyInstance. The first to acquire\n // the lock recomputes; the rest no-op.\n await this.handleRemoteRecomputeRace(parsed);\n }\n\n private async handleRemoteRecomputeRace(req: RemoteRecomputeRace): Promise<void> {\n if (!this.hasLocalSessionFor(req.subId)) {\n // We're in notifyInstances per sub-manager but the sub has since\n // been dropped here. Another holder may still pick it up — no warn.\n return;\n }\n\n const won = await this.cache.tryAcquireRecomputeLock(\n this.instanceId, req.subId, req.commitTs, RECOMPUTE_LOCK_TTL_SECONDS,\n );\n if (!won) {\n logger.debug(\n `recompute-race: lost lock for ${req.subId}@${req.commitTs}, waiting for winner's update`,\n );\n return;\n }\n\n await this.reexecuteAffectedQuery({\n subId: req.subId,\n queryName: req.queryName,\n args: req.args,\n cacheKey: req.cacheKey,\n notifyInstances: req.notifyInstances,\n });\n }\n\n private async publishSubscriptionUpdate(\n subId: string,\n data: unknown,\n dataHash: string,\n notifyInstances: string[]\n ): Promise<void> {\n const payload = supaliveStringify({\n type: \"update\",\n subId,\n data,\n dataHash,\n originInstance: this.instanceId,\n });\n\n const publishPromises = notifyInstances.map(async (instance) => {\n if (instance === this.instanceId) return;\n\n const channel = `sub:instance:${instance}`;\n try {\n await redisRetryPolicy.execute(() => this.cache.redis.publish(channel, payload));\n } catch (err) {\n logger.error(err, `Failed to publish to ${channel} after many retries`);\n }\n });\n\n await Promise.all(publishPromises);\n }\n}\n","/**\n * FNV-1a 32-bit hash.\n *\n * Used for routing subscription operations to logical workers inside the\n * sub-manager process. Same input always returns the same 32-bit unsigned\n * integer in both JavaScript and Go, so a future Go port of the sub-manager\n * can use the identical routing without rebalancing keys.\n *\n * Reference: http://www.isthe.com/chongo/tech/comp/fnv/\n */\nexport function fnv1a32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n // h = h * 16777619, expressed as shifts to stay in 32-bit space\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n return h >>> 0;\n}\n","import { Server as WebSocketServer } from \"rpc-websockets\";\nimport { Worker } from \"node:worker_threads\";\nimport { fileURLToPath } from \"node:url\";\nimport { SubscriptionManagerConfig } from \"../config\";\nimport { fnv1a32 } from \"./hash\";\nimport { subscriptionManagerDataPack } from \"./subscription-manager-client\";\n\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport {\n AffectedSubscription,\n InvalidateWritesetResult,\n RegisterSubscriptionBatchResult,\n RegisterSubscriptionParams,\n RegisterSubscriptionResult,\n UnregisterSubscriptionParams,\n UnregisterSubscriptionResult,\n UnregisterSubscriptionsParams,\n UnregisterSubscriptionsResult,\n UpdateSubscriptionReadSetResult,\n} from \"./subscription-manager-types\";\nimport { groupByToMap } from \"../helper\";\nimport {\n MsgType,\n type WorkerInitPayload,\n type WorkerRequest,\n type WorkerResponse,\n} from \"./subscription-manager-worker-protocol\";\n\n// Re-export SubscriptionWorker so existing test imports\n// (`import { SubscriptionWorker } from \"./subscription-manager\"`) keep working.\nexport { SubscriptionWorker } from \"./subscription-worker\";\n\nconst logger = baseLogger.child(\n { name: \"SUBS_MGR\" },\n { level: logLevelOf(\"SUBS_MANAGER_SERVER_LOG_LEVEL\") },\n);\n\n// Resolve the entry script next to this compiled file. `import.meta.url` works\n// for both Bun (running TS source) and Node (running emitted JS) — we just\n// swap the extension based on the current module's path.\nfunction resolveWorkerEntry(): { url: URL; isTs: boolean } {\n const here = new URL(import.meta.url);\n const isTs = here.pathname.endsWith(\".ts\");\n const ext = isTs ? \".ts\" : \".js\";\n return {\n url: new URL(`./subscription-manager-worker-entry${ext}`, import.meta.url),\n isTs,\n };\n}\n\n// When the entry is a `.ts` file and we're running on Node (not Bun), the\n// worker thread inherits no module loader — Node can strip types but its bare\n// ESM resolver requires explicit file extensions, which our extensionless\n// internal imports don't satisfy. Register tsx as the worker loader so it\n// resolves the same way the parent module does.\nfunction workerExecArgv(isTs: boolean): string[] | undefined {\n if (!isTs) return undefined;\n if (typeof (globalThis as any).Bun !== \"undefined\") return undefined;\n return [\"--import\", \"tsx\"];\n}\n\n// ─── Worker-thread proxy ─────────────────────────────────────────────────────\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (err: Error) => void;\n}\n\nclass SubscriptionWorkerHandle {\n readonly id: number;\n private worker: Worker;\n private pending = new Map<number, PendingRequest>();\n private nextReqId = 1;\n private readyPromise: Promise<void>;\n\n constructor(id: number, init: WorkerInitPayload) {\n this.id = id;\n const entry = resolveWorkerEntry();\n this.worker = new Worker(fileURLToPath(entry.url), {\n execArgv: workerExecArgv(entry.isTs),\n });\n\n this.worker.on(\"message\", (res: WorkerResponse) => {\n const pending = this.pending.get(res.reqId);\n if (!pending) return;\n\n this.pending.delete(res.reqId);\n if (res.ok) {\n pending.resolve(res.result);\n } else {\n const err = new Error(res.error.message);\n if (res.error.stack) err.stack = res.error.stack;\n pending.reject(err);\n }\n });\n\n this.worker.on(\"error\", (err: unknown) => {\n logger.error(err, `[worker ${this.id}] worker thread errored`);\n const e = err instanceof Error ? err : new Error(String(err));\n for (const [, p] of this.pending) p.reject(e);\n this.pending.clear();\n });\n\n this.readyPromise = this.send<null>(MsgType.Init, init).then(() => { });\n }\n\n ready(): Promise<void> {\n return this.readyPromise;\n }\n\n send<T>(type: MsgType, payload: unknown): Promise<T> {\n const reqId = this.nextReqId++;\n return new Promise<T>((resolve, reject) => {\n this.pending.set(reqId, {\n resolve: resolve as (v: unknown) => void,\n reject,\n });\n const req: WorkerRequest = { type, reqId, payload };\n this.worker.postMessage(req);\n });\n }\n\n async shutdown(): Promise<void> {\n try {\n await this.send<null>(MsgType.Shutdown, null);\n } catch (_) { /* ignore */ }\n await this.worker.terminate();\n }\n}\n\n// ─── Dispatcher ──────────────────────────────────────────────────────────────\n\n/**\n * One process per deployment. Routes each subscription operation to one of N\n * worker threads by hashing subId. Worker threads run in parallel on separate\n * CPU cores; this dispatcher does no Zod parsing — it reads `subId` directly\n * from the decoded payload (the internal channel is trusted) and forwards the\n * raw payload. The worker validates and parses with Zod before executing.\n */\nexport class SubscriptionManager {\n private wss: WebSocketServer;\n private port: number;\n private workers: SubscriptionWorkerHandle[];\n private pruneInterval: ReturnType<typeof setInterval> | null = null;\n private static readonly PRUNE_INTERVAL_MS = 30 * 60 * 1000;\n\n constructor(config: SubscriptionManagerConfig) {\n this.port = config.port;\n this.wss = new WebSocketServer({ port: this.port }, subscriptionManagerDataPack);\n this.wss.dataPack = subscriptionManagerDataPack;\n\n const numWorkers = resolveWorkerCount(config.workers);\n const maxConnectionsPerWorker = resolveMaxConnectionsPerWorker(config, numWorkers);\n\n logger.info(`Spawning ${numWorkers} subscription worker${numWorkers === 1 ? \"\" : \"s\"}`);\n this.workers = Array.from({ length: numWorkers }, (_, i) =>\n new SubscriptionWorkerHandle(i + 1, {\n workerId: i + 1,\n database: config.database,\n upstash: config.upstash,\n cacheTTLSeconds: config.cacheTTLSeconds,\n maxConnectionsPerWorker,\n }),\n );\n\n this.registerMethods();\n }\n\n async start(): Promise<void> {\n await Promise.all(this.workers.map((w) => w.ready()));\n logger.info(`Listening on port ${this.port}`);\n this.pruneInterval = setInterval(\n () => { this.pruneCommitLogs().catch((err) => logger.error(err, \"commit_logs prune failed\")); },\n SubscriptionManager.PRUNE_INTERVAL_MS,\n );\n if (this.pruneInterval && typeof this.pruneInterval.unref === \"function\") {\n this.pruneInterval.unref();\n }\n }\n\n async stop(): Promise<void> {\n if (this.pruneInterval) {\n clearInterval(this.pruneInterval);\n this.pruneInterval = null;\n }\n this.wss.close();\n await Promise.all(this.workers.map((w) => w.shutdown()));\n logger.info(\"Stopped\");\n }\n\n private async pruneCommitLogs(): Promise<void> {\n const minPerWorker = await Promise.all(\n this.workers.map((w) => w.send<bigint | null>(MsgType.MinSubscriptionTs, null)),\n );\n let floor: bigint | null = null;\n for (const m of minPerWorker) {\n if (m === null) continue;\n if (floor === null || m < floor) floor = m;\n }\n\n if (floor === null) {\n const latest = await this.workers[0]!.send<bigint>(MsgType.LatestSnapshotTs, null);\n if (latest <= 0n) return;\n floor = latest;\n }\n\n const deleted = await this.workers[0]!.send<bigint>(MsgType.Prune, floor);\n if (deleted > 0n) {\n logger.info(`pruned ${deleted} commit_logs older than ts=${floor}`);\n }\n }\n\n workerCount(): number {\n return this.workers.length;\n }\n\n workerIndexFor(subId: string): number {\n return fnv1a32(subId) % this.workers.length;\n }\n\n private workerFor(subId: string): SubscriptionWorkerHandle {\n return this.workers[this.workerIndexFor(subId)]!;\n }\n\n private registerMethods(): void {\n this.wss.register(\"registerSubscription\", this.registerSubscription.bind(this));\n this.wss.register(\"registerSubscriptionBatch\", this.registerSubscriptionBatch.bind(this));\n this.wss.register(\"updateSubscriptionReadSet\", this.updateSubscriptionReadSet.bind(this));\n this.wss.register(\"invalidateWriteset\", this.invalidateWriteset.bind(this));\n this.wss.register(\"unregisterSubscription\", this.unregisterSubscription.bind(this));\n this.wss.register(\"unregisterSubscriptions\", this.unregisterSubscriptions.bind(this));\n }\n\n private readSubId(params: unknown): string {\n return (params as { subId: string }).subId;\n }\n\n private async registerSubscription(params: unknown): Promise<RegisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<RegisterSubscriptionResult>(MsgType.Register, params);\n }\n\n private async registerSubscriptionBatch(params: unknown): Promise<RegisterSubscriptionBatchResult> {\n // Group entries by destination worker, dispatch one batch per worker, then\n // restore the original order.\n const arr = params as RegisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<RegisterSubscriptionResult[]>(\n MsgType.RegisterBatch,\n items.map((t) => t.p),\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: RegisterSubscriptionBatchResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async updateSubscriptionReadSet(params: unknown): Promise<UpdateSubscriptionReadSetResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UpdateSubscriptionReadSetResult>(MsgType.UpdateReadSet, params);\n }\n\n private async unregisterSubscription(params: unknown): Promise<UnregisterSubscriptionResult> {\n const subId = this.readSubId(params);\n return this.workerFor(subId).send<UnregisterSubscriptionResult>(MsgType.Unregister, params);\n }\n\n private async unregisterSubscriptions(params: unknown): Promise<UnregisterSubscriptionsResult> {\n const arr = params as UnregisterSubscriptionParams[];\n const tagged = arr.map((p, index) => ({ index, p, worker: this.workerFor(p.subId) }));\n const byWorker = groupByToMap(tagged, (t) => t.worker);\n\n const settled = await Promise.all(\n [...byWorker.entries()].map(async ([worker, items]) => {\n const results = await worker.send<UnregisterSubscriptionResult[]>(\n MsgType.Unregisters,\n items.map((t) => t.p) satisfies UnregisterSubscriptionsParams,\n );\n return items.map((t, i) => ({ index: t.index, result: results[i]! }));\n }),\n );\n\n const result: UnregisterSubscriptionsResult = new Array(arr.length);\n for (const { index, result: r } of settled.flat()) {\n result[index] = r;\n }\n return result;\n }\n\n private async invalidateWriteset(params: unknown): Promise<InvalidateWritesetResult> {\n // Fan the raw payload out to every worker. Each parses its own copy of the\n // writeSet (cheap relative to the scan) and returns its affected subs.\n const perWorker = await Promise.all(\n this.workers.map((w) => w.send<AffectedSubscription[]>(MsgType.Invalidate, params)),\n );\n return { affected: perWorker.flat() };\n }\n}\n\nfunction resolveWorkerCount(configured?: number): number {\n if (typeof configured === \"number\" && configured > 0) return Math.floor(configured);\n\n const envVal = process.env.SUPALIVE_SUB_MANAGER_WORKERS;\n if (envVal) {\n const n = parseInt(envVal, 10);\n if (Number.isFinite(n) && n > 0) return n;\n }\n return 1;\n}\n\nfunction resolveMaxConnectionsPerWorker(config: SubscriptionManagerConfig, workers: number): number {\n if (config.maxConnectionsPerWorker && config.maxConnectionsPerWorker > 0) {\n return Math.floor(config.maxConnectionsPerWorker);\n }\n const total = config.database.maxConnections ?? (config.database.type === \"mysql\" ? 20 : 10);\n return Math.max(1, Math.floor(total / workers));\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAOA,eAAsB,UAAU,OAAiC;CAE/D,OAAO,MADW,OAAO,UAAU,WAAW,QAAQ,gBAAgB,KAAK,MAClD,EAAE;AAC7B;;;;;AAMA,MAAa,qBAAqB;;;;;;;;;AAUlC,eAAsB,uBACpB,WACA,OACA,eACiB;CAGjB,OAAO,OAAO,MADK,UAAU,GAAG,UAAU,GADzB,OAAO,UAAU,WAAW,QAAQ,gBAAgB,KAAK,EACpB,GAAG,eAAe;AAE1E;;;;;;AAOA,eAAsB,iBACpB,WACA,OACA,eACiB;CAGjB,OAAO,MAAM,MADM,UAAU,GAAG,UAAU,GAD1B,OAAO,UAAU,WAAW,QAAQ,gBAAgB,KAAK,EACpB,GAAG,eAAe;AAEzE;;;AC1CA,MAAMA,WAASC,OAAW,MACxB,EAAE,MAAM,kBAAkB,GAC1B,EAAE,OAAO,WAAW,8BAA8B,EAAE,CACtD;AAkCA,MAAa,8BAA8B;CACzC,SAAS,SAAkB,kBAAkB,IAAI;CACjD,SAAS,SAAiB,KAAK,MAAM,IAAI;AAC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,mBAAb,MAA8B;CAC5B;CACA,QAA4B;CAC5B,mBAA2B;;CAG3B;CACA;;CAGA,oBAA0C,CAAC;CAC3C;CACA,4BAAoC;CAEpC;CACA;CAEA,YAAY,cAAgD;EAC1D,MAAM,OAAO,OAAO,iBAAiB,WACjC,EAAE,KAAK,aAAa,IACpB;EAEJ,KAAK,cAAc,KAAK,yBAAyB;EACjD,KAAK,eAAe,IAAI,SAAe,MAAM;GAAE,KAAK,eAAe;EAAG,CAAC;EAEvE,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,EACjC,UAAU,4BACZ,CAAC;EACD,KAAK,OAAO,iBAAiB,IAAI;EACjC,KAAK,OAAO,iBAAiB,CAAC;EAE9B,KAAK,OAAO,WAAW;EAEvB,KAAK,OAAO,GAAG,QAAQ,KAAK,WAAW,KAAK,IAAI,CAAC;EACjD,KAAK,OAAO,GAAG,SAAS,KAAK,YAAY,KAAK,IAAI,CAAC;EACnD,KAAK,OAAO,GAAG,UAAU,QAAiBD,SAAO,KAAK,KAAK,iCAAiC,CAAC;CAC/F;;;;;;CAOA,kBAAkB,QAA8B;EAC9C,KAAK,iBAAiB;CACxB;;;;;;CAOA,mBAAmB,SAAgC;EACjD,KAAK,kBAAkB;CACzB;CAIA,MAAM,qBACJ,QAC2C;EAC3C,OAAO,KAAK,KAAK,wBAAwB,QAAQE,gCAAsC;CACzF;CAEA,MAAM,0BACJ,QAC6C;EAC7C,OAAO,KAAK,KAAK,6BAA6B,QAAQC,qCAA2C;CACnG;CAEA,MAAM,0BACJ,QACgD;EAChD,OAAO,KAAK,KAAK,6BAA6B,QAAQC,qCAA2C;CACnG;CAEA,MAAM,uBACJ,QAC6C;EAC7C,OAAO,KAAK,KAAK,0BAA0B,QAAQC,kCAAwC;CAC7F;CAEA,MAAM,wBACJ,QAC8C;EAC9C,OAAO,KAAK,KAAK,2BAA2B,QAAQC,mCAAyC;CAC/F;;;;;;;;CASA,MAAM,mBACJ,QACyC;EACzC,IAAI,KAAK,UAAU,QAAQ;GACzB,KAAK,eAAe,OAAO,QAAQ;GACnC,OAAO,EAAE,UAAU,CAAC,EAAE;EACxB;EACA,OAAO,KAAK,KAAK,sBAAsB,QAAQC,8BAAoC;CACrF;;;;;;;;CAWA,MAAc,KACZ,UACA,QACA,SACqB;EACrB,MAAM,SAAS,MAAM,KAAK,OAAO,KAAK,UAAU,MAAM;EACtD,OAAO,QAAQ,MAAM,MAAM;CAC7B;CAEA,eAAuB,UAA8B;EACnD,IAAI,KAAK,kBAAkB,SAAS,SAAS,SAAS,KAAK,aAAa;GACtE,IAAI,CAAC,KAAK,2BAA2B;IACnC,SAAO,KACL,kCAAkC,KAAK,YAAY,mGAErD;IACA,KAAK,4BAA4B;GACnC;GACA,KAAK,oBAAoB,CAAC;GAC1B;EACF;EACA,KAAK,kBAAkB,KAAK,GAAG,QAAQ;CACzC;CAEA,MAAc,cAA6B;EACzC,IAAI,KAAK,kBAAkB,WAAW,GAAG;EACzC,MAAM,WAAW,KAAK;EACtB,KAAK,oBAAoB,CAAC;EAC1B,KAAK,4BAA4B;EAEjC,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,KACxB,sBACA,EAAE,SAAS,GACXA,8BACF;GACA,IAAI,KAAK,mBAAmB,OAAO,SAAS,SAAS,GACnD,MAAM,KAAK,gBAAgB,OAAO,QAAQ;EAE9C,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,kDAAkD;EACtE;CACF;CAEA,WAAyB;EACvB,IAAI,KAAK,UAAU,QAAQ;EAC3B,KAAK,QAAQ;EACb,KAAK,eAAe,IAAI,SAAe,MAAM;GAAE,KAAK,eAAe;EAAG,CAAC;CACzE;CAEA,YAA0B;EACxB,IAAI,KAAK,UAAU,SAAS;EAC5B,KAAK,QAAQ;EACb,KAAK,aAAa;CACpB;CAEA,cAA4B;EAC1B,SAAO,KAAK,mCAAmC;EAC/C,KAAK,SAAS;CAChB;CAEA,aAA2B;EACzB,IAAI,CAAC,KAAK,kBAAkB;GAC1B,KAAK,mBAAmB;GACxB,SAAO,KAAK,qCAAqC;GACjD,KAAK,UAAU;GACf;EACF;EACA,SAAO,KAAK,qDAAqD;EACjE,KAAU,YAAY;CACxB;CAEA,MAAc,cAA6B;EAGzC,IAAI;GACF,IAAI,KAAK,gBACP,MAAM,KAAK,eAAe;QAE1B,SAAO,KAAK,2EAA2E;GAEzF,MAAM,KAAK,YAAY;EACzB,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,oDAAoD;EACxE,UAAU;GACR,SAAO,KAAK,uCAAuC;GACnD,KAAK,UAAU;EACjB;CACF;;;;;CAMA,UAAmB;EAAE,OAAO,KAAK,UAAU;CAAS;;CAEpD,aAAqB;EAAE,OAAO,KAAK,kBAAkB;CAAQ;AAC/D;;;ACrPA,MAAM,mBAAmB,MAAM,WAAW;CACxC,aAAa;CACb,SAAS,IAAI,mBAAmB;EAC9B,cAAc;EACd,UAAU;EACV,UAAU;CACZ,CAAC;AACH,CAAC;;;;;;;;;AAUD,MAAM,wBAAwB;;;;;;;;;;AAW9B,MAAM,4BAA4B;;;;;;;AAQlC,MAAM,yBAAyB;AAe/B,MAAM,wBAAwB,OAC5B,QAAQ,IAAI,kCAAkC,IAChD;AACA,MAAM,oBAAoB;;;;;;;;AAS1B,MAAM,6BAA6B,OACjC,QAAQ,IAAI,uCAAuC,EACrD;AAEA,MAAMC,WAASC,OAAW,MAAM,EAC9B,QAAQ,WACV,GACE,EAAE,OAAO,WAAW,kBAAkB,EAAE,CAC1C;;;;AAKA,IAAa,0BAAb,MAA+D;CAC7D;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA,2BAAsD,IAAI,IAAI;CAC9D,sCAA6D,IAAI,IAAI;CACrE;CAEA,QAAgB;EACd,mBAAmB;EACnB,kBAAkB;EAClB,qBAAqB;CACvB;CAEA,YAAY,QAAwC;EAClD,KAAK,SAAS;GACZ,MAAM;GACN,MAAM;GACN,aAAa;GACb,mBAAmB;GACnB,GAAG;EACL;EACA,KAAK,aAAa;EAElB,IAAI,CAAC,OAAO,eACV,MAAM,IAAI,MAAM,6EAA6E;EAE/F,KAAK,mBAAmB,IAAI,iBAAiB,OAAO,aAAa;EACjE,KAAK,QAAQ,OAAO;EACpB,KAAK,WAAW,OAAO;EACvB,KAAK,KAAK,OAAO;EAKjB,KAAK,iBAAiB,kBAAkB,KAAK,sBAAsB,KAAK,IAAI,CAAC;EAC7E,KAAK,iBAAiB,mBAAmB,KAAK,mBAAmB,KAAK,IAAI,CAAC;EAE3E,KAAK,aAAa,aAAa;EAC/B,KAAK,MAAM,IAAI,gBAAgB;GAC7B,QAAQ,KAAK;GACb,UAAU;GACV,YAAY,MAAM,OAAO;EAE3B,CAAC;EAED,KAAK,uBAAuB;EAC5B,KAAK,2BAA2B;CAClC;;;;;;;;;;;;;CAcA,sBAA8B,SAAkC;EAC9D,QAAQ,iBAAiB,kBAAkB;GAEzC,IAAI,KAAK,IAAI,IAAI,QAAQ,iBAAiB,mBAAmB;IAC3D,SAAO,KAAK,qCAAqC,QAAQ,IAAI;IAC7D,IAAI;KAAE,QAAQ,GAAG,UAAU;IAAG,QAAQ,CAAgC;IACtE;GACF;GACA,IAAI;IACF,QAAQ,GAAG,KAAK;GAClB,QAAQ,CAER;GAGA,IAAI,KAAK,IAAI,IAAI,QAAQ,cAAc,uBACrC,KAAK,YAAY,SAAS,EAAE,MAAM,OAAO,CAAC;EAE9C,GAAG,qBAAqB;CAC1B;CAEA,mBAA2B,SAAkC;EAC3D,IAAI,QAAQ,gBAAgB;GAC1B,cAAc,QAAQ,cAAc;GACpC,QAAQ,iBAAiB;EAC3B;CACF;CAEA,eAAe,QAAqC;EAClD,KAAK,SAAS;EACd,KAAK,kBAAkB,mBAAmB,OAAO,WAAW;CAC9D;CAEA,MAAM,QAAuB;EAC3B,OAAO,IAAI,SAAS,YAAY;GAC9B,KAAK,WAAW,OAAO,KAAK,OAAO,MAAM,KAAK,OAAO,YAAY;IAC/D,SAAO,KAAK,WAAW,KAAK,WAAW,gBAAgB,KAAK,OAAO,KAAK,GAAG,KAAK,OAAO,MAAM;IAC7F,QAAQ;GACV,CAAC;EACH,CAAC;CACH;CAEA,MAAM,OAAsB;EAC1B,KAAK,IAAI,MAAM;EACf,KAAK,MAAM,CAAC,OAAO,KAAK,UACtB,GAAG,MAAM,MAAM,sBAAsB;EAEvC,KAAK,WAAW,MAAM;EACtB,SAAO,KAAK,gBAAgB;CAC9B;CAEA,WAAW;EACT,OAAO;GACL,YAAY,KAAK;GACjB,mBAAmB,KAAK,MAAM;GAC9B,kBAAkB,KAAK,MAAM;GAC7B,qBAAqB,KAAK,MAAM;EAClC;CACF;CAEA,gBAAwB;EACtB,OAAO,KAAK;CACd;CAEA,yBAAuC;EACrC,KAAK,IAAI,GAAG,eAAe,IAAI,QAAQ;GACrC,KAAK,iBAAiB,IAAI,GAAG,CAAC,CAAC,OAAO,QAAQ;IAC5C,SAAO,MAAM,KAAK,0BAA0B;GAC9C,CAAC;EACH,CAAC;CACH;CAEA,MAAc,iBAAiB,IAAe,KAAqC;EACjF,KAAK,MAAM;EACX,KAAK,MAAM;EAEX,MAAM,UAA6B;GACjC,IAAIC,GAAO;GACX;GACA,WAAW,KAAA;GACX,wBAAQ,IAAI,IAAI;GAChB,eAAe,CAAC;GAChB,kBAAkB;GAClB,gBAAgB;GAChB,gBAAgB,KAAK,IAAI;GACzB,YAAY,KAAK,IAAI;GACrB,gBAAgB;EAClB;EACA,SAAO,KAAK,gBAAgB,QAAQ,IAAI;EAExC,KAAK,SAAS,IAAI,IAAI,OAAO;EAC7B,KAAK,YAAY,SAAS;GAAE,MAAM;GAAa,WAAW,QAAQ;EAAG,CAAC;EAMtE,GAAG,GAAG,YAAY,SAAS;GACzB,QAAQ,iBAAiB,KAAK,IAAI;GAClC,KAAK,cAAc,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,QAAQ;IAC1D,SAAO,MAAM,KAAK,uBAAuB;GAC3C,CAAC;EACH,CAAC;EACD,GAAG,GAAG,cAAc;GAAE,QAAQ,iBAAiB,KAAK,IAAI;EAAG,CAAC;EAC5D,GAAG,GAAG,cAAc;GAAE,QAAQ,iBAAiB,KAAK,IAAI;EAAG,CAAC;EAE5D,KAAK,sBAAsB,OAAO;EAElC,GAAG,GAAG,UAAU,SAAS;GACvB,SAAO,KAAK,sBAAsB,QAAQ,GAAG,UAAU,MAAM;GAC7D,KAAK,mBAAmB,OAAO;GAC/B,KAAK,iBAAiB,OAAO;GAC7B,KAAK,SAAS,OAAO,EAAE;GACvB,KAAK,MAAM;EACb,CAAC;EAED,GAAG,GAAG,UAAU,QAAQ;GACtB,SAAO,MAAM,KAAK,iBAAiB;EACrC,CAAC;CACH;CAEA,MAAc,cAAc,SAA4B,SAAgC;EACtF,IAAI;GACF,MAAM,SAAS,KAAK,MAAM,OAAO;GACjC,MAAM,cAAc,oBAAoB,UAAU,MAAM;GACxD,IAAI,CAAC,YAAY,SAAS;IACxB,KAAK,UAAU,SAAS,IAAI,0BAA0B,iBAAiB;IACvE;GACF;GACA,MAAM,KAAK,eAAe,SAAS,YAAY,IAAI;EACrD,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,wBAAwB;GAC1C,KAAK,UAAU,SAAS,IAAI,eAAe,aAAa;EAC1D;CACF;CAEA,MAAc,eAAe,SAA4B,KAAmC;EAC1F,QAAQ,IAAI,MAAZ;GACE,KAAK;IACH,MAAM,KAAK,WAAW,SAAS,IAAI,IAAI;IACvC;GACF,KAAK;IACH,MAAM,KAAK,WAAW,SAAS,IAAI,IAAI,IAAI,WAAW,IAAI,KAAK;IAC/D;GACF,KAAK;IACH,MAAM,KAAK,gBAAgB,SAAS,IAAI,IAAI,IAAI,WAAW,IAAI,KAAK;IACpE;GACF,KAAK;IACH,MAAM,KAAK,kBAAkB,SAAS,IAAI,KAAK;IAC/C;EACJ;CACF;CAEA,MAAc,WAAW,SAA4B,MAA+C;EAClG,IAAI,CAAC,KAAK,OAAO,cAAc,CAAC,MAAM;GACpC,KAAK,YAAY,SAAS;IAAE,MAAM;IAAgB,QAAQ;GAAY,CAAC;GACvE;EACF;EAEA,IAAI;GACF,MAAM,aAAa,MAAM,KAAK,OAAO,WAAW,MAAM,QAAQ,EAAE;GAChE,IAAI,CAAC,YAAY;IACf,KAAK,YAAY,SAAS;KAAE,MAAM;KAAe,SAAS;IAAyB,CAAC;IACpF;GACF;GAEA,QAAQ,YAAY;GACpB,KAAK,YAAY,SAAS;IAAE,MAAM;IAAgB,QAAQ,WAAW,KAAK;GAAO,CAAC;EACpF,SAAS,GAAG;GACV,IAAI,aAAa,qBACf,KAAK,YAAY,SAAS;IACxB,MAAM;IACN,SAAS,6BAA6B,EAAE;IACxC,OAAO,EAAE;GACX,CAAC;QAED,KAAK,YAAY,SAAS;IAAE,MAAM;IAAe,SAAS;GAAyB,CAAC;EAExF;CACF;;;;;;;;CASA,gBACE,SACA,WACA,gBACkC;EAClC,OAAO,IAAI,SAAkC,SAAS,WAAW;GAC/D,MAAM,UAAqC;IACzC;IACA;IACA;IACA;GACF;GACA,QAAQ,cAAc,KAAK,OAAO;GAClC,KAAK,mBAAmB,OAAO,CAAC,CAAC,OAAO,QACtCF,SAAO,MAAM,KAAK,4BAA4B,CAChD;EACF,CAAC;CACH;;;;;;CAOA,MAAc,mBAAmB,SAA2C;EAC1E,IAAI,QAAQ,kBAAkB;EAC9B,QAAQ,mBAAmB;EAC3B,IAAI;GACF,OAAO,QAAQ,cAAc,SAAS,GAAG;IACvC,MAAM,KAAK,QAAQ,cAAc,MAAM;IACvC,IAAI;KACF,MAAM,KAAK,GAAG,UAAU;KAIxB,MAAM,SAAS,MAAM,KAAK,GAAG,SAAS,OAAO,QAAQ;MACnD,OAAO,GAAG;OAAE,IAAI;OAAK,WAAW,QAAQ;MAAU,GAAG,GAAG,cAAc;KACxE,CAAC;KACD,GAAG,QAAQ,MAAM;IACnB,SAAS,KAAK;KACZ,GAAG,OAAO,GAAG;IACf;GACF;EACF,UAAU;GACR,QAAQ,mBAAmB;EAC7B;CACF;CAEA,MAAc,WACZ,SACA,IACA,eACA,OACe;EACf,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,UAAU,SAAS,IAAI,yBAAyB,cAAc;GACnE;EACF;EAEA,MAAM,YAAY,KAAK,gBAAgB,aAAa,aAAa;EACjE,IAAI,CAAC,WAAW;GACd,KAAK,UAAU,SAAS,IAAI,cAAc,cAAc,cAAc,WAAW;GACjF;EACF;EAEA,IAAI;GACF,MAAM,iBAAkB,UAAU,YAAwB,MAAM,KAAK;GAErE,IAAI,UAAU,SAAS,SAAS;IAC9B,MAAM,SAAS,MAAM,KAAK,GAAG,MAAM,OAAO,QAAQ;KAChD,MAAM,KAAK,UAAU;KACrB,OAAO,GAAG;MAAE,IAAI;MAAK,WAAW,QAAQ;KAAU,GAAG,cAAc;IACrE,CAAC;IAED,KAAK,YAAY,SAAS;KACxB,MAAM;KACF;KACJ,SAAS;KACT,MAAM;IACR,CAAC;GACH,OAAO,IAAI,UAAU,SAAS,YAAY;IACxC,IAAI,QAAQ,cAAc,UAAU,2BAA2B;KAC7D,KAAK,UACH,SACA,IACA,yDAAyD,0BAA0B,IACnF,+BACF;KACA;IACF;IAEA,MAAM,iBAAiB,MAAM,KAAK,gBAAgB,SAAS,WAAW,cAAc;IAEpF,KAAK,YAAY,SAAS;KACxB,MAAM;KACF;KACJ,SAAS;KACT,MAAM,eAAe;IACvB,CAAC;IAED,MAAM,KAAK,qBAAqB,eAAe,UAAU,eAAe,QAAQ;GAClF,OAAO,IAAI,UAAU,SAAS,UAAU;IACtC,IAAI,QAAQ,kBAAkB,wBAAwB;KACpD,KAAK,UACH,SACA,IACA,uDAAuD,uBAAuB,IAC9E,6BACF;KACA;IACF;IAEA,QAAQ;IACR,IAAI;IACJ,IAAI;KACF,MAAM,KAAK,UAAU;KACrB,SAAS,MAAM,GAAG;MAAE,IAAI,KAAK;MAAI,WAAW,QAAQ;KAAU,GAAG,cAAc;IACjF,UAAU;KACR,QAAQ;IACV;IAEA,KAAK,YAAY,SAAS;KACxB,MAAM;KACF;KACJ,SAAS;KACT,MAAM;IACR,CAAC;GACH;EAGF,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,oBAAoB,cAAc,EAAE;GACtD,KAAK,UACH,SACA,IACA,eAAe,QAAQ,IAAI,UAAU,oBACrC,iBACF;EACF;CACF;CAEA,MAAc,gBACZ,SACA,IACA,eACA,OACe;EACf,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,UAAU,SAAS,IAAI,yBAAyB,cAAc;GACnE;EACF;EAEA,MAAM,YAAY,KAAK,gBAAgB,aAAa,aAAa;EACjE,IAAI,CAAC,aAAa,UAAU,SAAS,SAAS;GAC5C,KAAK,UAAU,SAAS,IAAI,cAAc,cAAc,6BAA6B,WAAW;GAChG;EACF;EAEA,IAAI;GACF,MAAM,iBAAiB,UAAU,YAAY,MAAM,KAAK;GACxD,MAAM,gBAAgB,KAAK,qBAAqB,WAAW,SAAS,cAAc;GAClF,MAAM,kBAAkB,gBAAgB,cAAc;GACtD,MAAM,QAAQ,MAAM,uBAAuB,eAAe,iBAAiB,aAAa;GACxF,MAAM,WAAW,MAAM,iBAAiB,eAAe,iBAAiB,aAAa;GACrF,MAAM,oBAAoB,KAAK,oBAAoB,IAAI,KAAK;GAE5D,IAAI;GACJ,IAAI;GACJ,IAAI,aAAyC;GAG7C,QAAQ,OAAO,IAAI,KAAK;GAExB,IAAI,mBAAmB;IACrB,kBAAkB,mBAAmB;IAErC,MAAM,SAAS,MAAM,KAAK,MAAM,iBAAiB,kBAAkB,QAAQ;IAC3E,IAAI,QACF,OAAO,OAAO;SACT;KACL,aAAa,MAAM,KAAK,iBAAiB,eAAe,gBAAgB,QAAQ,SAAS;KACzF,OAAO,WAAW;IACpB;GACF,OAAO;IACL,MAAM,iBAAiB,MAAM,KAAK,GAAG,KAAK,2BAA2B;IACrE,MAAM,mBAAmB,MAAM,KAAK,iBAAiB,qBAAqB;KACxE;KACA;KACA,WAAW;KACX,MAAM;KACN;KACA,cAAc,KAAK;KACH;IAClB,CAAC;IAED,MAAM,SAAS,MAAM,KAAK,MAAM,iBAAiB,iBAAiB,QAAQ;IAC1E,IAAI,UAAU,CAAC,iBAAiB,WAC9B,OAAO,OAAO;SACT;KACL,aAAa,MAAM,KAAK,iBAAiB,eAAe,gBAAgB,QAAQ,SAAS;KACzF,OAAO,WAAW;IACpB;IAEA,KAAK,oBAAoB,IAAI,OAAO;KAClC;KACA;KACA,iBAAiB;IACnB,CAAC;IACD,KAAK,MAAM;GACb;GAEA,IAAI,YAAY;IACd,MAAM,CAAC,gBAAgB,MAAM,QAAQ,IAAI,CACvC,KAAK,iBAAiB,0BAA0B;KAC9C;KACA;KACA,gBAAgB,WAAW;KAC3B,SAAS,WAAW;KACpB,cAAc,KAAK;IACrB,CAAC,GACD,KAAK,MAAM,iBAAiB,UAAU,IAAI,CAC5C,CAAC;IAKD,IAAI,CAAC,aAAa,SAChB,SAAO,KACL,mFAAmF,MAAM,8CAE3F;GAEJ;GAEA,WAAW,MAAM,UAAU,IAAI;GAK/B,KAAK,YAAY,SAAS;IAAE,MAAM;IAAY;IAAI,SAAS;IAAM;GAAM,CAAC;GACxE,KAAK,YAAY,SAAS;IAAE,MAAM;IAAc;IAAO;IAAM;GAAS,CAAC;GAEvE,SAAO,KAAK,eAAe,MAAM,eAAe,QAAQ,IAAI;EAC9D,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,yBAAyB,cAAc,EAAE;GAC3D,KAAK,UAAU,SAAS,IAAI,eAAe,QAAQ,IAAI,UAAU,oBAAoB,iBAAiB;EACxG;CACF;CAEA,MAAc,kBAAkB,SAA4B,OAA8B;EACxF,MAAM,YAAY,KAAK,oBAAoB,IAAI,KAAK;EACpD,IAAI,CAAC,WAAW;GACd,QAAQ,OAAO,OAAO,KAAK;GAC3B;EACF;EAEA,UAAU,mBAAmB;EAC7B,IAAI,UAAU,mBAAmB,GAAG;GAClC,QAAQ,OAAO,OAAO,KAAK;GAC3B,KAAK,oBAAoB,OAAO,KAAK;GACrC,KAAK,MAAM;GAEX,IAAI;IACF,MAAM,KAAK,iBAAiB,uBAAuB;KAAE;KAAO,cAAc,KAAK;IAAW,CAAC;GAC7F,SAAS,KAAK;IACZ,SAAO,MAAM,KAAK,wCAAwC;GAC5D;EACF;EAEA,SAAO,KAAK,iBAAiB,MAAM,eAAe,QAAQ,GAAG,2BAA2B,UAAU,iBAAiB;CACrH;CAEA,MAAc,iBAAiB,SAA2C;EACxE,SAAO,KAAK,yBAAyB,QAAQ,IAAI;EAEjD,IAAI,iBAAkD,KAAA;EACtD,KAAK,MAAM,SAAS,QAAQ,QAAQ;GAClC,MAAM,MAAM,KAAK,oBAAoB,IAAI,KAAK;GAC9C,IAAI,CAAC,KAAK;GAEV,IAAI,mBAAmB;GACvB,IAAI,IAAI,mBAAmB,GAAG;IAC5B,IAAI,CAAC,gBACH,iBAAiB,CAAC;IAGpB,eAAe,KAAK,GAAG;GACzB;EACF;EAEA,IAAI,CAAC,kBAAkB,eAAe,WAAW,GAAG;EAEpD,IAAI;GACF,MAAM,KAAK,iBAAiB,wBAC1B,eAAe,KAAK,SAAS;IAAE,OAAO,IAAI;IAAO,cAAc,KAAK;GAAW,EAAE,CACnF;EACF,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,wCAAwC;EAC5D;EAEA,KAAK,MAAM,OAAO,gBAEhB,IAAI,IAAI,mBAAmB,GAAG;GAC5B,KAAK,oBAAoB,OAAO,IAAI,KAAK;GACzC,KAAK,MAAM;EACb;EAGF,SAAO,KAAK,8CAA8C,QAAQ,GAAG,UAAU,eAAe,QAAQ;CACxG;;;;;;;;;;;;;;;CAgBA,MAAc,wBAAuC;EACnD,MAAM,WAAW,MAAM,KAAK,KAAK,oBAAoB,OAAO,CAAC;EAC7D,IAAI,SAAS,WAAW,GAAG;GACzB,SAAO,KAAK,mDAAmD;GAC/D;EACF;EAEA,SAAO,KAAK,mCAAmC,SAAS,OAAO,iBAAiB;EAEhF,MAAM,iBAAiB,MAAM,KAAK,GAAG,KAAK,2BAA2B;EACrE,MAAM,SAAS,SAAS,KAAK,MAAM,EAAE,KAAK;EAC1C,MAAM,aAAa,MAAM,KAAK,MAAM,8BAA8B,MAAM;EAExE,MAAM,iBAGD,CAAC;EACN,KAAK,MAAM,SAAS,UAAU;GAC5B,MAAM,SAAS,WAAW,IAAI,MAAM,KAAK,KAAK;GAC9C,IAAI,CAAC,QAAQ;IACX,KAAK,wBAAwB,MAAM,OAAO,uCAAuC;IACjF;GACF;GACA,eAAe,KAAK;IAAE;IAAO;GAAO,CAAC;EACvC;EAEA,MAAM,YAAoC,CAAC;EAC3C,MAAM,aAAa;EACnB,KAAK,IAAI,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK,YAAY;GAC1D,MAAM,QAAQ,eAAe,MAAM,GAAG,IAAI,UAAU;GACpD,MAAM,SAAS,MAAM,KAAK,EAAE,OAAO,cAAc;IAC/C,OAAO,MAAM;IACb,UAAU,MAAM;IAChB,WAAW,OAAO;IAClB,MAAM,OAAO;IACb,eAAe,OAAO;IACtB,cAAc,KAAK;IACnB;GACF,EAAE;GACF,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,iBAAiB,0BAA0B,MAAM;IAC5E,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;KACrC,MAAM,EAAE,OAAO,WAAW,MAAM;KAEhC,IADe,QAAQ,EACb,CAAC,WACT,UAAU,KAAK;MACb,OAAO,MAAM;MACb,WAAW,OAAO;MAClB,MAAM,OAAO;MACb,UAAU,MAAM;MAChB,iBAAiB,CAAC;KACpB,CAAC;IAEL;GACF,SAAS,KAAK;IACZ,SAAO,MAAM,KAAK,kDAAkD,EAAE,GAAG,IAAI,MAAM,QAAQ;GAC7F;EACF;EAEA,MAAM,QAAQ,WAAW,UAAU,KAAK,MAAM,KAAK,uBAAuB,CAAC,CAAC,CAAC;EAE7E,SAAO,KAAK,+BAA+B,UAAU,OAAO,WAAW;CACzE;CAEA,MAAc,mBAAmB,UAAkC;EACjE,MAAM,QAAQ,WACZ,SAAS,KAAK,MAAM,KAAK,uBAAuB,CAAC,CAAC,CACpD;CACF;;;;;;;CAQA,wBAAgC,OAAe,QAAsB;EACnE,MAAM,YAAY,KAAK,oBAAoB,IAAI,KAAK;EACpD,IAAI,WAAW;EACf,IAAI,WAAW;GACb,KAAK,oBAAoB,OAAO,KAAK;GACrC,KAAK,MAAM,sBAAsB,KAAK,IAAI,GAAG,KAAK,MAAM,sBAAsB,CAAC;GAE/E,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,KAAK,GAAG;IAC7B;IACA,QAAQ,OAAO,OAAO,KAAK;IAC3B,KAAK,YAAY,SAAS;KAAE,MAAM;KAAY;KAAO;IAAO,CAAC;GAC/D;EAEJ;EAEA,SAAO,KAAK,wBAAwB,MAAM,YAAY,OAAO,cAAc,SAAS,YAAY;CAClG;CAEA,MAAc,qBAAqB,UAAwB,UAAmC;EAC5F,IAAI,SAAS,WAAW,GAAG;EAE3B,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC;EACxD,SAAO,MAAM,mCAAmC,OAAO,KAAK,IAAI,EAAE,EAAE;EAEpE,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,iBAAiB,mBAAmB,EAAE,SAAS,CAAC;GAC1E,SAAO,MAAM,EAAE,UAAU,OAAO,GAAG,kCAAkC;GAErE,IAAI,QAAQ,UAAU,SAAS,GAC7B,MAAM,QAAQ,WACZ,OAAO,SAAS,KAAK,aAAa,KAAK,iBAAiB,UAAU,QAAQ,CAAC,CAC7E;EAEJ,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,+BAA+B;EACnD;CACF;;;;;;;;;;;;;;CAeA,MAAc,iBAAiB,UAAgC,UAAmC;EAChG,IAAI,KAAK,mBAAmB,SAAS,KAAK,GAAG;GAC3C,MAAM,KAAK,uBAAuB,QAAQ;GAC1C;EACF;EAEA,IAAI,SAAS,gBAAgB,WAAW,GAAG;GACzC,SAAO,MACL,4CAA4C,SAAS,MAAM,YAC7D;GACA;EACF;EAEA,MAAM,KAAK,qBAAqB,UAAU,QAAQ;CACpD;CAEA,mBAA2B,OAAuB;EAChD,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,KAAK,GAAG,OAAO;EAExC,OAAO;CACT;CAEA,MAAc,qBACZ,UACA,UACe;EACf,MAAM,UAAU,kBAAkB;GAChC,MAAM;GACN,OAAO,SAAS;GAChB,UAAU,SAAS;GACnB,WAAW,SAAS;GACpB,MAAM,SAAS;GACf,iBAAiB,SAAS;GAC1B,UAAU,SAAS,SAAS;GAC5B,gBAAgB,KAAK;EACvB,CAAC;EAED,MAAM,kBAAkB,SAAS,gBAAgB,IAAI,OAAO,aAAa;GACvE,IAAI,aAAa,KAAK,YAAY;GAElC,MAAM,UAAU,gBAAgB;GAChC,IAAI;IACF,MAAM,iBAAiB,cAAc,KAAK,MAAM,MAAM,QAAQ,SAAS,OAAO,CAAC;GACjF,SAAS,KAAK;IACZ,SAAO,MACL,KACA,uCAAuC,QAAQ,oBACjD;GACF;EACF,CAAC;EAED,MAAM,QAAQ,IAAI,eAAe;CACnC;CAEA,MAAc,uBAAuB,UAA+C;EAClF,IAAI,eAAyC;EAE7C,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,SAAS,KAAK,GAAG;GACtC,eAAe;GACf;EACF;EAGF,IAAI,CAAC,cAAc;GAKjB,SAAO,MAAM,gCAAgC,SAAS,OAAO;GAC7D;EACF;EAEA,IAAI;GACF,MAAM,aAAa,MAAM,KAAK,iBAAiB,SAAS,WAAW,SAAS,MAAM,aAAa,SAAS;GACxG,MAAM,WAAW,MAAM,UAAU,WAAW,IAAI;GAEhD,MAAM,CAAC,gBAAgB,MAAM,QAAQ,IAAI,CACvC,KAAK,iBAAiB,0BAA0B;IAC9C,UAAU,SAAS;IACnB,OAAO,SAAS;IAChB,gBAAgB,WAAW;IAC3B,SAAS,WAAW;IACpB,cAAc,KAAK;GACrB,CAAC,GACD,KAAK,MAAM,iBAAiB,SAAS,UAAU,WAAW,IAAI,CAChE,CAAC;GAKD,IAAI,CAAC,aAAa,SAAS;IACzB,KAAK,wBAAwB,SAAS,OAAO,kCAAkC;IAC/E;GACF;GAGA,KAAK,MAAM,GAAG,YAAY,KAAK,UAC7B,IAAI,QAAQ,OAAO,IAAI,SAAS,KAAK,GACnC,KAAK,YAAY,SAAS;IACxB,MAAM;IACN,OAAO,SAAS;IAChB,MAAM,WAAW;IACjB;GACF,CAAC;GAIL,IAAI,SAAS,gBAAgB,SAAS,GACpC,MAAM,KAAK,0BACT,SAAS,OACT,WAAW,MACX,UACA,SAAS,eACX;GAGF,SAAO,MAAM,yCAAyC,SAAS,OAAO;EACxE,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,wCAAwC,SAAS,OAAO;EAC5E;CACF;CAEA,MAAc,iBACZ,eACA,OACA,WAC8B;EAC9B,MAAM,YAAY,KAAK,gBAAgB,aAAa,aAAa;EACjE,IAAI,CAAC,aAAa,UAAU,SAAS,SACnC,MAAM,IAAI,MAAM,0BAA0B,eAAe;EAG3D,OAAO,KAAK,GAAG,UAAU,OAAO,QAAQ;GACtC,MAAM,KAAK,UAAU;GACrB,OAAO,GAAG;IAAE,IAAI;IAAK;GAAU,GAAG,KAAK;EACzC,CAAC;CACH;;;;;;;;;;;;CAaA,qBACE,WACA,SACA,OACQ;EACR,MAAM,WAAW,UAAU;EAC3B,IAAI,aAAa,KAAA,GAAW;GAC1B,IAAI,aAAa,OAAO,OAAO;GAC/B,IAAI,OAAO,aAAa,UAAU,OAAO;GACzC,IAAI,OAAO,aAAa,YAAY;IAClC,MAAM,QAAQ,SAAS,QAAQ,WAAW,KAAK;IAC/C,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;IAClD,OAAO;GACT;EACF;EACA,IAAI,CAAC,KAAK,OAAO,WAAW,OAAO;EACnC,OAAO,KAAK,OAAO,UAAU,QAAQ,SAAS,KAAA;CAChD;CAEA,YAAoB,SAA4B,SAA8B;EAC5E,IAAI,QAAQ,GAAG,eAAe,UAAU,MAAM;EAC9C,QAAQ,GAAG,KAAK,kBAAkB,OAAO,CAAC;EAK1C,QAAQ,aAAa,KAAK,IAAI;CAChC;CAEA,eAAuB,SAA4B,SAAuB;EACxE,IAAI,QAAQ,GAAG,eAAe,UAAU,MACtC,QAAQ,GAAG,KAAK,OAAO;CAE3B;CAEA,UAAkB,SAA4B,IAAY,OAAe,MAAoB;EAC3F,KAAK,YAAY,SAAS;GAAE,MAAM;GAAY;GAAI,SAAS;GAAO;GAAO;EAAK,CAAC;CACjF;CAEA,MAAc,6BAA4C;EACxD,MAAM,kBAAkB,gBAAgB,KAAK;EAC7C,MAAM,gBAAgB;EAEtB,MAAM,KAAK,SAAS,UAAU,iBAAiB,aAAa;EAC5D,KAAK,SAAS,GAAG,iBAAiB;GAChC,SAAO,KAAK,iCAAiC,gBAAgB,IAAI,eAAe;EAClF,CAAC;EACD,KAAK,SAAS,GAAG,YAAY,SAAiB,YAAoB;GAChE,IAAI,YAAY,iBACd,KAAK,4BAA4B,OAAO,CAAC,CAAC,OAAO,QAAQ;IACvD,SAAO,MAAM,KAAK,wCAAwC;GAC5D,CAAC;QACI,IAAI,YAAY,eACrB,KAAK,mBAAmB,CAAC,CAAC,OAAO,QAAQ;IACvC,SAAO,MAAM,KAAK,8BAA8B;GAClD,CAAC;EAEL,CAAC;EACD,KAAK,SAAS,GAAG,UAAU,QAAe;GACxC,SAAO,MAAM,KAAK,kCAAkC,gBAAgB,IAAI,eAAe;EACzF,CAAC;CACH;;;;;;;CAQA,MAAc,qBAAoC;EAChD,SAAO,KAAK,8CAA8C,KAAK,WAAW,uBAAuB;EACjG,IAAI;GACF,MAAM,MAAM,MAAQ,KAAK,OAAO,IAAI,GAAK;GACzC,MAAM,iBAAiB,KAAK,IAAI,KAAK,OAAO,IAAI;GAChD,SAAO,KAAK,qCAAqC,KAAK,YAAY;EACpE,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,yBAAyB;GAC3C;EACF;CACF;;;;;;;CAQA,MAAM,oBAAoB,QAAkC;EAC1D,MAAM,UAAU,KAAK,UAAU;GAC7B,MAAM;GACN;GACA,gBAAgB,KAAK;EACvB,CAAC;EACD,MAAM,iBAAiB,cACrB,KAAK,MAAM,MAAM,QAAQ,uBAAuB,OAAO,CAAC;CAC5D;CAEA,MAAc,4BAA4B,SAAgC;EACxE,IAAI;EACJ,IAAI;GACF,SAAS,gCAAgC,MAAM,KAAK,MAAM,OAAO,CAAC;EACpE,SAAS,KAAK;GACZ,SAAO,MAAM,KAAK,yCAAyC;GAC3D;EACF;EAEA,IAAI,OAAO,mBAAmB,KAAK,YAAY;EAE/C,IAAI,OAAO,SAAS,UAAU;GAC5B,KAAK,MAAM,GAAG,YAAY,KAAK,UAAU;IACvC,IAAI,CAAC,QAAQ,OAAO,IAAI,OAAO,KAAK,GAAG;IAGvC,IAAI,CADa,KAAK,oBAAoB,IAAI,OAAO,KACzC,GAAG;IAEf,KAAK,YAAY,SAAS;KACxB,MAAM;KACN,OAAO,OAAO;KACd,MAAM,OAAO;KACb,UAAU,OAAO;IACnB,CAAC;GACH;GACA;EACF;EAKA,MAAM,KAAK,0BAA0B,MAAM;CAC7C;CAEA,MAAc,0BAA0B,KAAyC;EAC/E,IAAI,CAAC,KAAK,mBAAmB,IAAI,KAAK,GAGpC;EAMF,IAAI,CAAC,MAHa,KAAK,MAAM,wBAC3B,KAAK,YAAY,IAAI,OAAO,IAAI,UAAU,0BAC5C,GACU;GACR,SAAO,MACL,iCAAiC,IAAI,MAAM,GAAG,IAAI,SAAS,8BAC7D;GACA;EACF;EAEA,MAAM,KAAK,uBAAuB;GAChC,OAAO,IAAI;GACX,WAAW,IAAI;GACf,MAAM,IAAI;GACV,UAAU,IAAI;GACd,iBAAiB,IAAI;EACvB,CAAC;CACH;CAEA,MAAc,0BACZ,OACA,MACA,UACA,iBACe;EACf,MAAM,UAAU,kBAAkB;GAChC,MAAM;GACN;GACA;GACA;GACA,gBAAgB,KAAK;EACvB,CAAC;EAED,MAAM,kBAAkB,gBAAgB,IAAI,OAAO,aAAa;GAC9D,IAAI,aAAa,KAAK,YAAY;GAElC,MAAM,UAAU,gBAAgB;GAChC,IAAI;IACF,MAAM,iBAAiB,cAAc,KAAK,MAAM,MAAM,QAAQ,SAAS,OAAO,CAAC;GACjF,SAAS,KAAK;IACZ,SAAO,MAAM,KAAK,wBAAwB,QAAQ,oBAAoB;GACxE;EACF,CAAC;EAED,MAAM,QAAQ,IAAI,eAAe;CACnC;AACF;;;;;;;;;;;;;ACpoCA,SAAgB,QAAQ,GAAmB;CACzC,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,KAAK,EAAE,WAAW,CAAC;EAEnB,IAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,SAAU;CACxE;CACA,OAAO,MAAM;AACf;;;ACcA,MAAMG,WAASC,OAAW,MACxB,EAAE,MAAM,WAAW,GACnB,EAAE,OAAO,WAAW,+BAA+B,EAAE,CACvD;AAKA,SAAS,qBAAkD;CAEzD,MAAM,OAAO,IADI,IAAI,OAAO,KAAK,GACjB,CAAC,CAAC,SAAS,SAAS,KAAK;CAEzC,OAAO;EACL,KAAK,IAAI,IAAI,sCAFH,OAAO,QAAQ,SAEiC,OAAO,KAAK,GAAG;EACzE;CACF;AACF;AAOA,SAAS,eAAe,MAAqC;CAC3D,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,IAAI,OAAQ,WAAmB,QAAQ,aAAa,OAAO,KAAA;CAC3D,OAAO,CAAC,YAAY,KAAK;AAC3B;AASA,IAAM,2BAAN,MAA+B;CAC7B;CACA;CACA,0BAAkB,IAAI,IAA4B;CAClD,YAAoB;CACpB;CAEA,YAAY,IAAY,MAAyB;EAC/C,KAAK,KAAK;EACV,MAAM,QAAQ,mBAAmB;EACjC,KAAK,SAAS,IAAI,OAAO,cAAc,MAAM,GAAG,GAAG,EACjD,UAAU,eAAe,MAAM,IAAI,EACrC,CAAC;EAED,KAAK,OAAO,GAAG,YAAY,QAAwB;GACjD,MAAM,UAAU,KAAK,QAAQ,IAAI,IAAI,KAAK;GAC1C,IAAI,CAAC,SAAS;GAEd,KAAK,QAAQ,OAAO,IAAI,KAAK;GAC7B,IAAI,IAAI,IACN,QAAQ,QAAQ,IAAI,MAAM;QACrB;IACL,MAAM,MAAM,IAAI,MAAM,IAAI,MAAM,OAAO;IACvC,IAAI,IAAI,MAAM,OAAO,IAAI,QAAQ,IAAI,MAAM;IAC3C,QAAQ,OAAO,GAAG;GACpB;EACF,CAAC;EAED,KAAK,OAAO,GAAG,UAAU,QAAiB;GACxC,SAAO,MAAM,KAAK,WAAW,KAAK,GAAG,wBAAwB;GAC7D,MAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;GAC5D,KAAK,MAAM,GAAG,MAAM,KAAK,SAAS,EAAE,OAAO,CAAC;GAC5C,KAAK,QAAQ,MAAM;EACrB,CAAC;EAED,KAAK,eAAe,KAAK,KAAA,GAAyB,IAAI,CAAC,CAAC,WAAW,CAAE,CAAC;CACxE;CAEA,QAAuB;EACrB,OAAO,KAAK;CACd;CAEA,KAAQ,MAAe,SAA8B;EACnD,MAAM,QAAQ,KAAK;EACnB,OAAO,IAAI,SAAY,SAAS,WAAW;GACzC,KAAK,QAAQ,IAAI,OAAO;IACb;IACT;GACF,CAAC;GACD,MAAM,MAAqB;IAAE;IAAM;IAAO;GAAQ;GAClD,KAAK,OAAO,YAAY,GAAG;EAC7B,CAAC;CACH;CAEA,MAAM,WAA0B;EAC9B,IAAI;GACF,MAAM,KAAK,KAAA,IAA6B,IAAI;EAC9C,SAAS,GAAG,CAAe;EAC3B,MAAM,KAAK,OAAO,UAAU;CAC9B;AACF;;;;;;;;AAWA,IAAa,sBAAb,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA,gBAA+D;CAC/D,OAAwB,oBAAoB,OAAU;CAEtD,YAAY,QAAmC;EAC7C,KAAK,OAAO,OAAO;EACnB,KAAK,MAAM,IAAIC,OAAgB,EAAE,MAAM,KAAK,KAAK,GAAG,2BAA2B;EAC/E,KAAK,IAAI,WAAW;EAEpB,MAAM,aAAa,mBAAmB,OAAO,OAAO;EACpD,MAAM,0BAA0B,+BAA+B,QAAQ,UAAU;EAEjF,SAAO,KAAK,YAAY,WAAW,sBAAsB,eAAe,IAAI,KAAK,KAAK;EACtF,KAAK,UAAU,MAAM,KAAK,EAAE,QAAQ,WAAW,IAAI,GAAG,MACpD,IAAI,yBAAyB,IAAI,GAAG;GAClC,UAAU,IAAI;GACd,UAAU,OAAO;GACjB,SAAS,OAAO;GAChB,iBAAiB,OAAO;GACxB;EACF,CAAC,CACH;EAEA,KAAK,gBAAgB;CACvB;CAEA,MAAM,QAAuB;EAC3B,MAAM,QAAQ,IAAI,KAAK,QAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC;EACpD,SAAO,KAAK,qBAAqB,KAAK,MAAM;EAC5C,KAAK,gBAAgB,kBACb;GAAE,KAAK,gBAAgB,CAAC,CAAC,OAAO,QAAQF,SAAO,MAAM,KAAK,0BAA0B,CAAC;EAAG,GAC9F,oBAAoB,iBACtB;EACA,IAAI,KAAK,iBAAiB,OAAO,KAAK,cAAc,UAAU,YAC5D,KAAK,cAAc,MAAM;CAE7B;CAEA,MAAM,OAAsB;EAC1B,IAAI,KAAK,eAAe;GACtB,cAAc,KAAK,aAAa;GAChC,KAAK,gBAAgB;EACvB;EACA,KAAK,IAAI,MAAM;EACf,MAAM,QAAQ,IAAI,KAAK,QAAQ,KAAK,MAAM,EAAE,SAAS,CAAC,CAAC;EACvD,SAAO,KAAK,SAAS;CACvB;CAEA,MAAc,kBAAiC;EAC7C,MAAM,eAAe,MAAM,QAAQ,IACjC,KAAK,QAAQ,KAAK,MAAM,EAAE,KAAA,GAA+C,IAAI,CAAC,CAChF;EACA,IAAI,QAAuB;EAC3B,KAAK,MAAM,KAAK,cAAc;GAC5B,IAAI,MAAM,MAAM;GAChB,IAAI,UAAU,QAAQ,IAAI,OAAO,QAAQ;EAC3C;EAEA,IAAI,UAAU,MAAM;GAClB,MAAM,SAAS,MAAM,KAAK,QAAQ,EAAE,CAAE,KAAA,GAAuC,IAAI;GACjF,IAAI,UAAU,IAAI;GAClB,QAAQ;EACV;EAEA,MAAM,UAAU,MAAM,KAAK,QAAQ,EAAE,CAAE,KAAA,IAA4B,KAAK;EACxE,IAAI,UAAU,IACZ,SAAO,KAAK,UAAU,QAAQ,6BAA6B,OAAO;CAEtE;CAEA,cAAsB;EACpB,OAAO,KAAK,QAAQ;CACtB;CAEA,eAAe,OAAuB;EACpC,OAAO,QAAQ,KAAK,IAAI,KAAK,QAAQ;CACvC;CAEA,UAAkB,OAAyC;EACzD,OAAO,KAAK,QAAQ,KAAK,eAAe,KAAK;CAC/C;CAEA,kBAAgC;EAC9B,KAAK,IAAI,SAAS,wBAAwB,KAAK,qBAAqB,KAAK,IAAI,CAAC;EAC9E,KAAK,IAAI,SAAS,6BAA6B,KAAK,0BAA0B,KAAK,IAAI,CAAC;EACxF,KAAK,IAAI,SAAS,6BAA6B,KAAK,0BAA0B,KAAK,IAAI,CAAC;EACxF,KAAK,IAAI,SAAS,sBAAsB,KAAK,mBAAmB,KAAK,IAAI,CAAC;EAC1E,KAAK,IAAI,SAAS,0BAA0B,KAAK,uBAAuB,KAAK,IAAI,CAAC;EAClF,KAAK,IAAI,SAAS,2BAA2B,KAAK,wBAAwB,KAAK,IAAI,CAAC;CACtF;CAEA,UAAkB,QAAyB;EACzC,OAAQ,OAA6B;CACvC;CAEA,MAAc,qBAAqB,QAAsD;EACvF,MAAM,QAAQ,KAAK,UAAU,MAAM;EACnC,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAA,GAAmD,MAAM;CACxF;CAEA,MAAc,0BAA0B,QAA2D;EAGjG,MAAM,MAAM;EAEZ,MAAM,WAAW,aADF,IAAI,KAAK,GAAG,WAAW;GAAE;GAAO;GAAG,QAAQ,KAAK,UAAU,EAAE,KAAK;EAAE,EAC/C,IAAI,MAAM,EAAE,MAAM;EAErD,MAAM,UAAU,MAAM,QAAQ,IAC5B,CAAC,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,QAAQ,WAAW;GACrD,MAAM,UAAU,MAAM,OAAO,KAAA,GAE3B,MAAM,KAAK,MAAM,EAAE,CAAC,CACtB;GACA,OAAO,MAAM,KAAK,GAAG,OAAO;IAAE,OAAO,EAAE;IAAO,QAAQ,QAAQ;GAAI,EAAE;EACtE,CAAC,CACH;EAEA,MAAM,SAA0C,IAAI,MAAM,IAAI,MAAM;EACpE,KAAK,MAAM,EAAE,OAAO,QAAQ,OAAO,QAAQ,KAAK,GAC9C,OAAO,SAAS;EAElB,OAAO;CACT;CAEA,MAAc,0BAA0B,QAA2D;EACjG,MAAM,QAAQ,KAAK,UAAU,MAAM;EACnC,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAA,GAA6D,MAAM;CAClG;CAEA,MAAc,uBAAuB,QAAwD;EAC3F,MAAM,QAAQ,KAAK,UAAU,MAAM;EACnC,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAA,GAAuD,MAAM;CAC5F;CAEA,MAAc,wBAAwB,QAAyD;EAC7F,MAAM,MAAM;EAEZ,MAAM,WAAW,aADF,IAAI,KAAK,GAAG,WAAW;GAAE;GAAO;GAAG,QAAQ,KAAK,UAAU,EAAE,KAAK;EAAE,EAC/C,IAAI,MAAM,EAAE,MAAM;EAErD,MAAM,UAAU,MAAM,QAAQ,IAC5B,CAAC,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,QAAQ,WAAW;GACrD,MAAM,UAAU,MAAM,OAAO,KAAA,GAE3B,MAAM,KAAK,MAAM,EAAE,CAAC,CACtB;GACA,OAAO,MAAM,KAAK,GAAG,OAAO;IAAE,OAAO,EAAE;IAAO,QAAQ,QAAQ;GAAI,EAAE;EACtE,CAAC,CACH;EAEA,MAAM,SAAwC,IAAI,MAAM,IAAI,MAAM;EAClE,KAAK,MAAM,EAAE,OAAO,QAAQ,OAAO,QAAQ,KAAK,GAC9C,OAAO,SAAS;EAElB,OAAO;CACT;CAEA,MAAc,mBAAmB,QAAoD;EAMnF,OAAO,EAAE,WAAU,MAHK,QAAQ,IAC9B,KAAK,QAAQ,KAAK,MAAM,EAAE,KAAA,GAAiD,MAAM,CAAC,CACpF,EAAA,CAC6B,KAAK,EAAE;CACtC;AACF;AAEA,SAAS,mBAAmB,YAA6B;CACvD,IAAI,OAAO,eAAe,YAAY,aAAa,GAAG,OAAO,KAAK,MAAM,UAAU;CAElF,MAAM,SAAS,QAAQ,IAAI;CAC3B,IAAI,QAAQ;EACV,MAAM,IAAI,SAAS,QAAQ,EAAE;EAC7B,IAAI,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG,OAAO;CAC1C;CACA,OAAO;AACT;AAEA,SAAS,+BAA+B,QAAmC,SAAyB;CAClG,IAAI,OAAO,2BAA2B,OAAO,0BAA0B,GACrE,OAAO,KAAK,MAAM,OAAO,uBAAuB;CAElD,MAAM,QAAQ,OAAO,SAAS,mBAAmB,OAAO,SAAS,SAAS,UAAU,KAAK;CACzF,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC;AAChD"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { i as mapWriteSetToRaw } from "../../overlap-checker-Ct_HP_iB.js";
|
|
2
|
-
import { C as UpdateSubscriptionReadSetParamsSchema, _ as RegisterSubscriptionParamsSchema, a as initCacheLayer,
|
|
2
|
+
import { C as UpdateSubscriptionReadSetParamsSchema, _ as RegisterSubscriptionParamsSchema, a as initCacheLayer, h as RegisterSubscriptionBatchParamsSchema, i as createDatabase, p as InvalidateWritesetParamsSchema, t as SubscriptionWorker, x as UnregisterSubscriptionsParamsSchema, y as UnregisterSubscriptionParamsSchema } from "../../subscription-worker-CqoWp6zB.js";
|
|
3
3
|
import { parentPort } from "node:worker_threads";
|
|
4
4
|
//#region src/server/subscription-manager-worker-entry.ts
|
|
5
5
|
if (!parentPort) throw new Error("subscription-manager-worker-entry must run inside a worker_threads Worker");
|
|
6
6
|
const port = parentPort;
|
|
7
|
-
logger.child({ name: "SUBS_WRK_ENTRY" });
|
|
8
7
|
let worker = null;
|
|
9
8
|
let db = null;
|
|
10
9
|
async function init(payload) {
|