@supalive/core 0.1.5 → 1.0.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 (80) hide show
  1. package/dist/bytes-AuEkyxeU.js +19 -0
  2. package/dist/bytes-AuEkyxeU.js.map +1 -0
  3. package/dist/codec-BHAmGEIQ.js +62 -0
  4. package/dist/codec-BHAmGEIQ.js.map +1 -0
  5. package/dist/{helper-zdJT5FUc.js → helper-wGbzz6zo.js} +2 -18
  6. package/dist/helper-wGbzz6zo.js.map +1 -0
  7. package/dist/{index-CTyXKcq_.d.ts → index-CaWMDqq0.d.ts} +21 -3
  8. package/dist/index-CaWMDqq0.d.ts.map +1 -0
  9. package/dist/index-DouKwYL6.d.ts +1754 -0
  10. package/dist/index-DouKwYL6.d.ts.map +1 -0
  11. package/dist/{mysql-C8SN443m.d.ts → mysql-Bxurg_PH.d.ts} +2 -2
  12. package/dist/{mysql-C8SN443m.d.ts.map → mysql-Bxurg_PH.d.ts.map} +1 -1
  13. package/dist/mysql-Cxd4Uq5p.d.ts +104 -0
  14. package/dist/mysql-Cxd4Uq5p.d.ts.map +1 -0
  15. package/dist/{mysql-DELSRrpY.js → mysql-bvnjYWvZ.js} +5 -4
  16. package/dist/{mysql-DELSRrpY.js.map → mysql-bvnjYWvZ.js.map} +1 -1
  17. package/dist/{overlap-checker-CVkbbuEQ.js → overlap-checker-Ct_HP_iB.js} +4 -4
  18. package/dist/{overlap-checker-CVkbbuEQ.js.map → overlap-checker-Ct_HP_iB.js.map} +1 -1
  19. package/dist/{postgres-DE-XyNAQ.js → postgres-B8u1YN8X.js} +5 -4
  20. package/dist/{postgres-DE-XyNAQ.js.map → postgres-B8u1YN8X.js.map} +1 -1
  21. package/dist/{postgres-D_gwCHx-.d.ts → postgres-CfT_aDx3.d.ts} +2 -2
  22. package/dist/{postgres-D_gwCHx-.d.ts.map → postgres-CfT_aDx3.d.ts.map} +1 -1
  23. package/dist/postgres-Ck-xt72U.d.ts +108 -0
  24. package/dist/postgres-Ck-xt72U.d.ts.map +1 -0
  25. package/dist/{schema-SKcIWHfo.js → procedure-DX87tyef.js} +2 -45
  26. package/dist/procedure-DX87tyef.js.map +1 -0
  27. package/dist/{query-CbQLjeME.js → query-Bh0RNxDz.js} +24 -64
  28. package/dist/query-Bh0RNxDz.js.map +1 -0
  29. package/dist/{realtime_db-D9p2X2PH.js → realtime_db-DOCgqbwi.js} +4 -3
  30. package/dist/{realtime_db-D9p2X2PH.js.map → realtime_db-DOCgqbwi.js.map} +1 -1
  31. package/dist/router-DlTYWpop.js.map +1 -1
  32. package/dist/schema-Dd7u0A1L.js +109 -0
  33. package/dist/schema-Dd7u0A1L.js.map +1 -0
  34. package/dist/src/client/index.d.ts +1 -1
  35. package/dist/src/client/index.js +6 -5
  36. package/dist/src/client/index.js.map +1 -1
  37. package/dist/src/exports/codegen.d.ts +38 -0
  38. package/dist/src/exports/codegen.d.ts.map +1 -0
  39. package/dist/src/exports/codegen.js +134 -0
  40. package/dist/src/exports/codegen.js.map +1 -0
  41. package/dist/src/exports/mysql.d.ts +1 -1
  42. package/dist/src/exports/mysql.js +1 -1
  43. package/dist/src/exports/postgres.d.ts +1 -1
  44. package/dist/src/exports/postgres.js +1 -1
  45. package/dist/src/exports/procedure.d.ts +2 -2
  46. package/dist/src/exports/procedure.js +5 -4
  47. package/dist/src/exports/schema-sql.d.ts +18 -0
  48. package/dist/src/exports/schema-sql.d.ts.map +1 -0
  49. package/dist/src/exports/schema-sql.js +161 -0
  50. package/dist/src/exports/schema-sql.js.map +1 -0
  51. package/dist/src/exports/server.d.ts +4 -4
  52. package/dist/src/exports/server.d.ts.map +1 -1
  53. package/dist/src/exports/server.js +4 -9
  54. package/dist/src/exports/server.js.map +1 -1
  55. package/dist/src/exports/subscription-manager-worker-entry.js +2 -3
  56. package/dist/src/exports/subscription-manager-worker-entry.js.map +1 -1
  57. package/dist/src/exports/types.d.ts +2 -2
  58. package/dist/src/exports/types.js +2 -2
  59. package/dist/subscription-worker-CqoWp6zB.js +511 -0
  60. package/dist/subscription-worker-CqoWp6zB.js.map +1 -0
  61. package/dist/subscription-worker-PtmJWEj1.js +511 -0
  62. package/dist/subscription-worker-PtmJWEj1.js.map +1 -0
  63. package/dist/{subscription-worker-BurdPO8d.js → subscription-worker-pahX0zxg.js} +7 -7
  64. package/dist/{subscription-worker-BurdPO8d.js.map → subscription-worker-pahX0zxg.js.map} +1 -1
  65. package/dist/{types_db-Ctf-ksoy.js → types_db-DXj_l9n-.js} +2 -2
  66. package/dist/{types_db-Ctf-ksoy.js.map → types_db-DXj_l9n-.js.map} +1 -1
  67. package/dist/types_server-BW1ys_SK.d.ts +216 -0
  68. package/dist/types_server-BW1ys_SK.d.ts.map +1 -0
  69. package/dist/types_server-Dk6o_B7N.js.map +1 -1
  70. package/dist/types_server-Dpsi0wpG.d.ts +216 -0
  71. package/dist/types_server-Dpsi0wpG.d.ts.map +1 -0
  72. package/dist/types_server-DzrIccyO.d.ts +216 -0
  73. package/dist/types_server-DzrIccyO.d.ts.map +1 -0
  74. package/dist/{types_server-Ch56y8L3.d.ts → types_server-TFo6SWDH.d.ts} +2 -2
  75. package/dist/{types_server-Ch56y8L3.d.ts.map → types_server-TFo6SWDH.d.ts.map} +1 -1
  76. package/package.json +7 -2
  77. package/dist/helper-zdJT5FUc.js.map +0 -1
  78. package/dist/index-CTyXKcq_.d.ts.map +0 -1
  79. package/dist/query-CbQLjeME.js.map +0 -1
  80. package/dist/schema-SKcIWHfo.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"subscription-worker-BurdPO8d.js","names":["uuidv4","logger","baseLogger","logger","Redis","baseLogger"],"sources":["../src/server/subscription-manager-types.ts","../src/logger.ts","../src/db/cache.ts","../src/db/init_db.ts","../src/server/subscription-worker.ts"],"sourcesContent":["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});\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});\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\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\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 /** All app server instances that currently hold this subscription. */\n instances: Set<string>;\n timeout: NodeJS.Timeout | null;\n}","import pino, { Level } from \"pino\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport pinoPretty from \"pino-pretty\";\nconst _ = pinoPretty; // Prevents tree shaking of pino-pretty\n\nexport const appInstanceId = `instance-${uuidv4().slice(0, 8)}`;\nexport const isProduction = process.env.ENV === \"prod\";\nexport const logger = pino({\n level: process.env.LOG_LEVEL || \"info\",\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: { paths: [\"user.password\"] },\n transport: isProduction ? undefined : {\n target: 'pino-pretty',\n options: {\n colorize: true\n },\n },\n serializers: {\n \"columnsTypes\": (map) => {\n if (map instanceof Map) return serializeMapValue(map);\n return map;\n },\n },\n base: {\n \"srv\": appInstanceId,\n },\n});\n\nexport function logLevelOf(name: string): Level {\n const defaultLogLevel = process.env.DEFAULT_LOG_LEVEL || \"info\";\n return (process.env[name]?.toLowerCase() || defaultLogLevel) as Level;\n}\n\nexport function serializeMapValue<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V> {\n const obj: Record<K, any> = {} as Record<K, any>;\n for (const [k, v] of map.entries()) {\n obj[k] = {} as V;\n if (v instanceof Map) {\n for (const [innerK, innerV] of v.entries()) {\n obj[k][innerK] = innerV;\n }\n } else {\n obj[k] = v;\n }\n }\n\n return obj as Record<K, V>;\n}","import type { Redis as UpstashRedis } from \"@upstash/redis\";\nimport {\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/subscription-manager-types\";\nimport Redis from \"ioredis\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { supaliveStringify } from \"../helper\";\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\nexport class CacheLayer {\n public redis: Redis | UpstashRedis;\n private readonly ttlSeconds: number;\n\n constructor(upstash: Redis | UpstashRedis, ttlSeconds: number = 3600) {\n this.redis = upstash;\n this.ttlSeconds = ttlSeconds;\n }\n\n async setQueryCacheFor(cacheKey: string, data: unknown, ttlSeconds?: number): Promise<void> {\n const combined = supaliveStringify({ data: data });\n await redisRetryPolicy.execute(async () => {\n await this.redis.setex(this.getQueryCacheDataKey(cacheKey), ttlSeconds ?? this.ttlSeconds, combined);\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 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 setQueryCacheMetadata(cacheKey: string, metaData: QueryCacheMetadata): Promise<void> {\n const metadata = supaliveStringify(metaData);\n await redisRetryPolicy.execute(() => this.redis.set(this.getQeruyCacheMetaKey(cacheKey), metadata));\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(this.getRegistrationKey.bind(this))),\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 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}","import { DatabaseConfig, SupaliveServerConfig, UpstashConfig } from \"../config\";\nimport { logger } from \"../logger\";\nimport { CacheLayer } from \"./cache\";\nimport { MySqlDatabase } from \"./mysql\";\nimport { PgDatabase } from \"./postgres\";\nimport { SupaliveDb } from \"./realtime_db\";\nimport Redis from \"ioredis\";\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 CacheLayer: CacheLayer;\n forceBootstrapReload?: boolean;\n workerId?: number;\n};\n\nexport async function initDatabase(options: InitDbOptions): Promise<SupaliveDb> {\n const impl = createDatabase(options.DbConfig, options.workerId);\n const supaliveDb = SupaliveDb.create({ db: 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 ...config,\n connectionLimit: config.maxConnections ?? 20,\n connectTimeout: config.connectionTimeoutMillis ?? 15_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 // 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:${SUPALIVE_SCHEMA_VERSION ?? \"v1\"}` so other\n * instances starting cold can skip the pg_attribute round-trip. 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 version = process.env.SUPALIVE_SCHEMA_VERSION ?? \"v1\";\n const key = `supalive:schema:types:${version}`;\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?: Redis } {\n const redis = new Redis(config.upstash.redisUrl, {\n connectTimeout: config.connectionTimeout ?? 15_000,\n maxRetriesPerRequest: config.commandMaxRetries ?? 1,\n keepAlive: 3000,\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 checkLogsAffectReadSet,\n checkWriteSetAffectsReadSet,\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 \"./subscription-manager-types\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\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 private cache: CacheLayer;\n private db: Database;\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 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 floor.\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 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 };\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 let needsRecompute = true;\n let readSet: ReadEntry[];\n let rawReadSet: RawReadEntry[];\n\n if (metadata) {\n readSet = metadata.readSet;\n rawReadSet = mapReadSetToRaw(readSet);\n\n const tableIds = Array.from(new Set(readSet.map((r) => r.tableId)));\n const logs = await this.db.getCommitLogsBetweenTs(metadata.lastSnapshotTs, lastSnapshotTs, tableIds);\n needsRecompute = checkLogsAffectReadSet(logs, rawReadSet);\n\n if (!needsRecompute) {\n if (lastSnapshotTs !== metadata.lastSnapshotTs) {\n this.cache\n .setQueryCacheMetadata(cacheKey, { lastSnapshotTs: lastSnapshotTs, readSet })\n .catch((err) => this.logger.error(err, `Failed to update cache metadata for ${cacheKey}`));\n }\n metadata.lastSnapshotTs = lastSnapshotTs;\n }\n } else {\n readSet = [];\n rawReadSet = [];\n }\n\n let newSubscription: TrackedSubscription = {\n subId,\n queryName,\n args,\n cacheKey,\n lastSnapshotTs: lastSnapshotTs,\n readSet,\n rawReadSet,\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 this.subscriptions.set(subId, newSubscription);\n\n this.persistRegistration(subId, { queryName, args, queryIdentity, cacheKey });\n\n return { cacheKey, recompute: needsRecompute, readSet };\n }\n\n async updateReadSet(\n params: UpdateSubscriptionReadSetParams,\n ): Promise<UpdateSubscriptionReadSetResult> {\n const { cacheKey, subId, instanceName, lastSnapshotTs, readSet } = 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.subscriptions.set(subId, { ...existing, readSet, rawReadSet, lastSnapshotTs });\n await this.cache.setQueryCacheMetadata(cacheKey, { lastSnapshotTs, readSet });\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.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 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"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,mCAAmC,EAAE,OAAO;;;;;;;;CAQrD,OAAO,EAAE,OAAO;CAChB,UAAU,EAAE,OAAO;CACnB,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,QAAQ;;;;;;CAMhB,eAAe,EAAE,OAAO;CACxB,cAAc,EAAE,OAAO;CACvB,gBAAgB;AACpB,CAAC;AAGD,MAAa,mCAAmC,EAAE,OAAO;CACrD,UAAU,EAAE,OAAO;CACnB,WAAW,EAAE,QAAQ;CACrB,SAAS,EAAE,MAAM,eAAe;AACpC,CAAC;AAGD,MAAa,wCAAwC,EAAE,MAAM,gCAAgC;AAG7F,MAAa,wCAAwC,EAAE,MAAM,gCAAgC;AAG7F,MAAa,wCAAwC,EAAE,OAAO;CAC1D,UAAU,EAAE,OAAO;CACnB,OAAO,EAAE,OAAO;CAChB,cAAc,EAAE,OAAO;CACvB,gBAAgB;CAChB,SAAS,EAAE,MAAM,eAAe;AACpC,CAAC;;;;;;AAQD,MAAa,wCAAwC,EAAE,OAAO,EAC1D,SAAS,EAAE,QAAQ,EACvB,CAAC;AAGD,MAAa,qCAAqC,EAAE,OAAO;CACvD,OAAO,EAAE,OAAO;CAChB,cAAc,EAAE,OAAO;AAC3B,CAAC;AAGD,MAAa,sCAAsC,EAAE,MAAM,kCAAkC;AAG7F,MAAa,qCAAqC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAG9E,MAAa,sCAAsC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;AAGxF,MAAa,iCAAiC,EAAE,OAAO,EACnD,UAAU,EAAE,MAAM,gBAAgB,EACtC,CAAC;AAGD,MAAa,6BAA6B,EAAE,OAAO;CAC/C,OAAO,EAAE,OAAO;CAChB,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,QAAQ;CAChB,UAAU,EAAE,OAAO;;;;;;;;CAQnB,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC;AACvC,CAAC;AAGD,MAAa,iCAAiC,EAAE,OAAO,EACnD,UAAU,EAAE,MAAM,0BAA0B,EAChD,CAAC;AAUD,MAAa,2BAA2B,EAAE,OAAO;CAC7C,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,QAAQ;CAChB,eAAe,EAAE,OAAO;CACxB,UAAU,EAAE,OAAO;AACvB,CAAC;;;ACpHD,MAAa,gBAAgB,YAAYA,GAAO,CAAC,CAAC,MAAM,GAAG,CAAC;AAC5D,MAAa,eAAe,QAAQ,IAAI,QAAQ;AAChD,MAAaC,WAAS,KAAK;CACvB,OAAO,QAAQ,IAAI,aAAa;CAChC,WAAW,KAAK,iBAAiB;CACjC,QAAQ,EAAE,OAAO,CAAC,eAAe,EAAE;CACnC,WAAW,eAAe,KAAA,IAAY;EAClC,QAAQ;EACR,SAAS,EACL,UAAU,KACd;CACJ;CACA,aAAa,EACT,iBAAiB,QAAQ;EACrB,IAAI,eAAe,KAAK,OAAO,kBAAkB,GAAG;EACpD,OAAO;CACX,EACJ;CACA,MAAM,EACF,OAAO,cACX;AACJ,CAAC;AAED,SAAgB,WAAW,MAAqB;CAC5C,MAAM,kBAAkB,QAAQ,IAAI,qBAAqB;CACzD,OAAQ,QAAQ,IAAI,KAAK,EAAE,YAAY,KAAK;AAChD;AAEA,SAAgB,kBAAyD,KAA8B;CACnG,MAAM,MAAsB,CAAC;CAC7B,KAAK,MAAM,CAAC,GAAG,MAAM,IAAI,QAAQ,GAAG;EAChC,IAAI,KAAK,CAAC;EACV,IAAI,aAAa,KACb,KAAK,MAAM,CAAC,QAAQ,WAAW,EAAE,QAAQ,GACrC,IAAI,EAAE,CAAC,UAAU;OAGrB,IAAI,KAAK;CAEjB;CAEA,OAAO;AACX;;;AClCA,MAAM,SAASC,SAAW,MAAM,EAC9B,QAAQ,cACV,GACE,EAAE,OAAO,WAAW,kBAAkB,EAAE,CAC1C;AAEA,MAAa,mBAAmB,MAAM,WAAW;CAC/C,aAAa;CACb,SAAS,IAAI,mBAAmB;EAC9B,cAAc;EACd,UAAU;EACV,UAAU;CACZ,CAAC;AACH,CAAC;AAED,IAAa,aAAb,MAAwB;CACtB;CACA;CAEA,YAAY,SAA+B,aAAqB,MAAM;EACpE,KAAK,QAAQ;EACb,KAAK,aAAa;CACpB;CAEA,MAAM,iBAAiB,UAAkB,MAAe,YAAoC;EAC1F,MAAM,WAAW,kBAAkB,EAAQ,KAAK,CAAC;EACjD,MAAM,iBAAiB,QAAQ,YAAY;GACzC,MAAM,KAAK,MAAM,MAAM,KAAK,qBAAqB,QAAQ,GAAG,cAAc,KAAK,YAAY,QAAQ;EACrG,CAAC;CACH;CAEA,MAAM,iBAAiB,UAAmD;EACxE,MAAM,UAAU,MAAM,iBAAiB,cAAc,KAAK,MAAM,IAAI,KAAK,qBAAqB,QAAQ,CAAC,CAAC;EAExG,IAAI,CAAC,SAAS,OAAO;EAErB,IAAI;GACF,MAAM,SAAS,OAAO,YAAY,WAAW,KAAK,MAAM,OAAO,IAAI;GACnE,OAAO,sBAAsB,MAAM,MAAM;EAC3C,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,sBAAsB,UAAsD;EAChF,MAAM,UAAU,MAAM,iBAAiB,cAAc,KAAK,MAAM,IAAI,KAAK,qBAAqB,QAAQ,CAAC,CAAC;EAExG,IAAI,CAAC,SAAS,OAAO;EAErB,IAAI;GACF,MAAM,SAAS,OAAO,YAAY,WAAW,KAAK,MAAM,OAAO,IAAI;GACnE,OAAO,yBAAyB,MAAM,MAAM;EAC9C,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,sBAAsB,UAAkB,UAA6C;EACzF,MAAM,WAAW,kBAAkB,QAAQ;EAC3C,MAAM,iBAAiB,cAAc,KAAK,MAAM,IAAI,KAAK,qBAAqB,QAAQ,GAAG,QAAQ,CAAC;CACpG;CAEA,MAAM,0BAA0B,OAAe,QAA2C;EACxF,MAAM,OAAO,kBAAkB,MAAM;EACrC,MAAM,iBAAiB,cAAc,KAAK,MAAM,IAAI,KAAK,mBAAmB,KAAK,GAAG,IAAI,CAAC;CAC3F;CAEA,MAAM,yBAAyB,OAA8B;EAC3D,MAAM,iBAAiB,cAAc,KAAK,MAAM,IAAI,KAAK,mBAAmB,KAAK,CAAC,CAAC;CACrF;CAEA,MAAM,0BAA0B,QAAiC;EAC/D,IAAI,OAAO,WAAW,GAAG;EAEzB,MAAM,iBAAiB,cACrB,KAAK,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,mBAAmB,KAAK,IAAI,CAAC,CAAC,CAClE;CACF;CAEA,MAAM,8BACJ,QACiD;EACjD,MAAM,yBAAS,IAAI,IAAuC;EAC1D,MAAM,aAAa;EAEnB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,YAAY;GAClD,MAAM,QAAQ,OAAO,MAAM,GAAG,IAAI,UAAU;GAC5C,MAAM,OAAO,MAAM,KAAK,OAAO,UAAU,IAAI;GAC7C,IAAI;IACF,IAAI,KAAK,WAAW,GAAG;IAEvB,MAAM,YAAY,MAAM,iBAAiB,cACvC,KAAK,MAAM,KAAK,GAAG,IAAI,CACzB;IAEA,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;KACrC,MAAM,QAAQ,MAAM;KACpB,MAAM,MAAM,UAAU;KACtB,MAAM,UAAU,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;KAE5D,IAAI,CAAC,KACH,OAAO,IAAI,OAAO,IAAI;UAEtB,IAAI;MACF,MAAM,SAAS,yBAAyB,MAAM,OAAO;MACrD,OAAO,IAAI,OAAO,MAAM;KAC1B,QAAQ;MACN,OAAO,MAAM,0BAA0B,MAAM,uBAAuB;MACpE,OAAO,IAAI,OAAO,IAAI;KACxB;IAEJ;GACF,SAAS,KAAK;IACZ,OAAO,MAAM,KAAK,4CAA4C;IAC9D,KAAK,MAAM,SAAS,OAClB,OAAO,IAAI,OAAO,IAAI;GAE1B;EACF;EACA,OAAO;CACT;CAEA,MAAM,0BAA0B,KAA+C;EAC7E,MAAM,UAAU,MAAM,iBAAiB,cAAc,KAAK,MAAM,IAAI,GAAG,CAAC;EACxE,IAAI,CAAC,SAAS,OAAO;EAErB,IAAI;GACF,MAAM,SAAS,OAAO,YAAY,WAAW,KAAK,MAAM,OAAO,IAAI;GACnE,OAAO,uBAAuB,MAAM,MAAM;EAC5C,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,2BAA2B,KAAa,OAAwC;EACpF,MAAM,iBAAiB,cACrB,KAAK,MAAM,IAAI,KAAK,kBAAkB,KAAK,CAAC,CAC9C;CACF;;;;;;CAOA,MAAM,wBAAwB,YAAoB,OAAe,UAAkB,KAA+B;EAChH,MAAM,MAAM,kBAAkB,MAAM,GAAG;EACvC,OAAO,iBAAiB,QAAQ,YAAY;GAC1C,IAAI;IAEF,OAAO,MADW,KAAK,MAAM,IAAI,KAAK,YAAY,MAAM,KAAK,IAAI,MAClD;GACjB,SAAS,KAAK;IACZ,OAAO,MAAM,KAAK,oCAAoC,KAAK;IAG3D,OAAO;GACT;EACF,CAAC;CACH;CAGA,mBAA2B,OAAuB;EAChD,OAAO,UAAU;CACnB;CAEA,qBAA6B,UAA0B;EACrD,OAAO,YAAY;CACrB;CAEA,qBAA6B,UAA0B;EACrD,OAAO,WAAW;CACpB;AACF;;;;;;;;AC5KA,MAAa,sBAAsB,OAC/B,QAAQ,IAAI,gCAAgC,GAChD;AASA,eAAsB,aAAa,SAA6C;CAC5E,MAAM,OAAO,eAAe,QAAQ,UAAU,QAAQ,QAAQ;CAC9D,MAAM,aAAa,WAAW,OAAO,EAAE,IAAI,KAAK,CAAC;CACjD,MAAM,iBAAiB,YAAY,QAAQ,YAAY,QAAQ,wBAAwB,KAAK;CAC5F,OAAO;AACX;AAEA,SAAgB,eAAe,QAAwB,UAA+C;CAClG,MAAM,WAAgC,EAClC,MAAM,OAAO,KACjB;CACA,IAAI,UACA,SAAS,YAAY;CAGzB,IAAI,OAAO,SAAS,YAChB,OAAO,IAAI,WACPC,SAAO,MAAM,QAAQ,GAAG;EACxB,GAAG;EACH,kBAAkB,OAAO;EACzB,KAAK,OAAO,kBAAkB;EAC9B,KAAK,OAAO,kBAAkB;EAC9B,yBAAyB,OAAO,2BAA2B;EAC3D,mBAAmB,OAAO,qBAAqB;EAG/C,mBAAmB,sBAAsB,IAAI,sBAAsB,KAAA;CACvE,CAAC;MAED,OAAO,IAAI,cACPA,SAAO,MAAM,QAAQ,GAAG;EACxB,GAAG;EACH,iBAAiB,OAAO,kBAAkB;EAC1C,gBAAgB,OAAO,2BAA2B;EAIlD,YAAY,OAAO,cAAc;EAGjC,gBAAgB;CACpB,CAAC;AAET;;;;;;;;;;;AAYA,eAAsB,iBAAiB,IAAgB,OAAmB,cAAc,OAAsB;CAE1G,IAAI,GAAG,KAAK,WAAW,SAEnB;CAGJ,MAAM,SAAS,QAAQ,IAAI,aAAa;CAExC,MAAM,MAAM,yBADI,QAAQ,IAAI,2BAA2B;CAEvD,IAAI;EACA,MAAM,GAAG,KAAK,qBAAqB;GAC/B,UAAU;GACV,OAAO,UAAU,CAAC,cAAc,QAAQ,KAAA;EAC5C,CAAC;EACD,SAAO,KAAK,EAAE,cAAc,GAAG,KAAK,YAAY,GAAG,uCAAuC,OAAO,QAAQ,IAAI,EAAE;CACnH,SAAS,KAAK;EACV,SAAO,MAAM,KAAK,qCAAqC;EACvD,MAAM;CACV;AACJ;AAgBA,SAAgB,eAAe,QAAmF;CAC9G,MAAM,QAAQ,IAAIC,QAAM,OAAO,QAAQ,UAAU;EAC7C,gBAAgB,OAAO,qBAAqB;EAC5C,sBAAsB,OAAO,qBAAqB;EAClD,WAAW;CACf,CAAC;CAED,MAAM,WAAW,OAAO,kBAAkB,IAAIA,QAAM,OAAO,QAAQ,UAAU;EACzE,gBAAgB,OAAO,qBAAqB;EAC5C,sBAAsB,OAAO,wBAAwB;EACrD,mBAAmB;EACnB,gBAAgB,UAAU;GACtB,IAAI,QAAQ,GAAG,OAAO;GACtB,OAAO,KAAK,IAAI,QAAQ,KAAK,GAAI;EACrC;CACJ,CAAC,IAAI,KAAA;CAEL,OAAO;EACH,YAAY,IAAI,WAAW,OAAO,OAAO,mBAAmB,IAAI;EAChE,gBAAgB;CACpB;AACJ;;;AC3GA,IAAa,qBAAb,MAAgC;CAC9B;CACA,gCAAwB,IAAI,IAAiC;CAC7D;CACA;CACA;CAEA,YAAY,IAAY,OAAmB,IAAc;EACvD,KAAK,KAAK;EACV,KAAK,QAAQ;EACb,KAAK,KAAK;EACV,KAAK,SAASC,SAAW,MACvB;GAAE,MAAM;GAAY,KAAK;EAAG,GAC5B,EAAE,OAAO,WAAW,+BAA+B,EAAE,CACvD;CACF;CAEA,OAAe;EACb,OAAO,KAAK,cAAc;CAC5B;CAEA,IAAI,OAAwB;EAC1B,OAAO,KAAK,cAAc,IAAI,KAAK;CACrC;;;;;CAMA,oBAAmC;EACjC,IAAI,MAAqB;EACzB,KAAK,MAAM,OAAO,KAAK,cAAc,OAAO,GAC1C,IAAI,QAAQ,QAAQ,IAAI,iBAAiB,KAAK,MAAM,IAAI;EAE1D,OAAO;CACT;CAEA,MAAM,SACJ,QACqC;EACrC,MAAM,EAAE,OAAO,UAAU,WAAW,MAAM,eAAe,cAAc,mBAAmB;EAC1F,KAAK,OAAO,KAAK,6BAA6B,MAAM,QAAQ,cAAc;EAE1E,MAAM,WAAW,KAAK,cAAc,IAAI,KAAK;EAE7C,IAAI,YAAY,CAAC,SAAS,SAAS;GACjC,SAAS,UAAU,IAAI,YAAY;GACnC,OAAO;IACL;IACA,WAAW;IACX,SAAS,SAAS;GACpB;EACF;EAEA,IAAI,UAAU,SAAS;GACrB,KAAK,OAAO,KAAK,8BAA8B,MAAM,SAAS,cAAc;GAC5E,aAAa,SAAS,OAAO;GAC7B,SAAS,UAAU;EACrB;EAEA,MAAM,WAAW,MAAM,KAAK,MAAM,sBAAsB,QAAQ;EAChE,IAAI,iBAAiB;EACrB,IAAI;EACJ,IAAI;EAEJ,IAAI,UAAU;GACZ,UAAU,SAAS;GACnB,aAAa,gBAAgB,OAAO;GAEpC,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC;GAElE,iBAAiB,uBAAuB,MADrB,KAAK,GAAG,uBAAuB,SAAS,gBAAgB,gBAAgB,QAAQ,GACrD,UAAU;GAExD,IAAI,CAAC,gBAAgB;IACnB,IAAI,mBAAmB,SAAS,gBAC9B,KAAK,MACF,sBAAsB,UAAU;KAAkB;KAAgB;IAAQ,CAAC,CAAC,CAC5E,OAAO,QAAQ,KAAK,OAAO,MAAM,KAAK,uCAAuC,UAAU,CAAC;IAE7F,SAAS,iBAAiB;GAC5B;EACF,OAAO;GACL,UAAU,CAAC;GACX,aAAa,CAAC;EAChB;EAEA,IAAI,kBAAuC;GACzC;GACA;GACA;GACA;GACgB;GAChB;GACA;GACA,2BAAW,IAAI,IAAI,CAAC,YAAY,CAAC;GACjC,SAAS;EACX;EAEA,MAAM,uBAAuB,KAAK,cAAc,IAAI,KAAK;EACzD,IAAI,sBAAsB;GACxB,IAAI,qBAAqB,SAAS;IAChC,aAAa,qBAAqB,OAAO;IACzC,qBAAqB,UAAU;GACjC;GAEA,IAAI,qBAAqB,iBAAiB,gBAAgB;IACxD,kBAAkB;KAChB,GAAG;KACH,GAAG;KACH,2BAAW,IAAI,IAAI,CAAC,GAAG,qBAAqB,WAAW,YAAY,CAAC;IACtE;IACA,iBAAiB;GACnB;EACF;EAEA,KAAK,cAAc,IAAI,OAAO,eAAe;EAE7C,KAAK,oBAAoB,OAAO;GAAE;GAAW;GAAM;GAAe;EAAS,CAAC;EAE5E,OAAO;GAAE;GAAU,WAAW;GAAgB;EAAQ;CACxD;CAEA,MAAM,cACJ,QAC0C;EAC1C,MAAM,EAAE,UAAU,OAAO,cAAc,gBAAgB,YAAY;EACnE,KAAK,OAAO,KAAK,kCAAkC,MAAM,QAAQ,cAAc;EAE/E,MAAM,WAAW,KAAK,cAAc,IAAI,KAAK;EAC7C,IAAI,CAAC,UACH,OAAO,EAAE,SAAS,MAAM;EAG1B,IAAI,iBAAiB,SAAS,gBAC5B,OAAO,EAAE,SAAS,KAAK;EAGzB,MAAM,aAAa,gBAAgB,OAAO;EAC1C,KAAK,cAAc,IAAI,OAAO;GAAE,GAAG;GAAU;GAAS;GAAY;EAAe,CAAC;EAClF,MAAM,KAAK,MAAM,sBAAsB,UAAU;GAAE;GAAgB;EAAQ,CAAC;EAE5E,OAAO,EAAE,SAAS,KAAK;CACzB;CAEA,MAAM,WACJ,QACA,SAAiB,GACsB;EACvC,MAAM,EAAE,OAAO,iBAAiB;EAChC,KAAK,OAAO,KAAK,+BAA+B,MAAM,SAAS,cAAc;EAE7E,MAAM,MAAM,KAAK,cAAc,IAAI,KAAK;EACxC,IAAI,CAAC,KAAK,OAAO,EAAE,IAAI,KAAK;EAE5B,IAAI,IAAI,SACN,aAAa,IAAI,OAAO;EAG1B,IAAI,IAAI,UAAU,OAAO,GAAG;GAC1B,IAAI,UAAU,OAAO,YAAY;GACjC,OAAO,EAAE,IAAI,KAAK;EACpB;EAEA,KAAK,OAAO,MAAM,uCAAuC,MAAM,SAAS,aAAa,QAAQ;EAC7F,IAAI,UAAU,WAAW,YAAY;GACnC,IAAI,UAAU,OAAO,YAAY;GAEjC,IAAI,IAAI,UAAU,SAAS,GAAG;IAC5B,KAAK,OAAO,KAAK,8BAA8B,MAAM,SAAS,cAAc;IAC5E,KAAK,cAAc,OAAO,KAAK;IAC/B,MAAM,KAAK,mBAAmB,KAAK;GACrC;EACF,GAAG,OAAS,MAAM;EAElB,OAAO,EAAE,IAAI,KAAK;CACpB;CAEA,MAAM,YACJ,QACwC;EACxC,KAAK,OAAO,KAAK,EAAE,QAAQ,OAAO,GAAG,wCAAwC;EAC7E,KAAK,MAAM,SAAS,QAClB,KAAK,WAAW,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG,CAAC;EAGxD,OAAO,OAAO,WAAW,EAAE,IAAI,KAAK,EAAE;CACxC;;;;;CAMA,WAAW,aAAsD;EAC/D,MAAM,WAAmC,CAAC;EAE1C,KAAK,MAAM,GAAG,QAAQ,KAAK,eAAe;GACxC,IAAI,IAAI,UAAU,SAAS,GAAG;GAC9B,IAAI,CAAC,4BAA4B,aAAa,IAAI,UAAU,GAAG;GAE/D,SAAS,KAAK;IACZ,OAAO,IAAI;IACX,WAAW,IAAI;IACf,MAAM,IAAI;IACV,UAAU,IAAI;IACd,iBAAiB,MAAM,KAAK,IAAI,SAAS;GAC3C,CAAC;EACH;EAEA,OAAO;CACT;CAEA,MAAc,oBAAoB,OAAe,QAA2C;EAC1F,IAAI;GACF,MAAM,KAAK,MAAM,0BAA0B,OAAO,MAAM;EAC1D,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,KAAK,WAAW,KAAK,GAAG,uCAAuC,OAAO;EAC1F;CACF;CAEA,MAAc,mBAAmB,OAA8B;EAC7D,IAAI;GACF,MAAM,KAAK,MAAM,yBAAyB,KAAK;EACjD,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,KAAK,WAAW,KAAK,GAAG,sCAAsC,OAAO;EACzF;CACF;AACF"}
1
+ {"version":3,"file":"subscription-worker-pahX0zxg.js","names":["uuidv4","logger","baseLogger","logger","Redis","baseLogger"],"sources":["../src/server/subscription-manager-types.ts","../src/logger.ts","../src/db/cache.ts","../src/db/init_db.ts","../src/server/subscription-worker.ts"],"sourcesContent":["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});\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});\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\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\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 /** All app server instances that currently hold this subscription. */\n instances: Set<string>;\n timeout: NodeJS.Timeout | null;\n}","import pino, { Level } from \"pino\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport pinoPretty from \"pino-pretty\";\nconst _ = pinoPretty; // Prevents tree shaking of pino-pretty\n\nexport const appInstanceId = `instance-${uuidv4().slice(0, 8)}`;\nexport const isProduction = process.env.ENV === \"prod\";\nexport const logger = pino({\n level: process.env.LOG_LEVEL || \"info\",\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: { paths: [\"user.password\"] },\n transport: isProduction ? undefined : {\n target: 'pino-pretty',\n options: {\n colorize: true\n },\n },\n serializers: {\n \"columnsTypes\": (map) => {\n if (map instanceof Map) return serializeMapValue(map);\n return map;\n },\n },\n base: {\n \"srv\": appInstanceId,\n },\n});\n\nexport function logLevelOf(name: string): Level {\n const defaultLogLevel = process.env.DEFAULT_LOG_LEVEL || \"info\";\n return (process.env[name]?.toLowerCase() || defaultLogLevel) as Level;\n}\n\nexport function serializeMapValue<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V> {\n const obj: Record<K, any> = {} as Record<K, any>;\n for (const [k, v] of map.entries()) {\n obj[k] = {} as V;\n if (v instanceof Map) {\n for (const [innerK, innerV] of v.entries()) {\n obj[k][innerK] = innerV;\n }\n } else {\n obj[k] = v;\n }\n }\n\n return obj as Record<K, V>;\n}","import type { Redis as UpstashRedis } from \"@upstash/redis\";\nimport {\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/subscription-manager-types\";\nimport Redis from \"ioredis\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\nimport { supaliveStringify } from \"../helper\";\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\nexport class CacheLayer {\n public redis: Redis | UpstashRedis;\n private readonly ttlSeconds: number;\n\n constructor(upstash: Redis | UpstashRedis, ttlSeconds: number = 3600) {\n this.redis = upstash;\n this.ttlSeconds = ttlSeconds;\n }\n\n async setQueryCacheFor(cacheKey: string, data: unknown, ttlSeconds?: number): Promise<void> {\n const combined = supaliveStringify({ data: data });\n await redisRetryPolicy.execute(async () => {\n await this.redis.setex(this.getQueryCacheDataKey(cacheKey), ttlSeconds ?? this.ttlSeconds, combined);\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 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 setQueryCacheMetadata(cacheKey: string, metaData: QueryCacheMetadata): Promise<void> {\n const metadata = supaliveStringify(metaData);\n await redisRetryPolicy.execute(() => this.redis.set(this.getQeruyCacheMetaKey(cacheKey), metadata));\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(this.getRegistrationKey.bind(this))),\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 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}","import { DatabaseConfig, SupaliveServerConfig, UpstashConfig } from \"../config\";\nimport { logger } from \"../logger\";\nimport { CacheLayer } from \"./cache\";\nimport { MySqlDatabase } from \"./mysql\";\nimport { PgDatabase } from \"./postgres\";\nimport { SupaliveDb } from \"./realtime_db\";\nimport Redis from \"ioredis\";\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 CacheLayer: CacheLayer;\n forceBootstrapReload?: boolean;\n workerId?: number;\n};\n\nexport async function initDatabase(options: InitDbOptions): Promise<SupaliveDb> {\n const impl = createDatabase(options.DbConfig, options.workerId);\n const supaliveDb = SupaliveDb.create({ db: 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 ...config,\n connectionLimit: config.maxConnections ?? 20,\n connectTimeout: config.connectionTimeoutMillis ?? 15_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 // 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:${SUPALIVE_SCHEMA_VERSION ?? \"v1\"}` so other\n * instances starting cold can skip the pg_attribute round-trip. 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 version = process.env.SUPALIVE_SCHEMA_VERSION ?? \"v1\";\n const key = `supalive:schema:types:${version}`;\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?: Redis } {\n const redis = new Redis(config.upstash.redisUrl, {\n connectTimeout: config.connectionTimeout ?? 15_000,\n maxRetriesPerRequest: config.commandMaxRetries ?? 1,\n keepAlive: 3000,\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 checkLogsAffectReadSet,\n checkWriteSetAffectsReadSet,\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 \"./subscription-manager-types\";\nimport { logger as baseLogger, logLevelOf } from \"../logger\";\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 private cache: CacheLayer;\n private db: Database;\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 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 floor.\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 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 };\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 let needsRecompute = true;\n let readSet: ReadEntry[];\n let rawReadSet: RawReadEntry[];\n\n if (metadata) {\n readSet = metadata.readSet;\n rawReadSet = mapReadSetToRaw(readSet);\n\n const tableIds = Array.from(new Set(readSet.map((r) => r.tableId)));\n const logs = await this.db.getCommitLogsBetweenTs(metadata.lastSnapshotTs, lastSnapshotTs, tableIds);\n needsRecompute = checkLogsAffectReadSet(logs, rawReadSet);\n\n if (!needsRecompute) {\n if (lastSnapshotTs !== metadata.lastSnapshotTs) {\n this.cache\n .setQueryCacheMetadata(cacheKey, { lastSnapshotTs: lastSnapshotTs, readSet })\n .catch((err) => this.logger.error(err, `Failed to update cache metadata for ${cacheKey}`));\n }\n metadata.lastSnapshotTs = lastSnapshotTs;\n }\n } else {\n readSet = [];\n rawReadSet = [];\n }\n\n let newSubscription: TrackedSubscription = {\n subId,\n queryName,\n args,\n cacheKey,\n lastSnapshotTs: lastSnapshotTs,\n readSet,\n rawReadSet,\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 this.subscriptions.set(subId, newSubscription);\n\n this.persistRegistration(subId, { queryName, args, queryIdentity, cacheKey });\n\n return { cacheKey, recompute: needsRecompute, readSet };\n }\n\n async updateReadSet(\n params: UpdateSubscriptionReadSetParams,\n ): Promise<UpdateSubscriptionReadSetResult> {\n const { cacheKey, subId, instanceName, lastSnapshotTs, readSet } = 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.subscriptions.set(subId, { ...existing, readSet, rawReadSet, lastSnapshotTs });\n await this.cache.setQueryCacheMetadata(cacheKey, { lastSnapshotTs, readSet });\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.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 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"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { o as utf8Encode } from "./helper-zdJT5FUc.js";
1
+ import { r as utf8Encode } from "./bytes-AuEkyxeU.js";
2
2
  import z from "zod";
3
3
  //#region src/db/types_db.ts
4
4
  const BigIntSchema = z.string().transform((val) => BigInt(val));
@@ -165,4 +165,4 @@ var OccAbortError = class extends Error {
165
165
  //#endregion
166
166
  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 };
167
167
 
168
- //# sourceMappingURL=types_db-Ctf-ksoy.js.map
168
+ //# sourceMappingURL=types_db-DXj_l9n-.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types_db-Ctf-ksoy.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\",\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 tableId: Uint8ArraySchema,\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 tableId: Uint8ArraySchema,\n predicate: PredicateSchema,\n columns: z.array(z.string()),\n orderBy: 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.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\nexport const QueryCacheMetadataSchema = z.object({\n lastSnapshotTs: BigIntSchema,\n readSet: z.array(ReadEntrySchema),\n});\nexport type QueryCacheMetadata = z.infer<typeof QueryCacheMetadataSchema>;\n\nexport const QueryCacheEntrySchema = z.object({\n data: z.unknown(),\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":";;;AAMA,MAAa,eAAe,EAAE,OAAO,CAAC,CAAC,WAAW,QAAQ,OAAO,GAAG,CAAC;AAErE,MAAa,wBAAwB,EAAE,KAAK;CAC1C;CAAK;CAAM;CAAK;CAAM;CAAK;CAC3B;CAAQ;CAAS;CAAY;CAC7B;CACA;CAAgB;CAAmB;CAAc;CACjD;CAAU;AACZ,CAAC;AAQD,MAAa,kBAAwC,EAAE,WACrD,EAAE,MAAM;CACN;CACA;CACA;AACF,CAAC,CACH;AAEA,MAAa,sBAAsB,EAAE,OAAO;CAC1C,QAAQ,EAAE,QAAQ,MAAM;CACxB,QAAQ,EAAE,OAAO;CACjB,UAAU;CACV,OAAO,EAAE,QAAQ;CACjB,QAAQ,EAAE,QAAQ,CAAC,CAAC,SAAS;CAI7B,KAAK,EAAE,QAAQ,CAAC,CAAC,SAAS;CAI1B,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAI1B,MAAM,EAAE,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,SAAS;AACxC,CAAC;AAGD,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,QAAQ,KAAK;CACrB,UAAU,EAAE,MAAM,eAAe;AACnC,CAAC;AAGD,MAAa,oBAAoB,EAAE,OAAO;CACxC,MAAM,EAAE,QAAQ,IAAI;CACpB,UAAU,EAAE,MAAM,eAAe;AACnC,CAAC;AAMD,MAAM,mBAAmB,EAAE,IAAI,CAAC,CAAC,WAAW,QAAQ;CAClD,IAAI,eAAe,YAAY,OAAO;CACtC,OAAO,cAAc,GAAG;AAC1B,CAAC;AAED,SAAgB,mBAAmB,IAAqB,UAAU,OAAmB;CAEnF,MAAM,QADQ,OAAO,OAAO,WAAW,OAAO,EAAE,IAAI,GAAA,CACjC,QAAQ,MAAM,EAAE;CACnC,IAAI,YAAY,MAAM;EACpB,MAAM,MAAM,KAAK,WAAW;EAC5B,MAAM,MAAM,IAAI,WAAW,GAAG;EAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KACvB,IAAI,KAAK,SAAS,KAAK,UAAU,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;EAExD,OAAO;CACT;CACA,OAAO,WAAW,IAAI;AACxB;AAEA,SAAgB,iBAAiB,OAAyC;CACxE,IAAI;EACF,OAAO,cAAc,KAAK;CAC5B,SAAS,GAAG;EACV,uBAAO,IAAI,WAAW,CAAC;CACzB;AACF;AAEA,SAAgB,cAAc,OAAyC;CACrE,IAAI,iBAAiB,YACnB,OAAO;CAET,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,WAAW,KAAK,KAAiB;CAE1C,IAAI,SAAS,OAAO,UAAU,YAAY,UAAU,SAAU,MAAc,SAAS,YAAY,UAAU,OACzG,OAAO,WAAW,KAAM,MAAc,IAAI;CAE5C,IAAI,OAAO,UAAU,UACnB,OAAO,WAAW,KAAK;CAEzB,IAAI,OAAO,UAAU,UACnB,OAAO,WAAW,OAAO,KAAK,CAAC;CAGjC,MAAM,IAAI,MAAM,sBAAsB;AACxC;AAKA,MAAa,kBAAkB,EAAE,OAAO;CACtC,MAAM,EAAE,QAAQ,OAAO;CACvB,OAAO,EAAE,OAAO;CAChB,IAAI,EAAE,OAAO;CACb,SAAS;CAIT,kBAAkB,aAAa,SAAS,CAAC,CAAC,SAAS;AACrD,CAAC;AAGD,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,QAAQ,OAAO;CACvB,SAAS;CACT,IAAI;CACJ,OAAO,EAAE,OAAO;CAChB,kBAAkB,aAAa,SAAS,CAAC,CAAC,SAAS;AACrD,CAAC;;;;;AAOD,MAAa,kBAAkB,EAAE,OAAO;CACtC,MAAM,EAAE,QAAQ,OAAO;CACvB,OAAO,EAAE,OAAO;CAChB,SAAS;CACT,WAAW;CACX,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;CAC3B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACxC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACtC,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;AACzC,CAAC;AAGD,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,QAAQ,OAAO;CACvB,OAAO,EAAE,OAAO;CAChB,SAAS;CACT,WAAW;CACX,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;CAC3B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACxC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACtC,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;AACzC,CAAC;AAGD,MAAa,kBAAkB,EAAE,MAAM,CAAC,iBAAiB,eAAe,CAAC;AAGzE,MAAa,qBAAqB,EAAE,MAAM,CAAC,oBAAoB,kBAAkB,CAAC;AAGlF,MAAa,gBAAgB,EAAE,KAAK;CAAC;CAAU;CAAU;AAAQ,CAAC;AAGlE,MAAa,mBAAmB,EAAE,OAAO;CACvC,OAAO,EAAE,OAAO;CAChB,SAAS;CACT,IAAI,EAAE,OAAO;CACb,IAAI;CACJ,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;CAKjD,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;CAKrD,kBAAkB,aAAa,SAAS,CAAC,CAAC,SAAS;AACrD,CAAC;AAwBD,MAAa,2BAA2B,EAAE,OAAO;CAC/C,gBAAgB;CAChB,SAAS,EAAE,MAAM,eAAe;AAClC,CAAC;AAGD,MAAa,wBAAwB,EAAE,OAAO,EAC5C,MAAM,EAAE,QAAQ,EAClB,CAAC;AAGD,MAAa,yBAAyB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC;AAU3F,MAAa,WAAwB;CAAE,aAAa;CAAG,SAAS;AAAO;AAEvE,MAAa,gBAA6B;CACxC,aAAa;CACb,SAAS;CACT,SAAS;AACX;AAEA,IAAa,mBAAb,MAAa,yBAAyB,MAAM;CAExB;CACA;CAFlB,YACE,QACA,gBACA;EACA,MAAM,iBAAiB,QAAQ;EAHf,KAAA,SAAA;EACA,KAAA,iBAAA;EAGhB,KAAK,OAAO;CACd;CAEA,OAAO,mBAAmB,KAAuB;EAC/C,IAAI,eAAe,oBAChB,OAAO,OAAO,QAAQ,YAAa,IAA0B,SAAS,oBACvE,OAAO;EAET,OAAO;CACT;AACF;AAEA,IAAa,gBAAb,cAAmC,MAAM;CACvC,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF"}
1
+ {"version":3,"file":"types_db-DXj_l9n-.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\",\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 tableId: Uint8ArraySchema,\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 tableId: Uint8ArraySchema,\n predicate: PredicateSchema,\n columns: z.array(z.string()),\n orderBy: 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.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\nexport const QueryCacheMetadataSchema = z.object({\n lastSnapshotTs: BigIntSchema,\n readSet: z.array(ReadEntrySchema),\n});\nexport type QueryCacheMetadata = z.infer<typeof QueryCacheMetadataSchema>;\n\nexport const QueryCacheEntrySchema = z.object({\n data: z.unknown(),\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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"}
@@ -0,0 +1,216 @@
1
+ import { An as CacheLayer, ar as MutationResult, tt as RegisteredProcedure, wt as SupaliveDb } from "./index-DouKwYL6.js";
2
+ import z from "zod";
3
+ import { WebSocket } from "ws";
4
+ import { Level } from "pino";
5
+ import { Redis } from "ioredis";
6
+
7
+ //#region src/config.d.ts
8
+ interface UpstashConfig {
9
+ url: string;
10
+ token: string;
11
+ redisUrl: string;
12
+ }
13
+ /** Database configuration for PostgreSQL */
14
+ interface PostgresConfig {
15
+ type: "postgres";
16
+ connectionString: string;
17
+ /** Optional max connections for the Postgres connection pool. Defaults to 10.*/
18
+ maxConnections?: number;
19
+ /** Optional min connections for the Postgres connection pool. Defaults to 1.*/
20
+ minConnections?: number;
21
+ /** Optional idle timeout for Postgres connections in milliseconds. Defaults to 30000 (30 seconds). */
22
+ idleTimeoutMillis?: number;
23
+ /** Optional connection timeout for acquiring Postgres connections in milliseconds. Defaults to 7000 (7 seconds). */
24
+ connectionTimeoutMillis?: number;
25
+ }
26
+ /** Database configuration for MySQL */
27
+ interface MySQLConfig {
28
+ type: "mysql";
29
+ connectionString: string;
30
+ /** Optional max connections for the MySQL connection pool. Defaults to 20.*/
31
+ maxConnections?: number;
32
+ /** Optional min connections for the MySQL connection pool. Defaults to 1.*/
33
+ minConnections?: number;
34
+ /** Optional idle timeout for MySQL connections in milliseconds. Defaults to 30000 (30 seconds). */
35
+ idleTimeoutMillis?: number;
36
+ /** Optional connection timeout for acquiring MySQL connections in milliseconds. Defaults to 7000 (7 seconds). */
37
+ connectionTimeoutMillis?: number;
38
+ /** Optional flag to queue connection requests when pool is exhausted. Defaults to true (queue requests). */
39
+ queueLimit?: number;
40
+ }
41
+ type DatabaseConfig = PostgresConfig | MySQLConfig;
42
+ /**
43
+ * Configuration for the Supalive WebSocket server.
44
+ */
45
+ interface SupaliveServerConfig<TContext = Record<string, unknown>> {
46
+ /** Server port (default: 3000) */
47
+ port?: number;
48
+ /** Server host (default: "0.0.0.0") */
49
+ host?: string;
50
+ cacheLayer: CacheLayer;
51
+ redisSubClient: Redis;
52
+ /**
53
+ * Subscription Manager WebSocket URL for RPC communication.
54
+ * A single sub-manager process owns all subscription state for this
55
+ * deployment. Internally it spawns N logical workers (configured on the
56
+ * sub-manager side) and routes by hash(subId) to spread CPU across them.
57
+ */
58
+ subManagerUrl: string;
59
+ /** Database instance */
60
+ database: SupaliveDb;
61
+ /**
62
+ * Optional authentication callback.
63
+ * Called when client sends auth message.
64
+ * Return null to reject authentication.
65
+ */
66
+ verifyAuth?: (data: Record<string, unknown>, sessionId: string) => Promise<TContext | null>;
67
+ /**
68
+ * Server context name for procedure routing.
69
+ * Must match the router's contextName.
70
+ */
71
+ contextName?: string;
72
+ /**
73
+ * Session TTL in seconds (default: 3600 = 1 hour).
74
+ * Sessions are kept alive while connected and expire after disconnect.
75
+ */
76
+ sessionTtlSeconds?: number;
77
+ /**
78
+ * Enable detailed logging.
79
+ */
80
+ logLevel?: Level;
81
+ /**
82
+ * Extract user ID from context for cache key generation.
83
+ * If not provided, caching is disabled (each sub recomputes independently).
84
+ */
85
+ getUserId?: (ctx: TContext | undefined) => string | null;
86
+ /**
87
+ * Cache TTL in seconds (default: 3600).
88
+ * How long query results are cached in Redis.
89
+ */
90
+ cacheTtlSeconds?: number;
91
+ }
92
+ interface SubscriptionManagerConfig {
93
+ port: number;
94
+ upstash: {
95
+ url: string;
96
+ token: string;
97
+ redisUrl: string;
98
+ };
99
+ database: DatabaseConfig;
100
+ cacheTTLSeconds: number;
101
+ /**
102
+ * Number of logical workers inside the sub-manager process.
103
+ *
104
+ * Subscriptions are routed by FNV-1a(subId) % workers so that all operations
105
+ * for a given subId always land on the same worker (preserving the existing
106
+ * `instances: Set<string>` dedup). Each worker owns an independent
107
+ * subscriptions Map; on invalidate, all workers scan their own slice in
108
+ * parallel (logically — JS still runs one at a time inside one event loop,
109
+ * but per-worker scans are smaller so total work is split N ways).
110
+ *
111
+ * Defaults to `SUPALIVE_SUB_MANAGER_WORKERS` env var if set, else 1.
112
+ */
113
+ workers?: number;
114
+ /**
115
+ * Connection pool size *per worker*. If omitted, falls back to
116
+ * `floor(database.maxConnections / workers)` (min 1) so the total pool
117
+ * size stays close to the legacy single-worker configuration.
118
+ */
119
+ maxConnectionsPerWorker?: number;
120
+ }
121
+ //#endregion
122
+ //#region src/server/types_server.d.ts
123
+ type InstanceId = string;
124
+ type SubId = string;
125
+ /** Local tracking of an active subscription (per-session) */
126
+ interface SubscriptionEntry {
127
+ subId: SubId;
128
+ cacheKey: string;
129
+ subscribedTimes: number;
130
+ }
131
+ /**
132
+ * A mutation that has been validated and parked on the per-session FIFO
133
+ * queue, waiting for the in-flight mutation on this socket to finish. Each
134
+ * `handleCall` for a mutation owns one of these and awaits its own
135
+ * resolve/reject so it can send the response.
136
+ */
137
+ interface PendingMutation<TContext = Record<string, unknown>> {
138
+ procedure: RegisteredProcedure<TContext>;
139
+ validatedInput: unknown;
140
+ resolve: (result: MutationResult<unknown>) => void;
141
+ reject: (err: unknown) => void;
142
+ }
143
+ /** WebSocket session (runtime, with WebSocket) */
144
+ interface Session<TContext = Record<string, unknown>> {
145
+ id: string;
146
+ ws: WebSocket;
147
+ /** Auth context from verifyAuth callback */
148
+ serverCtx?: TContext;
149
+ subIds: Set<string>;
150
+ /**
151
+ * Per-socket FIFO mutation queue. at most one mutation
152
+ * drains at a time so a single client cannot saturate the OCC retry
153
+ * loop or fan out concurrent commits that race the same rows.
154
+ */
155
+ mutationQueue: PendingMutation<TContext>[];
156
+ mutationInFlight: boolean;
157
+ /** Per-socket count of currently executing actions. */
158
+ actionInFlight: number;
159
+ /**
160
+ * Wall-clock of the last inbound frame (text message, ws-ping, or ws-pong).
161
+ * Used by the per-session heartbeat to terminate sockets that have gone
162
+ * silent past `CLIENT_TIMEOUT_MS`.
163
+ */
164
+ lastReceivedAt: number;
165
+ /**
166
+ * Wall-clock of the last outbound frame. Gates the idle-only app-level
167
+ * ping so a busy session (frequent sub:updates etc.) doesn't emit a
168
+ * redundant `{type:"ping"}` on every heartbeat tick.
169
+ */
170
+ lastSentAt: number;
171
+ /**
172
+ * Per-session combined heartbeat. Single `setInterval` that handles
173
+ * both dead-TCP detection (ws-ping or terminate) and client-watchdog
174
+ * refresh (idle-only app-level ping) on each tick.
175
+ */
176
+ heartbeatTimer: ReturnType<typeof setInterval> | null;
177
+ }
178
+ declare const RemoteSubscriptionUpdateSchema: z.ZodObject<{
179
+ type: z.ZodLiteral<"update">;
180
+ subId: z.ZodString;
181
+ data: z.ZodUnknown;
182
+ dataHash: z.ZodString;
183
+ originInstance: z.ZodString;
184
+ }, z.core.$strip>;
185
+ type RemoteSubscriptionUpdate = z.infer<typeof RemoteSubscriptionUpdateSchema>;
186
+ declare const RemoteRecomputeRaceSchema: z.ZodObject<{
187
+ type: z.ZodLiteral<"recompute-race">;
188
+ subId: z.ZodString;
189
+ cacheKey: z.ZodString;
190
+ queryName: z.ZodString;
191
+ args: z.ZodUnknown;
192
+ notifyInstances: z.ZodArray<z.ZodString>;
193
+ commitTs: z.ZodString;
194
+ originInstance: z.ZodString;
195
+ }, z.core.$strip>;
196
+ type RemoteRecomputeRace = z.infer<typeof RemoteRecomputeRaceSchema>;
197
+ declare const RemoteSubscriptionMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
198
+ type: z.ZodLiteral<"update">;
199
+ subId: z.ZodString;
200
+ data: z.ZodUnknown;
201
+ dataHash: z.ZodString;
202
+ originInstance: z.ZodString;
203
+ }, z.core.$strip>, z.ZodObject<{
204
+ type: z.ZodLiteral<"recompute-race">;
205
+ subId: z.ZodString;
206
+ cacheKey: z.ZodString;
207
+ queryName: z.ZodString;
208
+ args: z.ZodUnknown;
209
+ notifyInstances: z.ZodArray<z.ZodString>;
210
+ commitTs: z.ZodString;
211
+ originInstance: z.ZodString;
212
+ }, z.core.$strip>], "type">;
213
+ type RemoteSubscriptionMessage = z.infer<typeof RemoteSubscriptionMessageSchema>;
214
+ //#endregion
215
+ export { UpstashConfig as _, RemoteSubscriptionMessage as a, RemoteSubscriptionUpdateSchema as c, SubscriptionEntry as d, DatabaseConfig as f, SupaliveServerConfig as g, SubscriptionManagerConfig as h, RemoteRecomputeRaceSchema as i, Session as l, PostgresConfig as m, PendingMutation as n, RemoteSubscriptionMessageSchema as o, MySQLConfig as p, RemoteRecomputeRace as r, RemoteSubscriptionUpdate as s, InstanceId as t, SubId as u };
216
+ //# sourceMappingURL=types_server-BW1ys_SK.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types_server-BW1ys_SK.d.ts","names":[],"sources":["../src/config.ts","../src/server/types_server.ts"],"mappings":";;;;;;;UAKiB,aAAA;EACb,GAAA;EACA,KAAA;EACA,QAAA;AAAA;;UAIa,cAAA;EACb,IAAA;EACA,gBAAA;EANA;EAQA,cAAA;EARQ;EAWR,cAAA;EAP2B;EAU3B,iBAAA;EAV2B;EAa3B,uBAAA;AAAA;;UAIa,WAAA;EACb,IAAA;EACA,gBAAA;EANuB;EAQvB,cAAA;EAJa;EAOb,cAAA;;EAGA,iBAAA;EATA;EAYA,uBAAA;EATA;EAYA,UAAA;AAAA;AAAA,KAGQ,cAAA,GAAiB,cAAA,GAAiB,WAAW;;;AAH3C;UAQG,oBAAA,YAAgC,MAAA;EALvB;EAOtB,IAAA;EAPyB;EAUzB,IAAA;EAKA,UAAA,EAAY,UAAA;EAIZ,cAAA,EAAgB,KAAA;EAdiB;;;;;;EAsBjC,aAAA;EAaK;EAVL,QAAA,EAAU,UAAA;EAiCQ;;;;;EA1BlB,UAAA,IACI,IAAA,EAAM,MAAA,mBACN,SAAA,aACC,OAAA,CAAQ,QAAA;EA9Bb;;;;EAoCA,WAAA;EAnBA;;;;EAyBA,iBAAA;EAdI;;;EAmBJ,QAAA,GAAW,KAAA;EAXX;;;;EAiBA,SAAA,IAAa,GAAA,EAAK,QAAA;EAAA;;;;EAMlB,eAAA;AAAA;AAAA,UAGa,yBAAA;EACb,IAAA;EACA,OAAA;IAAW,GAAA;IAAa,KAAA;IAAe,QAAA;EAAA;EACvC,QAAA,EAAU,cAAc;EACxB,eAAA;EADU;;;;;AAoBa;;;;ACxI3B;;;EDkII,OAAA;EClIkB;AAEtB;;;;EDsII,uBAAA;AAAA;;;KCxIQ,UAAA;AAAA,KAEA,KAAA;;UAGK,iBAAA;EACf,KAAA,EAAO,KAAK;EACZ,QAAA;EACA,eAAA;AAAA;;;;ADPU;AAIZ;;UCYiB,eAAA,YAA2B,MAAA;EAC1C,SAAA,EAAW,mBAAA,CAAoB,QAAA;EAC/B,cAAA;EACA,OAAA,GAAU,MAAA,EAAQ,cAAA;EAClB,MAAA,GAAS,GAAA;AAAA;;UAIM,OAAA,YAAmB,MAAA;EAClC,EAAA;EACA,EAAA,EAAI,SAAA;EDLW;ECOf,SAAA,GAAY,QAAA;EACZ,MAAA,EAAQ,GAAA;EDRkB;;;;;ECc1B,aAAA,EAAe,eAAA,CAAgB,QAAA;EAC/B,gBAAA;EDCE;ECCF,cAAA;EDDY;AAGd;;;;ECIE,cAAA;EDCe;;;;;ECKf,UAAA;EDoBY;;;;;ECdZ,cAAA,EAAgB,UAAA,QAAkB,WAAA;AAAA;AAAA,cAgBvB,8BAAA,EAA8B,CAAA,CAAA,SAAA;;;;;;;KAO/B,wBAAA,GAA2B,CAAA,CAAE,KAAK,QAAQ,8BAAA;AAAA,cAEzC,yBAAA,EAAyB,CAAA,CAAA,SAAA;;;;;;;;;;KAU1B,mBAAA,GAAsB,CAAA,CAAE,KAAK,QAAQ,yBAAA;AAAA,cAEpC,+BAAA,EAA+B,CAAA,CAAA,qBAAA,EAAA,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;KAIhC,yBAAA,GAA4B,CAAA,CAAE,KAAK,QAAQ,+BAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types_server-Dk6o_B7N.js","names":[],"sources":["../src/server/types_server.ts"],"sourcesContent":["import type { WebSocket } from \"ws\";\nimport type { ReadEntry, MutationResult } from \"../db/types_db\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport z from \"zod\";\n\n// ─── Core Types ───────────────────────────────────────────────────────────────\n\nexport type InstanceId = string;\n\nexport type SubId = string;\n\n/** Local tracking of an active subscription (per-session) */\nexport interface SubscriptionEntry {\n subId: SubId;\n cacheKey: string;\n subscribedTimes: number;\n}\n\n/**\n * A mutation that has been validated and parked on the per-session FIFO\n * queue, waiting for the in-flight mutation on this socket to finish. Each\n * `handleCall` for a mutation owns one of these and awaits its own\n * resolve/reject so it can send the response.\n */\nexport interface PendingMutation<TContext = Record<string, unknown>> {\n procedure: RegisteredProcedure<TContext>;\n validatedInput: unknown;\n resolve: (result: MutationResult<unknown>) => void;\n reject: (err: unknown) => void;\n}\n\n/** WebSocket session (runtime, with WebSocket) */\nexport interface Session<TContext = Record<string, unknown>> {\n id: string;\n ws: WebSocket;\n /** Auth context from verifyAuth callback */\n serverCtx: TContext;\n subIds: Set<string>;\n /**\n * Per-socket FIFO mutation queue. at most one mutation\n * drains at a time so a single client cannot saturate the OCC retry\n * loop or fan out concurrent commits that race the same rows.\n */\n mutationQueue: PendingMutation<TContext>[];\n mutationInFlight: boolean;\n /** Per-socket count of currently executing actions. */\n actionInFlight: number;\n /**\n * Wall-clock of the last inbound frame (text message, ws-ping, or ws-pong).\n * Used by the per-session heartbeat to terminate sockets that have gone\n * silent past `CLIENT_TIMEOUT_MS`.\n */\n lastReceivedAt: number;\n /**\n * Wall-clock of the last outbound frame. Gates the idle-only app-level\n * ping so a busy session (frequent sub:updates etc.) doesn't emit a\n * redundant `{type:\"ping\"}` on every heartbeat tick.\n */\n lastSentAt: number;\n /**\n * Per-session combined heartbeat. Single `setInterval` that handles\n * both dead-TCP detection (ws-ping or terminate) and client-watchdog\n * refresh (idle-only app-level ping) on each tick.\n */\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n}\n\n// Messages published on `sub:instance:<instanceId>` for cross-instance fan-out.\n//\n// Two variants:\n// • update — the writer (or race winner) recomputed and is pushing\n// the result; receivers forward `data` to their local\n// sessions.\n// • recompute-race — the writer does not hold the affected sub locally, so\n// it broadcasts a race hint to every instance that does\n// (`notifyInstances`). Receivers try to acquire a Redis\n// lock keyed by (subId, commitTs); the winner runs the\n// query against one of its own sessions' serverCtx and\n// publishes the resulting `update` back out. Losers\n// no-op and pick up the winner's `update` over Redis.\nexport const RemoteSubscriptionUpdateSchema = z.object({\n type: z.literal(\"update\"),\n subId: z.string(),\n data: z.unknown(),\n dataHash: z.string(),\n originInstance: z.string(),\n});\nexport type RemoteSubscriptionUpdate = z.infer<typeof RemoteSubscriptionUpdateSchema>;\n\nexport const RemoteRecomputeRaceSchema = z.object({\n type: z.literal(\"recompute-race\"),\n subId: z.string(),\n cacheKey: z.string(),\n queryName: z.string(),\n args: z.unknown(),\n notifyInstances: z.array(z.string()),\n commitTs: z.string(),\n originInstance: z.string(),\n});\nexport type RemoteRecomputeRace = z.infer<typeof RemoteRecomputeRaceSchema>;\n\nexport const RemoteSubscriptionMessageSchema = z.discriminatedUnion(\"type\", [\n RemoteSubscriptionUpdateSchema,\n RemoteRecomputeRaceSchema,\n]);\nexport type RemoteSubscriptionMessage = z.infer<typeof RemoteSubscriptionMessageSchema>;\n"],"mappings":";;AAgFA,MAAa,iCAAiC,EAAE,OAAO;CACrD,MAAM,EAAE,QAAQ,QAAQ;CACxB,OAAO,EAAE,OAAO;CAChB,MAAM,EAAE,QAAQ;CAChB,UAAU,EAAE,OAAO;CACnB,gBAAgB,EAAE,OAAO;AAC3B,CAAC;AAGD,MAAa,4BAA4B,EAAE,OAAO;CAChD,MAAM,EAAE,QAAQ,gBAAgB;CAChC,OAAO,EAAE,OAAO;CAChB,UAAU,EAAE,OAAO;CACnB,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,QAAQ;CAChB,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC;CACnC,UAAU,EAAE,OAAO;CACnB,gBAAgB,EAAE,OAAO;AAC3B,CAAC;AAGD,MAAa,kCAAkC,EAAE,mBAAmB,QAAQ,CAC1E,gCACA,yBACF,CAAC"}
1
+ {"version":3,"file":"types_server-Dk6o_B7N.js","names":[],"sources":["../src/server/types_server.ts"],"sourcesContent":["import type { WebSocket } from \"ws\";\nimport type { ReadEntry, MutationResult } from \"../db/types_db\";\nimport type { RegisteredProcedure } from \"../router/router\";\nimport z from \"zod\";\n\n// ─── Core Types ───────────────────────────────────────────────────────────────\n\nexport type InstanceId = string;\n\nexport type SubId = string;\n\n/** Local tracking of an active subscription (per-session) */\nexport interface SubscriptionEntry {\n subId: SubId;\n cacheKey: string;\n subscribedTimes: number;\n}\n\n/**\n * A mutation that has been validated and parked on the per-session FIFO\n * queue, waiting for the in-flight mutation on this socket to finish. Each\n * `handleCall` for a mutation owns one of these and awaits its own\n * resolve/reject so it can send the response.\n */\nexport interface PendingMutation<TContext = Record<string, unknown>> {\n procedure: RegisteredProcedure<TContext>;\n validatedInput: unknown;\n resolve: (result: MutationResult<unknown>) => void;\n reject: (err: unknown) => void;\n}\n\n/** WebSocket session (runtime, with WebSocket) */\nexport interface Session<TContext = Record<string, unknown>> {\n id: string;\n ws: WebSocket;\n /** Auth context from verifyAuth callback */\n serverCtx?: TContext;\n subIds: Set<string>;\n /**\n * Per-socket FIFO mutation queue. at most one mutation\n * drains at a time so a single client cannot saturate the OCC retry\n * loop or fan out concurrent commits that race the same rows.\n */\n mutationQueue: PendingMutation<TContext>[];\n mutationInFlight: boolean;\n /** Per-socket count of currently executing actions. */\n actionInFlight: number;\n /**\n * Wall-clock of the last inbound frame (text message, ws-ping, or ws-pong).\n * Used by the per-session heartbeat to terminate sockets that have gone\n * silent past `CLIENT_TIMEOUT_MS`.\n */\n lastReceivedAt: number;\n /**\n * Wall-clock of the last outbound frame. Gates the idle-only app-level\n * ping so a busy session (frequent sub:updates etc.) doesn't emit a\n * redundant `{type:\"ping\"}` on every heartbeat tick.\n */\n lastSentAt: number;\n /**\n * Per-session combined heartbeat. Single `setInterval` that handles\n * both dead-TCP detection (ws-ping or terminate) and client-watchdog\n * refresh (idle-only app-level ping) on each tick.\n */\n heartbeatTimer: ReturnType<typeof setInterval> | null;\n}\n\n// Messages published on `sub:instance:<instanceId>` for cross-instance fan-out.\n//\n// Two variants:\n// • update — the writer (or race winner) recomputed and is pushing\n// the result; receivers forward `data` to their local\n// sessions.\n// • recompute-race — the writer does not hold the affected sub locally, so\n// it broadcasts a race hint to every instance that does\n// (`notifyInstances`). Receivers try to acquire a Redis\n// lock keyed by (subId, commitTs); the winner runs the\n// query against one of its own sessions' serverCtx and\n// publishes the resulting `update` back out. Losers\n// no-op and pick up the winner's `update` over Redis.\nexport const RemoteSubscriptionUpdateSchema = z.object({\n type: z.literal(\"update\"),\n subId: z.string(),\n data: z.unknown(),\n dataHash: z.string(),\n originInstance: z.string(),\n});\nexport type RemoteSubscriptionUpdate = z.infer<typeof RemoteSubscriptionUpdateSchema>;\n\nexport const RemoteRecomputeRaceSchema = z.object({\n type: z.literal(\"recompute-race\"),\n subId: z.string(),\n cacheKey: z.string(),\n queryName: z.string(),\n args: z.unknown(),\n notifyInstances: z.array(z.string()),\n commitTs: z.string(),\n originInstance: z.string(),\n});\nexport type RemoteRecomputeRace = z.infer<typeof RemoteRecomputeRaceSchema>;\n\nexport const RemoteSubscriptionMessageSchema = z.discriminatedUnion(\"type\", [\n RemoteSubscriptionUpdateSchema,\n RemoteRecomputeRaceSchema,\n]);\nexport type RemoteSubscriptionMessage = z.infer<typeof RemoteSubscriptionMessageSchema>;\n"],"mappings":";;AAgFA,MAAa,iCAAiC,EAAE,OAAO;CACrD,MAAM,EAAE,QAAQ,QAAQ;CACxB,OAAO,EAAE,OAAO;CAChB,MAAM,EAAE,QAAQ;CAChB,UAAU,EAAE,OAAO;CACnB,gBAAgB,EAAE,OAAO;AAC3B,CAAC;AAGD,MAAa,4BAA4B,EAAE,OAAO;CAChD,MAAM,EAAE,QAAQ,gBAAgB;CAChC,OAAO,EAAE,OAAO;CAChB,UAAU,EAAE,OAAO;CACnB,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,QAAQ;CAChB,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC;CACnC,UAAU,EAAE,OAAO;CACnB,gBAAgB,EAAE,OAAO;AAC3B,CAAC;AAGD,MAAa,kCAAkC,EAAE,mBAAmB,QAAQ,CAC1E,gCACA,yBACF,CAAC"}