@supalive/core 1.20.1 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ephemeral-BVeIVuYw.js +276 -0
- package/dist/ephemeral-BVeIVuYw.js.map +1 -0
- package/dist/ephemeral-CkidSG9y.js +295 -0
- package/dist/ephemeral-CkidSG9y.js.map +1 -0
- package/dist/index-CzW0TkpK.d.ts +2729 -0
- package/dist/index-CzW0TkpK.d.ts.map +1 -0
- package/dist/index-DB7_pv9P.d.ts +2746 -0
- package/dist/index-DB7_pv9P.d.ts.map +1 -0
- package/dist/logger-BJNCJkFh.js +463 -0
- package/dist/logger-BJNCJkFh.js.map +1 -0
- package/dist/logger-By9Pw-X2.js +454 -0
- package/dist/logger-By9Pw-X2.js.map +1 -0
- package/dist/mysql-Bvc_iWUy.d.ts +114 -0
- package/dist/mysql-Bvc_iWUy.d.ts.map +1 -0
- package/dist/mysql-CcIuIAt0.js +622 -0
- package/dist/mysql-CcIuIAt0.js.map +1 -0
- package/dist/mysql-DTK2f5oU.d.ts +114 -0
- package/dist/mysql-DTK2f5oU.d.ts.map +1 -0
- package/dist/one-shot-query-BNYLU19W.js +573 -0
- package/dist/one-shot-query-BNYLU19W.js.map +1 -0
- package/dist/one-shot-query-BSK7adwG.js +573 -0
- package/dist/one-shot-query-BSK7adwG.js.map +1 -0
- package/dist/one-shot-query-DAAHGTcI.js +573 -0
- package/dist/one-shot-query-DAAHGTcI.js.map +1 -0
- package/dist/overlap-checker-DseE1Lvh.js +300 -0
- package/dist/overlap-checker-DseE1Lvh.js.map +1 -0
- package/dist/postgres-Bp7SMb9D.d.ts +118 -0
- package/dist/postgres-Bp7SMb9D.d.ts.map +1 -0
- package/dist/postgres-C5CbPBcp.d.ts +118 -0
- package/dist/postgres-C5CbPBcp.d.ts.map +1 -0
- package/dist/postgres-CLhoKn_R.js +867 -0
- package/dist/postgres-CLhoKn_R.js.map +1 -0
- package/dist/realtime_db-CTGOrFuV.js +463 -0
- package/dist/realtime_db-CTGOrFuV.js.map +1 -0
- package/dist/src/client/index.d.ts +2 -2
- package/dist/src/client/index.js +6 -5
- package/dist/src/client/index.js.map +1 -1
- package/dist/src/exports/mysql.d.ts +1 -1
- package/dist/src/exports/mysql.js +1 -1
- package/dist/src/exports/postgres.d.ts +1 -1
- package/dist/src/exports/postgres.js +1 -1
- package/dist/src/exports/procedure.d.ts +2 -2
- package/dist/src/exports/procedure.d.ts.map +1 -1
- package/dist/src/exports/procedure.js +2 -1
- package/dist/src/exports/procedure.js.map +1 -1
- package/dist/src/exports/schema-sql.d.ts +1 -1
- package/dist/src/exports/server.d.ts +56 -5
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +168 -10
- package/dist/src/exports/server.js.map +1 -1
- package/dist/src/exports/sub-manager-worker-entry.js +1 -1
- package/dist/src/exports/types.d.ts +2 -2
- package/dist/src/exports/types.js +2 -2
- package/dist/sub-worker-dispatch-C20KTMfg.js +925 -0
- package/dist/sub-worker-dispatch-C20KTMfg.js.map +1 -0
- package/dist/sub-worker-dispatch-CQCuwZPm.js +925 -0
- package/dist/sub-worker-dispatch-CQCuwZPm.js.map +1 -0
- package/dist/sub-worker-dispatch-Dwrp3c40.js +925 -0
- package/dist/sub-worker-dispatch-Dwrp3c40.js.map +1 -0
- package/dist/types_client_rpc-CqUVHTwT.js +163 -0
- package/dist/types_client_rpc-CqUVHTwT.js.map +1 -0
- package/dist/types_db-DPMeNETt.js +173 -0
- package/dist/types_db-DPMeNETt.js.map +1 -0
- package/dist/types_server-hiwqDm4G.d.ts +737 -0
- package/dist/types_server-hiwqDm4G.d.ts.map +1 -0
- package/dist/types_server-xHmZ6baD.d.ts +737 -0
- package/dist/types_server-xHmZ6baD.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sub-worker-dispatch-CQCuwZPm.js","names":["baseLogger","logger","baseLogger"],"sources":["../src/server/retention-watermark.ts","../src/server/sub-manager-types.ts","../src/db/cache.ts","../src/db/init_db.ts","../src/server/sub-worker.ts","../src/server/sub-worker-dispatch.ts"],"sourcesContent":["import type { Database } from \"../db/db\";\n\n/** Redis pub/sub channel the sub-manager publishes the retention watermark on\n * after each prune (and at startup). App-server instances subscribe and update\n * their in-memory {@link RetentionWatermark}. Payload is the watermark `ts` as a\n * string. */\nexport const RETENTION_WATERMARK_CHANNEL = \"supalive:retention_watermark\";\n\n/**\n * In-memory holder for the commit-log retention watermark: the oldest commit-log\n * ts still retained after prune (see {@link Database.getMinSnapshotTimestamp}).\n * Read on the hot path by the query-cache freshness check — a plain variable, no\n * per-check Redis/DB round trip. Loaded once at startup from the `metadata`\n * table and kept fresh out-of-band: the sub-manager broadcasts the new watermark\n * (in-process to its workers, over Redis pub/sub to app servers) each time it\n * prunes.\n *\n * `set` is monotonic — the watermark only ever moves forward (pruning never\n * un-deletes rows), so an out-of-order or stale update can never lower it, which\n * keeps the freshness guard on the safe side (an over-estimate only costs an\n * extra recompute; an under-estimate could serve stale data).\n *\n * Initial value is the -1n genesis (not 0), matching `getMinSnapshotTimestamp`\n * and the empty-DB snapshot ts — so before anything is pruned the guard never\n * rejects a genesis-era entry (whose snapshot is -1).\n */\nexport class RetentionWatermark {\n private ts: bigint = -1n;\n\n get(): bigint {\n return this.ts;\n }\n\n set(ts: bigint): void {\n if (ts > this.ts) this.ts = ts;\n }\n\n /** Seed from the persisted watermark (metadata table). Returns the current value. */\n async loadFrom(db: Pick<Database, \"getMinSnapshotTimestamp\">): Promise<bigint> {\n this.set(await db.getMinSnapshotTimestamp());\n return this.ts;\n }\n}\n","import z from \"zod\";\nimport { BigIntSchema, RawReadEntry, ReadEntry, ReadEntrySchema, WriteEntrySchema } from \"../db/types_db\";\n\n// ─── RPC Schemas ──────────────────────────────────────────────────────────────\n//\n// The wire protocol intentionally has NO `_clientVersion` / `_resync` /\n// `serverVersion` fields. Ordering of concurrent `updateSubscriptionReadSet`\n// calls is enforced by `lastSnapshotTs` (which is monotonic — it comes from\n// `Database.getLatestSnapshotTimestamp()`). Recovery after a sub-manager\n// restart is driven by the app server iterating its own\n// `serverSubscriptions` map and re-registering each one using params\n// persisted in Redis under `sl:reg:<subId>`.\n\nexport const RegisterSubscriptionParamsSchema = z.object({\n /**\n * Caller-computed FNV-1a routing key + identity for this subscription.\n * The sub-manager trusts these values rather than re-deriving them from\n * (queryName, args, queryIdentity); this avoids one stringify+SHA1 per\n * register RPC. Trust is fine: the sub-manager RPC channel is fully\n * internal.\n */\n subId: z.string(),\n cacheKey: z.string(),\n queryName: z.string(),\n args: z.unknown(),\n /**\n * Segmentation key used in the subId/cacheKey hash. Defaults to the\n * authenticated user's id (\"anonymous\" when none), unless the\n * procedure overrides it via `QueryConfig.queryIdentity`.\n */\n queryIdentity: z.string(),\n instanceName: z.string(),\n lastSnapshotTs: BigIntSchema,\n});\nexport type RegisterSubscriptionParams = z.infer<typeof RegisterSubscriptionParamsSchema>;\n\nexport const RegisterSubscriptionResultSchema = z.object({\n cacheKey: z.string(),\n recompute: z.boolean(),\n readSet: z.array(ReadEntrySchema),\n /**\n * Ephemeral dependencies recovered from the cache metadata, mirroring\n * `readSet`. Non-empty always implies `recompute: true` (ephemeral state has\n * no commit log, so freshness can never be proven) — it is returned so the\n * worker's key index survives a restart without waiting for the app server's\n * first `updateSubscriptionReadSet`.\n */\n ephemeralKeys: z.array(z.string()).default([]),\n // Version of the cached data the freshness check validated. The app server\n // serves the cached data blob only when its version matches this (guards a\n // data/metadata divergence). Absent when the worker had no metadata to\n // validate against (e.g. a plain recompute), in which case the server does\n // not serve from cache anyway.\n version: z.string().optional(),\n});\nexport type RegisterSubscriptionResult = z.infer<typeof RegisterSubscriptionResultSchema>;\n\nexport const RegisterSubscriptionBatchParamsSchema = z.array(RegisterSubscriptionParamsSchema);\nexport type RegisterSubscriptionBatchParams = z.infer<typeof RegisterSubscriptionBatchParamsSchema>;\n\nexport const RegisterSubscriptionBatchResultSchema = z.array(RegisterSubscriptionResultSchema);\nexport type RegisterSubscriptionBatchResult = z.infer<typeof RegisterSubscriptionBatchResultSchema>;\n\nexport const UpdateSubscriptionReadSetParamsSchema = z.object({\n cacheKey: z.string(),\n subId: z.string(),\n instanceName: z.string(),\n lastSnapshotTs: BigIntSchema,\n readSet: z.array(ReadEntrySchema),\n /** Ephemeral keys the recompute read (db/ephemeral.ts). Replaces the\n * previously tracked set for this subId, so keys a handler stopped reading\n * stop invalidating it. */\n ephemeralKeys: z.array(z.string()).default([]),\n});\nexport type UpdateSubscriptionReadSetParams = z.infer<typeof UpdateSubscriptionReadSetParamsSchema>;\n\n/**\n * `tracked: false` means the sub-manager has no record of this subId (e.g.\n * a restart wiped state and the caller hasn't re-registered yet). The app\n * server should treat this as a signal to register the sub before retrying.\n */\nexport const UpdateSubscriptionReadSetResultSchema = z.object({\n tracked: z.boolean(),\n});\nexport type UpdateSubscriptionReadSetResult = z.infer<typeof UpdateSubscriptionReadSetResultSchema>;\n\nexport const UnregisterSubscriptionParamsSchema = z.object({\n subId: z.string(),\n instanceName: z.string(),\n});\nexport type UnregisterSubscriptionParams = z.infer<typeof UnregisterSubscriptionParamsSchema>;\n\nexport const UnregisterSubscriptionsParamsSchema = z.array(UnregisterSubscriptionParamsSchema);\nexport type UnregisterSubscriptionsParams = z.infer<typeof UnregisterSubscriptionsParamsSchema>;\n\nexport const UnregisterSubscriptionResultSchema = z.object({ ok: z.boolean() });\nexport type UnregisterSubscriptionResult = z.infer<typeof UnregisterSubscriptionResultSchema>;\n\nexport const UnregisterSubscriptionsResultSchema = z.array(z.object({ ok: z.boolean() }));\nexport type UnregisterSubscriptionsResult = z.infer<typeof UnregisterSubscriptionsResultSchema>;\n\nexport const InvalidateWritesetParamsSchema = z.object({\n writeSet: z.array(WriteEntrySchema),\n});\nexport type InvalidateWritesetParams = z.infer<typeof InvalidateWritesetParamsSchema>;\n\n/**\n * Ephemeral counterpart of {@link InvalidateWritesetParamsSchema}. Carries just\n * the written keys — matching is an exact key lookup in the worker's index, with\n * no predicate to evaluate and no row images to compare, so there is nothing\n * else to send. The recompute re-reads the current value from Redis, so the\n * VALUE never travels on this path either.\n */\nexport const InvalidateEphemeralParamsSchema = z.object({\n keys: z.array(z.string()),\n /**\n * Monotonic write sequence (db/ephemeral.ts). Stands in for `commitTs` as\n * the cross-instance recompute-lock discriminator, so every instance racing\n * over the same write agrees on which write it is racing for.\n */\n version: z.string(),\n});\nexport type InvalidateEphemeralParams = z.infer<typeof InvalidateEphemeralParamsSchema>;\n\nexport const AffectedSubscriptionSchema = z.object({\n subId: z.string(),\n queryName: z.string(),\n args: z.unknown(),\n cacheKey: z.string(),\n /**\n * Every live instance that currently holds this subId. Routing rules:\n * • If the writer is in this list, it recomputes locally and publishes\n * an `update` to the rest (its own id is filtered by the publisher).\n * • Otherwise the writer broadcasts a `recompute-race` to every entry;\n * receivers race for a Redis lock and the winner recomputes.\n */\n notifyInstances: z.array(z.string()),\n});\nexport type AffectedSubscription = z.infer<typeof AffectedSubscriptionSchema>;\n\nexport const InvalidateWritesetResultSchema = z.object({\n affected: z.array(AffectedSubscriptionSchema),\n});\nexport type InvalidateWritesetResult = z.infer<typeof InvalidateWritesetResultSchema>;\n\nexport const InvalidateEphemeralResultSchema = InvalidateWritesetResultSchema;\nexport type InvalidateEphemeralResult = z.infer<typeof InvalidateEphemeralResultSchema>;\n\n// ─── Registration record persisted to Redis ──────────────────────────────────\n//\n// Stored at `sl:reg:<subId>` by the sub-manager when a brand-new subscription\n// is created, and deleted by the sub-manager when the last instance leaves.\n// The app server reads this on reconnect to know how to re-register its\n// known subIds.\n\nexport const RegistrationRecordSchema = z.object({\n queryName: z.string(),\n args: z.unknown(),\n queryIdentity: z.string(),\n cacheKey: z.string(),\n});\nexport type RegistrationRecord = z.infer<typeof RegistrationRecordSchema>;\n\nexport interface TrackedSubscription {\n subId: string;\n queryName: string;\n args: unknown;\n cacheKey: string;\n lastSnapshotTs: bigint;\n readSet: ReadEntry[];\n rawReadSet: RawReadEntry[];\n /** Ephemeral keys this subscription depends on (db/ephemeral.ts). Mirrored\n * into the worker's `key → subIds` index, which is what an ephemeral\n * invalidation actually scans. */\n ephemeralKeys: string[];\n /** All app server instances that currently hold this subscription. */\n instances: Set<string>;\n timeout: NodeJS.Timeout | null;\n}","import {\n CachedPgMetadata,\n CachedPgMetadataSchema,\n QueryCacheEntry, QueryCacheEntrySchema,\n QueryCacheMetadata, QueryCacheMetadataSchema\n} from \"./types_db\";\nimport { retry, handleAll, ExponentialBackoff } from \"cockatiel\";\nimport { RegistrationRecord, RegistrationRecordSchema } from \"../server/sub-manager-types\";\nimport { createHash } from \"node:crypto\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { supaliveStringify } from \"../helper\";\nimport Redis from \"ioredis\";\n\nconst logger = baseLogger.child({\n \"name\": \"CACHE_LAYER\",\n},\n { level: logLevelOf(\"SERVER_LOG_LEVEL\") },\n);\n\nexport const redisRetryPolicy = retry(handleAll, {\n maxAttempts: 3,\n backoff: new ExponentialBackoff({\n initialDelay: 5,\n maxDelay: 2300,\n exponent: 2,\n }),\n});\n\n// ─── Query-cache Lua scripts ────────────────────────────────────────────────\n//\n// The query cache is two string keys per query — data (`sl:cache:*`) and\n// metadata (`sl:meta:*`). They are written by several code paths across two\n// processes (app server + sub-worker), so a plain pipeline can't keep them\n// consistent (and on a non-atomic client a pipeline isn't even atomic). These\n// scripts make each write ATOMIC and MONOTONIC:\n// • atomic — data + metadata are set in one server-side script.\n// • monotonic (CAS) — a write is applied only when it is NOT older than what\n// is stored, so a slow/older writer can never clobber a newer entry.\n// \"Newer\" is compared on `version` (the data's compute-ts, a decimal string;\n// `tonumber` is exact for the real ts range and handles the -1 genesis value).\n// cjson is used ONLY to READ the stored comparand; the value written is always\n// the caller-serialized ARGV string, so we never hit cjson's empty-array\n// re-encode pitfall.\n\n/** Atomic one-shot recompute write: set data + metadata iff incoming version\n * (ARGV[3]) is not older than the stored one. KEYS: data, meta. */\nconst WRITE_DATA_AND_META = `\nlocal cur = redis.call('GET', KEYS[2])\nif cur then\n local ok, m = pcall(cjson.decode, cur)\n if ok and type(m) == 'table' and m.version and tonumber(ARGV[3]) < tonumber(m.version) then\n return 0\n end\nend\nredis.call('SET', KEYS[1], ARGV[1], 'EX', tonumber(ARGV[4]))\nredis.call('SET', KEYS[2], ARGV[2], 'EX', tonumber(ARGV[4]))\nreturn 1`;\n\n/** Data-only write (subscribe path): set data iff incoming version (ARGV[2]) is\n * not older than the stored metadata's version. KEYS: data, meta. */\nconst WRITE_DATA_ONLY = `\nlocal cur = redis.call('GET', KEYS[2])\nif cur then\n local ok, m = pcall(cjson.decode, cur)\n if ok and type(m) == 'table' and m.version and tonumber(ARGV[2]) < tonumber(m.version) then\n return 0\n end\nend\nredis.call('SET', KEYS[1], ARGV[1], 'EX', tonumber(ARGV[3]))\nreturn 1`;\n\n/** Metadata-only replace (subscription recompute): set meta iff incoming version\n * (ARGV[2]) is not older than the stored one. KEYS: meta. */\nconst REPLACE_META = `\nlocal cur = redis.call('GET', KEYS[1])\nif cur then\n local ok, m = pcall(cjson.decode, cur)\n if ok and type(m) == 'table' and m.version and tonumber(ARGV[2]) < tonumber(m.version) then\n return 0\n end\nend\nredis.call('SET', KEYS[1], ARGV[1], 'EX', tonumber(ARGV[3]))\nreturn 1`;\n\n/** Metadata-only horizon advance (still-fresh subscription): bump lastSnapshotTs\n * ONLY, without recomputing. Applies ARGV[1] iff the new horizon (ARGV[2]) is\n * strictly ahead of the stored one AND no newer computation (stored.version >\n * ARGV[3], the version the caller based its advance on) has landed meanwhile —\n * so an advance never reverts a fresh recompute. KEYS: meta. */\nconst ADVANCE_META = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, m = pcall(cjson.decode, cur)\nif not ok or type(m) ~= 'table' then return 0 end\nif tonumber(ARGV[2]) <= tonumber(m.lastSnapshotTs) then return 0 end\nif m.version and tonumber(m.version) > tonumber(ARGV[3]) then return 0 end\nredis.call('SET', KEYS[1], ARGV[1], 'EX', tonumber(ARGV[4]))\nreturn 1`;\n\nexport class CacheLayer {\n public redis: Redis;\n private readonly ttlSeconds: number;\n /** Cached EVALSHA digests keyed by Lua source (see evalScript). */\n private readonly scriptShaCache = new Map<string, string>();\n\n constructor(redis: Redis, ttlSeconds: number = 3600) {\n this.redis = redis;\n this.ttlSeconds = ttlSeconds;\n }\n\n /**\n * Run a Lua script via EVALSHA using the script's SHA1 (computed locally — the\n * same digest Redis keys scripts by, so no SCRIPT LOAD round-trip is needed on\n * the hot path). On NOSCRIPT (the script isn't in Redis's cache yet, e.g. first\n * use or after a Redis restart/failover) load it explicitly via SCRIPT LOAD and\n * re-run via EVALSHA — so the script ends up cached server-side and subsequent\n * EVALSHA calls hit. The runtime client is always ioredis (see initCacheLayer).\n */\n async evalScript(src: string, keys: string[], args: (string | number)[]): Promise<unknown> {\n const redis = this.redis;\n let sha = this.scriptShaCache.get(src);\n if (!sha) {\n sha = createHash(\"sha1\").update(src).digest(\"hex\");\n this.scriptShaCache.set(src, sha);\n }\n return redisRetryPolicy.execute(async () => {\n try {\n return await redis.evalsha(sha!, keys.length, ...keys, ...args);\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"NOSCRIPT\")) {\n // Not in Redis's script cache yet — load it, then execute by digest.\n const loadedSha = (await redis.call(\"SCRIPT\", \"LOAD\", src)) as string;\n this.scriptShaCache.set(src, loadedSha);\n return await redis.evalsha(loadedSha, keys.length, ...keys, ...args);\n }\n throw err;\n }\n });\n }\n\n /** One-shot recompute: atomically write data + metadata (CAS on version). */\n async setQueryCacheAndMetadataFor(\n cacheKey: string, data: unknown,\n metaData: QueryCacheMetadata,\n ttlSeconds?: number,\n ): Promise<void> {\n const ttl = ttlSeconds ?? this.ttlSeconds;\n const dataJson = supaliveStringify({ data, version: metaData.version, lastSnapshotTs: metaData.lastSnapshotTs });\n const metaJson = supaliveStringify(metaData);\n await this.evalScript(\n WRITE_DATA_AND_META,\n [this.getQueryCacheDataKey(cacheKey), this.getQeruyCacheMetaKey(cacheKey)],\n [dataJson, metaJson, metaData.version, String(ttl)],\n );\n }\n\n /** Subscribe path: write the data blob only (CAS against the metadata's\n * version). The metadata is written separately by the sub-worker; the\n * `version` links the two so a divergent pair is detected on read. Takes the\n * snapshot ts directly (rather than a pre-stringified version) so the entry\n * can carry `lastSnapshotTs` as its own field; `version` is derived from it,\n * preserving the `version === String(ts)` convention the CAS relies on. */\n async setQueryCacheFor(cacheKey: string, data: unknown, lastSnapshotTs: bigint, ttlSeconds?: number): Promise<void> {\n const ttl = ttlSeconds ?? this.ttlSeconds;\n const version = String(lastSnapshotTs);\n const dataJson = supaliveStringify({ data, version, lastSnapshotTs });\n await this.evalScript(\n WRITE_DATA_ONLY,\n [this.getQueryCacheDataKey(cacheKey), this.getQeruyCacheMetaKey(cacheKey)],\n [dataJson, version, String(ttl)],\n );\n }\n\n async getQueryCacheFor(cacheKey: string): Promise<QueryCacheEntry | null> {\n const dataRaw = await redisRetryPolicy.execute(() => this.redis.get(this.getQueryCacheDataKey(cacheKey)));\n\n if (!dataRaw) return null;\n\n try {\n const parsed = typeof dataRaw === \"string\" ? JSON.parse(dataRaw) : dataRaw;\n return QueryCacheEntrySchema.parse(parsed);\n } catch {\n return null;\n }\n }\n\n /** Subscription recompute: replace the whole metadata (CAS on version). */\n async replaceQueryCacheMetadata(cacheKey: string, metaData: QueryCacheMetadata, ttlSeconds?: number): Promise<void> {\n const ttl = ttlSeconds ?? this.ttlSeconds;\n const metaJson = supaliveStringify(metaData);\n await this.evalScript(\n REPLACE_META,\n [this.getQeruyCacheMetaKey(cacheKey)],\n [metaJson, metaData.version, String(ttl)],\n );\n }\n\n /** Still-fresh subscription: advance `lastSnapshotTs` only, preserving the\n * data's `version` + `readSet` (no recompute). Monotonic and non-clobbering\n * — see ADVANCE_META. `metaData` carries the new horizon plus the version +\n * readSet the caller read, which the script keeps unless a newer computation\n * has since landed. */\n async advanceQueryCacheMetadata(cacheKey: string, metaData: QueryCacheMetadata, ttlSeconds?: number): Promise<void> {\n const ttl = ttlSeconds ?? this.ttlSeconds;\n const metaJson = supaliveStringify(metaData);\n await this.evalScript(\n ADVANCE_META,\n [this.getQeruyCacheMetaKey(cacheKey)],\n [metaJson, String(metaData.lastSnapshotTs), metaData.version, String(ttl)],\n );\n }\n\n async getQueryCacheMetaData(cacheKey: string): Promise<QueryCacheMetadata | null> {\n const dataRaw = await redisRetryPolicy.execute(() => this.redis.get(this.getQeruyCacheMetaKey(cacheKey)));\n\n if (!dataRaw) return null;\n\n try {\n const parsed = typeof dataRaw === \"string\" ? JSON.parse(dataRaw) : dataRaw;\n return QueryCacheMetadataSchema.parse(parsed);\n } catch {\n return null;\n }\n }\n\n async persistRegistrationRecord(subId: string, record: RegistrationRecord): Promise<void> {\n const data = supaliveStringify(record);\n await redisRetryPolicy.execute(() => this.redis.set(this.getRegistrationKey(subId), data));\n }\n\n async deleteRegistrationRecord(subId: string): Promise<void> {\n await redisRetryPolicy.execute(() => this.redis.del(this.getRegistrationKey(subId)));\n }\n\n async deleteRegistrationsRecord(subIds: string[]): Promise<void> {\n if (subIds.length === 0) return;\n\n await redisRetryPolicy.execute(() =>\n this.redis.del(...subIds.map((subId) => this.getRegistrationKey(subId))),\n );\n }\n\n async fetchRegistrationRecordsBatch(\n subIds: string[],\n ): Promise<Map<string, RegistrationRecord | null>> {\n const result = new Map<string, RegistrationRecord | null>();\n const CHUNK_SIZE = 100;\n\n for (let i = 0; i < subIds.length; i += CHUNK_SIZE) {\n const chunk = subIds.slice(i, i + CHUNK_SIZE);\n const keys = chunk.map((id) => `sl:reg:${id}`);\n try {\n if (keys.length === 0) continue;\n\n const rawValues = await redisRetryPolicy.execute(() =>\n this.redis.mget(...keys),\n );\n\n for (let j = 0; j < chunk.length; j++) {\n const subId = chunk[j]!;\n const raw = rawValues[j];\n const rawJson = typeof raw === \"string\" ? JSON.parse(raw) : raw;\n\n if (!raw) {\n result.set(subId, null);\n } else {\n try {\n const parsed = RegistrationRecordSchema.parse(rawJson);\n result.set(subId, parsed);\n } catch {\n logger.error(`Failed to parse sl:reg:${subId} during batch recovery`);\n result.set(subId, null);\n }\n }\n }\n } catch (err) {\n logger.error(err, \"Failed to fetch registration records batch\");\n for (const subId of chunk) {\n result.set(subId, null);\n }\n }\n }\n return result;\n }\n\n async getCachedTableColumnTypes(key: string): Promise<CachedPgMetadata | null> {\n const dataRaw = await redisRetryPolicy.execute(() => this.redis.get(key));\n if (!dataRaw) return null;\n\n try {\n const parsed = typeof dataRaw === \"string\" ? JSON.parse(dataRaw) : dataRaw;\n return CachedPgMetadataSchema.parse(parsed);\n } catch {\n return null;\n }\n }\n\n async setCachedTableColumnsTypes(key: string, value: CachedPgMetadata): Promise<void> {\n await redisRetryPolicy.execute(() =>\n this.redis.set(key, supaliveStringify(value)),\n );\n }\n\n /**\n * Race participant: try to claim `lock:recompute:<subId>:<commitTs>`\n * with `SET NX EX`. The single Redis round-trip both arbitrates and\n * sets a TTL fallback in case the winner crashes mid-recompute.\n */\n async tryAcquireRecomputeLock(instanceId: string, subId: string, commitTs: string, ttl: number): Promise<boolean> {\n const key = `lock:recompute:${subId}:${commitTs}`;\n return redisRetryPolicy.execute(async () => {\n try {\n const res = await this.redis.set(key, instanceId, \"EX\", ttl, \"NX\");\n return res === \"OK\";\n } catch (err) {\n logger.error(err, `Failed to acquire recompute lock ${key}`);\n // Fail-closed: if Redis is misbehaving, don't run a recompute that\n // we can't coordinate. The next mutation will trigger another race.\n return false;\n }\n });\n }\n\n\n // ─── Ephemeral store primitives ───────────────────────────────────────────\n //\n // Raw string get/mget for the ephemeral KV (see db/ephemeral.ts). Writes go\n // through `evalScript` with the store's own Lua, so there is no setter here —\n // the store needs the write and its version bump to be one atomic call.\n\n /** Raw value at an ephemeral key, or `null` when absent/expired. */\n async ephemeralGet(key: string): Promise<string | null> {\n const raw = await redisRetryPolicy.execute(() => this.redis.get(key));\n return typeof raw === \"string\" ? raw : null;\n }\n\n /** Raw values for `keys`, positionally aligned; `null` for each absent key. */\n async ephemeralGetMany(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n const raw = await redisRetryPolicy.execute(() => this.redis.mget(...keys));\n return raw.map((v) => (typeof v === \"string\" ? v : null));\n }\n\n private getRegistrationKey(subId: string): string {\n return `sl:reg:${subId}`;\n }\n\n private getQueryCacheDataKey(cacheKey: string): string {\n return `sl:cache:${cacheKey}`;\n }\n\n private getQeruyCacheMetaKey(cacheKey: string): string {\n return `sl:meta:${cacheKey}`;\n }\n}\n\nexport type IORedisClient = Redis;","import { DatabaseConfig, UpstashConfig } from \"../config\";\nimport { logger } from \"../logger\";\nimport { CacheLayer, IORedisClient } from \"./cache\";\nimport { Database } from \"./db\";\nimport { MySqlDatabase } from \"./mysql\";\nimport { PgDatabase } from \"./postgres\";\nimport { SupaliveDb } from \"./realtime_db\";\nimport { EphemeralStore } from \"./ephemeral\";\nimport Redis from \"ioredis\";\n\n/**\n * Current core runtime-bootstrap version. Bump this whenever core needs a new\n * runtime database operation — a data seed, a backfill, or a schema change that\n * must be applied directly at runtime rather than through the migration\n * pipeline. Each increment adds a case to `Database.coreMigrationStatements`.\n *\n * The core *schema* (the `commit_logs`/`metadata` tables and the\n * `global_commit_ts` sequence/counter) is NOT versioned here — it comes from\n * the generated schema (`coreTablesSql` / `generateSchemaSql`), which must be\n * applied to the database BEFORE `initCore` runs.\n */\nexport const CORE_VERSION = 1n;\n\n/**\n * Bring the core runtime state up to {@link CORE_VERSION}. Idempotent and safe\n * to call from every process at startup: a lock-free version read short-circuits\n * the common \"already current\" case, and the actual migration runs under a\n * cross-process lock with a re-check so only one process applies it.\n *\n * Assumes the core schema already exists (apply the generated schema first);\n * the `metadata` table is where the version and seed rows live.\n */\nexport async function initCore(db: Database): Promise<void> {\n // Fast path: no lock, no transaction.\n const current = await db.getCoreVersion();\n if (current >= CORE_VERSION) return;\n\n await db.withCoreInitLock(async (ctx) => {\n // Re-check under the lock — another process may have migrated while we\n // were waiting to acquire it.\n const from = await ctx.getVersion();\n if (from >= CORE_VERSION) return;\n\n for (let v = from + 1n; v <= CORE_VERSION; v++) {\n const statements = db.coreMigrationStatements(v);\n logger.info(`Migrating core schema ${v - 1n} -> ${v} (${statements.length} statements)`);\n for (const sql of statements) {\n await ctx.exec(sql);\n }\n }\n\n await ctx.setVersion(CORE_VERSION);\n logger.info(`Core schema now at version ${CORE_VERSION}`);\n });\n}\n\n/**\n * Default and env override for the per-statement DB timeout. PG enforces\n * this via `statement_timeout` (server cancels), MySQL via the per-query\n * `timeout` option (driver issues KILL QUERY). Set 0 to disable.\n */\nexport const DB_QUERY_TIMEOUT_MS = Number(\n process.env.SUPALIVE_DB_QUERY_TIMEOUT_MS ?? 30_000,\n);\n\nexport type InitDbOptions = {\n DbConfig: DatabaseConfig;\n /**\n * Optional read-replica connection. When set, query/action reads default to\n * it (see the routing in one-shot-query.ts) while all writes, the OCC\n * commit-ts source, and cache-freshness scans stay on the primary. Omit to\n * run everything on the primary.\n */\n ReplicaDbConfig?: DatabaseConfig;\n CacheLayer: CacheLayer;\n forceBootstrapReload?: boolean;\n workerId?: number;\n /** Default TTL for `ctx.db.ephemeral` writes (see db/ephemeral.ts).\n * Defaults to 7 days. A server overrides this from its own config. */\n ephemeralTtlSeconds?: number;\n};\n\nexport async function initDatabase(options: InitDbOptions): Promise<SupaliveDb> {\n const impl = createDatabase(options.DbConfig, options.workerId);\n const replicaImpl = options.ReplicaDbConfig\n ? createDatabase(options.ReplicaDbConfig, options.workerId)\n : undefined;\n // Ephemeral state is Redis-resident, so the cache layer is all it needs.\n // Building it here means a db created outside a server (scripts, jobs run\n // via the internal caller) can read and write it too; a server additionally\n // registers the hook that turns writes into subscription invalidations.\n const ephemeral = new EphemeralStore(options.CacheLayer, {\n defaultTtlSeconds: options.ephemeralTtlSeconds,\n });\n const supaliveDb = SupaliveDb.create({ db: impl, ephemeral, replica: replicaImpl });\n // Seed core runtime state (metadata rows, MySQL counter seed) up to date.\n // The core schema (tables, indexes, global_commit_ts) is expected to already\n // exist from the applied schema. Runs on the PRIMARY only — the replica\n // receives seeds/schema via replication, and bootstrapDbTypes (write-apply\n // column-type cache) is a primary-only concern (the replica serves reads).\n await initCore(impl);\n await bootstrapDbTypes(supaliveDb, options.CacheLayer, options.forceBootstrapReload ?? false);\n return supaliveDb;\n}\n\nexport function createDatabase(config: DatabaseConfig, workerId?: number): PgDatabase | MySqlDatabase {\n const bindings: Record<string, any> = {\n \"db\": config.type,\n };\n if (workerId) {\n bindings[\"worker\"] = workerId;\n }\n\n if (config.type === \"postgres\") {\n return new PgDatabase(\n logger.child(bindings), {\n ...config,\n connectionString: config.connectionString,\n min: config.minConnections ?? 0,\n max: config.maxConnections ?? 20,\n connectionTimeoutMillis: config.connectionTimeoutMillis ?? 15_000,\n idleTimeoutMillis: config.idleTimeoutMillis ?? 30_000,\n // server-enforced per-statement timeout; pg cancels the running\n // query when it fires. 0 disables it.\n statement_timeout: DB_QUERY_TIMEOUT_MS > 0 ? DB_QUERY_TIMEOUT_MS : undefined,\n });\n } else {\n return new MySqlDatabase(\n logger.child(bindings), {\n // mysql2 parses a DSN from `uri` (NOT `connectionString`), and warns\n // on any unknown option — so map explicitly rather than spreading the\n // framework config, whose keys mysql2 doesn't recognize.\n uri: config.connectionString,\n supportBigNumbers: true,\n connectionLimit: config.maxConnections ?? 20,\n connectTimeout: config.connectionTimeoutMillis ?? 15_000,\n idleTimeout: config.idleTimeoutMillis ?? 30_000,\n // mysql2 pool: 0 = wait forever for a free conn (default). queueLimit\n // bounds the wait queue length so backpressure surfaces as an error\n // rather than unbounded growth.\n queueLimit: config.queueLimit ?? 0,\n // Store and return DATETIME values in UTC so timestamps behave like\n // Postgres TIMESTAMPTZ — the app works entirely in UTC Date objects\n // and the driver never applies a local-time offset.\n timezone: \"Z\",\n // driver-side per-statement timeout applied uniformly in\n // MySqlDatabase.query.\n queryTimeoutMs: DB_QUERY_TIMEOUT_MS,\n });\n }\n}\n\n/**\n * Eagerly load column types into the apply-path cache at server start.\n * In production, the result is cached in Redis under\n * `supalive:schema:types:${app_migration}` (the applied-migration marker from\n * the `metadata` table, via {@link Database.getAppMigration}) so other\n * instances starting cold can skip the pg_attribute round-trip — and a schema\n * migration bumps that marker, so the key changes and stale types aren't\n * reused. In any\n * other env we go straight to PG every time so local schema iteration\n * is never blocked by stale Redis state. Migrations should publish a\n * `schema:reload` message (see {@link publishSchemaReload}) after\n * applying DDL — every instance picks that up and re-bootstraps.\n */\nexport async function bootstrapDbTypes(db: SupaliveDb, cache: CacheLayer, forceReload = false): Promise<void> {\n // TODO: remove this check if we needs mysql column types\n if (db.impl.dbType === \"mysql\") {\n // currently we don't use mysql column type\n return;\n }\n\n const inProd = process.env.NODE_ENV === \"production\";\n const appMigration = await db.impl.getAppMigration();\n const key = `supalive:schema:types:${appMigration}`;\n try {\n await db.impl.bootstrapColumnTypes({\n cacheKey: key,\n redis: inProd && !forceReload ? cache : undefined,\n });\n logger.info({ columnsTypes: db.impl.columnTypes }, `Bootstrapped db column types (redis=${inProd}, key=${key})`);\n } catch (err) {\n logger.error(err, \"Failed to bootstrap db column types\");\n throw err;\n }\n}\n\nexport type InitCacheLayerOptions = {\n upstash: UpstashConfig;\n /* Default 3600 */\n cacheTtlSeconds?: number;\n /* Default 15_000 */\n connectionTimeout?: number;\n /* Default 1 */\n commandMaxRetries?: number;\n /* Default 5 */\n commandSubMaxRetries?: number;\n /* Default true */\n createSubClient?: boolean;\n};\n\nexport function initCacheLayer(config: InitCacheLayerOptions): { cacheLayer: CacheLayer, redisSubClient?: IORedisClient } {\n const redis = new Redis(config.upstash.redisUrl, {\n connectTimeout: config.connectionTimeout ?? 15_000,\n maxRetriesPerRequest: config.commandMaxRetries ?? 1,\n keepAlive: 10000,\n });\n \n const redisSub = config.createSubClient ? new Redis(config.upstash.redisUrl, {\n connectTimeout: config.connectionTimeout ?? 15_000,\n maxRetriesPerRequest: config.commandSubMaxRetries ?? 5,\n disableClientInfo: true,\n retryStrategy: (times) => {\n if (times > 5) return null;\n return Math.min(times * 200, 2000);\n }\n }) : undefined;\n\n return {\n cacheLayer: new CacheLayer(redis, config.cacheTtlSeconds ?? 3600),\n redisSubClient: redisSub\n };\n}\n\n","import type { Database } from \"../db/db\";\nimport type { RawReadEntry, ReadEntry } from \"../db/types_db\";\nimport type { CacheLayer } from \"../db/cache\";\nimport {\n checkWriteSetAffectsReadSet,\n evaluateCacheFreshness,\n mapReadSetToRaw,\n type RawWriteEntry,\n} from \"./overlap-checker\";\nimport {\n AffectedSubscription,\n RegisterSubscriptionParams,\n RegisterSubscriptionResult,\n RegistrationRecord,\n TrackedSubscription,\n UnregisterSubscriptionParams,\n UnregisterSubscriptionResult,\n UnregisterSubscriptionsParams,\n UnregisterSubscriptionsResult,\n UpdateSubscriptionReadSetParams,\n UpdateSubscriptionReadSetResult,\n} from \"./sub-manager-types\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { RetentionWatermark, RETENTION_WATERMARK_CHANNEL } from \"./retention-watermark\";\n\n// A logical worker that owns a disjoint slice of subscriptions. The dispatcher\n// routes each operation here by FNV-1a(subId) % numWorkers. Each worker is\n// independent: no shared state, no locks, no cross-worker coordination.\n// invalidate() is a pure function of the worker's own state + the writeSet.\n\nexport class SubscriptionWorker {\n readonly id: number;\n private subscriptions = new Map<string, TrackedSubscription>();\n /**\n * Reverse index `ephemeral key → subIds that read it`, kept in sync with each\n * tracked subscription's `ephemeralKeys`. This is the whole ephemeral\n * invalidation path: an exact Map lookup per written key, versus the full\n * slice scan + predicate evaluation the write-set path needs.\n */\n private ephemeralIndex = new Map<string, Set<string>>();\n private cache: CacheLayer;\n private db: Database;\n // In-memory retained commit-log watermark. Seeded at startup and moved forward on\n // prune; read on the register hot path instead of a per-check Redis GET.\n private retentionWatermark = new RetentionWatermark();\n public logger: ReturnType<typeof baseLogger.child>;\n\n constructor(id: number, cache: CacheLayer, db: Database) {\n this.id = id;\n this.cache = cache;\n this.db = db;\n this.logger = baseLogger.child(\n { name: \"SUBS_WRK\", wid: id },\n { level: logLevelOf(\"SUBS_MANAGER_SERVER_LOG_LEVEL\") },\n );\n }\n\n size(): number {\n return this.subscriptions.size;\n }\n\n /**\n * Point `subId`'s ephemeral index entries at exactly `keys`, adding the new\n * ones and dropping any the subscription no longer reads. Called on every\n * register/recompute, because a handler's key set is data-dependent — a\n * conditional branch can start or stop reading a key between runs, and a key\n * left behind would keep waking a subscription that no longer cares.\n */\n private reindexEphemeralKeys(subId: string, keys: string[]): void {\n const next = new Set(keys);\n\n const previous = this.subscriptions.get(subId)?.ephemeralKeys;\n if (previous) {\n for (const key of previous) {\n if (next.has(key)) continue;\n this.dropEphemeralIndexEntry(key, subId);\n }\n }\n\n for (const key of next) {\n let holders = this.ephemeralIndex.get(key);\n if (!holders) this.ephemeralIndex.set(key, (holders = new Set()));\n holders.add(subId);\n }\n }\n\n /** Remove every ephemeral index entry pointing at `subId`. */\n private clearEphemeralKeys(subId: string): void {\n const keys = this.subscriptions.get(subId)?.ephemeralKeys;\n if (!keys) return;\n for (const key of keys) this.dropEphemeralIndexEntry(key, subId);\n }\n\n /** Drop one (key, subId) pair, deleting the bucket once it empties so the\n * index doesn't accumulate empty Sets for churning keys. */\n private dropEphemeralIndexEntry(key: string, subId: string): void {\n const holders = this.ephemeralIndex.get(key);\n if (!holders) return;\n holders.delete(subId);\n if (holders.size === 0) this.ephemeralIndex.delete(key);\n }\n\n /** Number of distinct ephemeral keys currently indexed (diagnostics/tests). */\n ephemeralKeyCount(): number {\n return this.ephemeralIndex.size;\n }\n\n has(subId: string): boolean {\n return this.subscriptions.has(subId);\n }\n\n /**\n * Lowest `lastSnapshotTs` across this worker's subscriptions, or `null`\n * when there are none. Used by the dispatcher to derive a safe prune watermark.\n */\n minSubscriptionTs(): bigint | null {\n let min: bigint | null = null;\n for (const sub of this.subscriptions.values()) {\n if (min === null || sub.lastSnapshotTs < min) min = sub.lastSnapshotTs;\n }\n return min;\n }\n\n /**\n * Seed/repair the persisted retained watermark from the actual oldest retained\n * commit log (`min(ts)`), then load it into memory and broadcast it. Called\n * once at sub-manager startup — the `min(ts)` scan is off the hot path, and\n * it corrects `metadata.min_retained_ts` even for a DB that was pruned before\n * this feature existed. Returns the resulting watermark so the manager can\n * broadcast it to the other workers.\n */\n async refreshRetentionWatermark(): Promise<bigint> {\n const real = (await this.db.getMinCommitLogTs()) ?? 0n;\n await this.db.updateMinSnapshotTimestamp(real);\n // Read back so our in-memory copy matches the persisted (monotonic) value.\n const persisted = await this.db.getMinSnapshotTimestamp();\n this.retentionWatermark.set(persisted);\n await this.publishRetentionWatermark(persisted);\n return this.retentionWatermark.get();\n }\n\n /** Adopt a watermark broadcast by the manager (in-process from the pruning\n * worker). Monotonic, so an out-of-order message can't lower it. */\n setRetentionWatermark(ts: bigint): void {\n this.retentionWatermark.set(ts);\n }\n\n /**\n * Prune commit logs older than `minRetainedTs`. Advertises the new watermark FIRST —\n * persist it, bump our in-memory copy, and broadcast it over Redis — and only\n * then deletes, so no reader ever trusts a cache entry whose freshness scan\n * would include rows we're about to remove. Returns rows deleted.\n */\n async prune(minRetainedTs: bigint): Promise<bigint> {\n await this.db.updateMinSnapshotTimestamp(minRetainedTs);\n this.retentionWatermark.set(minRetainedTs);\n await this.publishRetentionWatermark(minRetainedTs);\n const deleted = await this.db.pruneCommitLogsBefore(minRetainedTs);\n return deleted;\n }\n\n /** Broadcast the watermark to app-server instances over Redis pub/sub. Workers in\n * this process get it in-process via {@link setRetentionWatermark}. */\n private async publishRetentionWatermark(ts: bigint): Promise<void> {\n try {\n await this.cache.redis.publish(RETENTION_WATERMARK_CHANNEL, ts.toString());\n } catch (err) {\n this.logger.error(err, \"failed to publish commit-log watermark\");\n }\n }\n\n async register(\n params: RegisterSubscriptionParams,\n ): Promise<RegisterSubscriptionResult> {\n const { subId, cacheKey, queryName, args, queryIdentity, instanceName, lastSnapshotTs } = params;\n this.logger.info(`registering subscription: ${subId} from ${instanceName}`);\n\n const existing = this.subscriptions.get(subId);\n\n if (existing && !existing.timeout) {\n existing.instances.add(instanceName);\n return {\n cacheKey,\n recompute: false,\n readSet: existing.readSet,\n ephemeralKeys: existing.ephemeralKeys,\n };\n }\n\n if (existing?.timeout) {\n this.logger.info(`reactivating subscription: ${subId} from: ${instanceName}`);\n clearTimeout(existing.timeout);\n existing.timeout = null;\n }\n\n const metadata = await this.cache.getQueryCacheMetaData(cacheKey);\n const freshness = await evaluateCacheFreshness(this.db, metadata, lastSnapshotTs, this.retentionWatermark.get());\n let needsRecompute = !freshness.fresh;\n const readSet: ReadEntry[] = freshness.readSet;\n const rawReadSet: RawReadEntry[] = freshness.rawReadSet;\n // Recovered from the cache metadata, so a sub-manager that just restarted\n // re-indexes the subscription's ephemeral keys immediately instead of being\n // blind to them until the app server's first updateSubscriptionReadSet.\n const ephemeralKeys: string[] = freshness.ephemeralKeys;\n\n if (!needsRecompute && metadata && lastSnapshotTs > metadata.lastSnapshotTs) {\n // Cache is still valid at a newer snapshot — advance the stored horizon\n // so the next freshness scan starts from here (and stays above the\n // watermark). Preserve the data's version + readSet; the CAS in\n // advanceQueryCacheMetadata won't revert a fresher recompute. `version`\n // stays `metadata.version` (it identifies the not-recomputed data blob and\n // doubles as the ADVANCE_META CAS guard) — never stamp the new snapshot.\n this.cache\n .advanceQueryCacheMetadata(cacheKey, { lastSnapshotTs, version: metadata.version, readSet, ephemeralKeys })\n .catch((err) => this.logger.error(err, `Failed to advance cache metadata for ${cacheKey}`));\n }\n\n let newSubscription: TrackedSubscription = {\n subId,\n queryName,\n args,\n cacheKey,\n lastSnapshotTs: lastSnapshotTs,\n readSet,\n rawReadSet,\n ephemeralKeys,\n instances: new Set([instanceName]),\n timeout: null,\n };\n\n const createdWhileChecking = this.subscriptions.get(subId);\n if (createdWhileChecking) {\n if (createdWhileChecking.timeout) {\n clearTimeout(createdWhileChecking.timeout);\n createdWhileChecking.timeout = null;\n }\n\n if (createdWhileChecking.lastSnapshotTs > lastSnapshotTs) {\n newSubscription = {\n ...newSubscription,\n ...createdWhileChecking,\n instances: new Set([...createdWhileChecking.instances, instanceName]),\n };\n needsRecompute = false;\n }\n }\n\n // Reindex BEFORE the map write: it diffs against the currently stored\n // subscription to find keys to drop.\n this.reindexEphemeralKeys(subId, newSubscription.ephemeralKeys);\n this.subscriptions.set(subId, newSubscription);\n\n this.persistRegistration(subId, { queryName, args, queryIdentity, cacheKey });\n\n // `version` lets the app server verify the cached data blob it serves\n // matches the metadata we validated (undefined when there was no metadata,\n // i.e. a plain recompute — the server won't serve from cache then anyway).\n return {\n cacheKey,\n recompute: needsRecompute,\n readSet,\n ephemeralKeys: newSubscription.ephemeralKeys,\n version: metadata?.version,\n };\n }\n\n async updateReadSet(\n params: UpdateSubscriptionReadSetParams,\n ): Promise<UpdateSubscriptionReadSetResult> {\n const { cacheKey, subId, instanceName, lastSnapshotTs, readSet, ephemeralKeys } = params;\n this.logger.info(`updating subscription readSet: ${subId} from ${instanceName}`);\n\n const existing = this.subscriptions.get(subId);\n if (!existing) {\n return { tracked: false };\n }\n\n if (lastSnapshotTs < existing.lastSnapshotTs) {\n return { tracked: true };\n }\n\n const rawReadSet = mapReadSetToRaw(readSet);\n this.reindexEphemeralKeys(subId, ephemeralKeys);\n this.subscriptions.set(subId, { ...existing, readSet, rawReadSet, ephemeralKeys, lastSnapshotTs });\n // Fresh recompute: version == the compute ts (== lastSnapshotTs here). The\n // server writes the matching data blob with this same version.\n await this.cache.replaceQueryCacheMetadata(cacheKey, {\n lastSnapshotTs,\n version: String(lastSnapshotTs),\n readSet,\n ephemeralKeys,\n });\n\n return { tracked: true };\n }\n\n async unregister(\n params: UnregisterSubscriptionParams,\n jitter: number = 0,\n ): Promise<UnregisterSubscriptionResult> {\n const { subId, instanceName } = params;\n this.logger.info(`unregistering subscription: ${subId} from: ${instanceName}`);\n\n const sub = this.subscriptions.get(subId);\n if (!sub) return { ok: true };\n\n if (sub.timeout) {\n clearTimeout(sub.timeout);\n }\n\n if (sub.instances.size > 1) {\n sub.instances.delete(instanceName);\n return { ok: true };\n }\n\n this.logger.debug(`scheduling unregister subscription: ${subId} from: ${instanceName} in 15s`);\n sub.timeout = setTimeout(async () => {\n sub.instances.delete(instanceName);\n\n if (sub.instances.size === 0) {\n this.logger.info(`unregistered subscription: ${subId} from: ${instanceName}`);\n this.clearEphemeralKeys(subId);\n this.subscriptions.delete(subId);\n await this.deleteRegistration(subId);\n }\n }, 15_000 + jitter);\n\n return { ok: true };\n }\n\n async unregisters(\n params: UnregisterSubscriptionsParams,\n ): Promise<UnregisterSubscriptionsResult> {\n this.logger.info({ subIds: params }, `scheduling unregistering subscriptions`);\n for (const param of params) {\n this.unregister(param, Math.floor(Math.random() * 100));\n }\n\n return params.map(() => ({ ok: true }));\n }\n\n /**\n * Synchronous, CPU-bound. Scans this worker's slice of subscriptions and\n * returns the ones affected by the writeSet.\n */\n invalidate(rawWriteSet: RawWriteEntry[]): AffectedSubscription[] {\n const affected: AffectedSubscription[] = [];\n\n for (const [, sub] of this.subscriptions) {\n if (sub.instances.size === 0) continue;\n if (!checkWriteSetAffectsReadSet(rawWriteSet, sub.rawReadSet)) continue;\n\n affected.push({\n subId: sub.subId,\n queryName: sub.queryName,\n args: sub.args,\n cacheKey: sub.cacheKey,\n notifyInstances: Array.from(sub.instances),\n });\n }\n\n return affected;\n }\n\n /**\n * Ephemeral counterpart of {@link invalidate}. Synchronous and CPU-bound like\n * its sibling, but resolved by exact key lookup rather than a slice scan: an\n * ephemeral key has no predicate to satisfy, so any subscription that READ the\n * key is affected by definition.\n *\n * A subscription reading several written keys appears once — the writes of one\n * mutation flush as a single batch, and one recompute already reflects all of\n * them.\n */\n invalidateEphemeral(keys: string[]): AffectedSubscription[] {\n const affected: AffectedSubscription[] = [];\n const seen = new Set<string>();\n\n for (const key of keys) {\n const holders = this.ephemeralIndex.get(key);\n if (!holders) continue;\n\n for (const subId of holders) {\n if (seen.has(subId)) continue;\n seen.add(subId);\n\n const sub = this.subscriptions.get(subId);\n if (!sub || sub.instances.size === 0) continue;\n\n affected.push({\n subId: sub.subId,\n queryName: sub.queryName,\n args: sub.args,\n cacheKey: sub.cacheKey,\n notifyInstances: Array.from(sub.instances),\n });\n }\n }\n\n return affected;\n }\n\n private async persistRegistration(subId: string, record: RegistrationRecord): Promise<void> {\n try {\n await this.cache.persistRegistrationRecord(subId, record);\n } catch (err) {\n this.logger.error(err, `[worker ${this.id}] Failed to persist registration for ${subId}`);\n }\n }\n\n private async deleteRegistration(subId: string): Promise<void> {\n try {\n await this.cache.deleteRegistrationRecord(subId);\n } catch (err) {\n this.logger.error(err, `[worker ${this.id}] Failed to delete registration for ${subId}`);\n }\n }\n}\n","import type { Database } from \"../db/db\";\nimport { mapWriteSetToRaw } from \"./overlap-checker\";\nimport {\n InvalidateEphemeralParamsSchema,\n InvalidateWritesetParamsSchema,\n RegisterSubscriptionBatchParamsSchema,\n RegisterSubscriptionParamsSchema,\n UnregisterSubscriptionParamsSchema,\n UnregisterSubscriptionsParamsSchema,\n UpdateSubscriptionReadSetParamsSchema,\n type InvalidateEphemeralParams,\n type InvalidateWritesetParams,\n type RegisterSubscriptionParams,\n type UnregisterSubscriptionParams,\n type UnregisterSubscriptionsParams,\n type UpdateSubscriptionReadSetParams,\n} from \"./sub-manager-types\";\nimport type { SubscriptionWorker } from \"./sub-worker\";\nimport { MsgType } from \"./sub-manager-worker-protocol\";\n\n/**\n * Dispatch a single worker operation against an already-constructed\n * {@link SubscriptionWorker} + {@link Database}. Shared by:\n *\n * • the worker-thread entry (`subscription-manager-worker-entry.ts`), where\n * the payload just crossed a `postMessage` boundary and is untrusted, so it\n * is Zod-parsed against the matching schema; and\n * • the in-process {@link InlineWorkerHandle}, where the payload is the\n * manager's own already-typed object (no thread hop, no structured-clone) —\n * `trusted: true` skips the redundant parse so the call goes straight\n * through to the worker method.\n *\n * `MsgType.Init` is NOT handled here: it constructs the worker/db, which is\n * bootstrap logic owned by each caller (the thread entry builds its own pool;\n * the inline handle is handed live instances).\n */\nexport async function dispatchWorkerMessage(\n worker: SubscriptionWorker,\n db: Database,\n type: MsgType,\n payload: unknown,\n opts?: { trusted?: boolean },\n): Promise<unknown> {\n const trusted = opts?.trusted ?? false;\n\n switch (type) {\n case MsgType.Register: {\n const params = trusted\n ? (payload as RegisterSubscriptionParams)\n : RegisterSubscriptionParamsSchema.parse(payload);\n return worker.register(params);\n }\n case MsgType.RegisterBatch: {\n const params = trusted\n ? (payload as RegisterSubscriptionParams[])\n : RegisterSubscriptionBatchParamsSchema.parse(payload);\n return Promise.all(params.map((p) => worker.register(p)));\n }\n case MsgType.UpdateReadSet: {\n const params = trusted\n ? (payload as UpdateSubscriptionReadSetParams)\n : UpdateSubscriptionReadSetParamsSchema.parse(payload);\n return worker.updateReadSet(params);\n }\n case MsgType.Unregister: {\n const params = trusted\n ? (payload as UnregisterSubscriptionParams)\n : UnregisterSubscriptionParamsSchema.parse(payload);\n return worker.unregister(params);\n }\n case MsgType.Unregisters: {\n const params = trusted\n ? (payload as UnregisterSubscriptionsParams)\n : UnregisterSubscriptionsParamsSchema.parse(payload);\n return worker.unregisters(params);\n }\n case MsgType.Invalidate: {\n const { writeSet } = trusted\n ? (payload as InvalidateWritesetParams)\n : InvalidateWritesetParamsSchema.parse(payload);\n return worker.invalidate(mapWriteSetToRaw(writeSet));\n }\n case MsgType.InvalidateEphemeral: {\n const { keys } = trusted\n ? (payload as InvalidateEphemeralParams)\n : InvalidateEphemeralParamsSchema.parse(payload);\n return worker.invalidateEphemeral(keys);\n }\n case MsgType.MinSubscriptionTs: {\n return worker.minSubscriptionTs();\n }\n case MsgType.LatestSnapshotTs: {\n return db.getLatestSnapshotTimestamp();\n }\n case MsgType.Prune: {\n return worker.prune(payload as bigint);\n }\n case MsgType.RefreshRetentionWatermark: {\n return worker.refreshRetentionWatermark();\n }\n case MsgType.SetRetentionWatermark: {\n worker.setRetentionWatermark(payload as bigint);\n return null;\n }\n case MsgType.Shutdown: {\n try { await db.close(); } catch (_) { /* ignore */ }\n return null;\n }\n case MsgType.Init:\n throw new Error(\"MsgType.Init must be handled by the caller (worker bootstrap)\");\n default:\n throw new Error(`Unknown MsgType: ${type satisfies never}`);\n }\n}\n"],"mappings":"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"}
|