@supalive/core 1.20.1 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/ephemeral-BVeIVuYw.js +276 -0
  2. package/dist/ephemeral-BVeIVuYw.js.map +1 -0
  3. package/dist/ephemeral-CkidSG9y.js +295 -0
  4. package/dist/ephemeral-CkidSG9y.js.map +1 -0
  5. package/dist/index-CzW0TkpK.d.ts +2729 -0
  6. package/dist/index-CzW0TkpK.d.ts.map +1 -0
  7. package/dist/index-DB7_pv9P.d.ts +2746 -0
  8. package/dist/index-DB7_pv9P.d.ts.map +1 -0
  9. package/dist/logger-BJNCJkFh.js +463 -0
  10. package/dist/logger-BJNCJkFh.js.map +1 -0
  11. package/dist/logger-By9Pw-X2.js +454 -0
  12. package/dist/logger-By9Pw-X2.js.map +1 -0
  13. package/dist/mysql-Bvc_iWUy.d.ts +114 -0
  14. package/dist/mysql-Bvc_iWUy.d.ts.map +1 -0
  15. package/dist/mysql-CcIuIAt0.js +622 -0
  16. package/dist/mysql-CcIuIAt0.js.map +1 -0
  17. package/dist/mysql-DTK2f5oU.d.ts +114 -0
  18. package/dist/mysql-DTK2f5oU.d.ts.map +1 -0
  19. package/dist/one-shot-query-BNYLU19W.js +573 -0
  20. package/dist/one-shot-query-BNYLU19W.js.map +1 -0
  21. package/dist/one-shot-query-BSK7adwG.js +573 -0
  22. package/dist/one-shot-query-BSK7adwG.js.map +1 -0
  23. package/dist/one-shot-query-DAAHGTcI.js +573 -0
  24. package/dist/one-shot-query-DAAHGTcI.js.map +1 -0
  25. package/dist/overlap-checker-DseE1Lvh.js +300 -0
  26. package/dist/overlap-checker-DseE1Lvh.js.map +1 -0
  27. package/dist/postgres-Bp7SMb9D.d.ts +118 -0
  28. package/dist/postgres-Bp7SMb9D.d.ts.map +1 -0
  29. package/dist/postgres-C5CbPBcp.d.ts +118 -0
  30. package/dist/postgres-C5CbPBcp.d.ts.map +1 -0
  31. package/dist/postgres-CLhoKn_R.js +867 -0
  32. package/dist/postgres-CLhoKn_R.js.map +1 -0
  33. package/dist/realtime_db-CTGOrFuV.js +463 -0
  34. package/dist/realtime_db-CTGOrFuV.js.map +1 -0
  35. package/dist/src/client/index.d.ts +2 -2
  36. package/dist/src/client/index.js +6 -5
  37. package/dist/src/client/index.js.map +1 -1
  38. package/dist/src/exports/mysql.d.ts +1 -1
  39. package/dist/src/exports/mysql.js +1 -1
  40. package/dist/src/exports/postgres.d.ts +1 -1
  41. package/dist/src/exports/postgres.js +1 -1
  42. package/dist/src/exports/procedure.d.ts +2 -2
  43. package/dist/src/exports/procedure.d.ts.map +1 -1
  44. package/dist/src/exports/procedure.js +2 -1
  45. package/dist/src/exports/procedure.js.map +1 -1
  46. package/dist/src/exports/schema-sql.d.ts +1 -1
  47. package/dist/src/exports/server.d.ts +56 -5
  48. package/dist/src/exports/server.d.ts.map +1 -1
  49. package/dist/src/exports/server.js +168 -10
  50. package/dist/src/exports/server.js.map +1 -1
  51. package/dist/src/exports/sub-manager-worker-entry.js +1 -1
  52. package/dist/src/exports/types.d.ts +2 -2
  53. package/dist/src/exports/types.js +2 -2
  54. package/dist/sub-worker-dispatch-C20KTMfg.js +925 -0
  55. package/dist/sub-worker-dispatch-C20KTMfg.js.map +1 -0
  56. package/dist/sub-worker-dispatch-CQCuwZPm.js +925 -0
  57. package/dist/sub-worker-dispatch-CQCuwZPm.js.map +1 -0
  58. package/dist/sub-worker-dispatch-Dwrp3c40.js +925 -0
  59. package/dist/sub-worker-dispatch-Dwrp3c40.js.map +1 -0
  60. package/dist/types_client_rpc-CqUVHTwT.js +163 -0
  61. package/dist/types_client_rpc-CqUVHTwT.js.map +1 -0
  62. package/dist/types_db-DPMeNETt.js +173 -0
  63. package/dist/types_db-DPMeNETt.js.map +1 -0
  64. package/dist/types_server-hiwqDm4G.d.ts +737 -0
  65. package/dist/types_server-hiwqDm4G.d.ts.map +1 -0
  66. package/dist/types_server-xHmZ6baD.d.ts +737 -0
  67. package/dist/types_server-xHmZ6baD.d.ts.map +1 -0
  68. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sub-worker-dispatch-Dwrp3c40.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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"}
@@ -0,0 +1,163 @@
1
+ import z from "zod";
2
+ //#region src/db/types_client_rpc.ts
3
+ const AuthMessageSchema = z.object({
4
+ type: z.literal("auth"),
5
+ data: z.record(z.string(), z.any()).optional()
6
+ });
7
+ const CallMessageSchema = z.object({
8
+ type: z.literal("call"),
9
+ id: z.string().max(128),
10
+ procedure: z.string().max(256),
11
+ input: z.unknown(),
12
+ /**
13
+ * Client read high-water: the largest commit/snapshot ts the client has
14
+ * already observed (from a prior mutation's commitTs or a query's returned
15
+ * `ts`). Decimal string (bigint on the wire). The server routes a query's
16
+ * reads to the replica ONLY when the replica has applied at least this ts —
17
+ * giving read-your-writes + monotonic reads without a signed token. Omitted
18
+ * until the client has observed anything. The server clamps it to its own
19
+ * primary latest, so a client can't pin the primary forever with a bogus value.
20
+ */
21
+ minTs: z.string().max(40).optional(),
22
+ /**
23
+ * W3C `traceparent` for distributed tracing. When the client is running
24
+ * OpenTelemetry it injects the active context here; the server continues the
25
+ * trace so the operation span is a child of the client's. Ignored when tracing
26
+ * isn't enabled server-side. Optional and unauthenticated — purely diagnostic.
27
+ *
28
+ * Bounded: a valid W3C `traceparent` is 55 chars; the cap keeps an
29
+ * unauthenticated field from being used to ship a large blob into
30
+ * `propagation.extract`.
31
+ */
32
+ trace: z.string().max(256).optional()
33
+ });
34
+ const SubscribeMessageSchema = z.object({
35
+ type: z.literal("subscribe"),
36
+ id: z.string().max(128),
37
+ procedure: z.string().max(256),
38
+ input: z.unknown(),
39
+ /** W3C `traceparent` (bounded); see {@link CallMessageSchema}. */
40
+ trace: z.string().max(256).optional()
41
+ });
42
+ const UnsubscribeMessageSchema = z.object({
43
+ type: z.literal("unsubscribe"),
44
+ subId: z.string()
45
+ });
46
+ const ClientMessageSchema = z.union([
47
+ AuthMessageSchema,
48
+ CallMessageSchema,
49
+ SubscribeMessageSchema,
50
+ UnsubscribeMessageSchema
51
+ ]);
52
+ const ConnectedMessageSchema = z.object({
53
+ type: z.literal("connected"),
54
+ sessionId: z.string(),
55
+ build: z.number().int().nonnegative().optional()
56
+ });
57
+ const AuthSuccessMessageSchema = z.object({
58
+ type: z.literal("auth:success"),
59
+ userId: z.string(),
60
+ extra: z.object().optional(),
61
+ build: z.number().int().nonnegative().optional()
62
+ });
63
+ const AuthFailedMessageSchema = z.object({
64
+ type: z.literal("auth:failed"),
65
+ message: z.string(),
66
+ extra: z.record(z.string(), z.unknown()).optional(),
67
+ build: z.number().int().nonnegative().optional()
68
+ });
69
+ const ResponseMessageSchema = z.object({
70
+ type: z.literal("response"),
71
+ id: z.string(),
72
+ success: z.boolean(),
73
+ data: z.unknown().optional(),
74
+ error: z.string().optional(),
75
+ code: z.string().optional(),
76
+ /**
77
+ * Populated by the server only for a successful `subscribe` response.
78
+ * Carries the canonical, server-computed subscription id so the client
79
+ * can route subsequent `sub:update` / `sub:gone` messages without
80
+ * recomputing the hash locally (which would require the wasm runtime
81
+ * and would not reflect server-side per-procedure `queryIdentity`
82
+ * overrides).
83
+ */
84
+ subId: z.string().optional(),
85
+ /**
86
+ * Observed commit/snapshot ts for a successful `call`. Decimal string
87
+ * (bigint on the wire). For a query it's the snapshot the reads ran at; for a
88
+ * mutation it's the commitTs. The client folds it into its read high-water
89
+ * (see {@link CallMessageSchema.minTs}) so later reads preserve
90
+ * read-your-writes / monotonic reads. Absent for actions and error responses.
91
+ */
92
+ ts: z.string().optional(),
93
+ build: z.number().int().nonnegative().optional()
94
+ });
95
+ const SubscriptionUpdateMessageSchema = z.object({
96
+ type: z.literal("sub:update"),
97
+ subId: z.string(),
98
+ /**
99
+ * The query result. OPTIONAL because the encoder drops `undefined` (and maps
100
+ * `null` onto it — see `supaliveStringify`), so a query that legitimately
101
+ * resolves to nothing — `find()` on a row that was just deleted, an ephemeral
102
+ * key with no value yet — sends this key omitted entirely. A required
103
+ * `z.unknown()` makes that frame fail union validation client-side and the
104
+ * update is silently dropped, leaving the subscriber stuck on its last
105
+ * non-empty value.
106
+ */
107
+ data: z.unknown().optional(),
108
+ dataHash: z.string().optional(),
109
+ /**
110
+ * The primary snapshot ts this update's data reflects. Decimal string (bigint
111
+ * on the wire). The client folds it into its read high-water so a later
112
+ * one-shot query can't route to a replica that is behind what the live view
113
+ * already showed (cross-subscription monotonic reads). Present on server-side
114
+ * recomputes; omitted when the update is served straight from cache (no ts
115
+ * handy) — the client simply doesn't advance for those.
116
+ */
117
+ ts: z.string().optional(),
118
+ build: z.number().int().nonnegative().optional()
119
+ });
120
+ /**
121
+ * Notifies a client that a subscription is no longer tracked on the server
122
+ * and will not receive further updates. Sent when, during a sub-manager
123
+ * recovery cycle, the registration record could not be found (e.g. another
124
+ * app-server instance fully unregistered the sub while we were disconnected).
125
+ * The client may choose to re-subscribe or surface the state to the user.
126
+ */
127
+ const SubscriptionGoneMessageSchema = z.object({
128
+ type: z.literal("sub:gone"),
129
+ subId: z.string(),
130
+ reason: z.string().optional(),
131
+ build: z.number().int().nonnegative().optional()
132
+ });
133
+ /**
134
+ * Server-driven liveness signal. Sent when the channel has been idle for
135
+ * the configured app-ping interval (default 15s) and again after every
136
+ * outbound message. Pure no-op for the client: receiving any message
137
+ * (including this one) resets the client's inactivity watchdog. The client
138
+ * does NOT reply.
139
+ */
140
+ const ServerPingMessageSchema = z.object({
141
+ type: z.literal("ping"),
142
+ build: z.number().int().nonnegative().optional()
143
+ });
144
+ const ServerMessageSchema = z.union([
145
+ ConnectedMessageSchema,
146
+ AuthSuccessMessageSchema,
147
+ AuthFailedMessageSchema,
148
+ ResponseMessageSchema,
149
+ SubscriptionUpdateMessageSchema,
150
+ SubscriptionGoneMessageSchema,
151
+ ServerPingMessageSchema
152
+ ]);
153
+ var AuthenticationError = class extends Error {
154
+ extra;
155
+ constructor(message, extra = {}) {
156
+ super(message);
157
+ this.extra = extra;
158
+ }
159
+ };
160
+ //#endregion
161
+ export { CallMessageSchema as a, ResponseMessageSchema as c, SubscribeMessageSchema as d, SubscriptionGoneMessageSchema as f, AuthenticationError as i, ServerMessageSchema as l, UnsubscribeMessageSchema as m, AuthMessageSchema as n, ClientMessageSchema as o, SubscriptionUpdateMessageSchema as p, AuthSuccessMessageSchema as r, ConnectedMessageSchema as s, AuthFailedMessageSchema as t, ServerPingMessageSchema as u };
162
+
163
+ //# sourceMappingURL=types_client_rpc-CqUVHTwT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types_client_rpc-CqUVHTwT.js","names":[],"sources":["../src/db/types_client_rpc.ts"],"sourcesContent":["import z from \"zod\";\n\nexport interface ErrorMessage {\n type: \"error\";\n id?: string;\n error: string;\n code?: string;\n}\n\n// ─── WebSocket Message Schemas ────────────────────────────────────────────────\n// Zod schemas for runtime validation of WebSocket messages\n\nexport const AuthMessageSchema = z.object({\n type: z.literal(\"auth\"),\n data: z.record(z.string(), z.any()).optional(),\n});\n\nexport const CallMessageSchema = z.object({\n type: z.literal(\"call\"),\n // Bounded so a client can't push oversized correlation ids / procedure names\n // through the 100MB WS frame limit and burn CPU/memory before the procedure\n // lookup even runs. A request id is a short client token; a procedure name is\n // a symbol.\n id: z.string().max(128),\n procedure: z.string().max(256),\n input: z.unknown(),\n /**\n * Client read high-water: the largest commit/snapshot ts the client has\n * already observed (from a prior mutation's commitTs or a query's returned\n * `ts`). Decimal string (bigint on the wire). The server routes a query's\n * reads to the replica ONLY when the replica has applied at least this ts —\n * giving read-your-writes + monotonic reads without a signed token. Omitted\n * until the client has observed anything. The server clamps it to its own\n * primary latest, so a client can't pin the primary forever with a bogus value.\n */\n minTs: z.string().max(40).optional(),\n /**\n * W3C `traceparent` for distributed tracing. When the client is running\n * OpenTelemetry it injects the active context here; the server continues the\n * trace so the operation span is a child of the client's. Ignored when tracing\n * isn't enabled server-side. Optional and unauthenticated — purely diagnostic.\n *\n * Bounded: a valid W3C `traceparent` is 55 chars; the cap keeps an\n * unauthenticated field from being used to ship a large blob into\n * `propagation.extract`.\n */\n trace: z.string().max(256).optional(),\n});\n\nexport const SubscribeMessageSchema = z.object({\n type: z.literal(\"subscribe\"),\n id: z.string().max(128),\n procedure: z.string().max(256),\n input: z.unknown(),\n /** W3C `traceparent` (bounded); see {@link CallMessageSchema}. */\n trace: z.string().max(256).optional(),\n});\n\nexport const UnsubscribeMessageSchema = z.object({\n type: z.literal(\"unsubscribe\"),\n subId: z.string(),\n});\n\nexport const ClientMessageSchema = z.union([\n AuthMessageSchema,\n CallMessageSchema,\n SubscribeMessageSchema,\n UnsubscribeMessageSchema,\n]);\n\n// Infer TypeScript types from schemas\nexport type AuthMessage = z.infer<typeof AuthMessageSchema>;\nexport type CallMessage = z.infer<typeof CallMessageSchema>;\nexport type SubscribeMessage = z.infer<typeof SubscribeMessageSchema>;\nexport type UnsubscribeMessage = z.infer<typeof UnsubscribeMessageSchema>;\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>;\n\n// ─── Server Message Schemas ───────────────────────────────────────────────────\n\nexport const ConnectedMessageSchema = z.object({\n type: z.literal(\"connected\"),\n sessionId: z.string(),\n build: z.number().int().nonnegative().optional(),\n});\n\nexport const AuthSuccessMessageSchema = z.object({\n type: z.literal(\"auth:success\"),\n userId: z.string(),\n extra: z.object().optional(),\n build: z.number().int().nonnegative().optional(),\n});\n\nexport const AuthFailedMessageSchema = z.object({\n type: z.literal(\"auth:failed\"),\n message: z.string(),\n extra: z.record(z.string(), z.unknown()).optional(),\n build: z.number().int().nonnegative().optional(),\n});\n\nexport const ResponseMessageSchema = z.object({\n type: z.literal(\"response\"),\n id: z.string(),\n success: z.boolean(),\n data: z.unknown().optional(),\n error: z.string().optional(),\n code: z.string().optional(),\n /**\n * Populated by the server only for a successful `subscribe` response.\n * Carries the canonical, server-computed subscription id so the client\n * can route subsequent `sub:update` / `sub:gone` messages without\n * recomputing the hash locally (which would require the wasm runtime\n * and would not reflect server-side per-procedure `queryIdentity`\n * overrides).\n */\n subId: z.string().optional(),\n /**\n * Observed commit/snapshot ts for a successful `call`. Decimal string\n * (bigint on the wire). For a query it's the snapshot the reads ran at; for a\n * mutation it's the commitTs. The client folds it into its read high-water\n * (see {@link CallMessageSchema.minTs}) so later reads preserve\n * read-your-writes / monotonic reads. Absent for actions and error responses.\n */\n ts: z.string().optional(),\n build: z.number().int().nonnegative().optional(),\n});\n\nexport const SubscriptionUpdateMessageSchema = z.object({\n type: z.literal(\"sub:update\"),\n subId: z.string(),\n /**\n * The query result. OPTIONAL because the encoder drops `undefined` (and maps\n * `null` onto it — see `supaliveStringify`), so a query that legitimately\n * resolves to nothing — `find()` on a row that was just deleted, an ephemeral\n * key with no value yet — sends this key omitted entirely. A required\n * `z.unknown()` makes that frame fail union validation client-side and the\n * update is silently dropped, leaving the subscriber stuck on its last\n * non-empty value.\n */\n data: z.unknown().optional(),\n dataHash: z.string().optional(),\n /**\n * The primary snapshot ts this update's data reflects. Decimal string (bigint\n * on the wire). The client folds it into its read high-water so a later\n * one-shot query can't route to a replica that is behind what the live view\n * already showed (cross-subscription monotonic reads). Present on server-side\n * recomputes; omitted when the update is served straight from cache (no ts\n * handy) — the client simply doesn't advance for those.\n */\n ts: z.string().optional(),\n build: z.number().int().nonnegative().optional(),\n});\n\n/**\n * Notifies a client that a subscription is no longer tracked on the server\n * and will not receive further updates. Sent when, during a sub-manager\n * recovery cycle, the registration record could not be found (e.g. another\n * app-server instance fully unregistered the sub while we were disconnected).\n * The client may choose to re-subscribe or surface the state to the user.\n */\nexport const SubscriptionGoneMessageSchema = z.object({\n type: z.literal(\"sub:gone\"),\n subId: z.string(),\n reason: z.string().optional(),\n build: z.number().int().nonnegative().optional(),\n});\n\n/**\n * Server-driven liveness signal. Sent when the channel has been idle for\n * the configured app-ping interval (default 15s) and again after every\n * outbound message. Pure no-op for the client: receiving any message\n * (including this one) resets the client's inactivity watchdog. The client\n * does NOT reply.\n */\nexport const ServerPingMessageSchema = z.object({\n type: z.literal(\"ping\"),\n build: z.number().int().nonnegative().optional(),\n});\n\nexport const ServerMessageSchema = z.union([\n ConnectedMessageSchema,\n AuthSuccessMessageSchema,\n AuthFailedMessageSchema,\n ResponseMessageSchema,\n SubscriptionUpdateMessageSchema,\n SubscriptionGoneMessageSchema,\n ServerPingMessageSchema,\n]);\n\nexport type ConnectedMessage = z.infer<typeof ConnectedMessageSchema>;\nexport type AuthSuccessMessage = z.infer<typeof AuthSuccessMessageSchema>;\nexport type AuthFailedMessage = z.infer<typeof AuthFailedMessageSchema>;\nexport type ResponseMessage = z.infer<typeof ResponseMessageSchema>;\nexport type SubscriptionUpdateMessage = z.infer<typeof SubscriptionUpdateMessageSchema>;\nexport type SubscriptionGoneMessage = z.infer<typeof SubscriptionGoneMessageSchema>;\nexport type ServerPingMessage = z.infer<typeof ServerPingMessageSchema>;\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\nexport class AuthenticationError extends Error {\n constructor(\n message: string,\n public extra: Record<string, unknown> = {},\n ) {\n super(message);\n }\n}\n\n/**\n * Helper type to check if a procedure is internal.\n */\nexport type IsInternal<T> = T extends { internal: infer I } ? (I extends true ? true : false) : false;\n\nexport interface Context {\n requestId: string;\n /** Authenticated user info */\n user: {\n userId: string;\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ import { o as utf8Encode } from "./helper-zdJT5FUc.js";
2
+ import z from "zod";
3
+ //#region src/db/types_db.ts
4
+ const BigIntSchema = z.string().transform((val) => BigInt(val));
5
+ const CompareOperatorSchema = z.enum([
6
+ "=",
7
+ "!=",
8
+ ">",
9
+ ">=",
10
+ "<",
11
+ "<=",
12
+ "like",
13
+ "ilike",
14
+ "contains",
15
+ "icontains",
16
+ "between",
17
+ "in",
18
+ "jsonContains",
19
+ "jsonContainedBy",
20
+ "jsonHasKey",
21
+ "jsonArrayContains",
22
+ "isNull",
23
+ "isNotNull"
24
+ ]);
25
+ const PredicateSchema = z.lazy(() => z.union([
26
+ LeafPredicateSchema,
27
+ AndPredicateSchema,
28
+ OrPredicateSchema
29
+ ]));
30
+ const LeafPredicateSchema = z.object({
31
+ "kind": z.literal("leaf"),
32
+ column: z.string(),
33
+ operator: CompareOperatorSchema,
34
+ value: z.unknown(),
35
+ value2: z.unknown().optional(),
36
+ raw: z.boolean().optional(),
37
+ path: z.string().optional(),
38
+ mode: z.enum(["one", "all"]).optional()
39
+ });
40
+ const AndPredicateSchema = z.object({
41
+ kind: z.literal("and"),
42
+ children: z.array(PredicateSchema)
43
+ });
44
+ const OrPredicateSchema = z.object({
45
+ kind: z.literal("or"),
46
+ children: z.array(PredicateSchema)
47
+ });
48
+ const Uint8ArraySchema = z.any().transform((val) => {
49
+ if (val instanceof Uint8Array) return val;
50
+ return bytesFromJson(val);
51
+ });
52
+ function normalizeIdToBytes(id, fromHex = false) {
53
+ const data = (typeof id === "number" ? String(id) : id).replace(/-/g, "");
54
+ if (fromHex === true) {
55
+ const len = data.length >>> 1;
56
+ const out = new Uint8Array(len);
57
+ for (let i = 0; i < len; i++) out[i] = parseInt(data.substring(i * 2, i * 2 + 2), 16);
58
+ return out;
59
+ }
60
+ return utf8Encode(data);
61
+ }
62
+ function normalizeToBytes(value) {
63
+ try {
64
+ return bytesFromJson(value);
65
+ } catch (_) {
66
+ return /* @__PURE__ */ new Uint8Array(0);
67
+ }
68
+ }
69
+ function bytesFromJson(value) {
70
+ if (value instanceof Uint8Array) return value;
71
+ if (Array.isArray(value)) return Uint8Array.from(value);
72
+ if (value && typeof value === "object" && "type" in value && value.type === "Buffer" && "data" in value) return Uint8Array.from(value.data);
73
+ if (typeof value === "string") return utf8Encode(value);
74
+ if (typeof value === "number") return utf8Encode(String(value));
75
+ throw new Error("Invalid buffer value");
76
+ }
77
+ const PointReadSchema = z.object({
78
+ kind: z.literal("point"),
79
+ table: z.string(),
80
+ id: z.string(),
81
+ expectedCommitTs: BigIntSchema.nullable().optional()
82
+ });
83
+ const RawPointReadSchema = z.object({
84
+ kind: z.literal("point"),
85
+ tableId: Uint8ArraySchema,
86
+ id: Uint8ArraySchema,
87
+ table: z.string(),
88
+ expectedCommitTs: BigIntSchema.nullable().optional()
89
+ });
90
+ /**
91
+ * Query specification for a range read (get with filters).
92
+ * This is the query definition, not the result.
93
+ */
94
+ const RangeReadSchema = z.object({
95
+ kind: z.literal("range"),
96
+ table: z.string(),
97
+ predicate: PredicateSchema,
98
+ columns: z.array(z.string()),
99
+ orderBy: z.array(z.string()).nullable().optional(),
100
+ limit: z.number().nullable().optional(),
101
+ offset: z.number().nullable().optional()
102
+ });
103
+ const RawRangeReadSchema = z.object({
104
+ kind: z.literal("range"),
105
+ table: z.string(),
106
+ tableId: Uint8ArraySchema,
107
+ predicate: PredicateSchema,
108
+ columns: z.array(z.string()),
109
+ orderBy: z.array(z.string()).nullable().optional(),
110
+ limit: z.number().nullable().optional(),
111
+ offset: z.number().nullable().optional()
112
+ });
113
+ const ReadEntrySchema = z.union([PointReadSchema, RangeReadSchema]);
114
+ const RawReadEntrySchema = z.union([RawPointReadSchema, RawRangeReadSchema]);
115
+ const WriteOpSchema = z.enum([
116
+ "insert",
117
+ "update",
118
+ "delete"
119
+ ]);
120
+ const WriteEntrySchema = z.object({
121
+ table: z.string(),
122
+ tableId: Uint8ArraySchema,
123
+ id: z.string(),
124
+ op: WriteOpSchema,
125
+ data: z.record(z.string(), z.unknown()).optional(),
126
+ prevData: z.record(z.string(), z.unknown()).optional(),
127
+ expectedCommitTs: BigIntSchema.nullable().optional()
128
+ });
129
+ const QueryCacheMetadataSchema = z.object({
130
+ lastSnapshotTs: BigIntSchema,
131
+ version: z.string(),
132
+ readSet: z.array(ReadEntrySchema),
133
+ ephemeralKeys: z.array(z.string()).default([])
134
+ });
135
+ const QueryCacheEntrySchema = z.object({
136
+ lastSnapshotTs: BigIntSchema,
137
+ data: z.unknown(),
138
+ version: z.string()
139
+ });
140
+ const CachedPgMetadataSchema = z.record(z.string(), z.record(z.string(), z.string()));
141
+ const NO_RETRY = {
142
+ maxAttempts: 1,
143
+ backoff: "none"
144
+ };
145
+ const DEFAULT_RETRY = {
146
+ maxAttempts: 12,
147
+ backoff: "exponential",
148
+ delayMs: 15
149
+ };
150
+ var OccConflictError = class OccConflictError extends Error {
151
+ reason;
152
+ conflictDetail;
153
+ constructor(reason, conflictDetail) {
154
+ super(`OCC conflict: ${reason}`);
155
+ this.reason = reason;
156
+ this.conflictDetail = conflictDetail;
157
+ this.name = "OccConflictError";
158
+ }
159
+ static isOccConflictError(err) {
160
+ if (err instanceof OccConflictError || err && typeof err === "object" && err.name === "OccConflictError") return true;
161
+ return false;
162
+ }
163
+ };
164
+ var OccAbortError = class extends Error {
165
+ constructor(message) {
166
+ super(message);
167
+ this.name = "OccAbortError";
168
+ }
169
+ };
170
+ //#endregion
171
+ export { normalizeIdToBytes as C, bytesFromJson as S, RawRangeReadSchema as _, DEFAULT_RETRY as a, WriteEntrySchema as b, OccAbortError as c, PointReadSchema as d, PredicateSchema as f, RawPointReadSchema as g, RangeReadSchema as h, CompareOperatorSchema as i, OccConflictError as l, QueryCacheMetadataSchema as m, BigIntSchema as n, LeafPredicateSchema as o, QueryCacheEntrySchema as p, CachedPgMetadataSchema as r, NO_RETRY as s, AndPredicateSchema as t, OrPredicateSchema as u, RawReadEntrySchema as v, normalizeToBytes as w, WriteOpSchema as x, ReadEntrySchema as y };
172
+
173
+ //# sourceMappingURL=types_db-DPMeNETt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types_db-DPMeNETt.js","names":[],"sources":["../src/db/types_db.ts"],"sourcesContent":["import z from \"zod\";\nimport { utf8Encode } from \"./bytes\";\n\n/** Monotonically increasing integer from global_commit_ts sequence in Postgres */\nexport type CommitTs = bigint;\n\nexport const BigIntSchema = z.string().transform((val) => BigInt(val));\n\nexport const CompareOperatorSchema = z.enum([\n \"=\", \"!=\", \">\", \">=\", \"<\", \"<=\",\n \"like\", \"ilike\", \"contains\", \"icontains\",\n \"between\", \"in\",\n \"jsonContains\", \"jsonContainedBy\", \"jsonHasKey\", \"jsonArrayContains\",\n \"isNull\", \"isNotNull\"\n]);\nexport type CompareOperator = z.infer<typeof CompareOperatorSchema>;\n\n// Define the type first so z.lazy() can reference it\nexport type Predicate =\n | LeafPredicate\n | { kind: \"and\"; children: Predicate[] }\n | { kind: \"or\"; children: Predicate[] };\nexport const PredicateSchema: z.ZodType<Predicate> = z.lazy(() =>\n z.union([\n LeafPredicateSchema,\n AndPredicateSchema,\n OrPredicateSchema,\n ])\n);\n\nexport const LeafPredicateSchema = z.object({\n \"kind\": z.literal(\"leaf\"),\n column: z.string(),\n operator: CompareOperatorSchema,\n value: z.unknown(),\n value2: z.unknown().optional(),\n // For JSON ops: `true` means `value` is already in dialect-prepared form\n // (a path expression for jsonHasKey, a JSON string for the contains family)\n // and the SqlBuilder should pass it through without further escaping.\n raw: z.boolean().optional(),\n // For jsonContains/jsonContainedBy/jsonArrayContains: optional sub-path\n // into the target JSON value to scope the check (jsonpath syntax, e.g.\n // `$.\"a\".\"b\"`). Already compiled to a single string.\n path: z.string().optional(),\n // For jsonHasKey multi-path mode: when set, `value` is `string[]` (each\n // entry a jsonpath) and the predicate checks for existence of any (`one`)\n // or all (`all`) of them.\n mode: z.enum([\"one\", \"all\"]).optional(),\n});\nexport type LeafPredicate = z.infer<typeof LeafPredicateSchema>;\n\nexport const AndPredicateSchema = z.object({\n kind: z.literal(\"and\"),\n children: z.array(PredicateSchema),\n});\nexport type AndPredicate = z.infer<typeof AndPredicateSchema>;\n\nexport const OrPredicateSchema = z.object({\n kind: z.literal(\"or\"),\n children: z.array(PredicateSchema),\n});\nexport type OrPredicate = z.infer<typeof OrPredicateSchema>;\n\n/** Query specification - what was queried (used for cache key generation) */\nexport type QuerySpec = ReadEntry;\n\nconst Uint8ArraySchema = z.any().transform((val) => {\n if (val instanceof Uint8Array) return val;\n return bytesFromJson(val);\n});\n\nexport function normalizeIdToBytes(id: string | number, fromHex = false): Uint8Array {\n const idStr = typeof id === \"number\" ? String(id) : id;\n const data = idStr.replace(/-/g, \"\");\n if (fromHex === true) {\n const len = data.length >>> 1;\n const out = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n out[i] = parseInt(data.substring(i * 2, i * 2 + 2), 16);\n }\n return out;\n }\n return utf8Encode(data);\n}\n\nexport function normalizeToBytes(value: Uint8Array | unknown): Uint8Array {\n try {\n return bytesFromJson(value);\n } catch (_) {\n return new Uint8Array(0);\n }\n}\n\nexport function bytesFromJson(value: Uint8Array | unknown): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n if (Array.isArray(value)) {\n return Uint8Array.from(value as number[]);\n }\n if (value && typeof value === \"object\" && \"type\" in value && (value as any).type === \"Buffer\" && \"data\" in value) {\n return Uint8Array.from((value as any).data);\n }\n if (typeof value === \"string\") {\n return utf8Encode(value);\n }\n if (typeof value === \"number\") {\n return utf8Encode(String(value));\n }\n\n throw new Error(\"Invalid buffer value\");\n}\n\n/* \n * Query specification for a point read (find by ID).\n */\nexport const PointReadSchema = z.object({\n kind: z.literal(\"point\"),\n table: z.string(),\n id: z.string(),\n // No `tableId`: it is `utf8(table)` and is derived on demand (mapReadEntryToRaw\n // → tableIdOf), so it is never stored in the persisted/wire read set.\n // tableId: Uint8ArraySchema,\n\n // commit_ts of the row at read time. `null` = row didn't exist (so a later\n // insert is a conflict). `undefined` = the read was added synthetically and\n // OCC should fall back to the commit-log scan / general validator.\n expectedCommitTs: BigIntSchema.nullable().optional(),\n});\nexport type PointRead = z.infer<typeof PointReadSchema>;\n\nexport const RawPointReadSchema = z.object({\n kind: z.literal(\"point\"),\n tableId: Uint8ArraySchema,\n id: Uint8ArraySchema,\n table: z.string(),\n expectedCommitTs: BigIntSchema.nullable().optional(),\n});\nexport type RawPointRead = z.infer<typeof RawPointReadSchema>;\n\n/**\n * Query specification for a range read (get with filters).\n * This is the query definition, not the result.\n */\nexport const RangeReadSchema = z.object({\n kind: z.literal(\"range\"),\n table: z.string(),\n // No `tableId`: derived on demand from `table` (see PointReadSchema).\n // tableId: Uint8ArraySchema,\n\n predicate: PredicateSchema,\n columns: z.array(z.string()),\n orderBy: z.array(z.string()).nullable().optional(),\n limit: z.number().nullable().optional(),\n offset: z.number().nullable().optional(),\n});\nexport type RangeRead = z.infer<typeof RangeReadSchema>;\n\nexport const RawRangeReadSchema = z.object({\n kind: z.literal(\"range\"),\n table: z.string(),\n tableId: Uint8ArraySchema,\n predicate: PredicateSchema,\n columns: z.array(z.string()),\n orderBy: z.array(z.string()).nullable().optional(),\n limit: z.number().nullable().optional(),\n offset: z.number().nullable().optional(),\n});\nexport type RawRangeRead = z.infer<typeof RawRangeReadSchema>;\n\nexport const ReadEntrySchema = z.union([PointReadSchema, RangeReadSchema]);\nexport type ReadEntry = z.infer<typeof ReadEntrySchema>;\n\nexport const RawReadEntrySchema = z.union([RawPointReadSchema, RawRangeReadSchema]);\nexport type RawReadEntry = z.infer<typeof RawReadEntrySchema>;\n\nexport const WriteOpSchema = z.enum([\"insert\", \"update\", \"delete\"]);\nexport type WriteOp = z.infer<typeof WriteOpSchema>;\n\nexport const WriteEntrySchema = z.object({\n table: z.string(),\n tableId: Uint8ArraySchema,\n id: z.string(),\n op: WriteOpSchema,\n data: z.record(z.string(), z.unknown()).optional(),\n // Row state at beginTs, before this write. Populated server-side by\n // attachPrevDataToWriteSet for `update`/`delete`. Lets the invalidator\n // detect rows leaving a filtered result set (e.g. status changed from\n // matching to non-matching).\n prevData: z.record(z.string(), z.unknown()).optional(),\n // commit_ts the row had right before this write. Drives the CAS guard:\n // `UPDATE ... WHERE id=? AND commit_ts=expectedCommitTs`. Populated by\n // attachPrevDataToWriteSet or carried over from a prior find() in the\n // same txn. Undefined on inserts.\n expectedCommitTs: BigIntSchema.nullable().optional(),\n});\nexport type WriteEntry = z.infer<typeof WriteEntrySchema>;\n\nexport interface CommitLogEntry {\n id: Uint8Array;\n ts: bigint;\n tableId: Uint8Array;\n data: Uint8Array;\n\n _parsedData?: Record<string, unknown> | null\n}\n\nexport interface MutationResult<T> {\n result: T;\n writeSet: WriteEntry[];\n commitTs: CommitTs;\n}\n\nexport interface LiveResult<T> {\n ts: bigint,\n data: T;\n readSet: ReadEntry[];\n /**\n * Ephemeral keys (see db/ephemeral.ts) this computation read. A parallel\n * dependency set to `readSet`: these carry no snapshot ts and no commit log,\n * so they are invalidated by exact key match rather than predicate overlap,\n * and are deliberately kept OUT of `readSet` so the OCC validator and the\n * commit-log freshness scan never see them.\n */\n ephemeralKeys: string[];\n}\n\nexport const QueryCacheMetadataSchema = z.object({\n lastSnapshotTs: BigIntSchema,\n // Identity of the cached DATA this metadata describes = the data's compute\n // snapshot ts, stringified. Distinct from `lastSnapshotTs`, which the worker\n // can advance past the compute ts (a still-fresh entry gets a higher horizon\n // without recomputing). A reader trusts the cached data blob only when its\n // `version` matches this — so a data/metadata pair that diverged (torn read,\n // cross-process write race) is detected and recomputed instead of served stale.\n version: z.string(),\n readSet: z.array(ReadEntrySchema),\n // Ephemeral keys the cached computation read (see db/ephemeral.ts). Two jobs:\n // 1. Freshness — commit-log replay can say nothing about an ephemeral key,\n // so `evaluateCacheFreshness` treats a NON-EMPTY list as \"always stale\"\n // and forces a recompute. Without this, a query that reads ONLY ephemeral\n // state has an empty `readSet` and would be judged permanently fresh.\n // 2. Recovery — after a sub-manager restart, `register` rebuilds its\n // key → subId index from here, exactly as it rebuilds `readSet`.\n // Defaulted so cache entries written before this field existed still parse.\n ephemeralKeys: z.array(z.string()).default([]),\n});\nexport type QueryCacheMetadata = z.infer<typeof QueryCacheMetadataSchema>;\n\nexport const QueryCacheEntrySchema = z.object({\n lastSnapshotTs: BigIntSchema,\n data: z.unknown(),\n // Must equal the metadata's `version` for the data to be served (see above).\n version: z.string(),\n});\nexport type QueryCacheEntry = z.infer<typeof QueryCacheEntrySchema>;\n\nexport const CachedPgMetadataSchema = z.record(z.string(), z.record(z.string(), z.string()));\nexport type CachedPgMetadata = z.infer<typeof CachedPgMetadataSchema>;\n\nexport interface RetryConfig {\n maxAttempts: number;\n backoff: \"none\" | \"linear\" | \"exponential\";\n delayMs?: number;\n onConflict?: (attempt: number, error: OccConflictError) => void;\n}\n\nexport const NO_RETRY: RetryConfig = { maxAttempts: 1, backoff: \"none\" };\n\nexport const DEFAULT_RETRY: RetryConfig = {\n maxAttempts: 12,\n backoff: \"exponential\",\n delayMs: 15,\n};\n\nexport class OccConflictError extends Error {\n constructor(\n public readonly reason: string,\n public readonly conflictDetail?: unknown\n ) {\n super(`OCC conflict: ${reason}`);\n this.name = \"OccConflictError\";\n }\n\n static isOccConflictError(err: unknown): boolean {\n if (err instanceof OccConflictError ||\n (err && typeof err === \"object\" && (err as { name?: string }).name === \"OccConflictError\")) {\n return true;\n }\n return false;\n }\n}\n\nexport class OccAbortError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OccAbortError\";\n }\n}"],"mappings":"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"}