@supalive/core 1.20.1 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ephemeral-BVeIVuYw.js +276 -0
- package/dist/ephemeral-BVeIVuYw.js.map +1 -0
- package/dist/ephemeral-CkidSG9y.js +295 -0
- package/dist/ephemeral-CkidSG9y.js.map +1 -0
- package/dist/index-CzW0TkpK.d.ts +2729 -0
- package/dist/index-CzW0TkpK.d.ts.map +1 -0
- package/dist/index-DB7_pv9P.d.ts +2746 -0
- package/dist/index-DB7_pv9P.d.ts.map +1 -0
- package/dist/logger-BJNCJkFh.js +463 -0
- package/dist/logger-BJNCJkFh.js.map +1 -0
- package/dist/logger-By9Pw-X2.js +454 -0
- package/dist/logger-By9Pw-X2.js.map +1 -0
- package/dist/mysql-Bvc_iWUy.d.ts +114 -0
- package/dist/mysql-Bvc_iWUy.d.ts.map +1 -0
- package/dist/mysql-CcIuIAt0.js +622 -0
- package/dist/mysql-CcIuIAt0.js.map +1 -0
- package/dist/mysql-DTK2f5oU.d.ts +114 -0
- package/dist/mysql-DTK2f5oU.d.ts.map +1 -0
- package/dist/one-shot-query-BNYLU19W.js +573 -0
- package/dist/one-shot-query-BNYLU19W.js.map +1 -0
- package/dist/one-shot-query-BSK7adwG.js +573 -0
- package/dist/one-shot-query-BSK7adwG.js.map +1 -0
- package/dist/one-shot-query-DAAHGTcI.js +573 -0
- package/dist/one-shot-query-DAAHGTcI.js.map +1 -0
- package/dist/overlap-checker-DseE1Lvh.js +300 -0
- package/dist/overlap-checker-DseE1Lvh.js.map +1 -0
- package/dist/postgres-Bp7SMb9D.d.ts +118 -0
- package/dist/postgres-Bp7SMb9D.d.ts.map +1 -0
- package/dist/postgres-C5CbPBcp.d.ts +118 -0
- package/dist/postgres-C5CbPBcp.d.ts.map +1 -0
- package/dist/postgres-CLhoKn_R.js +867 -0
- package/dist/postgres-CLhoKn_R.js.map +1 -0
- package/dist/realtime_db-CTGOrFuV.js +463 -0
- package/dist/realtime_db-CTGOrFuV.js.map +1 -0
- package/dist/src/client/index.d.ts +2 -2
- package/dist/src/client/index.js +6 -5
- package/dist/src/client/index.js.map +1 -1
- package/dist/src/exports/mysql.d.ts +1 -1
- package/dist/src/exports/mysql.js +1 -1
- package/dist/src/exports/postgres.d.ts +1 -1
- package/dist/src/exports/postgres.js +1 -1
- package/dist/src/exports/procedure.d.ts +2 -2
- package/dist/src/exports/procedure.d.ts.map +1 -1
- package/dist/src/exports/procedure.js +2 -1
- package/dist/src/exports/procedure.js.map +1 -1
- package/dist/src/exports/schema-sql.d.ts +1 -1
- package/dist/src/exports/server.d.ts +56 -5
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +168 -10
- package/dist/src/exports/server.js.map +1 -1
- package/dist/src/exports/sub-manager-worker-entry.js +1 -1
- package/dist/src/exports/types.d.ts +2 -2
- package/dist/src/exports/types.js +2 -2
- package/dist/sub-worker-dispatch-C20KTMfg.js +925 -0
- package/dist/sub-worker-dispatch-C20KTMfg.js.map +1 -0
- package/dist/sub-worker-dispatch-CQCuwZPm.js +925 -0
- package/dist/sub-worker-dispatch-CQCuwZPm.js.map +1 -0
- package/dist/sub-worker-dispatch-Dwrp3c40.js +925 -0
- package/dist/sub-worker-dispatch-Dwrp3c40.js.map +1 -0
- package/dist/types_client_rpc-CqUVHTwT.js +163 -0
- package/dist/types_client_rpc-CqUVHTwT.js.map +1 -0
- package/dist/types_db-DPMeNETt.js +173 -0
- package/dist/types_db-DPMeNETt.js.map +1 -0
- package/dist/types_server-hiwqDm4G.d.ts +737 -0
- package/dist/types_server-hiwqDm4G.d.ts.map +1 -0
- package/dist/types_server-xHmZ6baD.d.ts +737 -0
- package/dist/types_server-xHmZ6baD.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-CLhoKn_R.js","names":[],"sources":["../src/db/postgres.ts"],"sourcesContent":["import { Pool, PoolConfig, PoolClient } from \"pg\";\nimport { Database, PooledClient, DbQueryResult, DbType, SqlBuilder, TxDatabase, PreparedQuery, PendingPointEntry, PreparedQueries, LazyCommitTsParam, CoreInitLockCtx, CORE_INIT_LOCK_KEY } from \"./db\";\nimport type { CommitLogEntry, ReadEntry, WriteEntry } from \"./types_db\";\nimport { normalizeIdToBytes, OccConflictError, normalizeToBytes } from \"./types_db\";\nimport { utf8Encode } from \"./bytes\";\n\n// pg's bytea encoder checks `Buffer.isBuffer`; a plain Uint8Array falls through\n// to its scalar path and corrupts the bytes. Wrap as a zero-copy Buffer view.\nfunction toBufferView(bytes: Uint8Array): Buffer {\n if (Buffer.isBuffer(bytes)) return bytes;\n return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n}\nimport { checkLogsAffectReadSet, mapReadSetToRaw } from \"../server/overlap-checker\";\nimport { supaliveStringify } from \"../helper\";\nimport { CacheLayer } from \"./cache\";\nimport { Logger } from \"pino\";\n\nexport class PgDatabase implements Database {\n constructor(\n logger: Logger,\n config: PoolConfig,\n pool: Pool | undefined = undefined,\n ) {\n this.logger = logger;\n this.config = config;\n if (pool) {\n this.pool = pool;\n } else {\n this.pool = new Pool(config);\n }\n }\n\n private logger: Logger;\n public config: PoolConfig;\n private pool: Pool;\n readonly sqlBuilder: SqlBuilder = pgSql;\n readonly dbType: DbType = \"postgres\";\n // Lazy per-table column-type cache. The apply path consults this for\n // unnest carriers/casts in batched writes; we never re-query pg_attribute\n // for a table we've already seen unless the cache is explicitly\n // invalidated (e.g. after a migration). Shared with tx clients via\n // adoptRegistries so a single startup load services all transactions.\n public columnTypes: Map<string, Map<string, string>> = new Map();\n // Compiled-SQL cache keyed by (op, table, sorted column signature).\n // Mirrors columnTypes — invalidated together when types change.\n protected sqlCache: Map<string, string> = new Map();\n\n /**\n * Fetches actual column types from Postgres for `table` and returns a\n * map of DB column name → PG type string (as produced by `format_type`,\n * e.g. `bigint`, `integer`, `character varying(32)`, `text[]`, `jsonb`).\n * No caching here — see {@link getColumnTypes} for the cached path.\n */\n async loadColumnTypesFromPg(table: string): Promise<Map<string, string>> {\n const result = await this.query<{ column_name: string; pg_type: string }>(\n `SELECT a.attname AS column_name, format_type(a.atttypid, a.atttypmod) AS pg_type\n FROM pg_attribute a\n WHERE a.attrelid = $1::regclass AND a.attnum > 0 AND NOT a.attisdropped\n ORDER BY a.attnum`,\n [table],\n );\n // PG folds unquoted identifiers to lowercase on the server side, so\n // pg_attribute.attname comes back lowercase. JS-side column keys\n // (`entry.data`) often arrive in camelCase from the schema layer; we\n // lookup via lowercase to bridge the two cases without making callers\n // worry about it.\n const out = new Map<string, string>();\n for (const row of result.rows) out.set(row.column_name.toLowerCase(), row.pg_type);\n return out;\n }\n\n /**\n * Synchronous column-type lookup. The apply path calls this on every\n * batched write, so it can't await — populate the cache via\n * {@link bootstrapColumnTypes} at server start. Throws if `table` is\n * missing (programmer error / bootstrap skipped).\n */\n getColumnTypes(table: string): Map<string, string> | null {\n const hit = this.columnTypes.get(table);\n if (!hit) {\n this.logger.warn({ table }, \"PgDatabase: no cached column types fallback to non-batched writes\");\n return null;\n }\n return hit;\n }\n\n /**\n * Eagerly load column types into the in-memory cache. With a Redis\n * client supplied, attempts the cache key first and falls through to\n * Postgres on miss (writing the result back to Redis). This is the\n * \"one server queries PG, the rest read from Redis\" pattern for cold\n * starts in a multi-instance deploy. Without Redis, always queries PG.\n *\n * Pass `tables` to limit the load to a specific list; otherwise every\n * user table in the connected database is loaded.\n *\n * The Redis cache key should encode whatever invalidates types in your\n * environment (typically a schema version / deploy version). The\n * subscriber in supalive-server.ts is expected to publish a reload\n * signal after migrations and invoke {@link invalidateColumnTypes}.\n */\n async bootstrapColumnTypes(opts: {\n redis?: CacheLayer;\n cacheKey: string;\n tables?: string[];\n }): Promise<void> {\n this.invalidateColumnTypes();\n\n const { redis, cacheKey, tables } = opts;\n\n // Redis hit path: deserialize the full table → column → pgType map\n // and populate in one shot, skipping any pg_attribute round-trips.\n if (redis) {\n const cached = await redis.getCachedTableColumnTypes(cacheKey);\n if (cached) {\n for (const [table, cols] of Object.entries(cached)) {\n this.columnTypes.set(table, new Map(Object.entries(cols)));\n }\n return;\n }\n }\n\n // Cache miss (or no Redis): query Postgres for the listed tables\n // (or enumerate all user tables) and populate.\n const names = tables ?? await this.listUserTables();\n for (const t of names) {\n if (this.columnTypes.has(t)) continue;\n\n const fresh = await this.loadColumnTypesFromPg(t);\n this.columnTypes.set(t, fresh);\n }\n\n // Write back to Redis so subsequent server instances skip the PG hop.\n if (redis) {\n const dump: Record<string, Record<string, string>> = {};\n for (const [table, cols] of this.columnTypes) {\n dump[table] = Object.fromEntries([...cols.entries()].sort());\n }\n await redis.setCachedTableColumnsTypes(cacheKey, dump);\n }\n }\n\n /**\n * Invalidate cached types (and dependent SQL) for `table`, or the entire\n * cache if `table` is omitted. The Redis-pubsub reload signal in\n * supalive-server.ts is expected to call this on schema changes.\n */\n private invalidateColumnTypes(table?: string): void {\n if (table) {\n this.columnTypes.delete(table);\n for (const k of [...this.sqlCache.keys()]) {\n if (k.includes(`:${table}:`) || k === `d:${table}`) this.sqlCache.delete(k);\n }\n } else {\n this.columnTypes.clear();\n this.sqlCache.clear();\n }\n }\n\n /** Enumerate the user-defined tables in the connected database — used\n * by bootstrapColumnTypes() when no explicit list is provided. */\n private async listUserTables(): Promise<string[]> {\n const res = await this.query<{ relname: string }>(\n `SELECT c.relname FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE c.relkind = 'r' AND n.nspname = ANY (current_schemas(false))\n AND c.relname NOT IN ('commit_logs','metadata')`,\n );\n return res.rows.map((r) => r.relname);\n }\n\n async getCoreVersion(): Promise<bigint> {\n const result = await this.query<{ meta_value: string | number | bigint }>(\n \"SELECT meta_value FROM metadata WHERE meta_key = 'core_version'\",\n );\n return BigInt(result.rows[0]?.meta_value ?? 0);\n }\n\n async getAppMigration(): Promise<bigint> {\n const result = await this.query<{ meta_value: string | number | bigint }>(\n \"SELECT meta_value FROM metadata WHERE meta_key = 'app_migration'\",\n );\n return BigInt(result.rows[0]?.meta_value ?? 0);\n }\n\n // Runtime core bootstrap applied per version step. All schema (tables,\n // indexes, the global_commit_ts sequence) lives in the generated schema and\n // is migrated ahead of runtime (see coreTablesSql); this covers data seeding\n // and any case where a schema change must be made directly at runtime.\n coreMigrationStatements(toVersion: bigint): string[] {\n if (toVersion === 1n) {\n return [\n `INSERT INTO metadata (meta_key, meta_value) VALUES ('latest_committed_ts', 0) ON CONFLICT (meta_key) DO NOTHING;`,\n `INSERT INTO metadata (meta_key, meta_value) VALUES ('min_retained_ts', 0) ON CONFLICT (meta_key) DO NOTHING;`,\n ];\n }\n return [];\n }\n\n async withCoreInitLock(fn: (ctx: CoreInitLockCtx) => Promise<void>): Promise<void> {\n const client = await this.pool.connect();\n try {\n await client.query(\"BEGIN\");\n // Transaction-level advisory lock: auto-released on COMMIT/ROLLBACK, so\n // no leak even if fn throws. Serializes core init across processes.\n await client.query(\"SELECT pg_advisory_xact_lock($1)\", [CORE_INIT_LOCK_KEY]);\n\n const ctx: CoreInitLockCtx = {\n exec: async (sql, params = []) => {\n const r = await client.query(sql, params);\n return { rows: r.rows, rowCount: r.rowCount };\n },\n getVersion: async () => {\n const r = await client.query<{ meta_value: string | number | bigint }>(\n \"SELECT meta_value FROM metadata WHERE meta_key = 'core_version'\",\n );\n return BigInt(r.rows[0]?.meta_value ?? 0);\n },\n setVersion: async (version) => {\n await client.query(\n `INSERT INTO metadata (meta_key, meta_value) VALUES ('core_version', $1)\n ON CONFLICT (meta_key) DO UPDATE SET meta_value = EXCLUDED.meta_value`,\n [version.toString()],\n );\n },\n };\n\n await fn(ctx);\n await client.query(\"COMMIT\");\n } catch (err) {\n await client.query(\"ROLLBACK\").catch(() => { });\n throw translatePgError(err);\n } finally {\n client.release();\n }\n }\n\n async query<T = any>(sql: string, params: unknown[] = []): Promise<DbQueryResult<T>> {\n const client = await this.pool.connect();\n try {\n const result = await client.query(sql, params);\n return {\n rows: result.rows as T[],\n rowCount: result.rowCount,\n };\n } catch (err) {\n throw translatePgError(err);\n } finally {\n client.release();\n }\n }\n\n async getClient(): Promise<PooledClient> {\n return await this.pool.connect() as PooledClient;\n }\n\n async getTransactionClient(): Promise<TxDatabase> {\n const client = await this.pool.connect();\n const tx = new PgTxDatabase(this.logger, this.config, this.pool, client);\n // Tx clients are short-lived. Share the parent's caches by reference so\n // (a) types loaded once at startup are visible inside transactions, and\n // (b) the SQL cache grows across the process rather than per-tx.\n tx.adoptCaches(this.columnTypes, this.sqlCache);\n return tx;\n }\n\n /** Internal: replace this instance's caches with shared references from\n * a parent. Used by PgDatabase.getTransactionClient. */\n adoptCaches(\n columnTypes: Map<string, Map<string, string>>,\n sqlCache: Map<string, string>,\n ): void {\n this.columnTypes = columnTypes;\n this.sqlCache = sqlCache;\n }\n\n async getLatestSnapshotTimestamp(): Promise<bigint> {\n const result = await this.query<{ meta_value: string | number | bigint }>(\"SELECT meta_value FROM metadata WHERE meta_key = 'latest_committed_ts'\");\n return BigInt(result.rows[0]?.meta_value ?? 0);\n }\n\n async updateLatestSnapshotTimestamp(commitTs: bigint): Promise<void> {\n await this.query(\n \"UPDATE metadata SET meta_value = $1 WHERE meta_key = 'latest_committed_ts' AND meta_value < $1\",\n [commitTs],\n );\n }\n\n async getMinSnapshotTimestamp(): Promise<bigint> {\n const result = await this.query<{ meta_value: string | number | bigint }>(\n \"SELECT meta_value FROM metadata WHERE meta_key = 'min_retained_ts'\",\n );\n return BigInt(result.rows[0]?.meta_value ?? 0);\n }\n\n async updateMinSnapshotTimestamp(ts: bigint): Promise<void> {\n await this.query(\n \"UPDATE metadata SET meta_value = $1 WHERE meta_key = 'min_retained_ts' AND meta_value < $1\",\n [ts],\n );\n }\n\n async getNextTimestamp(): Promise<bigint> {\n const result = await this.query<{ nextval: string | number | bigint }>(\n \"SELECT nextval('global_commit_ts') as nextval\"\n );\n return BigInt(result.rows[0]?.nextval ?? \"0\");\n }\n\n // Runs on the pool, not the tx client — intentionally. This keeps the\n // transaction window short, but it means `prevData` is not snapshot-\n // consistent with the eventual commitTs (a peer may have moved the row\n // between beginTs and this SELECT). That's safe because:\n // 1. The per-row CAS in pgApplyWrites is the real snapshot fence. If a\n // peer committed in our staleness window, the CAS sees a moved\n // commit_ts and throws OccConflictError → retry.\n // 2. The only window where CAS passes with \"stale\" prevData is when\n // the current row at our SELECT moment matches `expectedCommitTs`;\n // by construction nobody committed between our captured ts and now,\n // so prevData IS consistent with the ts we're CAS-ing against.\n // 3. The downstream consumer (overlap-checker.checkRowWriteAffectsRange)\n // is conservative on missing/odd prevData — \"unknown\" verdicts still\n // invalidate, so the worst case here is a spurious invalidation,\n // never a missed one.\n // Locked in by postgres.attach-prev-data.test.ts.\n async attachPrevDataToWriteSet(writeSet: WriteEntry[], readSet: ReadEntry[]): Promise<void> {\n // Index prior point reads by (table, id) so we can carry the\n // read-time commit_ts onto matching writes. Iterate in reverse so the\n // last read wins (handles handlers that read the same row twice).\n const readTsByKey = new Map<string, bigint | null>();\n for (let i = readSet.length - 1; i >= 0; i--) {\n const r = readSet[i];\n if (r.kind !== \"point\" || r.expectedCommitTs === undefined) continue;\n\n const key = `${r.table} ${r.id}`;\n if (!readTsByKey.has(key)) readTsByKey.set(key, r.expectedCommitTs);\n }\n\n const byTable = new Map<string, WriteEntry[]>();\n for (const entry of writeSet) {\n if (entry.op !== \"update\" && entry.op !== \"delete\") continue;\n\n const bucket = byTable.get(entry.table);\n if (bucket) bucket.push(entry);\n else byTable.set(entry.table, [entry]);\n }\n if (byTable.size === 0) return;\n\n for (const [table, entries] of byTable) {\n const ids = entries.map((e) => e.id);\n const placeholders = ids.map((_, i) => `$${i + 1}`).join(\", \");\n // No FOR UPDATE — the per-row CAS on the subsequent UPDATE/DELETE is\n // the fence. If a peer mutated the row since our read, the CAS will\n // see a stale expected commit_ts and throw OCC.\n const sql = `SELECT * FROM ${table} WHERE id IN (${placeholders})`;\n const result = await this.query<Record<string, unknown>>(sql, ids);\n\n const byId = new Map<string, Record<string, unknown>>();\n for (const row of result.rows) {\n byId.set(String(row.id), row);\n }\n for (const entry of entries) {\n const row = byId.get(entry.id);\n const key = `${entry.table} ${entry.id}`;\n const priorReadTs = readTsByKey.get(key);\n\n if (row) {\n entry.prevData = row;\n // Prefer the read-time ts so read-then-update patterns get\n // beginTs-level isolation; fall back to current row ts for\n // blind writes (no prior read).\n entry.expectedCommitTs = priorReadTs !== undefined\n ? priorReadTs\n : BigInt(row.commit_ts as string | number | bigint);\n } else if (priorReadTs !== undefined) {\n // Row gone now, but we had read it earlier: carry the read ts —\n // the CAS will then mismatch (row deleted) and throw OCC.\n entry.expectedCommitTs = priorReadTs;\n }\n // else: neither a prior read nor a current row → leave undefined\n // so CAS misses and surfaces OCC.\n }\n }\n }\n\n // Prepare BEFORE the conflict scan: each write is a CAS against the row's\n // commit_ts, so any concurrent peer that touched the row produces a\n // 0-row-affected UPDATE/DELETE → OccConflictError. Inserts surface as\n // PK violations, also translated to OCC.\n async prepareCommitWrites(\n readSet: ReadEntry[],\n writeSet: WriteEntry[],\n commitTs: LazyCommitTsParam,\n ): Promise<PreparedQueries> {\n\n const result: PreparedQuery[] = [];\n\n await this.pgPrepareBatchWrites(result, writeSet, commitTs);\n await this.pgPrepareCommitLogs(result, writeSet, commitTs);\n\n const hasRangeRead = readSet.some(r => r.kind === \"range\");\n if (!hasRangeRead) {\n const validateReads = this.pgPrepareValidatePointReadsByCommitTs(readSet, writeSet);\n return { commits: result, validateReadPoints: validateReads, hasRangeRead: false };\n }\n\n // has atleast one range read so use scan, prepare data. tableId is derived\n // (and interned) by mapReadSetToRaw, so the Set dedups per table by identity.\n const rawReadSet = mapReadSetToRaw(readSet);\n const tableIds = [...new Set(rawReadSet.map(r => r.tableId))];\n\n return { commits: result, hasRangeRead: true, tableIds: tableIds, rawReadSet: rawReadSet };\n };\n\n private async pgPrepareBatchWrites(\n result: PreparedQuery[],\n writeSet: WriteEntry[],\n commitTs: LazyCommitTsParam,\n ) {\n let i = 0;\n let counter = 0;\n while (i < writeSet.length) {\n counter++;\n const { table, op } = writeSet[i];\n let j = i + 1;\n while (j < writeSet.length && writeSet[j].table === table && writeSet[j].op === op) j++;\n\n const batch = writeSet.slice(i, j);\n if (op === \"insert\") await this.pgPrepareInsertBatch(result, table, batch, commitTs);\n else if (op === \"update\") await this.pgPrepareUpdateBatch(result, table, batch, commitTs);\n else await this.pgPrepareDeleteBatch(result, table, batch);\n i = j;\n\n if (counter % 50 == 0)\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n\n // N=1 INSERT fast-path: identical SQL shape to the original per-row\n // code, no unnest overhead. PG infers param types from the column\n // context directly, so we don't need pg_attribute either.\n private pgPrepareInsertSingle(\n result: PreparedQuery[],\n table: string,\n entry: WriteEntry,\n commitTs: LazyCommitTsParam,\n ) {\n const data = { ...(entry.data ?? {}), commit_ts: commitTs } as Record<string, unknown>;\n const cols = Object.keys(data);\n const placeholders = cols.map((_, i) => `$${i + 1}`);\n result.push({\n sql: `INSERT INTO ${table} (${cols.join(\", \")}) VALUES (${placeholders.join(\", \")})`,\n params: Object.values(data),\n operation: \"insertSingle\",\n table: table,\n entryId: entry.id,\n })\n }\n\n private async pgPrepareInsertBatch(\n result: PreparedQuery[],\n table: string,\n entries: WriteEntry[],\n commitTs: LazyCommitTsParam,\n ) {\n if (entries.length === 0) return;\n\n const types = this.getColumnTypes(table);\n if (!types) {\n this.logger.warn({ table }, \"No column types found, falling back to non-batched writes for insert\");\n let counter = 0;\n for (const entry of entries) {\n counter++;\n this.pgPrepareInsertSingle(result, table, entry, commitTs);\n\n if (counter % 50 == 0) {\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n return;\n }\n\n // Sub-bucket by sorted column signature: writes with different optional\n // columns can't share a statement.\n const groups = new Map<string, { cols: string[]; rows: WriteEntry[] }>();\n for (const entry of entries) {\n const cols = [...Object.keys(entry.data ?? {}), 'commit_ts'].sort();\n const key = cols.join(\"|\");\n let g = groups.get(key);\n if (!g) { g = { cols, rows: [] }; groups.set(key, g); }\n g.rows.push(entry);\n }\n\n let counter = 0;\n for (const { cols, rows } of groups.values()) {\n counter++;\n\n const sql = this.buildOrGetInsertSql(table, types, cols);\n // Params layout: one carrier array per column, each of length N.\n const params: unknown[][] = cols.map(() => new Array(rows.length));\n for (let r = 0; r < rows.length; r++) {\n const data = { ...(rows[r].data ?? {}), commit_ts: commitTs } as Record<string, unknown>;\n for (let c = 0; c < cols.length; c++) {\n const pgType = types.get(cols[c].toLowerCase()) ?? \"text\";\n params[c][r] = encodeForCarrier(data[cols[c]], pgType);\n }\n }\n\n if (counter % 50 == 0) {\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n\n result.push({\n sql: sql,\n params: params,\n operation: \"insertBatch\",\n table: table,\n })\n }\n }\n\n // N=1 UPDATE fast-path: same shape as the original per-row CAS.\n private pgPrepareUpdateSingle(\n result: PreparedQuery[],\n table: string,\n entry: WriteEntry,\n commitTs: LazyCommitTsParam,\n ) {\n const data = { ...(entry.data ?? {}), commit_ts: commitTs } as Record<string, unknown>;\n const cols = Object.keys(data);\n const sets = cols.map((col, i) => `${col} = $${i + 1}`);\n const values = Object.values(data);\n const idP = `$${values.length + 1}`;\n const tsP = `$${values.length + 2}`;\n const expected = entry.expectedCommitTs ?? -1n;\n\n result.push({\n sql: `UPDATE ${table} SET ${sets.join(\", \")} WHERE id = ${idP} AND commit_ts = ${tsP}`,\n params: [...values, entry.id, expected],\n operation: \"updateSingle\",\n table: table,\n entryId: entry.id,\n expectedTs: expected,\n })\n }\n\n private async pgPrepareUpdateBatch(\n result: PreparedQuery[],\n table: string,\n entries: WriteEntry[],\n commitTs: LazyCommitTsParam,\n ) {\n if (entries.length === 0) return;\n\n const types = this.getColumnTypes(table);\n if (!types) {\n this.logger.warn({ table }, \"No column types found, falling back to non-batched writes for update\");\n\n let counter = 0;\n for (const entry of entries) {\n counter++;\n this.pgPrepareUpdateSingle(result, table, entry, commitTs);\n\n if (counter % 50 == 0) {\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n return;\n }\n\n const groups = new Map<string, { cols: string[]; rows: WriteEntry[] }>();\n for (const entry of entries) {\n const cols = [...Object.keys(entry.data ?? {}), 'commit_ts'].sort();\n const key = cols.join(\"|\");\n let g = groups.get(key);\n if (!g) { g = { cols, rows: [] }; groups.set(key, g); }\n g.rows.push(entry);\n }\n\n let counter = 0;\n for (const { cols, rows } of groups.values()) {\n counter++;\n\n const sql = this.buildOrGetUpdateSql(table, types, cols);\n const idType = types.get(\"id\") ?? \"text\";\n const idArr: unknown[] = new Array(rows.length);\n const expArr: unknown[] = new Array(rows.length);\n const colArrs: unknown[][] = cols.map(() => new Array(rows.length));\n for (let r = 0; r < rows.length; r++) {\n const entry = rows[r];\n const data = { ...(entry.data ?? {}), commit_ts: commitTs } as Record<string, unknown>;\n const expected = entry.expectedCommitTs ?? -1n;\n idArr[r] = encodeForCarrier(entry.id, idType);\n expArr[r] = encodeForCarrier(expected, \"bigint\");\n for (let c = 0; c < cols.length; c++) {\n const pgType = types.get(cols[c].toLowerCase()) ?? \"text\";\n colArrs[c][r] = encodeForCarrier(data[cols[c]], pgType);\n }\n }\n\n if (counter % 50 == 0) {\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n\n const params: unknown[][] = [idArr, expArr, ...colArrs];\n result.push({\n sql: sql,\n params: params,\n operation: \"updateBatch\",\n table: table,\n entries: rows,\n })\n }\n }\n\n // N=1 DELETE fast-path: original per-row CAS shape.\n private pgPrepareDeleteSingle(\n result: PreparedQuery[],\n table: string,\n entry: WriteEntry,\n ) {\n const expected = entry.expectedCommitTs ?? -1n;\n result.push({\n sql: `DELETE FROM ${table} WHERE id = $1 AND commit_ts = $2`,\n params: [entry.id, expected],\n operation: \"deleteSingle\",\n table: table,\n entryId: entry.id,\n expectedTs: expected,\n });\n }\n\n private async pgPrepareDeleteBatch(\n result: PreparedQuery[],\n table: string,\n entries: WriteEntry[],\n ) {\n if (entries.length === 0) return;\n\n const types = this.getColumnTypes(table);\n if (!types) {\n this.logger.warn({ table }, \"No column types found, falling back to non-batched writes for delete\");\n\n let counter = 0;\n for (const entry of entries) {\n counter++;\n this.pgPrepareDeleteSingle(result, table, entry);\n\n if (counter % 50 == 0) {\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n return;\n }\n\n const sql = this.buildOrGetDeleteSql(table, types);\n const idType = types.get(\"id\") ?? \"text\";\n const idArr: unknown[] = new Array(entries.length);\n const expArr: unknown[] = new Array(entries.length);\n\n let counter = 0;\n for (let i = 0; i < entries.length; i++) {\n counter++;\n const entry = entries[i];\n const expected = entry.expectedCommitTs ?? -1n;\n idArr[i] = encodeForCarrier(entry.id, idType);\n expArr[i] = encodeForCarrier(expected, \"bigint\");\n\n if (counter % 50 == 0) {\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n result.push({\n sql: sql,\n params: [idArr, expArr],\n operation: \"deleteBatch\",\n table: table,\n entries: entries,\n });\n }\n\n private buildOrGetInsertSql(table: string, types: Map<string, string>, cols: string[]): string {\n const key = `i:${table}:${cols.join(\",\")}`;\n let sql = this.sqlCache.get(key);\n if (sql) return sql;\n\n const placeholders = cols.map((c, i) => {\n const pgType = types.get(c.toLowerCase()) ?? \"text\";\n return `$${i + 1}::${pgCarrierType(pgType)}`;\n });\n const aliases = cols.map((_, i) => `c${i}`);\n const selectExprs = cols.map((c, i) => {\n const pgType = types.get(c.toLowerCase()) ?? \"text\";\n return pgSelectExpr(aliases[i], pgType);\n });\n sql = `INSERT INTO ${table} (${cols.join(\", \")}) ` +\n `SELECT ${selectExprs.join(\", \")} ` +\n `FROM unnest(${placeholders.join(\", \")}) AS u(${aliases.join(\", \")})`;\n this.sqlCache.set(key, sql);\n return sql;\n }\n\n private buildOrGetUpdateSql(table: string, types: Map<string, string>, cols: string[]): string {\n const key = `u:${table}:${cols.join(\",\")}`;\n let sql = this.sqlCache.get(key);\n if (sql) return sql;\n\n const idType = types.get(\"id\") ?? \"text\";\n const idCarrier = `$1::${pgCarrierType(idType)}`;\n const expCarrier = `$2::bigint[]`;\n const dataCarriers = cols.map((c, i) => {\n const pgType = types.get(c.toLowerCase()) ?? \"text\";\n return `$${i + 3}::${pgCarrierType(pgType)}`;\n });\n const aliases = [\"id_c\", \"exp_c\", ...cols.map((_, i) => `c${i}`)];\n const setClause = cols\n .map((c, i) => {\n const pgType = types.get(c.toLowerCase()) ?? \"text\";\n return `${c} = ${pgSelectExpr(`u.${aliases[i + 2]}`, pgType)}`;\n })\n .join(\", \");\n sql =\n `UPDATE ${table} AS t SET ${setClause} ` +\n `FROM unnest(${idCarrier}, ${expCarrier}, ${dataCarriers.join(\", \")}) AS u(${aliases.join(\", \")}) ` +\n `WHERE t.id = u.id_c AND t.commit_ts = u.exp_c ` +\n `RETURNING t.id AS id`;\n this.sqlCache.set(key, sql);\n return sql;\n }\n\n private buildOrGetDeleteSql(table: string, types: Map<string, string>): string {\n const key = `d:${table}`;\n let sql = this.sqlCache.get(key);\n if (sql) return sql;\n const idType = types.get(\"id\") ?? \"text\";\n sql =\n `DELETE FROM ${table} AS t ` +\n `USING unnest($1::${pgCarrierType(idType)}, $2::bigint[]) AS u(id_c, exp_c) ` +\n `WHERE t.id = u.id_c AND t.commit_ts = u.exp_c ` +\n `RETURNING t.id AS id`;\n this.sqlCache.set(key, sql);\n return sql;\n }\n\n private async pgPrepareCommitLogs(\n result: PreparedQuery[],\n writeEntries: WriteEntry[],\n commitTs: LazyCommitTsParam,\n ) {\n if (writeEntries.length === 0) return;\n\n // unnest with column-typed arrays: 4 placeholders regardless of N\n // (vs 4N for multi-row VALUES). All four columns have fixed driver-side\n // types so no per-table metadata lookup is needed.\n const ids: Buffer[] = new Array(writeEntries.length);\n const tss: LazyCommitTsParam[] = new Array(writeEntries.length);\n const tableIds: Buffer[] = new Array(writeEntries.length);\n const datas: Buffer[] = new Array(writeEntries.length);\n\n let counter = 0;\n for (let i = 0; i < writeEntries.length; i++) {\n counter++;\n\n const entry = writeEntries[i];\n ids[i] = toBufferView(normalizeIdToBytes(entry.id));\n tss[i] = commitTs;\n tableIds[i] = toBufferView(entry.tableId);\n datas[i] = toBufferView(utf8Encode(supaliveStringify(entry)));\n\n if (counter % 50 == 0) {\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n }\n\n result.push({\n sql: `INSERT INTO commit_logs (id, ts, table_id, data) ` +\n `SELECT * FROM unnest($1::bytea[], $2::bigint[], $3::bytea[], $4::bytea[])`,\n params: [ids, tss, tableIds, datas],\n table: 'commit_logs',\n operation: 'commit_logs',\n })\n }\n\n /**\n * Point-only OCC check. For each genuine point read, compare the row's\n * current commit_ts against the value captured at read time. A `null`\n * expectedCommitTs means \"row didn't exist when we read\"; any row found\n * now is a conflict. Reads whose row is being written in this same txn\n * are skipped — the CAS UPDATE/DELETE already validated those.\n */\n private pgPrepareValidatePointReadsByCommitTs(\n readSet: ReadEntry[],\n writeSet: WriteEntry[],\n ): PreparedQuery[] {\n const result: PreparedQuery[] = [];\n\n // Skip reads whose row is being mutated in this same tx — CAS already\n // fenced them. We dedupe per (table, id) so duplicate reads of the same\n // row don't cause extra fetches.\n const writeKeys = new Set<string>();\n for (const w of writeSet) {\n writeKeys.add(`${w.table} ${w.id}`);\n }\n\n const byTable = new Map<string, PendingPointEntry[]>();\n const seen = new Set<string>();\n\n for (const entry of readSet) {\n if (entry.kind !== \"point\") continue;\n\n const key = `${entry.table} ${entry.id}`;\n if (writeKeys.has(key) || seen.has(key)) continue;\n\n seen.add(key);\n // expectedCommitTs === undefined → no commit_ts captured at read time\n // (synthetic or pre-migration). Skip — we cannot CAS-validate it. This\n // path should only fire when callers construct PointReads externally.\n if (entry.expectedCommitTs === undefined) continue;\n\n const bucket = byTable.get(entry.table);\n if (bucket) bucket.push({ entry, key });\n else byTable.set(entry.table, [{ entry, key }]);\n }\n\n for (const [table, pendings] of byTable) {\n const ids = pendings.map((p) => p.entry.id);\n const placeholders = ids.map((_, i) => `$${i + 1}`).join(\", \");\n\n result.push({\n sql: `SELECT id, commit_ts FROM ${table} WHERE id IN (${placeholders})`,\n params: ids,\n table: table,\n operation: \"pointReadValidation\",\n pendingsEntry: pendings,\n });\n }\n\n return result;\n }\n\n async commitWrites(\n beginTs: bigint,\n queries: PreparedQueries,\n commitTs: bigint,\n ): Promise<{ success: boolean }> {\n await this.pgApplyWrites(queries.commits);\n\n // Fast path: when the readSet has no range reads, we don't need a\n // commit-log scan at all — point reads validate via per-row commit_ts,\n // and writes already self-validated via CAS above. This is the dominant\n // shape (most mutations target known ids) and eliminates the heaviest\n // OCC cost in the previous design.\n if (!queries.hasRangeRead) {\n if ((queries.validateReadPoints?.length ?? 0) > 0) {\n await this.validatePointReadsByCommitTs(queries.validateReadPoints ?? []);\n }\n return { success: true };\n }\n\n // Slow path: at least one range read present. Fall back to the\n // commit-log scan so phantom inserts and predicate transitions-in are\n // still caught. Point reads in the same set are covered by the same\n // scan, so no separate validation is needed.\n const logs = await this.getCommitLogsSinceTs(beginTs, commitTs, queries.tableIds ?? []);\n const hasConflict = checkLogsAffectReadSet(logs, queries.rawReadSet ?? []);\n if (hasConflict) {\n throw new OccConflictError(\n `Conflict detected: overlapping commit logs since ${beginTs}`,\n { logs }\n );\n }\n\n return { success: true };\n };\n\n // Walks the writeSet in order and coalesces consecutive entries with the\n // same (table, op) into one batched statement. Order is preserved across\n // batch boundaries so replace-patterns (delete-then-insert of the same\n // id) behave identically to the per-row code path. Single-row batches\n // take a direct INSERT/UPDATE/DELETE fast-path to avoid the unnest+join\n // overhead, which matters under high contention.\n private async pgApplyWrites(result: PreparedQuery[]): Promise<void> {\n for (const q of result) {\n switch (q.operation) {\n case 'insertSingle': {\n try {\n await this.query(q.sql, q.params);\n } catch (err) {\n if (err && typeof err === \"object\" && (err as { code?: string }).code === \"23505\") {\n throw new OccConflictError(\n `Conflict: insert into ${q.table}/${q.entryId} hit duplicate key`,\n err,\n );\n }\n throw err;\n }\n break;\n }\n case 'insertBatch': {\n try {\n await this.query(q.sql, q.params);\n } catch (err) {\n if (err && typeof err === \"object\" && (err as { code?: string }).code === \"23505\") {\n const detail = (err as { detail?: string }).detail;\n const idMatch = detail?.match(/Key \\([^)]+\\)=\\(([^)]+)\\)/);\n const failedId = idMatch?.[1];\n throw new OccConflictError(\n failedId !== undefined\n ? `Conflict: insert into ${q.table}/${failedId} hit duplicate key`\n : `Conflict: batch insert into ${q.table} hit duplicate key`,\n err,\n );\n }\n throw err;\n }\n break;\n }\n case 'updateSingle': {\n const result = await this.query(q.sql, q.params);\n if ((result.rowCount ?? 0) === 0) {\n throw new OccConflictError(\n `Conflict: CAS update of ${q.table}/${q.entryId} (expected commit_ts=${q.expectedTs}) matched no rows`,\n { table: q.table, id: q.entryId, expectedTs: q.expectedTs },\n );\n }\n break;\n }\n case 'updateBatch': {\n const result = await this.query<{ id: string }>(q.sql, q.params);\n\n const returned = new Set<string>();\n for (const r of result.rows) {\n returned.add(r.id);\n }\n for (const entry of q.entries!) {\n if (!returned.has(entry.id)) {\n const expected = entry.expectedCommitTs ?? -1n;\n throw new OccConflictError(\n `Conflict: CAS update of ${q.table}/${entry.id} (expected commit_ts=${expected}) matched no rows`,\n { table: q.table, id: q.entryId, expectedTs: q.expectedTs },\n );\n }\n }\n break;\n }\n case 'deleteSingle': {\n const result = await this.query(q.sql, q.params);\n if ((result.rowCount ?? 0) === 0) {\n throw new OccConflictError(\n `Conflict: CAS delete of ${q.table}/${q.entryId} (expected commit_ts=${q.expectedTs}) matched no rows`,\n { table: q.table, id: q.entryId, expectedTs: q.expectedTs },\n );\n }\n break;\n }\n case 'deleteBatch': {\n const result = await this.query<{ id: string }>(q.sql, q.params);\n const returned = new Set<string>();\n for (const r of result.rows) returned.add(r.id);\n for (const entry of q.entries!) {\n if (!returned.has(entry.id)) {\n const expected = entry.expectedCommitTs ?? -1n;\n throw new OccConflictError(\n `Conflict: CAS delete of ${q.table}/${entry.id} (expected commit_ts=${expected}) matched no rows`,\n { table: q.table, id: entry.id, expected },\n );\n }\n }\n break;\n }\n case 'commit_logs': {\n await this.query(q.sql, q.params);\n break;\n }\n default:\n throw new Error(\"Unhandled Postgres Operation: \" + q.operation);\n\n }\n }\n }\n\n /**\n * Point-only OCC check. For each genuine point read, compare the row's\n * current commit_ts against the value captured at read time. A `null`\n * expectedCommitTs means \"row didn't exist when we read\"; any row found\n * now is a conflict. Reads whose row is being written in this same txn\n * are skipped — the CAS UPDATE/DELETE already validated those.\n */\n private async validatePointReadsByCommitTs(\n queries: PreparedQuery[],\n ): Promise<void> {\n\n for (const q of queries) {\n const result = await this.query<{ id: string; commit_ts: string | number | bigint }>(q.sql, q.params);\n\n const currentById = new Map<string, bigint>();\n for (const row of result.rows) {\n currentById.set(row.id, BigInt(row.commit_ts));\n }\n\n for (const { entry } of (q.pendingsEntry ?? [])) {\n const current = currentById.get(entry.id);\n const expected = entry.expectedCommitTs;\n\n if (expected === null) {\n // We read \"no row\"; conflict if a row now exists.\n if (current !== undefined) {\n throw new OccConflictError(\n `Conflict: row ${entry.table}/${entry.id} was inserted concurrently`,\n { table: entry.table, id: entry.id }\n );\n }\n } else {\n if (current === undefined) {\n throw new OccConflictError(\n `Conflict: row ${entry.table}/${entry.id} was deleted concurrently`,\n { table: entry.table, id: entry.id, expected }\n );\n }\n if (current !== expected) {\n throw new OccConflictError(\n `Conflict: row ${entry.table}/${entry.id} changed from ts=${expected} to ts=${current}`,\n { table: entry.table, id: entry.id, expected, current }\n );\n }\n }\n }\n }\n }\n\n async pruneCommitLogsBefore(ts: bigint): Promise<bigint> {\n const result = await this.query(\n `DELETE FROM commit_logs WHERE ts < $1`,\n [ts]\n );\n return BigInt(result.rowCount ?? 0);\n }\n\n async getMinCommitLogTs(): Promise<bigint | null> {\n const result = await this.query<{ min: string | number | bigint | null }>(\n `SELECT min(ts) AS min FROM commit_logs`,\n );\n const min = result.rows[0]?.min;\n return min === null || min === undefined ? null : BigInt(min);\n }\n\n async getCommitLogsBetweenTs(beginTs: bigint, endTs: bigint, tableIds: Uint8Array[]): Promise<CommitLogEntry[]> {\n if (tableIds.length === 0) return [];\n\n const placeholders = tableIds.map((_, i) => `$${i + 3}`).join(\", \");\n const sql = `SELECT id, ts, table_id, data FROM commit_logs WHERE ts >= $1 AND ts < $2 AND table_id IN (${placeholders})`;\n\n const result = await this.query<{ id: unknown; ts: string | number | bigint; table_id: unknown; data: unknown }>(\n sql,\n [beginTs, endTs, ...tableIds.map(toBufferView)]\n );\n\n return result.rows.map((row) => ({\n id: normalizeToBytes(row.id),\n ts: BigInt(row.ts),\n tableId: normalizeToBytes(row.table_id),\n data: normalizeToBytes(row.data),\n }));\n }\n\n async hasCommitForPoints(\n beginTs: bigint,\n endTs: bigint,\n points: { tableId: Uint8Array; id: Uint8Array }[],\n ): Promise<boolean> {\n if (points.length === 0) return false;\n\n // (table_id, id) IN ((),(),...) — a row-constructor tuple list. Each pair\n // is an index seek on commit_logs_by_table_and_id (table_id, id, ts) with\n // the ts range applied, so this is boolean-only: no data blobs, LIMIT 1.\n const tuples = points\n .map((_, i) => `($${i * 2 + 3}, $${i * 2 + 4})`)\n .join(\", \");\n const sql =\n `SELECT 1 FROM commit_logs ` +\n `WHERE ts >= $1 AND ts < $2 AND (table_id, id) IN (${tuples}) LIMIT 1`;\n\n const params: unknown[] = [beginTs, endTs];\n for (const p of points) {\n params.push(toBufferView(p.tableId), toBufferView(p.id));\n }\n\n const result = await this.query(sql, params);\n return result.rows.length > 0;\n }\n\n async getCommitLogsSinceTs(sinceTs: bigint, excludeTs: bigint, tableIds: Uint8Array[]): Promise<CommitLogEntry[]> {\n if (tableIds.length === 0) return [];\n\n const placeholders = tableIds.map((_, i) => `$${i + 3}`).join(\", \");\n const sql = `SELECT id, ts, table_id, data FROM commit_logs WHERE ts >= $1 AND ts <> $2 AND table_id IN (${placeholders})`;\n\n const result = await this.query<{ id: unknown; ts: string | number | bigint; table_id: unknown; data: unknown }>(\n sql,\n [sinceTs, excludeTs, ...tableIds.map(toBufferView)]\n );\n\n return result.rows.map((row) => ({\n id: normalizeToBytes(row.id),\n ts: BigInt(row.ts),\n tableId: normalizeToBytes(row.table_id),\n data: normalizeToBytes(row.data),\n }));\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n}\n\nexport class PgTxDatabase extends PgDatabase {\n constructor(logger: Logger, config: PoolConfig, pool: Pool, client: PoolClient) {\n super(logger, config, pool);\n this.client = client;\n }\n\n private client: PoolClient;\n\n override async query<T = any>(sql: string, params: unknown[] = []): Promise<DbQueryResult<T>> {\n try {\n const result = await this.client.query(sql, params);\n return {\n rows: result.rows as T[],\n rowCount: result.rowCount,\n };\n } catch (err) {\n throw translatePgError(err);\n }\n }\n\n async begin(): Promise<void> {\n await this.client.query(\"BEGIN\");\n }\n\n async commit(): Promise<void> {\n await this.client.query(\"COMMIT\");\n }\n\n async rollback(): Promise<void> {\n await this.client.query(\"ROLLBACK\");\n }\n\n release(): void {\n this.client.release();\n }\n}\n\n/**\n * Returns the unnest carrier array type for a column of `pgType`.\n * Scalars ride as their own array type so PG infers cleanly without an\n * extra cast on the SELECT/SET side. Array and jsonb columns can't ride\n * as their own array type (ragged 2D arrays aren't permitted, and the\n * driver can't tag jsonb sub-elements correctly inside a typed array),\n * so they get the universal `text[]` carrier with an explicit cast in\n * the SELECT/SET clause. Bytea rides natively as `bytea[]`.\n */\nfunction pgCarrierType(pgType: string): string {\n const base = pgType.trim().toLowerCase();\n if (base === \"bytea\") return \"bytea[]\";\n if (base === \"jsonb\" || base === \"json\") return \"text[]\";\n if (base.endsWith(\"[]\")) return \"text[]\";\n return `${pgType}[]`;\n}\n\n/** SELECT/SET expression for an unnest alias of a column with `pgType`.\n * When the carrier matches the column type (scalar / bytea), no cast is\n * needed and the alias passes through. When the carrier is text[] (jsonb\n * or array column), we cast back to the declared type. */\nfunction pgSelectExpr(alias: string, pgType: string): string {\n const base = pgType.trim().toLowerCase();\n if (base === \"jsonb\" || base === \"json\" || base.endsWith(\"[]\")) {\n return `(${alias})::${pgType}`;\n }\n return alias;\n}\n\n/**\n * Encodes a JS runtime value for transport via the chosen unnest carrier.\n * For scalar columns the pg driver handles type coercion directly (bigint,\n * number, boolean, Date, etc. all serialize cleanly). For jsonb columns we\n * stringify; for array columns we build a PG array literal. For bytea\n * columns we forward the bytes.\n *\n * Codecs declared by the upstream schema layer run before this point, so\n * `value` is already in the driver-side shape for its declared column.\n */\nfunction encodeForCarrier(value: unknown, pgTypeBase: string): unknown {\n if (value === null || value === undefined) return null;\n if (pgTypeBase === \"bytea\") {\n return toBufferView(normalizeToBytes(value));\n }\n if (pgTypeBase === \"jsonb\" || pgTypeBase === \"json\") {\n return typeof value === \"string\" ? value : supaliveStringify(value);\n }\n if (pgTypeBase.endsWith(\"[]\")) {\n if (!Array.isArray(value)) {\n throw new Error(`Expected array for column of type ${pgTypeBase}, got ${typeof value}`);\n }\n return pgArrayLiteral(value);\n }\n return value;\n}\n\n/** Serializes a JS array to a Postgres array literal `{a,b,c}`. Strings\n * are double-quoted with backslash/quote escaping; primitives stringify\n * directly; null becomes the literal NULL. */\nfunction pgArrayLiteral(arr: unknown[]): string {\n const parts: string[] = new Array(arr.length);\n for (let i = 0; i < arr.length; i++) {\n const v = arr[i];\n if (v === null || v === undefined) {\n parts[i] = \"NULL\";\n } else if (typeof v === \"string\") {\n parts[i] = `\"${v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"`;\n } else if (typeof v === \"number\" || typeof v === \"bigint\" || typeof v === \"boolean\") {\n parts[i] = String(v);\n } else {\n const s = supaliveStringify(v);\n parts[i] = `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"`;\n }\n }\n return `{${parts.join(\",\")}}`;\n}\n\n/**\n * Postgres deadlock_detected (40P01) is transient and semantically equivalent\n * to an OCC conflict from the mutation runner's perspective: re-execute the\n * handler with a fresh snapshot. Wrapping here lets realtime_db.ts stay\n * driver-agnostic — its retry loop already handles OccConflictError.\n */\nfunction translatePgError(err: unknown): unknown {\n if (err && typeof err === \"object\" && (err as { code?: string }).code === \"40P01\") {\n return new OccConflictError(\"deadlock detected\", err);\n }\n return err;\n}\n\n// PG: when a sub-path is set, scope the target via `jsonb_path_query_first`\n// (returns the value at the path, or NULL → falsy in WHERE) before the\n// containment / array-membership check. Cast bound text to jsonb so the\n// same code path works for both `json` and `jsonb` columns.\nfunction pgScopedTarget(col: string, pathIndex: number | undefined): string {\n return pathIndex === undefined\n ? col\n : `jsonb_path_query_first(${col}::jsonb, $${pathIndex}::jsonpath)`;\n}\n\nexport const pgSql: SqlBuilder = {\n placeholder: (i) => `$${i}`,\n like: (col, i) => `${col} LIKE $${i}`,\n ilike: (col, i) => `${col} ILIKE $${i}`,\n icontains: (col, i) => `${col} ILIKE $${i}`,\n jsonContains: (col, i, opts) =>\n `${pgScopedTarget(col, opts?.pathIndex)} @> $${i}::jsonb`,\n jsonContainedBy: (col, i, opts) =>\n `${pgScopedTarget(col, opts?.pathIndex)} <@ $${i}::jsonb`,\n jsonHasKey: (col, i, opts) => {\n if (opts?.pathCount && opts.pathCount > 0) {\n const joiner = opts.mode === \"all\" ? \" AND \" : \" OR \";\n const parts: string[] = [];\n for (let k = 0; k < opts.pathCount; k++) {\n parts.push(`jsonb_path_exists(${col}, $${i + k}::jsonpath)`);\n }\n return `(${parts.join(joiner)})`;\n }\n return opts?.raw\n ? `jsonb_path_exists(${col}, $${i}::jsonpath)`\n : `${col} ? $${i}`;\n },\n jsonArrayContains: (col, i, opts) =>\n `${pgScopedTarget(col, opts?.pathIndex)} @> jsonb_build_array($${i}::jsonb)`,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { a as unwrapZodType, i as normalizeCodec } from "./table-id-DyrJBKAQ.js";
|
|
2
|
+
import { c as traceDbCall, t as QueryBuilder } from "./query-LJranz0c.js";
|
|
3
|
+
import { i as EphemeralFlushError, l as createTrackedEphemeralReader, s as createBufferedEphemeralWriter, t as EMPTY_EPHEMERAL_KEYS } from "./ephemeral-CkidSG9y.js";
|
|
4
|
+
import { c as LazyCommitTsParam } from "./overlap-checker-DseE1Lvh.js";
|
|
5
|
+
import { l as OccConflictError } from "./types_db-DPMeNETt.js";
|
|
6
|
+
import { z as z$1 } from "zod";
|
|
7
|
+
import { v4 } from "uuid";
|
|
8
|
+
import { ExponentialBackoff, handleWhen, retry } from "cockatiel";
|
|
9
|
+
import pino from "pino";
|
|
10
|
+
import "pino-pretty";
|
|
11
|
+
//#region src/db/context.ts
|
|
12
|
+
/**
|
|
13
|
+
* Materialize a schema's runtime {@link Model}: column-name mapping, per-column
|
|
14
|
+
* coarse type tags, and the codec used to (de)serialize each column.
|
|
15
|
+
*
|
|
16
|
+
* Codecs are resolved via {@link normalizeCodec}, which supplies a sensible
|
|
17
|
+
* default per Zod type (e.g. a {@link dateCodec} for `z.date()`, so `Date`
|
|
18
|
+
* columns round-trip automatically) while letting a user-supplied codec in
|
|
19
|
+
* `schema.columnsOptions.codecs` override it — partially or fully.
|
|
20
|
+
*/
|
|
21
|
+
function buildModel(schema) {
|
|
22
|
+
const shape = schema.schema.shape;
|
|
23
|
+
const customColumns = schema.columns;
|
|
24
|
+
const columns = {};
|
|
25
|
+
const types = {};
|
|
26
|
+
const codecs = {};
|
|
27
|
+
const schemaCodecs = schema.columnsOptions.codecs ?? {};
|
|
28
|
+
for (const key of Object.keys(shape)) {
|
|
29
|
+
columns[key] = customColumns?.[key] ?? key;
|
|
30
|
+
const field = shape[key];
|
|
31
|
+
const baseField = unwrapZodType(field);
|
|
32
|
+
if (baseField instanceof z$1.ZodNumber) types[key] = "integer";
|
|
33
|
+
else if (baseField instanceof z$1.ZodString) types[key] = "string";
|
|
34
|
+
else if (baseField instanceof z$1.ZodBoolean) types[key] = "boolean";
|
|
35
|
+
else if (baseField instanceof z$1.ZodDate) types[key] = "date";
|
|
36
|
+
else if (baseField instanceof z$1.ZodArray) types[key] = "array";
|
|
37
|
+
else types[key] = "json";
|
|
38
|
+
codecs[key] = normalizeCodec(schemaCodecs[key], baseField);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
table: schema.table,
|
|
42
|
+
tableId: schema.tableId,
|
|
43
|
+
columns,
|
|
44
|
+
reverseColumns: Object.fromEntries(Object.entries(columns).map(([k, v]) => [v, k])),
|
|
45
|
+
types,
|
|
46
|
+
codecs
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
var TxContext = class {
|
|
50
|
+
beginTs;
|
|
51
|
+
db;
|
|
52
|
+
readSet = [];
|
|
53
|
+
writeSet = [];
|
|
54
|
+
/**
|
|
55
|
+
* Ephemeral keys read by this context, in first-read order and de-duplicated.
|
|
56
|
+
* Kept separate from {@link readSet} on purpose: an ephemeral key has no
|
|
57
|
+
* snapshot ts and no commit log, so letting it into the read set would feed
|
|
58
|
+
* the OCC validator and the commit-log freshness scan a dependency neither
|
|
59
|
+
* can reason about.
|
|
60
|
+
*
|
|
61
|
+
* `null` until the first ephemeral read. The vast majority of queries never
|
|
62
|
+
* touch the store, and this context is constructed once per query execution —
|
|
63
|
+
* so the array is allocated on demand rather than on every read path.
|
|
64
|
+
*/
|
|
65
|
+
ephemeralKeys = null;
|
|
66
|
+
/** `set`/`delete` calls awaiting a commit — see {@link DbWriter.ephemeral}.
|
|
67
|
+
* Lazily allocated, like {@link ephemeralKeys}. */
|
|
68
|
+
ephemeralWrites = null;
|
|
69
|
+
/** Backing store for `ctx.db.ephemeral`. */
|
|
70
|
+
ephemeralStore;
|
|
71
|
+
models = /* @__PURE__ */ new Map();
|
|
72
|
+
getOrCreateModel(schema) {
|
|
73
|
+
const existing = this.models.get(schema.table);
|
|
74
|
+
if (existing) return existing;
|
|
75
|
+
const model = buildModel(schema);
|
|
76
|
+
this.models.set(schema.table, model);
|
|
77
|
+
return model;
|
|
78
|
+
}
|
|
79
|
+
constructor(db, beginTs, ephemeralStore) {
|
|
80
|
+
this.db = db;
|
|
81
|
+
this.beginTs = beginTs;
|
|
82
|
+
this.ephemeralStore = ephemeralStore;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Record a live-query dependency on `key` (idempotent within a context).
|
|
86
|
+
* @internal — called by the wrappers in db/ephemeral.ts.
|
|
87
|
+
*/
|
|
88
|
+
internalTrackEphemeralRead(key) {
|
|
89
|
+
if (this.ephemeralKeys === null) {
|
|
90
|
+
this.ephemeralKeys = [key];
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!this.ephemeralKeys.includes(key)) this.ephemeralKeys.push(key);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Queue an ephemeral write for the post-commit flush, replacing any earlier
|
|
97
|
+
* write to the same key so the last write in an attempt wins.
|
|
98
|
+
* @internal — called by the wrapper in db/ephemeral.ts.
|
|
99
|
+
*/
|
|
100
|
+
internalBufferEphemeralWrite(write) {
|
|
101
|
+
if (this.ephemeralWrites === null) {
|
|
102
|
+
this.ephemeralWrites = [write];
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const existing = this.ephemeralWrites.findIndex((w) => w.key === write.key);
|
|
106
|
+
if (existing >= 0) this.ephemeralWrites[existing] = write;
|
|
107
|
+
else this.ephemeralWrites.push(write);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Rebind the read connection (and its snapshot ts). Used by the replica
|
|
111
|
+
* router when a query handler calls `ctx.usePrimaryConn()`/`useReplicaConn()`.
|
|
112
|
+
* Must be called BEFORE any read — the readSet/beginTs would otherwise mix
|
|
113
|
+
* snapshots — so this throws if a read or write has already happened.
|
|
114
|
+
*/
|
|
115
|
+
useConnection(db, beginTs) {
|
|
116
|
+
if (this.readSet.length > 0 || this.writeSet.length > 0) throw new Error("useConnection()/usePrimaryConn()/useReplicaConn() must be called before any read or write in the handler");
|
|
117
|
+
this.db = db;
|
|
118
|
+
this.beginTs = beginTs;
|
|
119
|
+
}
|
|
120
|
+
internalGetReadSet() {
|
|
121
|
+
return this.readSet;
|
|
122
|
+
}
|
|
123
|
+
internalGetWriteSet() {
|
|
124
|
+
return this.writeSet;
|
|
125
|
+
}
|
|
126
|
+
/** Null when this context read no ephemeral state — see {@link ephemeralKeys}. */
|
|
127
|
+
internalGetEphemeralKeys() {
|
|
128
|
+
return this.ephemeralKeys;
|
|
129
|
+
}
|
|
130
|
+
/** Null when this context buffered no ephemeral writes. */
|
|
131
|
+
internalGetEphemeralWrites() {
|
|
132
|
+
return this.ephemeralWrites;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Merge a parent procedure's reads into this context's readSet. Used when a
|
|
136
|
+
* `runMutation` sub-transaction inherits the reads of the procedure that
|
|
137
|
+
* invoked it, so the sub-tx's OCC validation also fences on those rows.
|
|
138
|
+
* Entries are shallow-cloned so the two readSets don't alias.
|
|
139
|
+
*/
|
|
140
|
+
appendInternalReadSet(parentEntries) {
|
|
141
|
+
this.readSet.push(...parentEntries.map((e) => ({ ...e })));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Merge ephemeral dependencies recorded elsewhere into this context. The
|
|
145
|
+
* counterpart of {@link appendInternalReadSet}: used when a nested query is
|
|
146
|
+
* served from cache, so the parent still depends on the keys the cached
|
|
147
|
+
* computation read and re-runs when any of them change.
|
|
148
|
+
*/
|
|
149
|
+
appendInternalEphemeralKeys(keys) {
|
|
150
|
+
for (const key of keys) this.internalTrackEphemeralRead(key);
|
|
151
|
+
}
|
|
152
|
+
findPendingWrite(table, id) {
|
|
153
|
+
return this.writeSet.find((w) => w.table === table && w.id === id);
|
|
154
|
+
}
|
|
155
|
+
findPendingInsert(table, id) {
|
|
156
|
+
return this.writeSet.find((w) => w.table === table && w.id === id && w.op === "insert");
|
|
157
|
+
}
|
|
158
|
+
encodeWithCodec(model, property, value) {
|
|
159
|
+
const codec = model.codecs[property];
|
|
160
|
+
if (!codec?.encode || value === void 0 || value === null) return value;
|
|
161
|
+
return codec.encode(value);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var DbReader = class extends TxContext {
|
|
165
|
+
ephemeralApi;
|
|
166
|
+
query(schema) {
|
|
167
|
+
const model = this.getOrCreateModel(schema);
|
|
168
|
+
return new QueryBuilder(this, model);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Read ephemeral state — values that drive live queries but are never stored
|
|
172
|
+
* in the database (a device's latest GPS fix, a presence flag).
|
|
173
|
+
*
|
|
174
|
+
* Every read here records a dependency on the key, so a subscribed query
|
|
175
|
+
* re-runs and pushes automatically when an action or mutation writes it. The
|
|
176
|
+
* dependency is on the KEY, not the value: reading a key that is currently
|
|
177
|
+
* absent still subscribes you to it being set later.
|
|
178
|
+
*
|
|
179
|
+
* Built on first access — a query that never reaches for ephemeral state
|
|
180
|
+
* allocates neither the wrapper nor its key list.
|
|
181
|
+
*/
|
|
182
|
+
get ephemeral() {
|
|
183
|
+
return this.ephemeralApi ??= createTrackedEphemeralReader(this, this.ephemeralStore);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var DbWriter = class extends DbReader {
|
|
187
|
+
ephemeralWriteApi;
|
|
188
|
+
/**
|
|
189
|
+
* Read AND write ephemeral state. Reads behave exactly as on a
|
|
190
|
+
* {@link DbReader}; writes are BUFFERED and applied only once the surrounding
|
|
191
|
+
* mutation commits (see `SupaliveDb.mutation` and
|
|
192
|
+
* {@link createBufferedEphemeralWriter} for why).
|
|
193
|
+
*/
|
|
194
|
+
get ephemeral() {
|
|
195
|
+
return this.ephemeralWriteApi ??= createBufferedEphemeralWriter(this, this.ephemeralStore);
|
|
196
|
+
}
|
|
197
|
+
async insert(schema, id, data) {
|
|
198
|
+
const model = this.getOrCreateModel(schema);
|
|
199
|
+
const mappedData = { id };
|
|
200
|
+
const shape = schema.schema.shape;
|
|
201
|
+
for (const key of Object.keys(shape)) {
|
|
202
|
+
let value = data[key];
|
|
203
|
+
if (value === void 0) {
|
|
204
|
+
const field = shape[key];
|
|
205
|
+
if (field instanceof z$1.ZodDefault) {
|
|
206
|
+
const defaultVal = field.def.defaultValue;
|
|
207
|
+
value = typeof defaultVal === "function" ? defaultVal() : defaultVal;
|
|
208
|
+
if (value !== void 0) {
|
|
209
|
+
const columnName = model.columns[key] ?? key;
|
|
210
|
+
mappedData[columnName] = this.encodeWithCodec(model, key, value);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
const columnName = model.columns[key] ?? key;
|
|
215
|
+
mappedData[columnName] = this.encodeWithCodec(model, key, value);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
mappedData["id"] = id;
|
|
219
|
+
const existing = this.findPendingWrite(schema.table, id);
|
|
220
|
+
if (existing) if (existing.op === "delete") {
|
|
221
|
+
existing.op = "update";
|
|
222
|
+
existing.data = mappedData;
|
|
223
|
+
} else existing.data = {
|
|
224
|
+
...existing.data,
|
|
225
|
+
...mappedData
|
|
226
|
+
};
|
|
227
|
+
else this.writeSet.push({
|
|
228
|
+
table: schema.table,
|
|
229
|
+
tableId: schema.tableId,
|
|
230
|
+
id,
|
|
231
|
+
op: "insert",
|
|
232
|
+
data: mappedData
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async update(schema, id, data) {
|
|
236
|
+
const model = this.getOrCreateModel(schema);
|
|
237
|
+
const mappedData = {};
|
|
238
|
+
for (const [key, value] of Object.entries(data)) {
|
|
239
|
+
const columnName = model.columns[key] ?? key;
|
|
240
|
+
mappedData[columnName] = this.encodeWithCodec(model, key, value);
|
|
241
|
+
}
|
|
242
|
+
const existing = this.findPendingWrite(schema.table, id);
|
|
243
|
+
if (existing) {
|
|
244
|
+
existing.op = existing.op === "insert" ? "insert" : "update";
|
|
245
|
+
existing.data = {
|
|
246
|
+
...existing.data,
|
|
247
|
+
...mappedData
|
|
248
|
+
};
|
|
249
|
+
} else this.writeSet.push({
|
|
250
|
+
table: schema.table,
|
|
251
|
+
tableId: schema.tableId,
|
|
252
|
+
id,
|
|
253
|
+
op: "update",
|
|
254
|
+
data: mappedData
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
async delete(schema, id) {
|
|
258
|
+
const existingInsert = this.findPendingInsert(schema.table, id);
|
|
259
|
+
if (existingInsert) {
|
|
260
|
+
this.writeSet = this.writeSet.filter((w) => w !== existingInsert);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.writeSet.push({
|
|
264
|
+
table: schema.table,
|
|
265
|
+
tableId: schema.tableId,
|
|
266
|
+
id,
|
|
267
|
+
op: "delete"
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/logger.ts
|
|
273
|
+
const appInstanceId = `instance-${v4().slice(0, 8)}`;
|
|
274
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
275
|
+
const logger = pino({
|
|
276
|
+
level: process.env.LOG_LEVEL || "info",
|
|
277
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
278
|
+
redact: { paths: ["user.password"] },
|
|
279
|
+
transport: isProduction ? void 0 : {
|
|
280
|
+
target: "pino-pretty",
|
|
281
|
+
options: { colorize: true }
|
|
282
|
+
},
|
|
283
|
+
serializers: { "columnsTypes": (map) => {
|
|
284
|
+
if (map instanceof Map) return serializeMapValue(map);
|
|
285
|
+
return map;
|
|
286
|
+
} },
|
|
287
|
+
base: { "srv": appInstanceId }
|
|
288
|
+
});
|
|
289
|
+
function logLevelOf(name) {
|
|
290
|
+
const defaultLogLevel = process.env.DEFAULT_LOG_LEVEL || "info";
|
|
291
|
+
return process.env[name]?.toLowerCase() || defaultLogLevel;
|
|
292
|
+
}
|
|
293
|
+
function serializeMapValue(map) {
|
|
294
|
+
const obj = {};
|
|
295
|
+
for (const [k, v] of map.entries()) {
|
|
296
|
+
obj[k] = {};
|
|
297
|
+
if (v instanceof Map) for (const [innerK, innerV] of v.entries()) obj[k][innerK] = innerV;
|
|
298
|
+
else obj[k] = v;
|
|
299
|
+
}
|
|
300
|
+
return obj;
|
|
301
|
+
}
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/db/realtime_db.ts
|
|
304
|
+
const occRetryPolicy = retry(handleWhen((err) => OccConflictError.isOccConflictError(err)), {
|
|
305
|
+
maxAttempts: 15,
|
|
306
|
+
backoff: new ExponentialBackoff({
|
|
307
|
+
initialDelay: 10,
|
|
308
|
+
maxDelay: 7e3,
|
|
309
|
+
exponent: 2
|
|
310
|
+
})
|
|
311
|
+
});
|
|
312
|
+
const getDbclientRetryPolicy = retry(handleWhen((err) => err.message.includes("timeout exceeded")), {
|
|
313
|
+
maxAttempts: 3,
|
|
314
|
+
backoff: new ExponentialBackoff({
|
|
315
|
+
initialDelay: 500,
|
|
316
|
+
maxDelay: 2e3,
|
|
317
|
+
exponent: 2
|
|
318
|
+
})
|
|
319
|
+
});
|
|
320
|
+
var SupaliveDb = class SupaliveDb {
|
|
321
|
+
impl;
|
|
322
|
+
ephemeral;
|
|
323
|
+
replica;
|
|
324
|
+
/**
|
|
325
|
+
* @param impl the PRIMARY (writer) connection — all mutations, the OCC
|
|
326
|
+
* commit-ts source, and cache-freshness commit-log scans run here.
|
|
327
|
+
* @param ephemeral Redis-backed store behind `ctx.db.ephemeral` (db/ephemeral.ts).
|
|
328
|
+
* Required: it is reachable from every handler context, so a
|
|
329
|
+
* db that could be missing one would push the failure out to
|
|
330
|
+
* request time. A replica SHARES the primary's store —
|
|
331
|
+
* ephemeral state lives in Redis rather than in either
|
|
332
|
+
* connection, so read routing must not change what it sees.
|
|
333
|
+
* @param replica optional read replica for query/action reads (see the
|
|
334
|
+
* routing in one-shot-query.ts). Undefined → reads use primary.
|
|
335
|
+
*/
|
|
336
|
+
constructor(impl, ephemeral, replica) {
|
|
337
|
+
this.impl = impl;
|
|
338
|
+
this.ephemeral = ephemeral;
|
|
339
|
+
this.replica = replica;
|
|
340
|
+
}
|
|
341
|
+
static create(config) {
|
|
342
|
+
return new SupaliveDb(config.db, config.ephemeral, config.replica ? new SupaliveDb(config.replica, config.ephemeral) : void 0);
|
|
343
|
+
}
|
|
344
|
+
/** True when a distinct read replica is configured. */
|
|
345
|
+
get hasReplica() {
|
|
346
|
+
return this.replica !== void 0;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Register a hook fired after every batch of ephemeral writes lands in Redis.
|
|
350
|
+
* The ephemeral twin of {@link setOnAfterCommit}: the server uses it to drive
|
|
351
|
+
* subscription invalidation for writes from any source — an action, a job, or
|
|
352
|
+
* a mutation's post-commit flush.
|
|
353
|
+
*/
|
|
354
|
+
setOnAfterEphemeralWrite(cb) {
|
|
355
|
+
this.ephemeral.onAfterWrite = cb;
|
|
356
|
+
}
|
|
357
|
+
onAfterCommit;
|
|
358
|
+
/**
|
|
359
|
+
* Register a hook fired once after every successful `mutation()` commit
|
|
360
|
+
* (never on an OCC conflict/rollback), with the committed writeSet and its
|
|
361
|
+
* commitTs. The server uses this to drive subscription invalidation for ALL
|
|
362
|
+
* mutation sources — client WS calls, actions, jobs, and the internal caller —
|
|
363
|
+
* so a server-side write pushes live updates just like a client write does.
|
|
364
|
+
* Non-server consumers (migration tooling, tests) leave it unset (no-op).
|
|
365
|
+
*/
|
|
366
|
+
setOnAfterCommit(cb) {
|
|
367
|
+
this.onAfterCommit = cb;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* One-shot read against an EXPLICIT connection + snapshot ts (rather than this
|
|
371
|
+
* db's own latest). Used by the replica router: the caller pre-resolves the
|
|
372
|
+
* default read connection (replica for queries) and hands the handler a
|
|
373
|
+
* `DbReader` it can rebind via `useConnection` (ctx.usePrimaryConn/…). The
|
|
374
|
+
* returned `ts` is the FINAL bound connection's snapshot, so the cache entry is
|
|
375
|
+
* stamped with the ts actually read at.
|
|
376
|
+
*/
|
|
377
|
+
async queryInternalOn(read, fn) {
|
|
378
|
+
const ctx = new DbReader(read.impl, read.beginTs, this.ephemeral);
|
|
379
|
+
const data = await fn(ctx);
|
|
380
|
+
return {
|
|
381
|
+
ts: ctx.beginTs,
|
|
382
|
+
data,
|
|
383
|
+
readSet: ctx.internalGetReadSet(),
|
|
384
|
+
ephemeralKeys: ctx.internalGetEphemeralKeys() ?? EMPTY_EPHEMERAL_KEYS
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
async query(fn) {
|
|
388
|
+
const beginTs = await this.impl.getLatestSnapshotTimestamp();
|
|
389
|
+
return await fn(new DbReader(this.impl, beginTs, this.ephemeral));
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Compute a query once at the latest snapshot, returning the result together
|
|
393
|
+
* with the snapshot `ts` and the tracked `readSet`. This is a plain one-shot
|
|
394
|
+
* evaluation — it registers NO live subscription; the `readSet`/`ts` are
|
|
395
|
+
* returned purely so the caller (e.g. the one-shot query cache) can store
|
|
396
|
+
* freshness metadata. Use this for cached one-shot query calls (WS `call`,
|
|
397
|
+
* HTTP RPC). `liveQuery` is the same computation under the subscription path's
|
|
398
|
+
* name and simply delegates here.
|
|
399
|
+
*/
|
|
400
|
+
async queryInternal(fn) {
|
|
401
|
+
const beginTs = await this.impl.getLatestSnapshotTimestamp();
|
|
402
|
+
const ctx = new DbReader(this.impl, beginTs, this.ephemeral);
|
|
403
|
+
return {
|
|
404
|
+
ts: beginTs,
|
|
405
|
+
data: await fn(ctx),
|
|
406
|
+
readSet: ctx.internalGetReadSet(),
|
|
407
|
+
ephemeralKeys: ctx.internalGetEphemeralKeys() ?? EMPTY_EPHEMERAL_KEYS
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async liveQuery(fn) {
|
|
411
|
+
return this.queryInternal(fn);
|
|
412
|
+
}
|
|
413
|
+
async mutation(fn, retryPolicy = occRetryPolicy) {
|
|
414
|
+
let committedEphemeralWrites = null;
|
|
415
|
+
const mutationResult = await retryPolicy.execute(async (p) => {
|
|
416
|
+
let db;
|
|
417
|
+
try {
|
|
418
|
+
const beginTs = await this.impl.getLatestSnapshotTimestamp() + 1n;
|
|
419
|
+
const ctx = new DbWriter(this.impl, beginTs, this.ephemeral);
|
|
420
|
+
const result = await fn(ctx);
|
|
421
|
+
const readSet = ctx.internalGetReadSet();
|
|
422
|
+
const writeSet = ctx.internalGetWriteSet();
|
|
423
|
+
const commitTsLazy = new LazyCommitTsParam(void 0);
|
|
424
|
+
await this.impl.attachPrevDataToWriteSet(writeSet, readSet);
|
|
425
|
+
const queries = await this.impl.prepareCommitWrites(readSet, writeSet, commitTsLazy);
|
|
426
|
+
db = await getDbclientRetryPolicy.execute(() => this.impl.getTransactionClient());
|
|
427
|
+
await db.begin();
|
|
428
|
+
const commitTs = await db.getNextTimestamp();
|
|
429
|
+
commitTsLazy.value = commitTs;
|
|
430
|
+
const commitResult = await traceDbCall("commit", "", this.impl.dbType, () => db.commitWrites(beginTs, queries, commitTs));
|
|
431
|
+
if (!commitResult.success) throw new OccConflictError("Commit failed", { commitResult });
|
|
432
|
+
await db.updateLatestSnapshotTimestamp(commitTs);
|
|
433
|
+
await db.commit();
|
|
434
|
+
this.onAfterCommit?.(writeSet, commitTs);
|
|
435
|
+
committedEphemeralWrites = ctx.internalGetEphemeralWrites();
|
|
436
|
+
return {
|
|
437
|
+
result,
|
|
438
|
+
writeSet,
|
|
439
|
+
commitTs
|
|
440
|
+
};
|
|
441
|
+
} catch (err) {
|
|
442
|
+
await db?.rollback();
|
|
443
|
+
throw err;
|
|
444
|
+
} finally {
|
|
445
|
+
db?.release();
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
if (committedEphemeralWrites) {
|
|
449
|
+
const writes = committedEphemeralWrites;
|
|
450
|
+
try {
|
|
451
|
+
await this.ephemeral.apply(writes);
|
|
452
|
+
} catch (cause) {
|
|
453
|
+
logger.error(new EphemeralFlushError(writes.map((w) => w.key), { cause }), "ephemeral flush failed after commit");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return mutationResult;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
460
|
+
//#endregion
|
|
461
|
+
export { logLevelOf as a, DbReader as c, isProduction as i, DbWriter as l, sleep as n, logger as o, appInstanceId as r, serializeMapValue as s, SupaliveDb as t, TxContext as u };
|
|
462
|
+
|
|
463
|
+
//# sourceMappingURL=realtime_db-CTGOrFuV.js.map
|