@rocicorp/zero 1.9.0-canary.0 → 1.9.0-canary.2

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 (38) hide show
  1. package/out/z2s/src/compiler.d.ts.map +1 -1
  2. package/out/z2s/src/compiler.js +1 -22
  3. package/out/z2s/src/compiler.js.map +1 -1
  4. package/out/zero/package.js +1 -1
  5. package/out/zero/package.js.map +1 -1
  6. package/out/zero-cache/src/custom/fetch.js +1 -1
  7. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  8. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  9. package/out/zero-cache/src/services/litestream/commands.js +12 -26
  10. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  11. package/out/zero-cache/src/services/litestream/config.yml +3 -2
  12. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +2 -2
  13. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  14. package/out/zero-cache/src/services/replicator/change-processor.js +45 -7
  15. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  16. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  17. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  18. package/out/zero-client/src/client/version.js +1 -1
  19. package/out/zero-client/src/client/zero.d.ts +6 -7
  20. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  21. package/out/zero-client/src/client/zero.js +125 -85
  22. package/out/zero-client/src/client/zero.js.map +1 -1
  23. package/out/zero-server/src/custom.js +8 -2
  24. package/out/zero-server/src/custom.js.map +1 -1
  25. package/out/zero-server/src/schema.d.ts +8 -0
  26. package/out/zero-server/src/schema.d.ts.map +1 -1
  27. package/out/zero-server/src/schema.js +63 -24
  28. package/out/zero-server/src/schema.js.map +1 -1
  29. package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
  30. package/out/zql/src/query/query-delegate-base.js +5 -1
  31. package/out/zql/src/query/query-delegate-base.js.map +1 -1
  32. package/out/zqlite/src/db.js +2 -2
  33. package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
  34. package/out/zqlite/src/resolve-scalar-subqueries.js +13 -1
  35. package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
  36. package/out/zqlite/src/sqlite-cost-model.js +5 -5
  37. package/out/zqlite/src/table-source.js +3 -3
  38. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","names":[],"sources":["../../../../../../zero-cache/src/services/litestream/commands.ts"],"sourcesContent":["import type {ChildProcess} from 'node:child_process';\nimport {spawn} from 'node:child_process';\nimport {existsSync, statSync} from 'node:fs';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../../shared/src/must.ts';\nimport {sleep} from '../../../../shared/src/sleep.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {assertNormalized} from '../../config/normalize.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport {deleteLiteDB} from '../../db/delete-lite-db.ts';\nimport {\n isSQLiteCorruption,\n logSQLiteCorruptionDiagnostics,\n} from '../../db/sqlite-corruption.ts';\nimport {StatementRunner} from '../../db/statements.ts';\nimport {getShardConfig} from '../../types/shards.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {ChangeStreamerHttpClient} from '../change-streamer/change-streamer-http.ts';\nimport type {\n SnapshotMessage,\n SnapshotStatus,\n} from '../change-streamer/snapshot.ts';\nimport {getSubscriptionState} from '../replicator/schema/replication-state.ts';\nimport {\n litestreamBackupListDuration,\n litestreamBackupMetricAttrs,\n litestreamBackupProcessMetricAttrs,\n litestreamBackupProcessDuration,\n litestreamBackupProcessRuns,\n litestreamRestoreAttempts,\n litestreamRestoredDbBytes,\n litestreamRestoreDuration,\n litestreamRestoreMetricAttrs,\n litestreamRestoreProcessDuration,\n litestreamRestoreRuns,\n litestreamRestoreValidationDuration,\n litestreamRestoreWaitDuration,\n type LitestreamRole,\n} from './metrics.ts';\n\nconst RETRY_INTERVAL_MS = 3000;\n\ntype ReplicaConstraints = {\n replicaVersion: string;\n minWatermark: string;\n};\n\ntype RestoreResult = 'success' | 'no_backup' | 'invalid_replica' | 'error';\n\ntype RestoreAttempt = {\n restored: boolean;\n backupURL: string | undefined;\n result: RestoreResult;\n};\n\nexport class BackupNotFoundException extends Error {\n static readonly name = 'BackupNotFoundException';\n\n constructor(backupURL: string | undefined) {\n super(`backup not found at ${backupURL}`);\n }\n}\n\n/**\n * @param replicaConstraints The constraints of the restored backup when\n * restoring for the change-streamer (replication-manager). For the\n * view-syncer, this should be unspecified so that the constraints are\n * retrieved from the replication-manager via the snapshot protocol.\n */\nexport async function restoreReplica(\n lc: LogContext,\n config: ZeroConfig,\n replicaConstraints: ReplicaConstraints | null,\n) {\n const start = performance.now();\n const role: LitestreamRole = replicaConstraints\n ? 'replication_manager'\n : 'view_syncer';\n let backupURL = config.litestream.backupURL;\n let result: RestoreResult | undefined;\n // View-syncers (no replicaConstraints) wait indefinitely for the\n // replication-manager to publish a restorable backup. On a fresh stack the\n // first backup is not durable until the initial sync completes and litestream\n // uploads the initial snapshot, which can take many minutes for a large\n // replica. The platform's startup probe budget (which scales with replica\n // size) is the backstop, so restoreReplica must not impose its own shorter\n // cap and self-terminate while the backup is still being produced.\n //\n // `consecutiveErrors` distinguishes a transient restore *error* (e.g. the\n // `replicate` process compacting a snapshot mid-restore) — which is retried\n // once — from a \"backup not found\" result, which is expected while waiting.\n let consecutiveErrors = 0;\n try {\n for (;;) {\n try {\n // `consecutiveErrors` is 0 on a fresh attempt and 1 on the retry, so\n // `consecutiveErrors >= 1` identifies the final attempt of a streak.\n // tryRestore surfaces litestream's failure output with a different\n // message on the final attempt (which the log classifier routes to a\n // paging alert) than on a retriable earlier attempt (routed to a\n // non-paging warning). See INC-961.\n const attempt = await tryRestore(\n lc,\n config,\n replicaConstraints,\n role,\n consecutiveErrors >= 1,\n );\n backupURL = attempt.backupURL;\n if (attempt.restored) {\n result = attempt.result;\n return;\n }\n consecutiveErrors = 0;\n if (replicaConstraints) {\n // The replication-manager restores against explicit constraints, so a\n // missing backup is fatal (e.g. the litestream URL was purposefully\n // changed to force a resync). Only the view-syncer (no constraints)\n // waits for a backup to appear.\n result = attempt.result;\n throw new BackupNotFoundException(config.litestream.backupURL);\n }\n } catch (e) {\n if (e instanceof BackupNotFoundException) {\n throw e;\n }\n if (++consecutiveErrors <= 1) {\n // A restore will fail if the `replicate` process creates a new\n // snapshot (and compacts old files) at the same time. Snapshots are\n // infrequent (e.g. once every 12 hours), and the scenario is\n // recoverable with a retry.\n lc.warn?.(`restore attempt failed. retrying once`, e);\n continue;\n }\n // If it fails again on the retry, though, bail.\n throw e;\n }\n lc.info?.(\n `replica not found. retrying in ${RETRY_INTERVAL_MS / 1000} seconds`,\n );\n await sleep(RETRY_INTERVAL_MS);\n }\n } catch (e) {\n if (e instanceof BackupNotFoundException && result === undefined) {\n result = 'no_backup';\n }\n throw e;\n } finally {\n const attrs = litestreamRestoreMetricAttrs(config, role, backupURL);\n const labels = {...attrs, result: result ?? 'error'};\n litestreamRestoreRuns().add(1, labels);\n litestreamRestoreDuration().recordMs(performance.now() - start, labels);\n }\n}\n\nfunction getLitestream(\n mode: 'restore' | 'replicate',\n config: ZeroConfig,\n logLevelOverride?: LogLevel,\n backupURLOverride?: string,\n): {\n litestream: string;\n env: NodeJS.ProcessEnv;\n} {\n const {\n executable,\n executableV5,\n restoreUsingV5,\n backupURL,\n logLevel,\n configPath,\n endpoint,\n region,\n port = config.port + 2,\n checkpointThresholdMB,\n minCheckpointPageCount = checkpointThresholdMB * 250, // SQLite page size is 4KB\n maxCheckpointPageCount = minCheckpointPageCount * 10,\n incrementalBackupIntervalMinutes,\n snapshotBackupIntervalHours,\n multipartConcurrency,\n multipartSize,\n } = config.litestream;\n\n // Set the snapshot interval to something smaller than x hours so that\n // the hourly check triggers on the hour, rather than the hour after.\n const snapshotBackupIntervalMinutes = snapshotBackupIntervalHours * 60 - 5;\n\n const litestream =\n // The v0.5.8+ litestream executable can restore from either the new LTX\n // format or the legacy WAL format, allowing forwards-compatibility /\n // rollback safety with zero-cache versions that backup to LTX.\n (mode === 'restore' && restoreUsingV5 ? executableV5 : executable) ??\n must(executable, `Missing --litestream-executable`);\n return {\n litestream,\n env: {\n ...process.env,\n ['ZERO_REPLICA_FILE']: config.replica.file,\n ['ZERO_LITESTREAM_BACKUP_URL']: must(backupURLOverride ?? backupURL),\n ['ZERO_LITESTREAM_MIN_CHECKPOINT_PAGE_COUNT']: String(\n minCheckpointPageCount,\n ),\n ['ZERO_LITESTREAM_MAX_CHECKPOINT_PAGE_COUNT']: String(\n maxCheckpointPageCount,\n ),\n ['ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES']: String(\n incrementalBackupIntervalMinutes,\n ),\n ['ZERO_LITESTREAM_LOG_LEVEL']: logLevelOverride ?? logLevel,\n ['ZERO_LITESTREAM_SNAPSHOT_BACKUP_INTERVAL_MINUTES']: String(\n snapshotBackupIntervalMinutes,\n ),\n ['ZERO_LITESTREAM_MULTIPART_CONCURRENCY']: String(multipartConcurrency),\n ['ZERO_LITESTREAM_MULTIPART_SIZE']: String(multipartSize),\n ['ZERO_LOG_FORMAT']: config.log.format,\n ['LITESTREAM_CONFIG']: configPath,\n ['LITESTREAM_PORT']: String(port),\n ...(endpoint ? {['ZERO_LITESTREAM_ENDPOINT']: endpoint} : {}),\n ...(region ? {['ZERO_LITESTREAM_REGION']: region} : {}),\n },\n };\n}\n\nasync function tryRestore(\n lc: LogContext,\n config: ZeroConfig,\n replicaConstraints: ReplicaConstraints | null,\n role: LitestreamRole,\n isFinalAttempt: boolean,\n): Promise<RestoreAttempt> {\n let snapshotStatus: SnapshotStatus | undefined;\n if (!replicaConstraints) {\n // view-syncers fetch replica constraints from the replication-manager\n // via the snapshot protocol.\n const waitStart = performance.now();\n snapshotStatus = await reserveAndGetSnapshotStatus(lc, config);\n litestreamRestoreWaitDuration().recordMs(\n performance.now() - waitStart,\n litestreamRestoreMetricAttrs(config, role, snapshotStatus.backupURL),\n );\n lc.info?.(`restoring backup from ${snapshotStatus.backupURL}`);\n replicaConstraints = snapshotStatus;\n }\n\n const backupURL = snapshotStatus?.backupURL ?? config.litestream.backupURL;\n const attrs = litestreamRestoreMetricAttrs(config, role, backupURL);\n let result: RestoreResult = 'error';\n try {\n const replicaExistedBeforeRestore = existsSync(config.replica.file);\n const {litestream, env} = getLitestream(\n 'restore',\n config,\n 'debug', // Include all output from `litestream restore`, as it's minimal.\n snapshotStatus?.backupURL,\n );\n const {\n restoreParallelism: parallelism,\n multipartConcurrency,\n multipartSize,\n } = config.litestream;\n lc.info?.(`starting litestream restore`, {\n restoreParallelism: parallelism,\n multipartConcurrency,\n multipartSize,\n });\n // Pipe (rather than inherit) litestream's stdout/stderr so that its own\n // `\"level\":\"ERROR\"` output on a failed restore does not go straight to the\n // pod's stdout — where a log-scraper alert would page on it — before our\n // code has decided whether the failure is retriable. The captured output is\n // re-surfaced through `lc` below with a distinct message per attempt, so a\n // retriable failure is routed to a non-paging warning and only a post-retry\n // failure pages. See INC-961.\n const proc = spawn(\n litestream,\n [\n 'restore',\n '-if-db-not-exists',\n '-if-replica-exists',\n '-parallelism',\n String(parallelism),\n config.replica.file,\n ],\n {env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true},\n );\n let stdout = '';\n let stderr = '';\n proc.stdout.setEncoding('utf-8');\n proc.stderr.setEncoding('utf-8');\n proc.stdout.on('data', chunk => (stdout += chunk));\n proc.stderr.on('data', chunk => (stderr += chunk));\n const {promise, resolve, reject} = resolver();\n proc.on('error', reject);\n proc.on('close', (code, signal) => {\n if (signal) {\n reject(`litestream killed with ${signal}`);\n } else if (code !== 0) {\n reject(`litestream exited with code ${code}`);\n } else {\n resolve();\n }\n });\n const processStart = performance.now();\n try {\n await promise;\n litestreamRestoreProcessDuration().recordMs(\n performance.now() - processStart,\n {...attrs, result: 'success'},\n );\n // A successful restore does not emit ERROR-level output; forward\n // litestream's own (minimal, debug-level) restore logging to the pod's\n // stdout/stderr, matching the previous `stdio: 'inherit'` behavior.\n if (stdout) {\n process.stdout.write(stdout);\n }\n if (stderr) {\n process.stderr.write(stderr);\n }\n } catch (e) {\n litestreamRestoreProcessDuration().recordMs(\n performance.now() - processStart,\n {...attrs, result: 'error'},\n );\n // litestream logs its restore failure at ERROR. Re-surface that captured\n // output through our logger with a distinct message per attempt, so the\n // cloudzero log classifier can route them differently: the retriable\n // earlier attempt to a non-paging warning, and the post-retry failure to\n // a paging critical. Both are emitted at ERROR because the log scraper\n // only ingests error-level lines, and both carry our `worker` context,\n // distinguishing them from litestream's untagged raw output. See INC-961.\n const output = (stdout + stderr).trim();\n if (isFinalAttempt) {\n lc.error?.(`litestream restore failed after retry`, output || e);\n } else {\n lc.error?.(\n `litestream restore attempt failed, will retry`,\n output || e,\n );\n }\n throw e;\n }\n if (!existsSync(config.replica.file)) {\n result = 'no_backup';\n return {restored: false, backupURL, result};\n }\n const validationStart = performance.now();\n const valid = replicaIsValid(lc, config.replica.file, replicaConstraints);\n litestreamRestoreValidationDuration().recordMs(\n performance.now() - validationStart,\n {...attrs, result: valid ? 'success' : 'invalid_replica'},\n );\n if (!valid) {\n result = 'invalid_replica';\n lc.info?.(`Deleting local replica and retrying restore`);\n deleteLiteDB(config.replica.file);\n return {restored: false, backupURL, result};\n }\n result = 'success';\n if (!replicaExistedBeforeRestore) {\n litestreamRestoredDbBytes().add(statSync(config.replica.file).size, {\n ...attrs,\n result: 'success',\n });\n }\n return {restored: true, backupURL, result};\n } finally {\n litestreamRestoreAttempts().add(1, {\n ...attrs,\n result,\n });\n }\n}\n\nfunction replicaIsValid(\n lc: LogContext,\n replica: string,\n constraints: ReplicaConstraints,\n) {\n let db: Database | undefined;\n try {\n db = new Database(lc, replica);\n const {replicaVersion, watermark} = getSubscriptionState(\n new StatementRunner(db),\n );\n if (replicaVersion !== constraints.replicaVersion) {\n lc.warn?.(\n `Local replica version ${replicaVersion} does not match expected replicaVersion ${constraints.replicaVersion}`,\n constraints,\n );\n return false;\n }\n if (watermark < constraints.minWatermark) {\n lc.warn?.(\n `Local replica watermark ${watermark} is earlier than minWatermark ${constraints.minWatermark}`,\n );\n return false;\n }\n lc.info?.(\n `Local replica at version ${replicaVersion} and watermark ${watermark} is compatible`,\n constraints,\n );\n return true;\n } catch (e) {\n if (isSQLiteCorruption(e)) {\n logSQLiteCorruptionDiagnostics(lc, 'restored replica', replica, e);\n }\n lc.error?.('Error while validating restored replica', e);\n return false;\n } finally {\n db?.close();\n }\n}\n\nexport function startReplicaBackupProcess(\n lc: LogContext,\n config: ZeroConfig,\n): ChildProcess {\n const {litestream, env} = getLitestream('replicate', config);\n const attrs = litestreamBackupProcessMetricAttrs(config);\n lc.info?.(`starting litestream backup to ${config.litestream.backupURL}`);\n const start = performance.now();\n const proc = spawn(litestream, ['replicate'], {\n env,\n stdio: 'inherit',\n windowsHide: true,\n });\n let recorded = false;\n const record = (result: 'success' | 'error' | 'stopped') => {\n if (recorded) {\n return;\n }\n recorded = true;\n const labels = {...attrs, result};\n litestreamBackupProcessRuns().add(1, labels);\n litestreamBackupProcessDuration().recordMs(\n performance.now() - start,\n labels,\n );\n };\n proc.on('error', e => {\n lc.warn?.(`litestream backup process error`, e);\n record('error');\n });\n proc.on('close', (code, signal) => {\n if (signal) {\n lc.info?.(`litestream backup process stopped`, {signal});\n record('stopped');\n } else if (code === 0) {\n record('success');\n } else {\n lc.warn?.(`litestream backup process exited with code ${code}`);\n record('error');\n }\n });\n return proc;\n}\n\n// Listing the backup state requires a few S3 LIST requests, which should\n// normally complete well within this timeout.\nconst LIST_BACKUP_TIMEOUT_MS = 30_000;\n\nconst wsRe = /\\s+/;\n\n/**\n * Returns the time of the most recent object (snapshot or WAL segment)\n * actually uploaded to the backup replica destination, as listed by the\n * bundled litestream CLI (`litestream snapshots` / `litestream wal`).\n *\n * This queries the replica destination (e.g. S3) directly, and thus serves\n * as a source of truth for backup durability. This is in contrast to the\n * `litestream_replica_progress` metric, which is exported when litestream\n * *believes* an upload has succeeded, and has been observed to advance even\n * when nothing is actually written to the destination.\n *\n * Rejects if the backup state cannot be determined (spawn error, non-zero\n * exit, timeout, or empty/unparseable listing).\n */\nexport async function getLastBackupTime(\n lc: LogContext,\n config: ZeroConfig,\n): Promise<Date> {\n const [snapshots, wal] = await Promise.all([\n listBackupCreatedTimes(lc, config, 'snapshots'),\n listBackupCreatedTimes(lc, config, 'wal'),\n ]);\n const times = [...snapshots, ...wal];\n if (times.length === 0) {\n // Note: the litestream CLI exits with code 0 and logs listing errors\n // (e.g. S3 failures) to stderr, so an empty listing cannot be\n // distinguished from a failed one. Since a valid backup always contains\n // at least one snapshot, an empty listing is treated as a failure.\n throw new Error(\n `no snapshots or WAL segments listed at ${config.litestream.backupURL}`,\n );\n }\n return new Date(Math.max(...times.map(time => time.getTime())));\n}\n\n/**\n * Runs `litestream <snapshots|wal> <replica-file>` with the same config /\n * environment used by the `litestream replicate` process (so that the\n * backupURL, endpoint, region, and credentials are identical), and parses\n * the `created` column (RFC3339) of the tab-formatted output, e.g.:\n *\n * ```\n * replica generation index size created\n * s3 1862f44967b3863f 0 4546445 2026-06-10T01:11:32Z\n * ```\n */\nasync function listBackupCreatedTimes(\n lc: LogContext,\n config: ZeroConfig,\n command: 'snapshots' | 'wal',\n): Promise<Date[]> {\n const start = performance.now();\n let result: 'success' | 'empty' | 'timeout' | 'error' = 'error';\n const {litestream, env} = getLitestream('replicate', config);\n const proc = spawn(litestream, [command, config.replica.file], {\n env,\n stdio: ['ignore', 'pipe', 'inherit'],\n windowsHide: true,\n });\n const {promise, resolve, reject} = resolver<string>();\n let stdout = '';\n proc.stdout.setEncoding('utf-8');\n proc.stdout.on('data', chunk => (stdout += chunk));\n proc.on('error', reject);\n proc.on('close', (code, signal) => {\n if (signal) {\n reject(new Error(`litestream ${command} killed with ${signal}`));\n } else if (code !== 0) {\n reject(new Error(`litestream ${command} exited with code ${code}`));\n } else {\n resolve(stdout);\n }\n });\n const timeout = setTimeout(() => {\n result = 'timeout';\n reject(new Error(`timed out listing backup state (litestream ${command})`));\n proc.kill('SIGKILL');\n }, LIST_BACKUP_TIMEOUT_MS);\n\n try {\n const output = await promise;\n const times = parseBackupCreatedTimes(lc, command, output);\n result = times.length ? 'success' : 'empty';\n return times;\n } finally {\n clearTimeout(timeout);\n litestreamBackupListDuration().recordMs(performance.now() - start, {\n ...litestreamBackupMetricAttrs(config),\n command,\n result,\n });\n }\n}\n\n/**\n * Parses the `created` column (the last, RFC3339-formatted column) from the\n * tab-formatted output of `litestream snapshots` / `litestream wal`. The\n * header row and any unparseable lines are skipped.\n *\n * Exported for testing.\n */\nexport function parseBackupCreatedTimes(\n lc: LogContext,\n command: 'snapshots' | 'wal',\n output: string,\n): Date[] {\n const times: Date[] = [];\n for (const line of output.split('\\n')) {\n const cols = line.trim().split(wsRe);\n const created = cols.at(-1);\n if (\n cols.length < 2 ||\n created === undefined ||\n created === 'created' /* header row */\n ) {\n continue;\n }\n const time = new Date(created);\n if (Number.isNaN(time.getTime())) {\n lc.warn?.(`unexpected line in litestream ${command} output: ${line}`);\n continue;\n }\n times.push(time);\n }\n return times;\n}\n\nfunction reserveAndGetSnapshotStatus(\n lc: LogContext,\n config: ZeroConfig,\n): Promise<SnapshotStatus> {\n const {promise: status, resolve, reject} = resolver<SnapshotStatus>();\n\n void (async function () {\n const abort = new AbortController();\n process.on('SIGINT', () => abort.abort());\n process.on('SIGTERM', () => abort.abort());\n\n for (let i = 0; ; i++) {\n let err: unknown;\n try {\n let resolved = false;\n const stream = await reserveSnapshot(lc, config);\n for await (const msg of stream) {\n // Capture the value of the status message that the change-streamer\n // backup monitor returns, and hold the connection open to\n // \"reserve\" the snapshot and prevent change log cleanup.\n resolve(msg[1]);\n resolved = true;\n }\n // The change-streamer itself closes the connection when the\n // subscription is started (or the reservation retried).\n if (resolved) {\n break;\n }\n } catch (e) {\n err = e;\n }\n // Retry in the view-syncer since it cannot proceed until it connects\n // to a (compatible) replication-manager. In particular, a\n // replication-manager that does not support the view-syncer's\n // change-streamer protocol will close the stream with an error; this\n // retry logic essentially delays the startup of a view-syncer until\n // a compatible replication-manager has been rolled out, allowing\n // replication-manager and view-syncer services to be updated in\n // parallel.\n lc.warn?.(\n `Unable to reserve snapshot (attempt ${i + 1}). Retrying in 5 seconds.`,\n String(err),\n );\n try {\n await sleep(5000, abort.signal);\n } catch (e) {\n return reject(e);\n }\n }\n })();\n\n return status;\n}\n\nfunction reserveSnapshot(\n lc: LogContext,\n config: ZeroConfig,\n): Promise<Source<SnapshotMessage>> {\n assertNormalized(config);\n const {taskID, change, changeStreamer} = config;\n const shardID = getShardConfig(config);\n\n const changeStreamerClient = new ChangeStreamerHttpClient(\n lc,\n shardID,\n change.db,\n changeStreamer.uri,\n );\n\n return changeStreamerClient.reserveSnapshot(taskID);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"commands.js","names":[],"sources":["../../../../../../zero-cache/src/services/litestream/commands.ts"],"sourcesContent":["import type {ChildProcess} from 'node:child_process';\nimport {spawn} from 'node:child_process';\nimport {existsSync, statSync} from 'node:fs';\nimport type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../../shared/src/must.ts';\nimport {sleep} from '../../../../shared/src/sleep.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {assertNormalized} from '../../config/normalize.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport {deleteLiteDB} from '../../db/delete-lite-db.ts';\nimport {\n isSQLiteCorruption,\n logSQLiteCorruptionDiagnostics,\n} from '../../db/sqlite-corruption.ts';\nimport {StatementRunner} from '../../db/statements.ts';\nimport {getShardConfig} from '../../types/shards.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {ChangeStreamerHttpClient} from '../change-streamer/change-streamer-http.ts';\nimport type {\n SnapshotMessage,\n SnapshotStatus,\n} from '../change-streamer/snapshot.ts';\nimport {getSubscriptionState} from '../replicator/schema/replication-state.ts';\nimport {\n litestreamBackupListDuration,\n litestreamBackupMetricAttrs,\n litestreamBackupProcessMetricAttrs,\n litestreamBackupProcessDuration,\n litestreamBackupProcessRuns,\n litestreamRestoreAttempts,\n litestreamRestoredDbBytes,\n litestreamRestoreDuration,\n litestreamRestoreMetricAttrs,\n litestreamRestoreProcessDuration,\n litestreamRestoreRuns,\n litestreamRestoreValidationDuration,\n litestreamRestoreWaitDuration,\n type LitestreamRole,\n} from './metrics.ts';\n\nconst RETRY_INTERVAL_MS = 3000;\n\ntype ReplicaConstraints = {\n replicaVersion: string;\n minWatermark: string;\n};\n\ntype RestoreResult = 'success' | 'no_backup' | 'invalid_replica' | 'error';\n\ntype RestoreAttempt = {\n restored: boolean;\n backupURL: string | undefined;\n result: RestoreResult;\n};\n\nexport class BackupNotFoundException extends Error {\n static readonly name = 'BackupNotFoundException';\n\n constructor(backupURL: string | undefined) {\n super(`backup not found at ${backupURL}`);\n }\n}\n\n/**\n * @param replicaConstraints The constraints of the restored backup when\n * restoring for the change-streamer (replication-manager). For the\n * view-syncer, this should be unspecified so that the constraints are\n * retrieved from the replication-manager via the snapshot protocol.\n */\nexport async function restoreReplica(\n lc: LogContext,\n config: ZeroConfig,\n replicaConstraints: ReplicaConstraints | null,\n) {\n const start = performance.now();\n const role: LitestreamRole = replicaConstraints\n ? 'replication_manager'\n : 'view_syncer';\n let backupURL = config.litestream.backupURL;\n let result: RestoreResult | undefined;\n // View-syncers (no replicaConstraints) wait indefinitely for the\n // replication-manager to publish a restorable backup. On a fresh stack the\n // first backup is not durable until the initial sync completes and litestream\n // uploads the initial snapshot, which can take many minutes for a large\n // replica. The platform's startup probe budget (which scales with replica\n // size) is the backstop, so restoreReplica must not impose its own shorter\n // cap and self-terminate while the backup is still being produced.\n try {\n for (;;) {\n const attempt = await tryRestore(lc, config, replicaConstraints, role);\n backupURL = attempt.backupURL;\n if (attempt.restored) {\n result = attempt.result;\n return;\n }\n if (replicaConstraints) {\n // The replication-manager restores against explicit constraints, so a\n // missing backup is fatal (e.g. the litestream URL was purposefully\n // changed to force a resync). Only the view-syncer (no constraints)\n // waits for a backup to appear.\n result = attempt.result;\n throw new BackupNotFoundException(config.litestream.backupURL);\n }\n lc.info?.(\n `replica not found. retrying in ${RETRY_INTERVAL_MS / 1000} seconds`,\n );\n await sleep(RETRY_INTERVAL_MS);\n }\n } catch (e) {\n if (e instanceof BackupNotFoundException && result === undefined) {\n result = 'no_backup';\n }\n throw e;\n } finally {\n const attrs = litestreamRestoreMetricAttrs(config, role, backupURL);\n const labels = {...attrs, result: result ?? 'error'};\n litestreamRestoreRuns().add(1, labels);\n litestreamRestoreDuration().recordMs(performance.now() - start, labels);\n }\n}\n\nfunction getLitestream(\n mode: 'restore' | 'replicate',\n config: ZeroConfig,\n logLevelOverride?: LogLevel,\n backupURLOverride?: string,\n): {\n litestream: string;\n env: NodeJS.ProcessEnv;\n} {\n const {\n executable,\n executableV5,\n restoreUsingV5,\n backupURL,\n logLevel,\n configPath,\n endpoint,\n region,\n port = config.port + 2,\n checkpointThresholdMB,\n minCheckpointPageCount = checkpointThresholdMB * 250, // SQLite page size is 4KB\n maxCheckpointPageCount = minCheckpointPageCount * 10,\n incrementalBackupIntervalMinutes,\n snapshotBackupIntervalHours,\n multipartConcurrency,\n multipartSize,\n } = config.litestream;\n\n const litestream =\n // The v0.5.8+ litestream executable can restore from either the new LTX\n // format or the legacy WAL format, allowing forwards-compatibility /\n // rollback safety with zero-cache versions that backup to LTX.\n (mode === 'restore' && restoreUsingV5 ? executableV5 : executable) ??\n must(executable, `Missing --litestream-executable`);\n return {\n litestream,\n env: {\n ...process.env,\n ['ZERO_REPLICA_FILE']: config.replica.file,\n ['ZERO_LITESTREAM_BACKUP_URL']: must(backupURLOverride ?? backupURL),\n ['ZERO_LITESTREAM_MIN_CHECKPOINT_PAGE_COUNT']: String(\n minCheckpointPageCount,\n ),\n ['ZERO_LITESTREAM_MAX_CHECKPOINT_PAGE_COUNT']: String(\n maxCheckpointPageCount,\n ),\n ['ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES']: String(\n incrementalBackupIntervalMinutes,\n ),\n ['ZERO_LITESTREAM_LOG_LEVEL']: logLevelOverride ?? logLevel,\n ['ZERO_LITESTREAM_SNAPSHOT_BACKUP_INTERVAL_HOURS']: String(\n snapshotBackupIntervalHours,\n ),\n ['ZERO_LITESTREAM_SNAPSHOT_RETENTION_INTERVAL_HOURS']: String(\n snapshotBackupIntervalHours + 6, // delete old snapshots after 6 hours\n ),\n ['ZERO_LITESTREAM_MULTIPART_CONCURRENCY']: String(multipartConcurrency),\n ['ZERO_LITESTREAM_MULTIPART_SIZE']: String(multipartSize),\n ['ZERO_LOG_FORMAT']: config.log.format,\n ['LITESTREAM_CONFIG']: configPath,\n ['LITESTREAM_PORT']: String(port),\n ...(endpoint ? {['ZERO_LITESTREAM_ENDPOINT']: endpoint} : {}),\n ...(region ? {['ZERO_LITESTREAM_REGION']: region} : {}),\n },\n };\n}\n\nasync function tryRestore(\n lc: LogContext,\n config: ZeroConfig,\n replicaConstraints: ReplicaConstraints | null,\n role: LitestreamRole,\n): Promise<RestoreAttempt> {\n let snapshotStatus: SnapshotStatus | undefined;\n if (!replicaConstraints) {\n // view-syncers fetch replica constraints from the replication-manager\n // via the snapshot protocol.\n const waitStart = performance.now();\n snapshotStatus = await reserveAndGetSnapshotStatus(lc, config);\n litestreamRestoreWaitDuration().recordMs(\n performance.now() - waitStart,\n litestreamRestoreMetricAttrs(config, role, snapshotStatus.backupURL),\n );\n lc.info?.(`restoring backup from ${snapshotStatus.backupURL}`);\n replicaConstraints = snapshotStatus;\n }\n\n const backupURL = snapshotStatus?.backupURL ?? config.litestream.backupURL;\n const attrs = litestreamRestoreMetricAttrs(config, role, backupURL);\n let result: RestoreResult = 'error';\n try {\n const replicaExistedBeforeRestore = existsSync(config.replica.file);\n const {litestream, env} = getLitestream(\n 'restore',\n config,\n 'debug', // Include all output from `litestream restore`, as it's minimal.\n snapshotStatus?.backupURL,\n );\n const {\n restoreParallelism: parallelism,\n multipartConcurrency,\n multipartSize,\n } = config.litestream;\n lc.info?.(`starting litestream restore`, {\n restoreParallelism: parallelism,\n multipartConcurrency,\n multipartSize,\n });\n // Pipe (rather than inherit) litestream's stdout/stderr so that its own\n // `\"level\":\"ERROR\"` output on a failed restore does not go straight to the\n // pod's stdout — where a log-scraper alert would page on it — before our\n // code has decided whether the failure is retriable. The captured output is\n // re-surfaced through `lc` below with a distinct message per attempt, so a\n // retriable failure is routed to a non-paging warning and only a post-retry\n // failure pages. See INC-961.\n const proc = spawn(\n litestream,\n [\n 'restore',\n '-if-db-not-exists',\n '-if-replica-exists',\n '-parallelism',\n String(parallelism),\n config.replica.file,\n ],\n {env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true},\n );\n let stdout = '';\n let stderr = '';\n proc.stdout.setEncoding('utf-8');\n proc.stderr.setEncoding('utf-8');\n proc.stdout.on('data', chunk => (stdout += chunk));\n proc.stderr.on('data', chunk => (stderr += chunk));\n const {promise, resolve, reject} = resolver();\n proc.on('error', reject);\n proc.on('close', (code, signal) => {\n if (signal) {\n reject(`litestream killed with ${signal}`);\n } else if (code !== 0) {\n reject(`litestream exited with code ${code}`);\n } else {\n resolve();\n }\n });\n const processStart = performance.now();\n try {\n await promise;\n litestreamRestoreProcessDuration().recordMs(\n performance.now() - processStart,\n {...attrs, result: 'success'},\n );\n // A successful restore does not emit ERROR-level output; forward\n // litestream's own (minimal, debug-level) restore logging to the pod's\n // stdout/stderr, matching the previous `stdio: 'inherit'` behavior.\n if (stdout) {\n process.stdout.write(stdout);\n }\n if (stderr) {\n process.stderr.write(stderr);\n }\n } catch (e) {\n litestreamRestoreProcessDuration().recordMs(\n performance.now() - processStart,\n {...attrs, result: 'error'},\n );\n throw e;\n }\n if (!existsSync(config.replica.file)) {\n result = 'no_backup';\n return {restored: false, backupURL, result};\n }\n const validationStart = performance.now();\n const valid = replicaIsValid(lc, config.replica.file, replicaConstraints);\n litestreamRestoreValidationDuration().recordMs(\n performance.now() - validationStart,\n {...attrs, result: valid ? 'success' : 'invalid_replica'},\n );\n if (!valid) {\n result = 'invalid_replica';\n lc.info?.(`Deleting local replica and retrying restore`);\n deleteLiteDB(config.replica.file);\n return {restored: false, backupURL, result};\n }\n result = 'success';\n if (!replicaExistedBeforeRestore) {\n litestreamRestoredDbBytes().add(statSync(config.replica.file).size, {\n ...attrs,\n result: 'success',\n });\n }\n return {restored: true, backupURL, result};\n } finally {\n litestreamRestoreAttempts().add(1, {\n ...attrs,\n result,\n });\n }\n}\n\nfunction replicaIsValid(\n lc: LogContext,\n replica: string,\n constraints: ReplicaConstraints,\n) {\n let db: Database | undefined;\n try {\n db = new Database(lc, replica);\n const {replicaVersion, watermark} = getSubscriptionState(\n new StatementRunner(db),\n );\n if (replicaVersion !== constraints.replicaVersion) {\n lc.warn?.(\n `Local replica version ${replicaVersion} does not match expected replicaVersion ${constraints.replicaVersion}`,\n constraints,\n );\n return false;\n }\n if (watermark < constraints.minWatermark) {\n lc.warn?.(\n `Local replica watermark ${watermark} is earlier than minWatermark ${constraints.minWatermark}`,\n );\n return false;\n }\n lc.info?.(\n `Local replica at version ${replicaVersion} and watermark ${watermark} is compatible`,\n constraints,\n );\n return true;\n } catch (e) {\n if (isSQLiteCorruption(e)) {\n logSQLiteCorruptionDiagnostics(lc, 'restored replica', replica, e);\n }\n lc.error?.('Error while validating restored replica', e);\n return false;\n } finally {\n db?.close();\n }\n}\n\nexport function startReplicaBackupProcess(\n lc: LogContext,\n config: ZeroConfig,\n): ChildProcess {\n const {litestream, env} = getLitestream('replicate', config);\n const attrs = litestreamBackupProcessMetricAttrs(config);\n lc.info?.(`starting litestream backup to ${config.litestream.backupURL}`);\n const start = performance.now();\n const proc = spawn(litestream, ['replicate'], {\n env,\n stdio: 'inherit',\n windowsHide: true,\n });\n let recorded = false;\n const record = (result: 'success' | 'error' | 'stopped') => {\n if (recorded) {\n return;\n }\n recorded = true;\n const labels = {...attrs, result};\n litestreamBackupProcessRuns().add(1, labels);\n litestreamBackupProcessDuration().recordMs(\n performance.now() - start,\n labels,\n );\n };\n proc.on('error', e => {\n lc.warn?.(`litestream backup process error`, e);\n record('error');\n });\n proc.on('close', (code, signal) => {\n if (signal) {\n lc.info?.(`litestream backup process stopped`, {signal});\n record('stopped');\n } else if (code === 0) {\n record('success');\n } else {\n lc.warn?.(`litestream backup process exited with code ${code}`);\n record('error');\n }\n });\n return proc;\n}\n\n// Listing the backup state requires a few S3 LIST requests, which should\n// normally complete well within this timeout.\nconst LIST_BACKUP_TIMEOUT_MS = 30_000;\n\nconst wsRe = /\\s+/;\n\n/**\n * Returns the time of the most recent object (snapshot or WAL segment)\n * actually uploaded to the backup replica destination, as listed by the\n * bundled litestream CLI (`litestream snapshots` / `litestream wal`).\n *\n * This queries the replica destination (e.g. S3) directly, and thus serves\n * as a source of truth for backup durability. This is in contrast to the\n * `litestream_replica_progress` metric, which is exported when litestream\n * *believes* an upload has succeeded, and has been observed to advance even\n * when nothing is actually written to the destination.\n *\n * Rejects if the backup state cannot be determined (spawn error, non-zero\n * exit, timeout, or empty/unparseable listing).\n */\nexport async function getLastBackupTime(\n lc: LogContext,\n config: ZeroConfig,\n): Promise<Date> {\n const [snapshots, wal] = await Promise.all([\n listBackupCreatedTimes(lc, config, 'snapshots'),\n listBackupCreatedTimes(lc, config, 'wal'),\n ]);\n const times = [...snapshots, ...wal];\n if (times.length === 0) {\n // Note: the litestream CLI exits with code 0 and logs listing errors\n // (e.g. S3 failures) to stderr, so an empty listing cannot be\n // distinguished from a failed one. Since a valid backup always contains\n // at least one snapshot, an empty listing is treated as a failure.\n throw new Error(\n `no snapshots or WAL segments listed at ${config.litestream.backupURL}`,\n );\n }\n return new Date(Math.max(...times.map(time => time.getTime())));\n}\n\n/**\n * Runs `litestream <snapshots|wal> <replica-file>` with the same config /\n * environment used by the `litestream replicate` process (so that the\n * backupURL, endpoint, region, and credentials are identical), and parses\n * the `created` column (RFC3339) of the tab-formatted output, e.g.:\n *\n * ```\n * replica generation index size created\n * s3 1862f44967b3863f 0 4546445 2026-06-10T01:11:32Z\n * ```\n */\nasync function listBackupCreatedTimes(\n lc: LogContext,\n config: ZeroConfig,\n command: 'snapshots' | 'wal',\n): Promise<Date[]> {\n const start = performance.now();\n let result: 'success' | 'empty' | 'timeout' | 'error' = 'error';\n const {litestream, env} = getLitestream('replicate', config);\n const proc = spawn(litestream, [command, config.replica.file], {\n env,\n stdio: ['ignore', 'pipe', 'inherit'],\n windowsHide: true,\n });\n const {promise, resolve, reject} = resolver<string>();\n let stdout = '';\n proc.stdout.setEncoding('utf-8');\n proc.stdout.on('data', chunk => (stdout += chunk));\n proc.on('error', reject);\n proc.on('close', (code, signal) => {\n if (signal) {\n reject(new Error(`litestream ${command} killed with ${signal}`));\n } else if (code !== 0) {\n reject(new Error(`litestream ${command} exited with code ${code}`));\n } else {\n resolve(stdout);\n }\n });\n const timeout = setTimeout(() => {\n result = 'timeout';\n reject(new Error(`timed out listing backup state (litestream ${command})`));\n proc.kill('SIGKILL');\n }, LIST_BACKUP_TIMEOUT_MS);\n\n try {\n const output = await promise;\n const times = parseBackupCreatedTimes(lc, command, output);\n result = times.length ? 'success' : 'empty';\n return times;\n } finally {\n clearTimeout(timeout);\n litestreamBackupListDuration().recordMs(performance.now() - start, {\n ...litestreamBackupMetricAttrs(config),\n command,\n result,\n });\n }\n}\n\n/**\n * Parses the `created` column (the last, RFC3339-formatted column) from the\n * tab-formatted output of `litestream snapshots` / `litestream wal`. The\n * header row and any unparseable lines are skipped.\n *\n * Exported for testing.\n */\nexport function parseBackupCreatedTimes(\n lc: LogContext,\n command: 'snapshots' | 'wal',\n output: string,\n): Date[] {\n const times: Date[] = [];\n for (const line of output.split('\\n')) {\n const cols = line.trim().split(wsRe);\n const created = cols.at(-1);\n if (\n cols.length < 2 ||\n created === undefined ||\n created === 'created' /* header row */\n ) {\n continue;\n }\n const time = new Date(created);\n if (Number.isNaN(time.getTime())) {\n lc.warn?.(`unexpected line in litestream ${command} output: ${line}`);\n continue;\n }\n times.push(time);\n }\n return times;\n}\n\nfunction reserveAndGetSnapshotStatus(\n lc: LogContext,\n config: ZeroConfig,\n): Promise<SnapshotStatus> {\n const {promise: status, resolve, reject} = resolver<SnapshotStatus>();\n\n void (async function () {\n const abort = new AbortController();\n process.on('SIGINT', () => abort.abort());\n process.on('SIGTERM', () => abort.abort());\n\n for (let i = 0; ; i++) {\n let err: unknown;\n try {\n let resolved = false;\n const stream = await reserveSnapshot(lc, config);\n for await (const msg of stream) {\n // Capture the value of the status message that the change-streamer\n // backup monitor returns, and hold the connection open to\n // \"reserve\" the snapshot and prevent change log cleanup.\n resolve(msg[1]);\n resolved = true;\n }\n // The change-streamer itself closes the connection when the\n // subscription is started (or the reservation retried).\n if (resolved) {\n break;\n }\n } catch (e) {\n err = e;\n }\n // Retry in the view-syncer since it cannot proceed until it connects\n // to a (compatible) replication-manager. In particular, a\n // replication-manager that does not support the view-syncer's\n // change-streamer protocol will close the stream with an error; this\n // retry logic essentially delays the startup of a view-syncer until\n // a compatible replication-manager has been rolled out, allowing\n // replication-manager and view-syncer services to be updated in\n // parallel.\n lc.warn?.(\n `Unable to reserve snapshot (attempt ${i + 1}). Retrying in 5 seconds.`,\n String(err),\n );\n try {\n await sleep(5000, abort.signal);\n } catch (e) {\n return reject(e);\n }\n }\n })();\n\n return status;\n}\n\nfunction reserveSnapshot(\n lc: LogContext,\n config: ZeroConfig,\n): Promise<Source<SnapshotMessage>> {\n assertNormalized(config);\n const {taskID, change, changeStreamer} = config;\n const shardID = getShardConfig(config);\n\n const changeStreamerClient = new ChangeStreamerHttpClient(\n lc,\n shardID,\n change.db,\n changeStreamer.uri,\n );\n\n return changeStreamerClient.reserveSnapshot(taskID);\n}\n"],"mappings":"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"}
@@ -8,8 +8,9 @@ dbs:
8
8
  - url: ${ZERO_LITESTREAM_BACKUP_URL}
9
9
  endpoint: ${ZERO_LITESTREAM_ENDPOINT}
10
10
  region: ${ZERO_LITESTREAM_REGION}
11
- retention: ${ZERO_LITESTREAM_SNAPSHOT_BACKUP_INTERVAL_MINUTES}m
12
- retention-check-interval: 1h
11
+ snapshot-interval: ${ZERO_LITESTREAM_SNAPSHOT_BACKUP_INTERVAL_HOURS}h
12
+ retention: ${ZERO_LITESTREAM_SNAPSHOT_RETENTION_INTERVAL_HOURS}h
13
+ retention-check-interval: 15m
13
14
  sync-interval: ${ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES}m
14
15
  multipart-concurrency: ${ZERO_LITESTREAM_MULTIPART_CONCURRENCY}
15
16
  multipart-size: ${ZERO_LITESTREAM_MULTIPART_SIZE}
@@ -1,6 +1,6 @@
1
1
  import { parse, valita_exports } from "../../../../shared/src/valita.js";
2
2
  import { Database } from "../../../../zqlite/src/db.js";
3
- import Sqlite3Database from "@rocicorp/zero-sqlite3";
3
+ import SQLite3Database from "@rocicorp/zero-sqlite3";
4
4
  //#region ../zero-cache/src/services/litestream/vfs-watermark-reader.ts
5
5
  var DEFAULT_VFS_DATABASE_URI = "file:zero-backup.db?vfs=litestream&mode=ro";
6
6
  var DEFAULT_VFS_LOG_LEVEL = "info";
@@ -27,7 +27,7 @@ var VfsBackupWatermarkReader = class {
27
27
  env["LITESTREAM_REPLICA_URL"] = options.replicaURL;
28
28
  env["LITESTREAM_LOG_LEVEL"] = (options.logLevel ?? DEFAULT_VFS_LOG_LEVEL).toUpperCase();
29
29
  setOptionalEnv(env, "LITESTREAM_LOG_FILE", options.logFile);
30
- const loaderFactory = deps.loaderFactory ?? (() => new Sqlite3Database(":memory:"));
30
+ const loaderFactory = deps.loaderFactory ?? (() => new SQLite3Database(":memory:"));
31
31
  const databaseFactory = deps.databaseFactory ?? ((lc, uri) => new Database(lc, uri, { readonly: true }));
32
32
  this.#loader = loaderFactory();
33
33
  this.#loader.loadExtension(options.extensionPath);
@@ -1 +1 @@
1
- {"version":3,"file":"change-processor.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAOjD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uCAAuC,CAAC;AAiB1E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAiC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iDAAiD,CAAC;AACtF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AASpD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,cAAc,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,cAAc,GAAG,SAAS,CAAC;IAC9C,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;;gBAiBxB,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,mBAAmB,EACzB,WAAW,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI;IAgCrD,KAAK,CAAC,EAAE,EAAE,UAAU;IAIpB,8CAA8C;IAC9C,cAAc,CACZ,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,gBAAgB,GAC3B,YAAY,GAAG,IAAI;CAyJvB"}
1
+ {"version":3,"file":"change-processor.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAOjD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uCAAuC,CAAC;AAiB1E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAiC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iDAAiD,CAAC;AACtF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AASpD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,cAAc,CAAC;AAWlE,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,cAAc,GAAG,SAAS,CAAC;IAC9C,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;;gBAiBxB,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,mBAAmB,EACzB,WAAW,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI;IAgCrD,KAAK,CAAC,EAAE,EAAE,UAAU;IAIpB,8CAA8C;IAC9C,cAAc,CACZ,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,gBAAgB,GAC3B,YAAY,GAAG,IAAI;CAyJvB"}
@@ -15,7 +15,12 @@ import { updateReplicationWatermark } from "./schema/replication-state.js";
15
15
  import { id } from "../../types/sql.js";
16
16
  import { createLiteIndexStatement, createLiteTableStatement, liteColumnDef } from "../../db/create.js";
17
17
  import { SqliteError } from "@rocicorp/zero-sqlite3";
18
+ import { constants } from "node:buffer";
18
19
  //#region ../zero-cache/src/services/replicator/change-processor.ts
20
+ var BIND_VALUE_TOO_BIG_ERROR = "The bound string, buffer, or bigint is too big";
21
+ var MAX_SQLITE_BIND_BYTES = Math.min(constants.MAX_LENGTH, constants.MAX_STRING_LENGTH);
22
+ var MIN_SQLITE_BIGINT = -(1n << 63n);
23
+ var MAX_SQLITE_BIGINT = (1n << 63n) - 1n;
19
24
  /**
20
25
  * The ChangeProcessor partitions the stream of messages into transactions
21
26
  * by creating a {@link TransactionProcessor} when a transaction begins, and dispatching
@@ -201,7 +206,7 @@ var TransactionProcessor = class {
201
206
  this.#jsonFormat = jsonFormat;
202
207
  switch (mode) {
203
208
  case "serving":
204
- db.beginConcurrent();
209
+ db.beginImmediate();
205
210
  break;
206
211
  case "backup":
207
212
  db.beginImmediate();
@@ -274,12 +279,16 @@ var TransactionProcessor = class {
274
279
  const currKey = oldKey ?? newKey;
275
280
  const conds = Object.keys(currKey).map((col) => `${id(col)}=?`);
276
281
  const setExprs = Object.keys(row).map((col) => `${id(col)}=?`);
277
- const { changes } = this.#db.run(`
278
- UPDATE ${id(table)}
279
- SET ${setExprs.join(",")}
280
- WHERE ${conds.join(" AND ")}
281
- `, [...Object.values(row), ...Object.values(currKey)]);
282
- if (changes === 0) this.#upsert(table, row);
282
+ try {
283
+ const { changes } = this.#db.run(`
284
+ UPDATE ${id(table)}
285
+ SET ${setExprs.join(",")}
286
+ WHERE ${conds.join(" AND ")}
287
+ `, [...Object.values(row), ...Object.values(currKey)]);
288
+ if (changes === 0) this.#upsert(table, row);
289
+ } catch (e) {
290
+ throw wrapBindError(e, this.#version, update.relation, row, currKey);
291
+ }
283
292
  }
284
293
  processDelete(del) {
285
294
  const table = liteTableName(del.relation);
@@ -512,6 +521,35 @@ function getBackfilledColumns(row, { backfilling }) {
512
521
  if (!backfilling?.length) return;
513
522
  return backfilling.filter((col) => col in row);
514
523
  }
524
+ var OversizedUpdateBindingError = class extends Error {
525
+ constructor(message, cause) {
526
+ super(message, { cause });
527
+ this.name = "OversizedUpdateBindingError";
528
+ }
529
+ };
530
+ function wrapBindError(error, tx, relation, row, currKey) {
531
+ if (!(error instanceof RangeError) || error.message !== BIND_VALUE_TOO_BIG_ERROR) return error;
532
+ const binding = [...Object.entries(row), ...Object.entries(currKey)].find(([, value]) => oversizedValueDescription(value));
533
+ const relationOid = getRelationOid(relation);
534
+ return new OversizedUpdateBindingError(`Oversized SQLite update binding: ${[
535
+ `tx=${tx}`,
536
+ relationOid === void 0 ? void 0 : `relationOid=${relationOid}`,
537
+ `table=${relation.schema}.${relation.name}`,
538
+ `column=${binding?.[0] ?? "unknown"}`,
539
+ binding && oversizedValueDescription(binding[1])
540
+ ].filter(Boolean).join(" ")}`, error);
541
+ }
542
+ function getRelationOid(relation) {
543
+ return "relationOid" in relation && typeof relation.relationOid === "number" ? relation.relationOid : void 0;
544
+ }
545
+ function oversizedValueDescription(value) {
546
+ if (typeof value === "bigint") return value < MIN_SQLITE_BIGINT || value > MAX_SQLITE_BIGINT ? "valueType=bigint fitsInt64=false" : void 0;
547
+ if (typeof value === "string") {
548
+ const sizeBytes = Buffer.byteLength(value);
549
+ return sizeBytes > MAX_SQLITE_BIND_BYTES ? `valueType=string sizeBytes=${sizeBytes} limitBytes=${MAX_SQLITE_BIND_BYTES}` : void 0;
550
+ }
551
+ if (value instanceof Uint8Array && value.byteLength > MAX_SQLITE_BIND_BYTES) return `valueType=buffer sizeBytes=${value.byteLength} limitBytes=${MAX_SQLITE_BIND_BYTES}`;
552
+ }
515
553
  function ensureError(err) {
516
554
  if (err instanceof Error) return err;
517
555
  const error = /* @__PURE__ */ new Error();
@@ -1 +1 @@
1
- {"version":3,"file":"change-processor.js","names":["#db","#changeLog","#tableMetadata","#mode","#failService","#tableSpecs","#failure","#currentTx","#fail","#processMessage","#beginTransaction","#lc","#startMs","#version","#jsonFormat","#columnMetadata","#reloadTableSpecs","#tableSpec","#upsert","#getKey","#logSetOp","#logDeleteOp","#delete","#logTruncateOp","#logResetOp","#bumpVersions","#pos","#numChangeLogEntries","#schemaChanged","#completedBackfill"],"sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {mapEntries} from '../../../../shared/src/objects.ts';\nimport type {DownloadStatus} from '../../../../zero-events/src/status.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n liteColumnDef,\n} from '../../db/create.ts';\nimport {\n computeZqlSpecs,\n listIndexes,\n listTables,\n type LiteTableSpecWithReplicationStatus,\n} from '../../db/lite-tables.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteColumn,\n mapPostgresToLiteIndex,\n} from '../../db/pg-to-lite.ts';\nimport type {StatementRunner} from '../../db/statements.ts';\nimport type {LexiVersion} from '../../types/lexi-version.ts';\nimport {\n JSON_PARSED,\n liteRow,\n type JSONFormat,\n type LiteRow,\n type LiteRowKey,\n type LiteValueType,\n} from '../../types/lite.ts';\nimport {liteTableName} from '../../types/names.ts';\nimport {id} from '../../types/sql.ts';\nimport type {\n BackfillCompleted,\n Change,\n ColumnAdd,\n ColumnDrop,\n ColumnUpdate,\n Identifier,\n IndexCreate,\n IndexDrop,\n MessageBackfill,\n MessageCommit,\n MessageDelete,\n MessageInsert,\n MessageRelation,\n MessageTruncate,\n MessageUpdate,\n TableCreate,\n TableDrop,\n TableRename,\n TableUpdateMetadata,\n} from '../change-source/protocol/current/data.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport type {ReplicatorMode} from './replicator.ts';\nimport {ChangeLog, DEL_OP, SET_OP} from './schema/change-log.ts';\nimport {ColumnMetadataStore} from './schema/column-metadata.ts';\nimport {\n ZERO_VERSION_COLUMN_NAME,\n updateReplicationWatermark,\n} from './schema/replication-state.ts';\nimport {TableMetadataTracker} from './schema/table-metadata.ts';\n\nexport type ChangeProcessorMode = ReplicatorMode | 'initial-sync';\n\nexport type CommitResult = {\n watermark: string;\n completedBackfill: DownloadStatus | undefined;\n schemaUpdated: boolean;\n changeLogUpdated: boolean;\n};\n\n/**\n * The ChangeProcessor partitions the stream of messages into transactions\n * by creating a {@link TransactionProcessor} when a transaction begins, and dispatching\n * messages to it until the commit is received.\n *\n * From https://www.postgresql.org/docs/current/protocol-logical-replication.html#PROTOCOL-LOGICAL-MESSAGES-FLOW :\n *\n * \"The logical replication protocol sends individual transactions one by one.\n * This means that all messages between a pair of Begin and Commit messages\n * belong to the same transaction.\"\n */\nexport class ChangeProcessor {\n readonly #db: StatementRunner;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #mode: ChangeProcessorMode;\n readonly #failService: (lc: LogContext, err: unknown) => void;\n\n // The TransactionProcessor lazily loads table specs into this Map,\n // and reloads them after a schema change. It is cached here to avoid\n // reading them from the DB on every transaction.\n readonly #tableSpecs = new Map<string, LiteTableSpecWithReplicationStatus>();\n\n #currentTx: TransactionProcessor | null = null;\n\n #failure: Error | undefined;\n\n constructor(\n db: StatementRunner,\n mode: ChangeProcessorMode,\n failService: (lc: LogContext, err: unknown) => void,\n ) {\n this.#db = db;\n this.#changeLog = new ChangeLog(db.db);\n this.#tableMetadata = new TableMetadataTracker(db.db);\n this.#mode = mode;\n this.#failService = failService;\n }\n\n #fail(lc: LogContext, err: unknown) {\n if (!this.#failure) {\n let failureError = err;\n try {\n this.#currentTx?.abort(lc); // roll back any pending transaction.\n } catch (rollbackError) {\n const combinedError = new Error(\n `Message processing failed and rollback also failed: operation error = ${String(err)}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = err;\n failureError = combinedError;\n }\n\n this.#failure = ensureError(failureError);\n\n if (!(this.#failure instanceof AbortError)) {\n // Propagate the failure up to the service.\n lc.error?.('Message Processing failed:', this.#failure);\n this.#failService(lc, this.#failure);\n }\n }\n }\n\n abort(lc: LogContext) {\n this.#fail(lc, new AbortError());\n }\n\n /** @return If a transaction was committed. */\n processMessage(\n lc: LogContext,\n downstream: ChangeStreamData,\n ): CommitResult | null {\n const [type, message] = downstream;\n if (this.#failure) {\n lc.debug?.(`Dropping ${message.tag}`);\n return null;\n }\n try {\n const watermark =\n type === 'begin'\n ? downstream[2].commitWatermark\n : type === 'commit'\n ? downstream[2].watermark\n : undefined;\n return this.#processMessage(lc, message, watermark);\n } catch (e) {\n this.#fail(lc, e);\n }\n return null;\n }\n\n #beginTransaction(\n lc: LogContext,\n commitVersion: string,\n jsonFormat: JSONFormat,\n ): TransactionProcessor {\n const start = Date.now();\n\n // litestream can technically hold the lock for an arbitrary amount of time\n // when checkpointing a large commit. Crashing on the busy-timeout in this\n // scenario will either produce a corrupt backup or otherwise prevent\n // replication from proceeding.\n //\n // Instead, retry the lock acquisition indefinitely. If this masks\n // an unknown deadlock situation, manual intervention will be necessary.\n for (let i = 0; ; i++) {\n try {\n return new TransactionProcessor(\n lc,\n this.#db,\n this.#mode,\n this.#changeLog,\n this.#tableMetadata,\n this.#tableSpecs,\n commitVersion,\n jsonFormat,\n );\n } catch (e) {\n if (e instanceof SqliteError && e.code === 'SQLITE_BUSY') {\n lc.warn?.(\n `SQLITE_BUSY for ${Date.now() - start} ms (attempt ${i + 1}). ` +\n `This is only expected if litestream is performing a large ` +\n `checkpoint.`,\n e,\n );\n continue;\n }\n throw e;\n }\n }\n }\n\n /** @return If a transaction was committed. */\n #processMessage(\n lc: LogContext,\n msg: Change,\n watermark: string | undefined,\n ): CommitResult | null {\n if (msg.tag === 'begin') {\n if (this.#currentTx) {\n throw new Error(`Already in a transaction ${stringify(msg)}`);\n }\n this.#currentTx = this.#beginTransaction(\n lc,\n must(watermark),\n msg.json ?? JSON_PARSED,\n );\n return null;\n }\n\n // For non-begin messages, there should be a #currentTx set.\n const tx = this.#currentTx;\n if (!tx) {\n throw new Error(\n `Received message outside of transaction: ${stringify(msg)}`,\n );\n }\n\n if (msg.tag === 'commit') {\n // Undef this.#currentTx to allow the assembly of the next transaction.\n this.#currentTx = null;\n\n assert(watermark, 'watermark is required for commit messages');\n return tx.processCommit(msg, watermark);\n }\n\n if (msg.tag === 'rollback') {\n this.#currentTx?.abort(lc);\n this.#currentTx = null;\n return null;\n }\n\n switch (msg.tag) {\n case 'insert':\n tx.processInsert(msg);\n break;\n case 'update':\n tx.processUpdate(msg);\n break;\n case 'delete':\n tx.processDelete(msg);\n break;\n case 'truncate':\n tx.processTruncate(msg);\n break;\n case 'create-table':\n tx.processCreateTable(msg);\n break;\n case 'rename-table':\n tx.processRenameTable(msg);\n break;\n case 'update-table-metadata':\n tx.processTableMetadata(msg);\n break;\n case 'add-column':\n tx.processAddColumn(msg);\n break;\n case 'update-column':\n tx.processUpdateColumn(msg);\n break;\n case 'drop-column':\n tx.processDropColumn(msg);\n break;\n case 'drop-table':\n tx.processDropTable(msg);\n break;\n case 'create-index':\n tx.processCreateIndex(msg);\n break;\n case 'drop-index':\n tx.processDropIndex(msg);\n break;\n case 'backfill':\n tx.processBackfill(msg);\n break;\n case 'backfill-completed':\n tx.processBackfillCompleted(msg);\n break;\n default:\n unreachable(msg);\n }\n\n return null;\n }\n}\n\n/**\n * The {@link TransactionProcessor} handles the sequence of messages from\n * upstream, from `BEGIN` to `COMMIT` and executes the corresponding mutations\n * on the {@link postgres.TransactionSql} on the replica.\n *\n * When applying row contents to the replica, the `_0_version` column is added / updated,\n * and a corresponding entry in the `ChangeLog` is added. The version value is derived\n * from the watermark of the preceding transaction (stored as the `nextStateVersion` in the\n * `ReplicationState` table).\n *\n * Side note: For non-streaming Postgres transactions, the commitEndLsn (and thus\n * commit watermark) is available in the `begin` message, so it could theoretically\n * be used for the row version of changes within the transaction. However, the\n * commitEndLsn is not available in the streaming (in-progress) transaction\n * protocol, and may not be available for CDC streams of other upstream types.\n * Therefore, the zero replication protocol is designed to not require the commit\n * watermark when a transaction begins.\n *\n * Also of interest is the fact that all INSERT Messages are logically applied as\n * UPSERTs. See {@link processInsert} for the underlying motivation.\n */\nclass TransactionProcessor {\n readonly #lc: LogContext;\n readonly #startMs: number;\n readonly #db: StatementRunner;\n readonly #mode: ChangeProcessorMode;\n readonly #version: LexiVersion;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>;\n readonly #jsonFormat: JSONFormat;\n readonly #columnMetadata: ColumnMetadataStore;\n\n #pos = 0;\n #schemaChanged = false;\n #numChangeLogEntries = 0;\n\n constructor(\n lc: LogContext,\n db: StatementRunner,\n mode: ChangeProcessorMode,\n changeLog: ChangeLog,\n tableMetadata: TableMetadataTracker,\n tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>,\n commitVersion: LexiVersion,\n jsonFormat: JSONFormat,\n ) {\n this.#startMs = Date.now();\n this.#mode = mode;\n this.#jsonFormat = jsonFormat;\n\n switch (mode) {\n case 'serving':\n // Although the Replicator / Incremental Syncer is the only writer of the replica,\n // a `BEGIN CONCURRENT` transaction is used to allow View Syncers to simulate\n // (i.e. and `ROLLBACK`) changes on historic snapshots of the database for the\n // purpose of IVM).\n //\n // This TransactionProcessor is the only logic that will actually\n // `COMMIT` any transactions to the replica.\n db.beginConcurrent();\n break;\n case 'backup':\n // For the backup-replicator (i.e. replication-manager), there are no View Syncers\n // and thus BEGIN CONCURRENT is not necessary. In fact, BEGIN CONCURRENT can cause\n // deadlocks with forced wal-checkpoints (which `litestream replicate` performs),\n // so it is important to use vanilla transactions in this configuration.\n db.beginImmediate();\n break;\n case 'initial-sync':\n // When the ChangeProcessor is used for initial-sync, the calling code\n // handles the transaction boundaries.\n break;\n default:\n unreachable();\n }\n this.#db = db;\n this.#version = commitVersion;\n this.#lc = lc.withContext('version', commitVersion);\n this.#changeLog = changeLog;\n this.#tableMetadata = tableMetadata;\n this.#tableSpecs = tableSpecs;\n // The column_metadata table is guaranteed to exist since the\n // replica-schema.ts migration to v8.\n this.#columnMetadata = must(ColumnMetadataStore.getInstance(db.db));\n\n if (this.#tableSpecs.size === 0) {\n this.#reloadTableSpecs();\n }\n }\n\n #reloadTableSpecs() {\n this.#tableSpecs.clear();\n // zqlSpecs include the primary key derived from unique indexes\n const zqlSpecs = computeZqlSpecs(this.#lc, this.#db.db, {\n includeBackfillingColumns: true,\n });\n for (let spec of listTables(this.#db.db)) {\n if (!spec.primaryKey) {\n spec = {\n ...spec,\n primaryKey: [\n ...(zqlSpecs.get(spec.name)?.tableSpec.primaryKey ?? []),\n ],\n };\n }\n this.#tableSpecs.set(spec.name, spec);\n }\n }\n\n #tableSpec(name: string) {\n return must(this.#tableSpecs.get(name), `Unknown table ${name}`);\n }\n\n #getKey(\n {row, numCols}: {row: LiteRow; numCols: number},\n {relation}: {relation: MessageRelation},\n ): LiteRowKey {\n const keyColumns =\n relation.rowKey.type !== 'full'\n ? relation.rowKey.columns // already a suitable key\n : this.#tableSpec(liteTableName(relation)).primaryKey;\n if (!keyColumns?.length) {\n throw new Error(\n `Cannot replicate table \"${relation.name}\" without a PRIMARY KEY or UNIQUE INDEX`,\n );\n }\n // For the common case (replica identity default), the row is already the\n // key for deletes and updates, in which case a new object can be avoided.\n if (numCols === keyColumns.length) {\n return row;\n }\n const key: Record<string, LiteValueType> = {};\n for (const col of keyColumns) {\n key[col] = row[col];\n }\n return key;\n }\n\n processInsert(insert: MessageInsert) {\n const table = liteTableName(insert.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(insert.new, tableSpec, this.#jsonFormat);\n\n this.#upsert(table, {\n ...newRow.row,\n [ZERO_VERSION_COLUMN_NAME]: this.#version,\n });\n\n if (insert.relation.rowKey.columns.length === 0) {\n // INSERTs can be replicated for rows without a PRIMARY KEY or a\n // UNIQUE INDEX. These are written to the replica but not recorded\n // in the changeLog, because these rows cannot participate in IVM.\n //\n // (Once the table schema has been corrected to include a key, the\n // associated schema change will reset pipelines and data can be\n // loaded via hydration.)\n return;\n }\n const key = this.#getKey(newRow, insert);\n this.#logSetOp(table, key, getBackfilledColumns(newRow.row, tableSpec));\n }\n\n #upsert(table: string, row: LiteRow) {\n const columns = Object.keys(row).map(c => id(c));\n this.#db.run(\n `\n INSERT OR REPLACE INTO ${id(table)} (${columns.join(',')})\n VALUES (${Array.from({length: columns.length}).fill('?').join(',')})\n `,\n Object.values(row),\n );\n }\n\n // Updates by default are applied as UPDATE commands to support partial\n // row specifications from the change source. In particular, this is needed\n // to handle updates for which unchanged TOASTed values are not sent:\n //\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-TUPLEDATA\n //\n // However, in certain cases an UPDATE may be received for a row that\n // was not initially synced, such as when, an existing table is added\n // to the app's publication.\n //\n // In order to facilitate \"resumptive\" replication, the logic falls back to\n // an INSERT if the update did not change any rows.\n processUpdate(update: MessageUpdate) {\n const table = liteTableName(update.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(update.new, tableSpec, this.#jsonFormat);\n const row = {...newRow.row, [ZERO_VERSION_COLUMN_NAME]: this.#version};\n\n // update.key is set with the old values if the key has changed.\n const oldKey = update.key\n ? this.#getKey(\n liteRow(update.key, this.#tableSpec(table), this.#jsonFormat),\n update,\n )\n : null;\n const newKey = this.#getKey(newRow, update);\n\n if (oldKey) {\n this.#logDeleteOp(table, oldKey, tableSpec.backfilling);\n }\n this.#logSetOp(table, newKey, getBackfilledColumns(newRow.row, tableSpec));\n\n const currKey = oldKey ?? newKey;\n const conds = Object.keys(currKey).map(col => `${id(col)}=?`);\n const setExprs = Object.keys(row).map(col => `${id(col)}=?`);\n\n const {changes} = this.#db.run(\n `\n UPDATE ${id(table)}\n SET ${setExprs.join(',')}\n WHERE ${conds.join(' AND ')}\n `,\n [...Object.values(row), ...Object.values(currKey)],\n );\n\n // If the UPDATE did not affect any rows, perform an UPSERT of the\n // new row for resumptive replication.\n if (changes === 0) {\n this.#upsert(table, row);\n }\n }\n\n processDelete(del: MessageDelete) {\n const table = liteTableName(del.relation);\n const tableSpec = this.#tableSpec(table);\n const rowKey = this.#getKey(\n liteRow(del.key, tableSpec, this.#jsonFormat),\n del,\n );\n\n this.#delete(table, rowKey);\n this.#logDeleteOp(table, rowKey, tableSpec.backfilling);\n }\n\n #delete(table: string, rowKey: LiteRowKey) {\n const conds = Object.keys(rowKey).map(col => `${id(col)}=?`);\n this.#db.run(\n `DELETE FROM ${id(table)} WHERE ${conds.join(' AND ')}`,\n Object.values(rowKey),\n );\n }\n\n processTruncate(truncate: MessageTruncate) {\n for (const relation of truncate.relations) {\n const table = liteTableName(relation);\n // Update replica data.\n this.#db.run(`DELETE FROM ${id(table)}`);\n\n // Update change log.\n this.#logTruncateOp(table);\n }\n }\n\n processCreateTable(create: TableCreate) {\n if (create.metadata) {\n this.#tableMetadata.setUpstreamMetadata(create.spec, create.metadata);\n }\n const table = mapPostgresToLite(create.spec);\n this.#db.db.exec(createLiteTableStatement(table));\n\n // Write to metadata table\n for (const [colName, colSpec] of Object.entries(create.spec.columns)) {\n this.#columnMetadata.insert(\n table.name,\n colName,\n colSpec,\n create.backfill?.[colName],\n );\n }\n\n if (\n Object.keys(create.backfill ?? {}).length ===\n Object.keys(create.spec.columns).length\n ) {\n this.#reloadTableSpecs();\n } else {\n // Make the table visible immediately unless all of the columns are\n // being backfilled. In the backfill case, the version bump will happen\n // with the backfill is complete.\n this.#logResetOp(table.name);\n }\n this.#lc.info?.(create.tag, table.name);\n }\n\n processTableMetadata(msg: TableUpdateMetadata) {\n this.#tableMetadata.setUpstreamMetadata(msg.table, msg.new);\n }\n\n processRenameTable(rename: TableRename) {\n this.#tableMetadata.rename(rename.old, rename.new);\n\n const oldName = liteTableName(rename.old);\n const newName = liteTableName(rename.new);\n this.#db.db.exec(`ALTER TABLE ${id(oldName)} RENAME TO ${id(newName)}`);\n\n // Rename in metadata table\n this.#columnMetadata.renameTable(oldName, newName);\n\n this.#bumpVersions(rename.new);\n this.#logResetOp(oldName);\n this.#lc.info?.(rename.tag, oldName, newName);\n }\n\n processAddColumn(msg: ColumnAdd) {\n if (msg.tableMetadata) {\n this.#tableMetadata.setUpstreamMetadata(msg.table, msg.tableMetadata);\n }\n const table = liteTableName(msg.table);\n const {name} = msg.column;\n const spec = mapPostgresToLiteColumn(table, msg.column);\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} ADD ${id(name)} ${liteColumnDef(spec)}`,\n );\n\n // Write to metadata table\n this.#columnMetadata.insert(table, name, msg.column.spec, msg.backfill);\n\n if (msg.backfill) {\n this.#reloadTableSpecs();\n } else {\n // Make the new column visible immediately if it's not being backfilled.\n // Otherwise, the version bump will happen with the backfill is complete.\n this.#bumpVersions(msg.table);\n }\n this.#lc.info?.(msg.tag, table, msg.column);\n }\n\n processUpdateColumn(msg: ColumnUpdate) {\n const table = liteTableName(msg.table);\n let oldName = msg.old.name;\n const newName = msg.new.name;\n\n // update-column can ignore defaults because it does not change the values\n // in existing rows.\n //\n // https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-SET-DROP-DEFAULT\n //\n // \"The new default value will only apply in subsequent INSERT or UPDATE\n // commands; it does not cause rows already in the table to change.\"\n //\n // This allows support for _changing_ column defaults to any expression,\n // since it does not affect what the replica needs to do.\n const oldSpec = mapPostgresToLiteColumn(table, msg.old, 'ignore-default');\n const newSpec = mapPostgresToLiteColumn(table, msg.new, 'ignore-default');\n\n // If neither the column name nor the SQLite data type changes, only the\n // upstream metadata needs to be updated. This includes changes such as a\n // varchar character limit, which SQLite does not enforce but a freshly\n // built replica still records.\n if (oldName === newName && oldSpec.dataType === newSpec.dataType) {\n this.#columnMetadata.update(\n table,\n msg.old.name,\n msg.new.name,\n msg.new.spec,\n );\n this.#lc.info?.(msg.tag, 'updated metadata only', oldSpec, newSpec);\n return;\n }\n // If the data type changes, we have to make a new column with the new data type\n // and copy the values over.\n if (oldSpec.dataType !== newSpec.dataType) {\n const tableSpec = must(\n listTables(this.#db.db, false, false).find(\n tableSpec => tableSpec.name === table,\n ),\n );\n const indexes = listIndexes(this.#db.db).filter(\n idx => idx.tableName === table,\n );\n const tmpTable = `tmp.${table}`;\n const newColumns = mapEntries(tableSpec.columns, (column, spec) => [\n column === oldName ? newName : column,\n column === oldName ? {...newSpec, pos: spec.pos} : spec,\n ]);\n const sourceColumns = Object.keys(tableSpec.columns);\n const destinationColumns = Object.keys(newColumns);\n const stmts = [\n createLiteTableStatement({\n ...tableSpec,\n name: tmpTable,\n columns: newColumns,\n }),\n `INSERT INTO ${id(tmpTable)} (${destinationColumns.map(id).join(',')})\n SELECT ${sourceColumns.map(id).join(',')} FROM ${id(table)};`,\n `DROP TABLE ${id(table)};`,\n `ALTER TABLE ${id(tmpTable)} RENAME TO ${id(table)};`,\n ...indexes.map(idx =>\n createLiteIndexStatement({\n ...idx,\n columns: mapEntries(idx.columns, (column, direction) => [\n column === oldName ? newName : column,\n direction,\n ]),\n }),\n ),\n ];\n this.#db.db.exec(stmts.join(''));\n oldName = newName;\n }\n if (oldName !== newName) {\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} RENAME ${id(oldName)} TO ${id(newName)}`,\n );\n }\n\n // Update metadata table\n this.#columnMetadata.update(\n table,\n msg.old.name,\n msg.new.name,\n msg.new.spec,\n );\n\n this.#bumpVersions(msg.table);\n this.#lc.info?.(msg.tag, table, msg.new);\n }\n\n processDropColumn(msg: ColumnDrop) {\n const table = liteTableName(msg.table);\n const {column} = msg;\n this.#db.db.exec(`ALTER TABLE ${id(table)} DROP ${id(column)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteColumn(table, column);\n\n this.#bumpVersions(msg.table);\n this.#lc.info?.(msg.tag, table, column);\n }\n\n processDropTable(drop: TableDrop) {\n this.#tableMetadata.drop(drop.id);\n\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP TABLE IF EXISTS ${id(name)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteTable(name);\n\n this.#logResetOp(name);\n this.#lc.info?.(drop.tag, name);\n }\n\n processCreateIndex(create: IndexCreate) {\n const index = mapPostgresToLiteIndex(create.spec);\n this.#db.db.exec(createLiteIndexStatement(index));\n\n // indexes affect tables visibility (e.g. sync-ability is gated on\n // having a unique index), so reset pipelines to refresh table schemas.\n // However, the reset is not necessary if the index is for a table\n // that is not yet visible due to backfilling.\n const tableSpec = must(this.#tableSpecs.get(index.tableName));\n if (\n (tableSpec.backfilling ?? []).length ===\n Object.entries(tableSpec.columns).length - 1 // don't count _0_version\n ) {\n this.#reloadTableSpecs();\n } else {\n this.#logResetOp(index.tableName);\n }\n this.#lc.info?.(create.tag, index.name);\n }\n\n processDropIndex(drop: IndexDrop) {\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP INDEX IF EXISTS ${id(name)}`);\n this.#lc.info?.(drop.tag, name);\n }\n\n #bumpVersions(table: Identifier) {\n this.#tableMetadata.setMinRowVersion(table, this.#version);\n this.#logResetOp(liteTableName(table));\n }\n\n /**\n * @param backfilledColumns `backfilling` columns for which values were set\n */\n #logSetOp(\n table: string,\n key: LiteRowKey,\n backfilledColumns: string[] | undefined,\n ) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilledColumns !== undefined) {\n this.#changeLog.logSetOp(\n this.#version,\n this.#pos++,\n table,\n key,\n backfilledColumns,\n );\n this.#numChangeLogEntries++;\n }\n }\n\n #logDeleteOp(table: string, key: LiteRowKey, backfilling?: string[]) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilling?.length) {\n this.#changeLog.logDeleteOp(this.#version, this.#pos++, table, key);\n this.#numChangeLogEntries++;\n }\n }\n\n #logTruncateOp(table: string) {\n if (this.#mode === 'serving') {\n this.#changeLog.logTruncateOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n }\n\n #logResetOp(table: string) {\n this.#schemaChanged = true;\n if (this.#mode === 'serving') {\n this.#changeLog.logResetOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n this.#reloadTableSpecs();\n }\n\n processBackfill({relation, watermark, columns, rowValues}: MessageBackfill) {\n const tableName = liteTableName(relation);\n const tableSpec = must(this.#tableSpecs.get(tableName));\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n // Common parts of the INSERT sql statement.\n const insertColsStr = [...cols, ZERO_VERSION_COLUMN_NAME].map(id).join(',');\n const qMarks = Array.from({length: cols.length + 1})\n .fill('?')\n .join(',');\n const rowKeyColsStr = rowKeyCols.map(id).join(',');\n\n let backfilled = 0;\n let skipped = 0;\n for (const v of rowValues) {\n const row = liteRow(\n Object.fromEntries(cols.map((c, i) => [c, v[i]])),\n tableSpec,\n this.#jsonFormat,\n );\n const rowKey = this.#getKey(row, {relation});\n const rowOp = this.#changeLog.getLatestRowOp(tableName, rowKey);\n if (rowOp?.op === DEL_OP && rowOp.stateVersion > watermark) {\n skipped++;\n continue; // the row was deleted after the backfill snapshot\n }\n const updates =\n rowOp?.op === SET_OP\n ? cols.filter(\n c => (rowOp.backfillingColumnVersions[c] ?? '') <= watermark,\n )\n : cols;\n if (updates.length === 0) {\n // row already has newer values for all backfilling columns.\n skipped++;\n continue;\n }\n const updateStmts = updates.map(col => `${id(col)}=excluded.${id(col)}`);\n this.#db.run(\n /*sql*/ `\n INSERT INTO ${id(tableName)} (${insertColsStr}) VALUES (${qMarks})\n ON CONFLICT (${rowKeyColsStr})\n DO UPDATE SET ${updateStmts.join(',')};\n `,\n ...Object.values(row.row),\n watermark, // the _0_version for new rows (i.e. table backfill)\n );\n backfilled++;\n }\n\n this.#lc.debug?.(\n `backfilled ${backfilled} rows (skipped ${skipped}) into ${tableName}`,\n );\n }\n\n #completedBackfill: DownloadStatus | undefined;\n\n processBackfillCompleted({relation, columns, status}: BackfillCompleted) {\n const tableName = liteTableName(relation);\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n const columnMetadata = must(ColumnMetadataStore.getInstance(this.#db.db));\n for (const col of cols) {\n columnMetadata.clearBackfilling(tableName, col);\n }\n // Given that new columns are being exposed for every row in the table, bump the\n // row version for all rows.\n this.#bumpVersions(relation);\n if (status) {\n this.#completedBackfill = {table: tableName, columns: cols, ...status};\n }\n this.#lc.info?.(`finished backfilling ${tableName}`);\n\n // Note that there is no need to clear the backfillingColumnVersions values\n // in the changeLog. It could theoretically be done for clarity but:\n // (1) it could be non-trivial in terms of latency introduced and\n // (2) the data must be preserved if _other_ columns are in the process\n // of being backfilled\n //\n // Thus, for speed and simplicity, the values are left as is. (Note that\n // subsequent replicated changes to those rows will clear the values if\n // no backfills are in progress).\n }\n\n processCommit(commit: MessageCommit, watermark: string): CommitResult {\n if (watermark !== this.#version) {\n throw new Error(\n `'commit' version ${watermark} does not match 'begin' version ${\n this.#version\n }: ${stringify(commit)}`,\n );\n }\n updateReplicationWatermark(this.#db, watermark);\n\n if (this.#schemaChanged) {\n const start = Date.now();\n this.#db.db.pragma('optimize');\n this.#lc.info?.(\n `PRAGMA optimized after schema change (${Date.now() - start} ms)`,\n );\n }\n\n if (this.#mode !== 'initial-sync') {\n this.#db.commit();\n }\n\n const elapsedMs = Date.now() - this.#startMs;\n this.#lc.debug?.(`Committed tx@${this.#version} (${elapsedMs} ms)`);\n\n return {\n watermark,\n completedBackfill: this.#completedBackfill,\n schemaUpdated: this.#schemaChanged,\n changeLogUpdated: this.#numChangeLogEntries > 0,\n };\n }\n\n abort(lc: LogContext) {\n lc.info?.(`aborting transaction ${this.#version}`);\n this.#db.rollback();\n }\n}\n\nfunction getBackfilledColumns(\n row: LiteRow,\n {backfilling}: LiteTableSpecWithReplicationStatus,\n): string[] | undefined {\n if (!backfilling?.length) {\n return undefined; // common case\n }\n return backfilling.filter(col => col in row);\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"change-processor.js","names":["#db","#changeLog","#tableMetadata","#mode","#failService","#tableSpecs","#failure","#currentTx","#fail","#processMessage","#beginTransaction","#lc","#startMs","#version","#jsonFormat","#columnMetadata","#reloadTableSpecs","#tableSpec","#upsert","#getKey","#logSetOp","#logDeleteOp","#delete","#logTruncateOp","#logResetOp","#bumpVersions","#pos","#numChangeLogEntries","#schemaChanged","#completedBackfill"],"sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"sourcesContent":["import {constants as bufferConstants} from 'node:buffer';\nimport type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {mapEntries} from '../../../../shared/src/objects.ts';\nimport type {DownloadStatus} from '../../../../zero-events/src/status.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n liteColumnDef,\n} from '../../db/create.ts';\nimport {\n computeZqlSpecs,\n listIndexes,\n listTables,\n type LiteTableSpecWithReplicationStatus,\n} from '../../db/lite-tables.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteColumn,\n mapPostgresToLiteIndex,\n} from '../../db/pg-to-lite.ts';\nimport type {StatementRunner} from '../../db/statements.ts';\nimport type {LexiVersion} from '../../types/lexi-version.ts';\nimport {\n JSON_PARSED,\n liteRow,\n type JSONFormat,\n type LiteRow,\n type LiteRowKey,\n type LiteValueType,\n} from '../../types/lite.ts';\nimport {liteTableName} from '../../types/names.ts';\nimport {id} from '../../types/sql.ts';\nimport type {\n BackfillCompleted,\n Change,\n ColumnAdd,\n ColumnDrop,\n ColumnUpdate,\n Identifier,\n IndexCreate,\n IndexDrop,\n MessageBackfill,\n MessageCommit,\n MessageDelete,\n MessageInsert,\n MessageRelation,\n MessageTruncate,\n MessageUpdate,\n TableCreate,\n TableDrop,\n TableRename,\n TableUpdateMetadata,\n} from '../change-source/protocol/current/data.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport type {ReplicatorMode} from './replicator.ts';\nimport {ChangeLog, DEL_OP, SET_OP} from './schema/change-log.ts';\nimport {ColumnMetadataStore} from './schema/column-metadata.ts';\nimport {\n ZERO_VERSION_COLUMN_NAME,\n updateReplicationWatermark,\n} from './schema/replication-state.ts';\nimport {TableMetadataTracker} from './schema/table-metadata.ts';\n\nexport type ChangeProcessorMode = ReplicatorMode | 'initial-sync';\n\nconst BIND_VALUE_TOO_BIG_ERROR =\n 'The bound string, buffer, or bigint is too big';\nconst MAX_SQLITE_BIND_BYTES = Math.min(\n bufferConstants.MAX_LENGTH,\n bufferConstants.MAX_STRING_LENGTH,\n);\nconst MIN_SQLITE_BIGINT = -(1n << 63n);\nconst MAX_SQLITE_BIGINT = (1n << 63n) - 1n;\n\nexport type CommitResult = {\n watermark: string;\n completedBackfill: DownloadStatus | undefined;\n schemaUpdated: boolean;\n changeLogUpdated: boolean;\n};\n\n/**\n * The ChangeProcessor partitions the stream of messages into transactions\n * by creating a {@link TransactionProcessor} when a transaction begins, and dispatching\n * messages to it until the commit is received.\n *\n * From https://www.postgresql.org/docs/current/protocol-logical-replication.html#PROTOCOL-LOGICAL-MESSAGES-FLOW :\n *\n * \"The logical replication protocol sends individual transactions one by one.\n * This means that all messages between a pair of Begin and Commit messages\n * belong to the same transaction.\"\n */\nexport class ChangeProcessor {\n readonly #db: StatementRunner;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #mode: ChangeProcessorMode;\n readonly #failService: (lc: LogContext, err: unknown) => void;\n\n // The TransactionProcessor lazily loads table specs into this Map,\n // and reloads them after a schema change. It is cached here to avoid\n // reading them from the DB on every transaction.\n readonly #tableSpecs = new Map<string, LiteTableSpecWithReplicationStatus>();\n\n #currentTx: TransactionProcessor | null = null;\n\n #failure: Error | undefined;\n\n constructor(\n db: StatementRunner,\n mode: ChangeProcessorMode,\n failService: (lc: LogContext, err: unknown) => void,\n ) {\n this.#db = db;\n this.#changeLog = new ChangeLog(db.db);\n this.#tableMetadata = new TableMetadataTracker(db.db);\n this.#mode = mode;\n this.#failService = failService;\n }\n\n #fail(lc: LogContext, err: unknown) {\n if (!this.#failure) {\n let failureError = err;\n try {\n this.#currentTx?.abort(lc); // roll back any pending transaction.\n } catch (rollbackError) {\n const combinedError = new Error(\n `Message processing failed and rollback also failed: operation error = ${String(err)}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = err;\n failureError = combinedError;\n }\n\n this.#failure = ensureError(failureError);\n\n if (!(this.#failure instanceof AbortError)) {\n // Propagate the failure up to the service.\n lc.error?.('Message Processing failed:', this.#failure);\n this.#failService(lc, this.#failure);\n }\n }\n }\n\n abort(lc: LogContext) {\n this.#fail(lc, new AbortError());\n }\n\n /** @return If a transaction was committed. */\n processMessage(\n lc: LogContext,\n downstream: ChangeStreamData,\n ): CommitResult | null {\n const [type, message] = downstream;\n if (this.#failure) {\n lc.debug?.(`Dropping ${message.tag}`);\n return null;\n }\n try {\n const watermark =\n type === 'begin'\n ? downstream[2].commitWatermark\n : type === 'commit'\n ? downstream[2].watermark\n : undefined;\n return this.#processMessage(lc, message, watermark);\n } catch (e) {\n this.#fail(lc, e);\n }\n return null;\n }\n\n #beginTransaction(\n lc: LogContext,\n commitVersion: string,\n jsonFormat: JSONFormat,\n ): TransactionProcessor {\n const start = Date.now();\n\n // litestream can technically hold the lock for an arbitrary amount of time\n // when checkpointing a large commit. Crashing on the busy-timeout in this\n // scenario will either produce a corrupt backup or otherwise prevent\n // replication from proceeding.\n //\n // Instead, retry the lock acquisition indefinitely. If this masks\n // an unknown deadlock situation, manual intervention will be necessary.\n for (let i = 0; ; i++) {\n try {\n return new TransactionProcessor(\n lc,\n this.#db,\n this.#mode,\n this.#changeLog,\n this.#tableMetadata,\n this.#tableSpecs,\n commitVersion,\n jsonFormat,\n );\n } catch (e) {\n if (e instanceof SqliteError && e.code === 'SQLITE_BUSY') {\n lc.warn?.(\n `SQLITE_BUSY for ${Date.now() - start} ms (attempt ${i + 1}). ` +\n `This is only expected if litestream is performing a large ` +\n `checkpoint.`,\n e,\n );\n continue;\n }\n throw e;\n }\n }\n }\n\n /** @return If a transaction was committed. */\n #processMessage(\n lc: LogContext,\n msg: Change,\n watermark: string | undefined,\n ): CommitResult | null {\n if (msg.tag === 'begin') {\n if (this.#currentTx) {\n throw new Error(`Already in a transaction ${stringify(msg)}`);\n }\n this.#currentTx = this.#beginTransaction(\n lc,\n must(watermark),\n msg.json ?? JSON_PARSED,\n );\n return null;\n }\n\n // For non-begin messages, there should be a #currentTx set.\n const tx = this.#currentTx;\n if (!tx) {\n throw new Error(\n `Received message outside of transaction: ${stringify(msg)}`,\n );\n }\n\n if (msg.tag === 'commit') {\n // Undef this.#currentTx to allow the assembly of the next transaction.\n this.#currentTx = null;\n\n assert(watermark, 'watermark is required for commit messages');\n return tx.processCommit(msg, watermark);\n }\n\n if (msg.tag === 'rollback') {\n this.#currentTx?.abort(lc);\n this.#currentTx = null;\n return null;\n }\n\n switch (msg.tag) {\n case 'insert':\n tx.processInsert(msg);\n break;\n case 'update':\n tx.processUpdate(msg);\n break;\n case 'delete':\n tx.processDelete(msg);\n break;\n case 'truncate':\n tx.processTruncate(msg);\n break;\n case 'create-table':\n tx.processCreateTable(msg);\n break;\n case 'rename-table':\n tx.processRenameTable(msg);\n break;\n case 'update-table-metadata':\n tx.processTableMetadata(msg);\n break;\n case 'add-column':\n tx.processAddColumn(msg);\n break;\n case 'update-column':\n tx.processUpdateColumn(msg);\n break;\n case 'drop-column':\n tx.processDropColumn(msg);\n break;\n case 'drop-table':\n tx.processDropTable(msg);\n break;\n case 'create-index':\n tx.processCreateIndex(msg);\n break;\n case 'drop-index':\n tx.processDropIndex(msg);\n break;\n case 'backfill':\n tx.processBackfill(msg);\n break;\n case 'backfill-completed':\n tx.processBackfillCompleted(msg);\n break;\n default:\n unreachable(msg);\n }\n\n return null;\n }\n}\n\n/**\n * The {@link TransactionProcessor} handles the sequence of messages from\n * upstream, from `BEGIN` to `COMMIT` and executes the corresponding mutations\n * on the {@link postgres.TransactionSql} on the replica.\n *\n * When applying row contents to the replica, the `_0_version` column is added / updated,\n * and a corresponding entry in the `ChangeLog` is added. The version value is derived\n * from the watermark of the preceding transaction (stored as the `nextStateVersion` in the\n * `ReplicationState` table).\n *\n * Side note: For non-streaming Postgres transactions, the commitEndLsn (and thus\n * commit watermark) is available in the `begin` message, so it could theoretically\n * be used for the row version of changes within the transaction. However, the\n * commitEndLsn is not available in the streaming (in-progress) transaction\n * protocol, and may not be available for CDC streams of other upstream types.\n * Therefore, the zero replication protocol is designed to not require the commit\n * watermark when a transaction begins.\n *\n * Also of interest is the fact that all INSERT Messages are logically applied as\n * UPSERTs. See {@link processInsert} for the underlying motivation.\n */\nclass TransactionProcessor {\n readonly #lc: LogContext;\n readonly #startMs: number;\n readonly #db: StatementRunner;\n readonly #mode: ChangeProcessorMode;\n readonly #version: LexiVersion;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>;\n readonly #jsonFormat: JSONFormat;\n readonly #columnMetadata: ColumnMetadataStore;\n\n #pos = 0;\n #schemaChanged = false;\n #numChangeLogEntries = 0;\n\n constructor(\n lc: LogContext,\n db: StatementRunner,\n mode: ChangeProcessorMode,\n changeLog: ChangeLog,\n tableMetadata: TableMetadataTracker,\n tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>,\n commitVersion: LexiVersion,\n jsonFormat: JSONFormat,\n ) {\n this.#startMs = Date.now();\n this.#mode = mode;\n this.#jsonFormat = jsonFormat;\n\n switch (mode) {\n case 'serving':\n // This is the only transaction that commits to the serving replica.\n // Snapshotters use BEGIN CONCURRENT for private changes that are\n // always rolled back, while BEGIN IMMEDIATE lets this writer spill\n // dirty pages during large transactions.\n db.beginImmediate();\n break;\n case 'backup':\n // For the backup-replicator (i.e. replication-manager), there are no View Syncers\n // and thus BEGIN CONCURRENT is not necessary. In fact, BEGIN CONCURRENT can cause\n // deadlocks with forced wal-checkpoints (which `litestream replicate` performs),\n // so it is important to use vanilla transactions in this configuration.\n db.beginImmediate();\n break;\n case 'initial-sync':\n // When the ChangeProcessor is used for initial-sync, the calling code\n // handles the transaction boundaries.\n break;\n default:\n unreachable();\n }\n this.#db = db;\n this.#version = commitVersion;\n this.#lc = lc.withContext('version', commitVersion);\n this.#changeLog = changeLog;\n this.#tableMetadata = tableMetadata;\n this.#tableSpecs = tableSpecs;\n // The column_metadata table is guaranteed to exist since the\n // replica-schema.ts migration to v8.\n this.#columnMetadata = must(ColumnMetadataStore.getInstance(db.db));\n\n if (this.#tableSpecs.size === 0) {\n this.#reloadTableSpecs();\n }\n }\n\n #reloadTableSpecs() {\n this.#tableSpecs.clear();\n // zqlSpecs include the primary key derived from unique indexes\n const zqlSpecs = computeZqlSpecs(this.#lc, this.#db.db, {\n includeBackfillingColumns: true,\n });\n for (let spec of listTables(this.#db.db)) {\n if (!spec.primaryKey) {\n spec = {\n ...spec,\n primaryKey: [\n ...(zqlSpecs.get(spec.name)?.tableSpec.primaryKey ?? []),\n ],\n };\n }\n this.#tableSpecs.set(spec.name, spec);\n }\n }\n\n #tableSpec(name: string) {\n return must(this.#tableSpecs.get(name), `Unknown table ${name}`);\n }\n\n #getKey(\n {row, numCols}: {row: LiteRow; numCols: number},\n {relation}: {relation: MessageRelation},\n ): LiteRowKey {\n const keyColumns =\n relation.rowKey.type !== 'full'\n ? relation.rowKey.columns // already a suitable key\n : this.#tableSpec(liteTableName(relation)).primaryKey;\n if (!keyColumns?.length) {\n throw new Error(\n `Cannot replicate table \"${relation.name}\" without a PRIMARY KEY or UNIQUE INDEX`,\n );\n }\n // For the common case (replica identity default), the row is already the\n // key for deletes and updates, in which case a new object can be avoided.\n if (numCols === keyColumns.length) {\n return row;\n }\n const key: Record<string, LiteValueType> = {};\n for (const col of keyColumns) {\n key[col] = row[col];\n }\n return key;\n }\n\n processInsert(insert: MessageInsert) {\n const table = liteTableName(insert.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(insert.new, tableSpec, this.#jsonFormat);\n\n this.#upsert(table, {\n ...newRow.row,\n [ZERO_VERSION_COLUMN_NAME]: this.#version,\n });\n\n if (insert.relation.rowKey.columns.length === 0) {\n // INSERTs can be replicated for rows without a PRIMARY KEY or a\n // UNIQUE INDEX. These are written to the replica but not recorded\n // in the changeLog, because these rows cannot participate in IVM.\n //\n // (Once the table schema has been corrected to include a key, the\n // associated schema change will reset pipelines and data can be\n // loaded via hydration.)\n return;\n }\n const key = this.#getKey(newRow, insert);\n this.#logSetOp(table, key, getBackfilledColumns(newRow.row, tableSpec));\n }\n\n #upsert(table: string, row: LiteRow) {\n const columns = Object.keys(row).map(c => id(c));\n this.#db.run(\n `\n INSERT OR REPLACE INTO ${id(table)} (${columns.join(',')})\n VALUES (${Array.from({length: columns.length}).fill('?').join(',')})\n `,\n Object.values(row),\n );\n }\n\n // Updates by default are applied as UPDATE commands to support partial\n // row specifications from the change source. In particular, this is needed\n // to handle updates for which unchanged TOASTed values are not sent:\n //\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-TUPLEDATA\n //\n // However, in certain cases an UPDATE may be received for a row that\n // was not initially synced, such as when, an existing table is added\n // to the app's publication.\n //\n // In order to facilitate \"resumptive\" replication, the logic falls back to\n // an INSERT if the update did not change any rows.\n processUpdate(update: MessageUpdate) {\n const table = liteTableName(update.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(update.new, tableSpec, this.#jsonFormat);\n const row = {...newRow.row, [ZERO_VERSION_COLUMN_NAME]: this.#version};\n\n // update.key is set with the old values if the key has changed.\n const oldKey = update.key\n ? this.#getKey(\n liteRow(update.key, this.#tableSpec(table), this.#jsonFormat),\n update,\n )\n : null;\n const newKey = this.#getKey(newRow, update);\n\n if (oldKey) {\n this.#logDeleteOp(table, oldKey, tableSpec.backfilling);\n }\n this.#logSetOp(table, newKey, getBackfilledColumns(newRow.row, tableSpec));\n\n const currKey = oldKey ?? newKey;\n const conds = Object.keys(currKey).map(col => `${id(col)}=?`);\n const setExprs = Object.keys(row).map(col => `${id(col)}=?`);\n\n try {\n const {changes} = this.#db.run(\n `\n UPDATE ${id(table)}\n SET ${setExprs.join(',')}\n WHERE ${conds.join(' AND ')}\n `,\n [...Object.values(row), ...Object.values(currKey)],\n );\n\n // If the UPDATE did not affect any rows, perform an UPSERT of the\n // new row for resumptive replication.\n if (changes === 0) {\n this.#upsert(table, row);\n }\n } catch (e) {\n throw wrapBindError(e, this.#version, update.relation, row, currKey);\n }\n }\n\n processDelete(del: MessageDelete) {\n const table = liteTableName(del.relation);\n const tableSpec = this.#tableSpec(table);\n const rowKey = this.#getKey(\n liteRow(del.key, tableSpec, this.#jsonFormat),\n del,\n );\n\n this.#delete(table, rowKey);\n this.#logDeleteOp(table, rowKey, tableSpec.backfilling);\n }\n\n #delete(table: string, rowKey: LiteRowKey) {\n const conds = Object.keys(rowKey).map(col => `${id(col)}=?`);\n this.#db.run(\n `DELETE FROM ${id(table)} WHERE ${conds.join(' AND ')}`,\n Object.values(rowKey),\n );\n }\n\n processTruncate(truncate: MessageTruncate) {\n for (const relation of truncate.relations) {\n const table = liteTableName(relation);\n // Update replica data.\n this.#db.run(`DELETE FROM ${id(table)}`);\n\n // Update change log.\n this.#logTruncateOp(table);\n }\n }\n\n processCreateTable(create: TableCreate) {\n if (create.metadata) {\n this.#tableMetadata.setUpstreamMetadata(create.spec, create.metadata);\n }\n const table = mapPostgresToLite(create.spec);\n this.#db.db.exec(createLiteTableStatement(table));\n\n // Write to metadata table\n for (const [colName, colSpec] of Object.entries(create.spec.columns)) {\n this.#columnMetadata.insert(\n table.name,\n colName,\n colSpec,\n create.backfill?.[colName],\n );\n }\n\n if (\n Object.keys(create.backfill ?? {}).length ===\n Object.keys(create.spec.columns).length\n ) {\n this.#reloadTableSpecs();\n } else {\n // Make the table visible immediately unless all of the columns are\n // being backfilled. In the backfill case, the version bump will happen\n // with the backfill is complete.\n this.#logResetOp(table.name);\n }\n this.#lc.info?.(create.tag, table.name);\n }\n\n processTableMetadata(msg: TableUpdateMetadata) {\n this.#tableMetadata.setUpstreamMetadata(msg.table, msg.new);\n }\n\n processRenameTable(rename: TableRename) {\n this.#tableMetadata.rename(rename.old, rename.new);\n\n const oldName = liteTableName(rename.old);\n const newName = liteTableName(rename.new);\n this.#db.db.exec(`ALTER TABLE ${id(oldName)} RENAME TO ${id(newName)}`);\n\n // Rename in metadata table\n this.#columnMetadata.renameTable(oldName, newName);\n\n this.#bumpVersions(rename.new);\n this.#logResetOp(oldName);\n this.#lc.info?.(rename.tag, oldName, newName);\n }\n\n processAddColumn(msg: ColumnAdd) {\n if (msg.tableMetadata) {\n this.#tableMetadata.setUpstreamMetadata(msg.table, msg.tableMetadata);\n }\n const table = liteTableName(msg.table);\n const {name} = msg.column;\n const spec = mapPostgresToLiteColumn(table, msg.column);\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} ADD ${id(name)} ${liteColumnDef(spec)}`,\n );\n\n // Write to metadata table\n this.#columnMetadata.insert(table, name, msg.column.spec, msg.backfill);\n\n if (msg.backfill) {\n this.#reloadTableSpecs();\n } else {\n // Make the new column visible immediately if it's not being backfilled.\n // Otherwise, the version bump will happen with the backfill is complete.\n this.#bumpVersions(msg.table);\n }\n this.#lc.info?.(msg.tag, table, msg.column);\n }\n\n processUpdateColumn(msg: ColumnUpdate) {\n const table = liteTableName(msg.table);\n let oldName = msg.old.name;\n const newName = msg.new.name;\n\n // update-column can ignore defaults because it does not change the values\n // in existing rows.\n //\n // https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-SET-DROP-DEFAULT\n //\n // \"The new default value will only apply in subsequent INSERT or UPDATE\n // commands; it does not cause rows already in the table to change.\"\n //\n // This allows support for _changing_ column defaults to any expression,\n // since it does not affect what the replica needs to do.\n const oldSpec = mapPostgresToLiteColumn(table, msg.old, 'ignore-default');\n const newSpec = mapPostgresToLiteColumn(table, msg.new, 'ignore-default');\n\n // If neither the column name nor the SQLite data type changes, only the\n // upstream metadata needs to be updated. This includes changes such as a\n // varchar character limit, which SQLite does not enforce but a freshly\n // built replica still records.\n if (oldName === newName && oldSpec.dataType === newSpec.dataType) {\n this.#columnMetadata.update(\n table,\n msg.old.name,\n msg.new.name,\n msg.new.spec,\n );\n this.#lc.info?.(msg.tag, 'updated metadata only', oldSpec, newSpec);\n return;\n }\n // If the data type changes, we have to make a new column with the new data type\n // and copy the values over.\n if (oldSpec.dataType !== newSpec.dataType) {\n const tableSpec = must(\n listTables(this.#db.db, false, false).find(\n tableSpec => tableSpec.name === table,\n ),\n );\n const indexes = listIndexes(this.#db.db).filter(\n idx => idx.tableName === table,\n );\n const tmpTable = `tmp.${table}`;\n const newColumns = mapEntries(tableSpec.columns, (column, spec) => [\n column === oldName ? newName : column,\n column === oldName ? {...newSpec, pos: spec.pos} : spec,\n ]);\n const sourceColumns = Object.keys(tableSpec.columns);\n const destinationColumns = Object.keys(newColumns);\n const stmts = [\n createLiteTableStatement({\n ...tableSpec,\n name: tmpTable,\n columns: newColumns,\n }),\n `INSERT INTO ${id(tmpTable)} (${destinationColumns.map(id).join(',')})\n SELECT ${sourceColumns.map(id).join(',')} FROM ${id(table)};`,\n `DROP TABLE ${id(table)};`,\n `ALTER TABLE ${id(tmpTable)} RENAME TO ${id(table)};`,\n ...indexes.map(idx =>\n createLiteIndexStatement({\n ...idx,\n columns: mapEntries(idx.columns, (column, direction) => [\n column === oldName ? newName : column,\n direction,\n ]),\n }),\n ),\n ];\n this.#db.db.exec(stmts.join(''));\n oldName = newName;\n }\n if (oldName !== newName) {\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} RENAME ${id(oldName)} TO ${id(newName)}`,\n );\n }\n\n // Update metadata table\n this.#columnMetadata.update(\n table,\n msg.old.name,\n msg.new.name,\n msg.new.spec,\n );\n\n this.#bumpVersions(msg.table);\n this.#lc.info?.(msg.tag, table, msg.new);\n }\n\n processDropColumn(msg: ColumnDrop) {\n const table = liteTableName(msg.table);\n const {column} = msg;\n this.#db.db.exec(`ALTER TABLE ${id(table)} DROP ${id(column)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteColumn(table, column);\n\n this.#bumpVersions(msg.table);\n this.#lc.info?.(msg.tag, table, column);\n }\n\n processDropTable(drop: TableDrop) {\n this.#tableMetadata.drop(drop.id);\n\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP TABLE IF EXISTS ${id(name)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteTable(name);\n\n this.#logResetOp(name);\n this.#lc.info?.(drop.tag, name);\n }\n\n processCreateIndex(create: IndexCreate) {\n const index = mapPostgresToLiteIndex(create.spec);\n this.#db.db.exec(createLiteIndexStatement(index));\n\n // indexes affect tables visibility (e.g. sync-ability is gated on\n // having a unique index), so reset pipelines to refresh table schemas.\n // However, the reset is not necessary if the index is for a table\n // that is not yet visible due to backfilling.\n const tableSpec = must(this.#tableSpecs.get(index.tableName));\n if (\n (tableSpec.backfilling ?? []).length ===\n Object.entries(tableSpec.columns).length - 1 // don't count _0_version\n ) {\n this.#reloadTableSpecs();\n } else {\n this.#logResetOp(index.tableName);\n }\n this.#lc.info?.(create.tag, index.name);\n }\n\n processDropIndex(drop: IndexDrop) {\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP INDEX IF EXISTS ${id(name)}`);\n this.#lc.info?.(drop.tag, name);\n }\n\n #bumpVersions(table: Identifier) {\n this.#tableMetadata.setMinRowVersion(table, this.#version);\n this.#logResetOp(liteTableName(table));\n }\n\n /**\n * @param backfilledColumns `backfilling` columns for which values were set\n */\n #logSetOp(\n table: string,\n key: LiteRowKey,\n backfilledColumns: string[] | undefined,\n ) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilledColumns !== undefined) {\n this.#changeLog.logSetOp(\n this.#version,\n this.#pos++,\n table,\n key,\n backfilledColumns,\n );\n this.#numChangeLogEntries++;\n }\n }\n\n #logDeleteOp(table: string, key: LiteRowKey, backfilling?: string[]) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilling?.length) {\n this.#changeLog.logDeleteOp(this.#version, this.#pos++, table, key);\n this.#numChangeLogEntries++;\n }\n }\n\n #logTruncateOp(table: string) {\n if (this.#mode === 'serving') {\n this.#changeLog.logTruncateOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n }\n\n #logResetOp(table: string) {\n this.#schemaChanged = true;\n if (this.#mode === 'serving') {\n this.#changeLog.logResetOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n this.#reloadTableSpecs();\n }\n\n processBackfill({relation, watermark, columns, rowValues}: MessageBackfill) {\n const tableName = liteTableName(relation);\n const tableSpec = must(this.#tableSpecs.get(tableName));\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n // Common parts of the INSERT sql statement.\n const insertColsStr = [...cols, ZERO_VERSION_COLUMN_NAME].map(id).join(',');\n const qMarks = Array.from({length: cols.length + 1})\n .fill('?')\n .join(',');\n const rowKeyColsStr = rowKeyCols.map(id).join(',');\n\n let backfilled = 0;\n let skipped = 0;\n for (const v of rowValues) {\n const row = liteRow(\n Object.fromEntries(cols.map((c, i) => [c, v[i]])),\n tableSpec,\n this.#jsonFormat,\n );\n const rowKey = this.#getKey(row, {relation});\n const rowOp = this.#changeLog.getLatestRowOp(tableName, rowKey);\n if (rowOp?.op === DEL_OP && rowOp.stateVersion > watermark) {\n skipped++;\n continue; // the row was deleted after the backfill snapshot\n }\n const updates =\n rowOp?.op === SET_OP\n ? cols.filter(\n c => (rowOp.backfillingColumnVersions[c] ?? '') <= watermark,\n )\n : cols;\n if (updates.length === 0) {\n // row already has newer values for all backfilling columns.\n skipped++;\n continue;\n }\n const updateStmts = updates.map(col => `${id(col)}=excluded.${id(col)}`);\n this.#db.run(\n /*sql*/ `\n INSERT INTO ${id(tableName)} (${insertColsStr}) VALUES (${qMarks})\n ON CONFLICT (${rowKeyColsStr})\n DO UPDATE SET ${updateStmts.join(',')};\n `,\n ...Object.values(row.row),\n watermark, // the _0_version for new rows (i.e. table backfill)\n );\n backfilled++;\n }\n\n this.#lc.debug?.(\n `backfilled ${backfilled} rows (skipped ${skipped}) into ${tableName}`,\n );\n }\n\n #completedBackfill: DownloadStatus | undefined;\n\n processBackfillCompleted({relation, columns, status}: BackfillCompleted) {\n const tableName = liteTableName(relation);\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n const columnMetadata = must(ColumnMetadataStore.getInstance(this.#db.db));\n for (const col of cols) {\n columnMetadata.clearBackfilling(tableName, col);\n }\n // Given that new columns are being exposed for every row in the table, bump the\n // row version for all rows.\n this.#bumpVersions(relation);\n if (status) {\n this.#completedBackfill = {table: tableName, columns: cols, ...status};\n }\n this.#lc.info?.(`finished backfilling ${tableName}`);\n\n // Note that there is no need to clear the backfillingColumnVersions values\n // in the changeLog. It could theoretically be done for clarity but:\n // (1) it could be non-trivial in terms of latency introduced and\n // (2) the data must be preserved if _other_ columns are in the process\n // of being backfilled\n //\n // Thus, for speed and simplicity, the values are left as is. (Note that\n // subsequent replicated changes to those rows will clear the values if\n // no backfills are in progress).\n }\n\n processCommit(commit: MessageCommit, watermark: string): CommitResult {\n if (watermark !== this.#version) {\n throw new Error(\n `'commit' version ${watermark} does not match 'begin' version ${\n this.#version\n }: ${stringify(commit)}`,\n );\n }\n updateReplicationWatermark(this.#db, watermark);\n\n if (this.#schemaChanged) {\n const start = Date.now();\n this.#db.db.pragma('optimize');\n this.#lc.info?.(\n `PRAGMA optimized after schema change (${Date.now() - start} ms)`,\n );\n }\n\n if (this.#mode !== 'initial-sync') {\n this.#db.commit();\n }\n\n const elapsedMs = Date.now() - this.#startMs;\n this.#lc.debug?.(`Committed tx@${this.#version} (${elapsedMs} ms)`);\n\n return {\n watermark,\n completedBackfill: this.#completedBackfill,\n schemaUpdated: this.#schemaChanged,\n changeLogUpdated: this.#numChangeLogEntries > 0,\n };\n }\n\n abort(lc: LogContext) {\n lc.info?.(`aborting transaction ${this.#version}`);\n this.#db.rollback();\n }\n}\n\nfunction getBackfilledColumns(\n row: LiteRow,\n {backfilling}: LiteTableSpecWithReplicationStatus,\n): string[] | undefined {\n if (!backfilling?.length) {\n return undefined; // common case\n }\n return backfilling.filter(col => col in row);\n}\n\nclass OversizedUpdateBindingError extends Error {\n constructor(message: string, cause: RangeError) {\n super(message, {cause});\n this.name = 'OversizedUpdateBindingError';\n }\n}\n\nfunction wrapBindError(\n error: unknown,\n tx: LexiVersion,\n relation: MessageRelation,\n row: LiteRow,\n currKey: LiteRowKey,\n) {\n if (\n !(error instanceof RangeError) ||\n error.message !== BIND_VALUE_TOO_BIG_ERROR\n ) {\n return error;\n }\n\n const binding = [...Object.entries(row), ...Object.entries(currKey)].find(\n ([, value]) => oversizedValueDescription(value),\n );\n const relationOid = getRelationOid(relation);\n const context = [\n `tx=${tx}`,\n relationOid === undefined ? undefined : `relationOid=${relationOid}`,\n `table=${relation.schema}.${relation.name}`,\n `column=${binding?.[0] ?? 'unknown'}`,\n binding && oversizedValueDescription(binding[1]),\n ]\n .filter(Boolean)\n .join(' ');\n\n return new OversizedUpdateBindingError(\n `Oversized SQLite update binding: ${context}`,\n error,\n );\n}\n\nfunction getRelationOid(relation: MessageRelation): number | undefined {\n return 'relationOid' in relation && typeof relation.relationOid === 'number'\n ? relation.relationOid\n : undefined;\n}\n\nfunction oversizedValueDescription(value: LiteValueType): string | undefined {\n if (typeof value === 'bigint') {\n return value < MIN_SQLITE_BIGINT || value > MAX_SQLITE_BIGINT\n ? 'valueType=bigint fitsInt64=false'\n : undefined;\n }\n\n if (typeof value === 'string') {\n const sizeBytes = Buffer.byteLength(value);\n return sizeBytes > MAX_SQLITE_BIND_BYTES\n ? `valueType=string sizeBytes=${sizeBytes} limitBytes=${MAX_SQLITE_BIND_BYTES}`\n : undefined;\n }\n\n if (value instanceof Uint8Array && value.byteLength > MAX_SQLITE_BIND_BYTES) {\n return `valueType=buffer sizeBytes=${value.byteLength} limitBytes=${MAX_SQLITE_BIND_BYTES}`;\n }\n\n return undefined;\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotter.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/snapshotter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"snapshotter.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/snapshotter.ts"],"names":[],"mappings":"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"}