@rocicorp/zero 1.12.0-canary.2 → 1.12.0-canary.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/_virtual/{_@oxc-project_runtime@0.130.0/helpers → _@oxc-project_runtime@0.154.0/helpers/esm}/usingCtx.js +2 -2
- package/out/analyze-query/src/analyze-cli.js +2 -1
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/analyze-query/src/bin-analyze.js +5 -3
- package/out/analyze-query/src/bin-analyze.js.map +1 -1
- package/out/analyze-query/src/bin-transform.js +3 -2
- package/out/analyze-query/src/bin-transform.js.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/ast-to-zql/src/bin.js +3 -2
- package/out/ast-to-zql/src/bin.js.map +1 -1
- package/out/ast-to-zql/src/format.js.map +1 -1
- package/out/datadog/src/datadog-log-sink.js +5 -4
- package/out/datadog/src/datadog-log-sink.js.map +1 -1
- package/out/otel/src/log-options.js.map +1 -1
- package/out/replicache/src/btree/node.js +13 -11
- package/out/replicache/src/btree/node.js.map +1 -1
- package/out/replicache/src/btree/read.js +4 -2
- package/out/replicache/src/btree/read.js.map +1 -1
- package/out/replicache/src/btree/splice.js.map +1 -1
- package/out/replicache/src/btree/write.js +9 -5
- package/out/replicache/src/btree/write.js.map +1 -1
- package/out/replicache/src/config.js +1 -2
- package/out/replicache/src/connection-loop.js.map +1 -1
- package/out/replicache/src/dag/chunk.js +1 -1
- package/out/replicache/src/dag/gc.js.map +1 -1
- package/out/replicache/src/dag/lazy-store.js +8 -7
- package/out/replicache/src/dag/lazy-store.js.map +1 -1
- package/out/replicache/src/dag/store-impl.js +1 -1
- package/out/replicache/src/dag/store-impl.js.map +1 -1
- package/out/replicache/src/dag/visitor.js.map +1 -1
- package/out/replicache/src/db/commit.js +5 -3
- package/out/replicache/src/db/commit.js.map +1 -1
- package/out/replicache/src/db/read.js +1 -1
- package/out/replicache/src/db/read.js.map +1 -1
- package/out/replicache/src/db/rebase.js.map +1 -1
- package/out/replicache/src/db/write.js +5 -2
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/deleted-clients.js +3 -2
- package/out/replicache/src/deleted-clients.js.map +1 -1
- package/out/replicache/src/expo-sqlite.js +1 -2
- package/out/replicache/src/frozen-json.js +1 -1
- package/out/replicache/src/frozen-json.js.map +1 -1
- package/out/replicache/src/get-default-puller.js +1 -1
- package/out/replicache/src/hash.js +1 -1
- package/out/replicache/src/hash.js.map +1 -1
- package/out/replicache/src/impl.js +1 -2
- package/out/replicache/src/index-defs.js +5 -3
- package/out/replicache/src/index-defs.js.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/idb-store.js +1 -1
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-fallback-store.js +1 -1
- package/out/replicache/src/kv/mem-fallback-store.js.map +1 -1
- package/out/replicache/src/kv/mem-store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/types.js.map +1 -1
- package/out/replicache/src/kv/read-impl.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +1 -1
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/kv/write-impl-base.js +4 -1
- package/out/replicache/src/kv/write-impl-base.js.map +1 -1
- package/out/replicache/src/kv/write-impl.js.map +1 -1
- package/out/replicache/src/log-options.js +2 -1
- package/out/replicache/src/log-options.js.map +1 -1
- package/out/replicache/src/make-idb-name.js +1 -1
- package/out/replicache/src/new-client-channel.js +9 -7
- package/out/replicache/src/new-client-channel.js.map +1 -1
- package/out/replicache/src/on-persist-channel.js +1 -1
- package/out/replicache/src/on-persist-channel.js.map +1 -1
- package/out/replicache/src/op-sqlite.js +1 -2
- package/out/replicache/src/pending-mutations.js +2 -1
- package/out/replicache/src/pending-mutations.js.map +1 -1
- package/out/replicache/src/persist/client-gc.js +2 -2
- package/out/replicache/src/persist/client-gc.js.map +1 -1
- package/out/replicache/src/persist/client-group-gc.js +2 -2
- package/out/replicache/src/persist/client-group-gc.js.map +1 -1
- package/out/replicache/src/persist/client-groups.js +1 -1
- package/out/replicache/src/persist/client-groups.js.map +1 -1
- package/out/replicache/src/persist/clients.js +1 -1
- package/out/replicache/src/persist/clients.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +5 -4
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/gather-mem-only-visitor.js.map +1 -1
- package/out/replicache/src/persist/gather-not-cached-visitor.js.map +1 -1
- package/out/replicache/src/persist/heartbeat.js +2 -2
- package/out/replicache/src/persist/heartbeat.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store.js +1 -1
- package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
- package/out/replicache/src/persist/make-client-id.js +1 -1
- package/out/replicache/src/persist/make-client-id.js.map +1 -1
- package/out/replicache/src/persist/persist.js +3 -2
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/persist/refresh.js +8 -5
- package/out/replicache/src/persist/refresh.js.map +1 -1
- package/out/replicache/src/process-scheduler.js.map +1 -1
- package/out/replicache/src/replicache-impl.js +3 -3
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/scan-iterator.js.map +1 -1
- package/out/replicache/src/scan-options.js +7 -6
- package/out/replicache/src/scan-options.js.map +1 -1
- package/out/replicache/src/sqlite.js +1 -2
- package/out/replicache/src/subscriptions.js +1 -1
- package/out/replicache/src/subscriptions.js.map +1 -1
- package/out/replicache/src/sync/diff.js +3 -1
- package/out/replicache/src/sync/diff.js.map +1 -1
- package/out/replicache/src/sync/ids.js.map +1 -1
- package/out/replicache/src/sync/patch.js +2 -4
- package/out/replicache/src/sync/patch.js.map +1 -1
- package/out/replicache/src/sync/pull.js +3 -1
- package/out/replicache/src/sync/pull.js.map +1 -1
- package/out/replicache/src/sync/push.js.map +1 -1
- package/out/replicache/src/sync/request-id.js +1 -1
- package/out/replicache/src/sync/request-id.js.map +1 -1
- package/out/replicache/src/transactions.js +1 -1
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/shared/src/arrays.js +4 -1
- package/out/shared/src/arrays.js.map +1 -1
- package/out/shared/src/asserts.js +1 -1
- package/out/shared/src/bigint-json.d.ts +9 -0
- package/out/shared/src/bigint-json.d.ts.map +1 -1
- package/out/shared/src/bigint-json.js +27 -2
- package/out/shared/src/bigint-json.js.map +1 -1
- package/out/shared/src/broadcast-channel.js +1 -1
- package/out/shared/src/btree-set.js +11 -8
- package/out/shared/src/btree-set.js.map +1 -1
- package/out/shared/src/cache.js.map +1 -1
- package/out/shared/src/config.js +1 -1
- package/out/shared/src/custom-key-map.js.map +1 -1
- package/out/shared/src/custom-key-set.js.map +1 -1
- package/out/shared/src/deep-clone.js +1 -1
- package/out/shared/src/document-visible.js.map +1 -1
- package/out/shared/src/dotenv.js.map +1 -1
- package/out/shared/src/error.js +1 -1
- package/out/shared/src/error.js.map +1 -1
- package/out/shared/src/hash.js +2 -2
- package/out/shared/src/hash.js.map +1 -1
- package/out/shared/src/iterables.js.map +1 -1
- package/out/shared/src/json-schema.js.map +1 -1
- package/out/shared/src/json.js.map +1 -1
- package/out/shared/src/logging-test-utils.js +1 -1
- package/out/shared/src/logging.js +1 -1
- package/out/shared/src/logging.js.map +1 -1
- package/out/shared/src/navigator.js +1 -1
- package/out/shared/src/options.js +9 -12
- package/out/shared/src/options.js.map +1 -1
- package/out/shared/src/promise-race.js.map +1 -1
- package/out/shared/src/queue.js.map +1 -1
- package/out/shared/src/record-proxy.js.map +1 -1
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/shared/src/size-of-value.js +7 -10
- package/out/shared/src/size-of-value.js.map +1 -1
- package/out/shared/src/sorted-entries.js.map +1 -1
- package/out/shared/src/tdigest-schema.js.map +1 -1
- package/out/shared/src/tdigest.js.map +1 -1
- package/out/shared/src/valita.js +1 -1
- package/out/shared/src/valita.js.map +1 -1
- package/out/z2s/src/compiler.js +2 -4
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/z2s/src/sql.js +1 -1
- package/out/z2s/src/sql.js.map +1 -1
- package/out/zero/package.js +212 -189
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/adapters/drizzle.js +1 -2
- package/out/zero/src/adapters/kysely.js +1 -2
- package/out/zero/src/adapters/pg.js +1 -2
- package/out/zero/src/adapters/postgresjs.js +1 -2
- package/out/zero/src/adapters/prisma.js +1 -2
- package/out/zero/src/analyze.js +1 -2
- package/out/zero/src/change-protocol/v0.js +1 -2
- package/out/zero/src/expo-sqlite.js +1 -2
- package/out/zero/src/op-sqlite.js +1 -2
- package/out/zero/src/react-native.js.map +1 -1
- package/out/zero/src/sqlite.js +1 -2
- package/out/zero/src/zero-cache-dev.js +10 -9
- package/out/zero/src/zero-cache-dev.js.map +1 -1
- package/out/zero/src/zero-out.js +2 -1
- package/out/zero/src/zero-out.js.map +1 -1
- package/out/zero-cache/src/auth/auth.js +1 -1
- package/out/zero-cache/src/auth/auth.js.map +1 -1
- package/out/zero-cache/src/auth/jwt.js +4 -1
- package/out/zero-cache/src/auth/jwt.js.map +1 -1
- package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
- package/out/zero-cache/src/auth/read-authorizer.js +3 -2
- package/out/zero-cache/src/auth/read-authorizer.js.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +5 -16
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/network.js +1 -1
- package/out/zero-cache/src/config/network.js.map +1 -1
- package/out/zero-cache/src/config/server-context.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +9 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +17 -2
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +1 -1
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
- package/out/zero-cache/src/db/create.js.map +1 -1
- package/out/zero-cache/src/db/index-predicate.js.map +1 -1
- package/out/zero-cache/src/db/lite-tables.d.ts +2 -2
- package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
- package/out/zero-cache/src/db/lite-tables.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +1 -1
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/migration.js +5 -3
- package/out/zero-cache/src/db/migration.js.map +1 -1
- package/out/zero-cache/src/db/pg-copy-binary.js +4 -3
- package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -1
- package/out/zero-cache/src/db/pg-copy.js +0 -1
- package/out/zero-cache/src/db/pg-copy.js.map +1 -1
- package/out/zero-cache/src/db/pg-to-lite.js +1 -1
- package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
- package/out/zero-cache/src/db/run-transaction.js.map +1 -1
- package/out/zero-cache/src/db/specs.js +7 -5
- package/out/zero-cache/src/db/specs.js.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
- package/out/zero-cache/src/db/statements.d.ts.map +1 -1
- package/out/zero-cache/src/db/statements.js +9 -2
- package/out/zero-cache/src/db/statements.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +5 -3
- package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
- package/out/zero-cache/src/db/warmup.js.map +1 -1
- package/out/zero-cache/src/observability/events.js.map +1 -1
- package/out/zero-cache/src/observability/log-throttle.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/scripts/deploy-permissions.js +4 -2
- package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
- package/out/zero-cache/src/scripts/permissions.js.map +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js +2 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +2 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
- package/out/zero-cache/src/server/logging.js +1 -1
- package/out/zero-cache/src/server/logging.js.map +1 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
- package/out/zero-cache/src/server/otel-start.js.map +1 -1
- package/out/zero-cache/src/server/reaper.js +2 -2
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +2 -1
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/runner/runtime.js +2 -1
- package/out/zero-cache/src/server/runner/runtime.js.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/shadow-syncer.js +1 -1
- package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
- package/out/zero-cache/src/server/slot-keeper.js.map +1 -1
- package/out/zero-cache/src/server/syncer-assigner.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +6 -1
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.js +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/analyze.js +1 -1
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/init-cleanup.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +3 -3
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-metadata.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +3 -3
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +4 -6
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/decommission.js +2 -1
- package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +10 -8
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/binary-reader.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +3 -2
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/lsn.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +3 -3
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +3 -3
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +13 -12
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +8 -8
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js +3 -2
- package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +13 -10
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/validation.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/control.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +1 -1
- package/out/zero-cache/src/services/change-source/protocol/mod.js +1 -2
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state.js +1 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js +2 -3
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +4 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +21 -21
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +11 -10
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +3 -3
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/replica-poller.js +1 -1
- package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/restore-admission.js +12 -1
- package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/init.js +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +2 -2
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +4 -6
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +1 -3
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +2 -2
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +2 -2
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +0 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
- package/out/zero-cache/src/services/heapz.js +2 -1
- package/out/zero-cache/src/services/heapz.js.map +1 -1
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +1 -1
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/limiter/sliding-window-limiter.js +4 -2
- package/out/zero-cache/src/services/limiter/sliding-window-limiter.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +1 -1
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +2 -2
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
- package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/litestream/restore-progress.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +3 -2
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.js +4 -3
- package/out/zero-cache/src/services/plannerz.js.map +1 -1
- package/out/zero-cache/src/services/profz.js +5 -3
- package/out/zero-cache/src/services/profz.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-db.js +3 -2
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +1 -1
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replication-status.js +4 -3
- package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.js +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +19 -7
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/change-log.js +31 -15
- package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js +10 -7
- package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/runner.js.map +1 -1
- package/out/zero-cache/src/services/running-state.js.map +1 -1
- package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
- package/out/zero-cache/src/services/statz.js +2 -1
- package/out/zero-cache/src/services/statz.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/active-users-gauge.js +2 -2
- package/out/zero-cache/src/services/view-syncer/active-users-gauge.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/advancement-metrics.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/batch-pacer.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +18 -0
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +70 -23
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-schema.d.ts +15 -2
- package/out/zero-cache/src/services/view-syncer/client-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-schema.js +46 -6
- package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-purger.js +2 -2
- package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +10 -5
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +12 -4
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +2 -2
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +5 -3
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +8 -5
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/push-cost.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/push-cutoff.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-stats.js +1 -1
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.js +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js +7 -1
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +12 -4
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +11 -7
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/ttl-clock.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +13 -9
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/error-with-level.js.map +1 -1
- package/out/zero-cache/src/types/lexi-version.js.map +1 -1
- package/out/zero-cache/src/types/lite.js +1 -1
- package/out/zero-cache/src/types/lite.js.map +1 -1
- package/out/zero-cache/src/types/pg-data-type.js +1 -1
- package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
- package/out/zero-cache/src/types/pg.js +6 -4
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/poke-chunk.js +2 -2
- package/out/zero-cache/src/types/poke-chunk.js.map +1 -1
- package/out/zero-cache/src/types/processes.js +1 -1
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/profiler.js.map +1 -1
- package/out/zero-cache/src/types/row-key.js.map +1 -1
- package/out/zero-cache/src/types/sql.js.map +1 -1
- package/out/zero-cache/src/types/streams.js +10 -9
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/strings.js.map +1 -1
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/types/timeout.js +1 -1
- package/out/zero-cache/src/types/websocket-handoff.js +1 -1
- package/out/zero-cache/src/types/ws.js +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/mutator.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +5 -4
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +9 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/active-clients-manager.js +1 -1
- package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
- package/out/zero-client/src/client/bindings.js +11 -11
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/connection.js +1 -1
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/crud-impl.js +1 -1
- package/out/zero-client/src/client/crud-impl.js.map +1 -1
- package/out/zero-client/src/client/crud.js +1 -1
- package/out/zero-client/src/client/crud.js.map +1 -1
- package/out/zero-client/src/client/custom.js +1 -1
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/delete-clients-manager.js.map +1 -1
- package/out/zero-client/src/client/enable-analytics.js +1 -1
- package/out/zero-client/src/client/enable-analytics.js.map +1 -1
- package/out/zero-client/src/client/error.js.map +1 -1
- package/out/zero-client/src/client/inspector/client-group.js.map +1 -1
- package/out/zero-client/src/client/inspector/client.js.map +1 -1
- package/out/zero-client/src/client/inspector/html-dialog-prompt.js +1 -1
- package/out/zero-client/src/client/inspector/html-dialog-prompt.js.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/query.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +1 -3
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/keys.js +2 -1
- package/out/zero-client/src/client/keys.js.map +1 -1
- package/out/zero-client/src/client/log-options.js.map +1 -1
- package/out/zero-client/src/client/make-replicache-mutators.js +5 -3
- package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
- package/out/zero-client/src/client/metrics.js +3 -5
- package/out/zero-client/src/client/metrics.js.map +1 -1
- package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/query-manager.js +2 -2
- package/out/zero-client/src/client/query-manager.js.map +1 -1
- package/out/zero-client/src/client/reload-error-handler.js +4 -3
- package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.js +19 -11
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.js +23 -23
- package/out/zero-client/src/util/nanoid.js.map +1 -1
- package/out/zero-permissions/src/permissions.js +3 -2
- package/out/zero-permissions/src/permissions.js.map +1 -1
- package/out/zero-pg/src/mod.js +9 -9
- package/out/zero-protocol/src/analyze-query-result.js +1 -1
- package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
- package/out/zero-protocol/src/application-error.js.map +1 -1
- package/out/zero-protocol/src/ast.js +19 -9
- package/out/zero-protocol/src/ast.js.map +1 -1
- package/out/zero-protocol/src/change-desired-queries.js.map +1 -1
- package/out/zero-protocol/src/client-schema.js +1 -1
- package/out/zero-protocol/src/client-schema.js.map +1 -1
- package/out/zero-protocol/src/close-connection.js +1 -1
- package/out/zero-protocol/src/close-connection.js.map +1 -1
- package/out/zero-protocol/src/connect.js +1 -1
- package/out/zero-protocol/src/connect.js.map +1 -1
- package/out/zero-protocol/src/custom-queries.js +1 -1
- package/out/zero-protocol/src/custom-queries.js.map +1 -1
- package/out/zero-protocol/src/data.js +4 -2
- package/out/zero-protocol/src/data.js.map +1 -1
- package/out/zero-protocol/src/delete-clients.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +27 -27
- package/out/zero-protocol/src/down.js.map +1 -1
- package/out/zero-protocol/src/error.js.map +1 -1
- package/out/zero-protocol/src/inspect-down.js +1 -1
- package/out/zero-protocol/src/inspect-down.js.map +1 -1
- package/out/zero-protocol/src/inspect-up.js +1 -1
- package/out/zero-protocol/src/inspect-up.js.map +1 -1
- package/out/zero-protocol/src/mutate-server.js +1 -1
- package/out/zero-protocol/src/mutate-server.js.map +1 -1
- package/out/zero-protocol/src/mutation-id.js.map +1 -1
- package/out/zero-protocol/src/mutation.js +1 -1
- package/out/zero-protocol/src/mutation.js.map +1 -1
- package/out/zero-protocol/src/mutations-patch.js +1 -1
- package/out/zero-protocol/src/mutations-patch.js.map +1 -1
- package/out/zero-protocol/src/ping.js +1 -1
- package/out/zero-protocol/src/ping.js.map +1 -1
- package/out/zero-protocol/src/poke.js +1 -1
- package/out/zero-protocol/src/poke.js.map +1 -1
- package/out/zero-protocol/src/pong.js +1 -1
- package/out/zero-protocol/src/pong.js.map +1 -1
- package/out/zero-protocol/src/primary-key.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.js +1 -0
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/pull.js +1 -1
- package/out/zero-protocol/src/pull.js.map +1 -1
- package/out/zero-protocol/src/push.js +1 -1
- package/out/zero-protocol/src/push.js.map +1 -1
- package/out/zero-protocol/src/queries-patch.js +1 -1
- package/out/zero-protocol/src/queries-patch.js.map +1 -1
- package/out/zero-protocol/src/query-hash-visitor.js.map +1 -1
- package/out/zero-protocol/src/query-hash.js +2 -2
- package/out/zero-protocol/src/query-server.js +1 -1
- package/out/zero-protocol/src/query-server.js.map +1 -1
- package/out/zero-protocol/src/row-patch.js.map +1 -1
- package/out/zero-protocol/src/up.js.map +1 -1
- package/out/zero-protocol/src/update-auth.js.map +1 -1
- package/out/zero-protocol/src/version.js.map +1 -1
- package/out/zero-react/src/bindings.js +11 -11
- package/out/zero-react/src/mod.js +5 -5
- package/out/zero-react/src/use-query.js +1 -1
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-react/src/zero-provider.d.ts +1 -1
- package/out/zero-react/src/zero-provider.d.ts.map +1 -1
- package/out/zero-react/src/zero-provider.js +2 -1
- package/out/zero-react/src/zero-provider.js.map +1 -1
- package/out/zero-react/src/zero.js +23 -23
- package/out/zero-schema/src/builder/schema-builder.js +3 -2
- package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
- package/out/zero-schema/src/builder/table-builder.js +1 -1
- package/out/zero-schema/src/builder/table-builder.js.map +1 -1
- package/out/zero-schema/src/compiled-permissions.js.map +1 -1
- package/out/zero-schema/src/name-mapper.js +7 -5
- package/out/zero-schema/src/name-mapper.js.map +1 -1
- package/out/zero-schema/src/schema-config.js +1 -1
- package/out/zero-schema/src/schema-config.js.map +1 -1
- package/out/zero-server/src/adapters/drizzle.js.map +1 -1
- package/out/zero-server/src/adapters/kysely.js.map +1 -1
- package/out/zero-server/src/adapters/pg.js.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
- package/out/zero-server/src/adapters/prisma.js.map +1 -1
- package/out/zero-server/src/closable-db-transaction.js.map +1 -1
- package/out/zero-server/src/custom.js +1 -1
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/mod.js +8 -8
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zero-server/src/push-processor.js.map +1 -1
- package/out/zero-server/src/queries/process-queries.js +2 -1
- package/out/zero-server/src/queries/process-queries.js.map +1 -1
- package/out/zero-server/src/schema.js +7 -7
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-server/src/zql-database.js.map +1 -1
- package/out/zero-solid/src/bindings.js +11 -11
- package/out/zero-solid/src/mod.js +5 -5
- package/out/zero-solid/src/solid-view.js +1 -1
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zero-solid/src/use-connection-state.js +2 -1
- package/out/zero-solid/src/use-connection-state.js.map +1 -1
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-zero-online.js +2 -1
- package/out/zero-solid/src/use-zero-online.js.map +1 -1
- package/out/zero-solid/src/use-zero.js +3 -2
- package/out/zero-solid/src/use-zero.js.map +1 -1
- package/out/zero-solid/src/zero.js +23 -23
- package/out/zero-types/src/name-mapper.js.map +1 -1
- package/out/zql/src/builder/builder.js +15 -13
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/builder/correlated-predicate-pushdown.js.map +1 -1
- package/out/zql/src/builder/debug-delegate.js.map +1 -1
- package/out/zql/src/builder/like.js +0 -1
- package/out/zql/src/builder/like.js.map +1 -1
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/cap.js +2 -4
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/constraint.js +1 -1
- package/out/zql/src/ivm/constraint.js.map +1 -1
- package/out/zql/src/ivm/data.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.js.map +1 -1
- package/out/zql/src/ivm/exists.js +20 -18
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +1 -1
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter-push.js.map +1 -1
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +1 -3
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/join-index-tap.js.map +1 -1
- package/out/zql/src/ivm/join-index.js.map +1 -1
- package/out/zql/src/ivm/join-utils.js +11 -13
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/ivm/memory-source.js +26 -22
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/memory-storage.js.map +1 -1
- package/out/zql/src/ivm/operator.js +2 -2
- package/out/zql/src/ivm/push-accumulated.js +0 -1
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/out/zql/src/ivm/skip-yields.js.map +1 -1
- package/out/zql/src/ivm/skip.js.map +1 -1
- package/out/zql/src/ivm/take-gate.js.map +1 -1
- package/out/zql/src/ivm/take.js +3 -5
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +1 -1
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/union-fan-out.js.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +1 -1
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/mutate/crud.js +3 -2
- package/out/zql/src/mutate/crud.js.map +1 -1
- package/out/zql/src/planner/planner-builder.js +1 -1
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/planner/planner-fan-in.js.map +1 -1
- package/out/zql/src/planner/planner-fan-out.js.map +1 -1
- package/out/zql/src/planner/planner-filter.js.map +1 -1
- package/out/zql/src/planner/planner-graph.js +1 -5
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-join.js +1 -1
- package/out/zql/src/planner/planner-join.js.map +1 -1
- package/out/zql/src/planner/planner-source.js.map +1 -1
- package/out/zql/src/planner/planner-terminus.js.map +1 -1
- package/out/zql/src/query/create-builder.js +2 -1
- package/out/zql/src/query/create-builder.js.map +1 -1
- package/out/zql/src/query/expression.js +1 -1
- package/out/zql/src/query/expression.js.map +1 -1
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/out/zql/src/query/named.js +2 -1
- package/out/zql/src/query/named.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +1 -1
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-impl.js +3 -2
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-registry.js +1 -1
- package/out/zql/src/query/query-registry.js.map +1 -1
- package/out/zql/src/query/query-transitions.js +1 -1
- package/out/zql/src/query/query-transitions.js.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +3 -2
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zql/src/query/static-query.js.map +1 -1
- package/out/zql/src/query/ttl.js +8 -8
- package/out/zql/src/query/ttl.js.map +1 -1
- package/out/zql/src/query/validate-input.js.map +1 -1
- package/out/zqlite/src/database-storage.d.ts +27 -8
- package/out/zqlite/src/database-storage.d.ts.map +1 -1
- package/out/zqlite/src/database-storage.js +195 -26
- package/out/zqlite/src/database-storage.js.map +1 -1
- package/out/zqlite/src/db.d.ts +47 -1
- package/out/zqlite/src/db.d.ts.map +1 -1
- package/out/zqlite/src/db.js +121 -21
- package/out/zqlite/src/db.js.map +1 -1
- package/out/zqlite/src/explain-queries.js.map +1 -1
- package/out/zqlite/src/fetch-sql.d.ts +27 -0
- package/out/zqlite/src/fetch-sql.d.ts.map +1 -0
- package/out/zqlite/src/fetch-sql.js +112 -0
- package/out/zqlite/src/fetch-sql.js.map +1 -0
- package/out/zqlite/src/index-usage.js.map +1 -1
- package/out/zqlite/src/internal/sql-inline.js.map +1 -1
- package/out/zqlite/src/internal/sql.js.map +1 -1
- package/out/zqlite/src/internal/statement-cache.d.ts +7 -0
- package/out/zqlite/src/internal/statement-cache.d.ts.map +1 -1
- package/out/zqlite/src/internal/statement-cache.js +11 -4
- package/out/zqlite/src/internal/statement-cache.js.map +1 -1
- package/out/zqlite/src/mod.js +5 -5
- package/out/zqlite/src/pending-delta.js.map +1 -1
- package/out/zqlite/src/query-builder.d.ts +11 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +22 -7
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/query-delegate.js.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +2 -5
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +3 -2
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +20 -2
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +73 -39
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +17 -15
- package/out/otel/src/maybe-time.d.ts +0 -3
- package/out/otel/src/maybe-time.d.ts.map +0 -1
- package/out/otel/src/maybe-time.js +0 -15
- package/out/otel/src/maybe-time.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type JSONValue } from '../../../../../shared/src/bigint-json.ts';
|
|
1
2
|
import * as v from '../../../../../shared/src/valita.ts';
|
|
2
3
|
import type { Database } from '../../../../../zqlite/src/db.ts';
|
|
3
4
|
import type { LexiVersion } from '../../../types/lexi-version.ts';
|
|
@@ -43,13 +44,24 @@ export declare const CREATE_CHANGELOG_SCHEMA = "\n CREATE TABLE \"_zero.changeL
|
|
|
43
44
|
* fields are excluded, though the query should be ordered by
|
|
44
45
|
* `<stateVersion, pos>`.
|
|
45
46
|
*/
|
|
46
|
-
export
|
|
47
|
-
stateVersion: string;
|
|
48
|
-
table: string;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
47
|
+
export type ChangeLogEntry = {
|
|
48
|
+
readonly stateVersion: string;
|
|
49
|
+
readonly table: string;
|
|
50
|
+
readonly rowKey: Readonly<Record<string, JSONValue>>;
|
|
51
|
+
readonly op: typeof SET_OP | typeof DEL_OP;
|
|
52
|
+
} | {
|
|
53
|
+
readonly stateVersion: string;
|
|
54
|
+
readonly table: string;
|
|
55
|
+
readonly rowKey: null;
|
|
56
|
+
readonly op: typeof TRUNCATE_OP | typeof RESET_OP;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The {@link ChangeLogEntry} for the columns of a change log row. This does
|
|
60
|
+
* not validate what zero-cache itself wrote: the columns are
|
|
61
|
+
* `TEXT NOT NULL`, and `rowKey` is the JSON of an object (see
|
|
62
|
+
* {@link ChangeLog}).
|
|
63
|
+
*/
|
|
64
|
+
export declare function parseChangeLogEntry(stateVersion: string, table: string, rowKey: string, op: string): ChangeLogEntry;
|
|
53
65
|
declare const rawChangeLogEntrySchema: v.ObjectType<{
|
|
54
66
|
stateVersion: v.Type<string>;
|
|
55
67
|
table: v.Type<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-log.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/replicator/schema/change-log.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"change-log.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/replicator/schema/change-log.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,CAAC,MAAM,qCAAqC,CAAC;AACzD,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAChE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,eAAO,MAAM,MAAM,MAAM,CAAC;AAC1B,eAAO,MAAM,MAAM,MAAM,CAAC;AAC1B,eAAO,MAAM,WAAW,MAAM,CAAC;AAC/B,eAAO,MAAM,QAAQ,MAAM,CAAC;AAG5B,eAAO,MAAM,uBAAuB,sbAmCjC,CAAC;AAEJ;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,EAAE,OAAO,MAAM,GAAG,OAAO,MAAM,CAAC;CAC5C,GACD;IACE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,OAAO,WAAW,GAAG,OAAO,QAAQ,CAAC;CACnD,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,GACT,cAAc,CAWhB;AAWD,QAAA,MAAM,uBAAuB;;;;;;aAQ3B,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,qBAAa,SAAS;;IAMpB,YAAY,EAAE,EAAE,QAAQ,EAqCvB;IAED;;;;;;;;;;OAUG;IACH,QAAQ,CACN,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,GAC/B,MAAM,CAER;IAED,WAAW,CACT,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,UAAU,GACd,MAAM,CAKR;IAED,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU;;;;;;kBAM5C;IA8BD,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,QAEhD;IAED,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,QAE7C;CACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { assert } from "../../../../../shared/src/asserts.js";
|
|
1
2
|
import { literalUnion, parse, valita_exports } from "../../../../../shared/src/valita.js";
|
|
2
|
-
import {
|
|
3
|
+
import { parse as parse$1, stringify } from "../../../../../shared/src/bigint-json.js";
|
|
3
4
|
import { normalizedKeyOrder } from "../../../types/row-key.js";
|
|
4
5
|
var CREATE_CHANGELOG_SCHEMA = `
|
|
5
6
|
CREATE TABLE "_zero.changeLog2" (
|
|
@@ -14,20 +15,35 @@ var CREATE_CHANGELOG_SCHEMA = `
|
|
|
14
15
|
);
|
|
15
16
|
`;
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* The {@link ChangeLogEntry} for the columns of a change log row. This does
|
|
19
|
+
* not validate what zero-cache itself wrote: the columns are
|
|
20
|
+
* `TEXT NOT NULL`, and `rowKey` is the JSON of an object (see
|
|
21
|
+
* {@link ChangeLog}).
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
23
|
+
function parseChangeLogEntry(stateVersion, table, rowKey, op) {
|
|
24
|
+
switch (op) {
|
|
25
|
+
case "s":
|
|
26
|
+
case "d": return {
|
|
27
|
+
stateVersion,
|
|
28
|
+
table,
|
|
29
|
+
rowKey: parseRowKey(rowKey),
|
|
30
|
+
op
|
|
31
|
+
};
|
|
32
|
+
case "t":
|
|
33
|
+
case "r": return {
|
|
34
|
+
stateVersion,
|
|
35
|
+
table,
|
|
36
|
+
rowKey: null,
|
|
37
|
+
op
|
|
38
|
+
};
|
|
39
|
+
default: throw new Error(`Invalid change log op "${op}" for table "${table}"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function parseRowKey(rowKey) {
|
|
43
|
+
const key = parse$1(rowKey);
|
|
44
|
+
assert(typeof key === "object" && key !== null && !Array.isArray(key), () => `Invalid change log rowKey ${rowKey}`);
|
|
45
|
+
return key;
|
|
46
|
+
}
|
|
31
47
|
var rawChangeLogEntrySchema = valita_exports.object({
|
|
32
48
|
stateVersion: valita_exports.string(),
|
|
33
49
|
table: valita_exports.string(),
|
|
@@ -128,6 +144,6 @@ var ChangeLog = class {
|
|
|
128
144
|
}
|
|
129
145
|
};
|
|
130
146
|
//#endregion
|
|
131
|
-
export { CREATE_CHANGELOG_SCHEMA, ChangeLog,
|
|
147
|
+
export { CREATE_CHANGELOG_SCHEMA, ChangeLog, parseChangeLogEntry };
|
|
132
148
|
|
|
133
149
|
//# sourceMappingURL=change-log.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-log.js","names":["#logRowOpStmt","#logRowOpWithBackfillStmt","#logTableWideOpStmt","#getRowOpStmt","#logRowOp"],"sources":["../../../../../../../zero-cache/src/services/replicator/schema/change-log.ts"],"sourcesContent":["import {\n jsonObjectSchema,\n parse,\n stringify,\n} from '../../../../../shared/src/bigint-json.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport type {Database, Statement} from '../../../../../zqlite/src/db.ts';\nimport type {LexiVersion} from '../../../types/lexi-version.ts';\nimport type {LiteRowKey} from '../../../types/lite.ts';\nimport {normalizedKeyOrder} from '../../../types/row-key.ts';\n\n/**\n * The Change Log tracks the last operation (set or delete) for each row in the\n * data base, ordered by state version; in other words, a cross-table\n * index of row changes ordered by version. This facilitates a minimal \"diff\"\n * of row changes needed to advance a pipeline from one state version to another.\n *\n * The Change Log stores identifiers only, i.e. it does not store contents.\n * A database snapshot at the previous version can be used to query a row's\n * old contents, if any, and the current snapshot can be used to query a row's\n * new contents. (In the common case, the new contents will have just been applied\n * and thus has a high likelihood of being in the SQLite cache.)\n *\n * There are two table-wide operations:\n * - `t` corresponds to the postgres `TRUNCATE` operation\n * - `r` represents any schema (i.e. column) change\n *\n * For both operations, the corresponding row changes are not explicitly included\n * in the change log. The consumer has the option of simulating them be reading\n * from pre- and post- snapshots, or resetting their state entirely with the current\n * snapshot.\n *\n * To achieve the desired ordering semantics when processing tables that have been\n * truncated, reset, and modified, the \"rowKey\" is set to `null` for resets and\n * the empty string `\"\"` for truncates. This means that resets will be encountered\n * before truncates, which will be processed before any subsequent row changes.\n *\n * This ordering is chosen because resets are currently the more \"destructive\" op\n * and result in aborting the processing (and starting from scratch); doing this\n * earlier reduces wasted work.\n */\n\nexport const SET_OP = 's';\nexport const DEL_OP = 'd';\nexport const TRUNCATE_OP = 't';\nexport const RESET_OP = 'r';\n\n// Exported for testing (and migrations)\nexport const CREATE_CHANGELOG_SCHEMA =\n // stateVersion : a.k.a. row version\n // pos : order in which to process the change (within the version)\n // table : The table associated with the change\n // rowKey : JSON row key for a row change. For table-wide changes RESET\n // and TRUNCATE, there is no associated row; instead, `pos` is\n // set to -1 and the rowKey is set to the stateVersion,\n // guaranteeing when attempting to process the transaction,\n // the pipeline is reset (and the change log traversal\n // aborted).\n // op : 's' for set (insert/update)\n // : 'd' for delete\n // : 'r' for table reset (schema change)\n // : 't' for table truncation (which also resets the pipeline)\n // backfillingColumnVersions\n // : A JSON mapping from column name to stateVersion tracked\n // for replicated writes of columns that are being backfilled.\n // This is used to prevent backfill data, which is at a\n // fixed snapshot/version outside of the replication stream,\n // from overwriting newer column values.\n //\n // Naming note: To maintain compatibility between a new replication-manager\n // and old view-syncers, the previous _zero.changeLog table is preserved\n // and its replacement given a new name \"changeLog2\".\n `\n CREATE TABLE \"_zero.changeLog2\" (\n \"stateVersion\" TEXT NOT NULL,\n \"pos\" INT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"rowKey\" TEXT NOT NULL,\n \"op\" TEXT NOT NULL,\n \"backfillingColumnVersions\" TEXT DEFAULT '{}',\n PRIMARY KEY(\"stateVersion\", \"pos\"),\n UNIQUE(\"table\", \"rowKey\")\n );\n `;\n\n/**\n * Contains the changeLog fields relevant for computing the diff between\n * two snapshots of a replica. The `pos` and `backfillingColumnVersions`\n * fields are excluded, though the query should be ordered by\n * `<stateVersion, pos>`.\n */\nexport const changeLogEntrySchema = v\n .object({\n stateVersion: v.string(),\n table: v.string(),\n rowKey: v.string(),\n op: v.literalUnion(SET_OP, DEL_OP, TRUNCATE_OP, RESET_OP),\n })\n .map(val => ({\n ...val,\n // Note: sets the rowKey to `null` for table-wide ops / resets\n rowKey:\n val.op === 't' || val.op === 'r'\n ? null\n : v.parse(parse(val.rowKey), jsonObjectSchema),\n }));\n\nexport type ChangeLogEntry = v.Infer<typeof changeLogEntrySchema>;\n\nconst rawChangeLogEntrySchema = v.object({\n stateVersion: v.string(),\n table: v.string(),\n rowKey: v.string(),\n op: v.literalUnion(SET_OP, DEL_OP, TRUNCATE_OP, RESET_OP),\n backfillingColumnVersions: v\n .string()\n .map(val => v.record(v.string()).parse(JSON.parse(val))),\n});\n\nexport type RawChangeLogEntry = v.Infer<typeof rawChangeLogEntrySchema>;\n\nexport class ChangeLog {\n readonly #logRowOpStmt: Statement;\n readonly #logRowOpWithBackfillStmt: Statement;\n readonly #logTableWideOpStmt;\n readonly #getRowOpStmt: Statement;\n\n constructor(db: Database) {\n this.#logRowOpStmt = db.prepare(/*sql*/ `\n INSERT OR REPLACE INTO \"_zero.changeLog2\" \n (stateVersion, pos, \"table\", rowKey, op)\n VALUES (@version, @pos, @table, JSON(@rowKey), @op)\n `);\n\n this.#logRowOpWithBackfillStmt = db.prepare(/*sql*/ `\n INSERT INTO \"_zero.changeLog2\" \n (stateVersion, pos, \"table\", rowKey, op, backfillingColumnVersions)\n VALUES (@version, @pos, @table, JSON(@rowKey), @op, \n JSON(@backfillingColumnVersions))\n ON CONFLICT (\"table\", rowKey) DO UPDATE \n SET stateVersion = excluded.stateVersion,\n pos = excluded.pos,\n op = excluded.op,\n backfillingColumnVersions = json_patch(\n backfillingColumnVersions, excluded.backfillingColumnVersions)\n `);\n\n // Because table-wide ops result in aborting an incremental update\n // and rehydrating all queries at \"head\", they are assigned pos = -1\n // as an optimization to abort as early as possible to skip unnecessary\n // updates.\n //\n // However, changeLog entries that are destined to be \"skipped\" are\n // nonetheless kept for the purpose of tracking backfillingColumnVersions.\n this.#logTableWideOpStmt = db.prepare(/*sql*/ `\n INSERT OR REPLACE INTO \"_zero.changeLog2\" \n (stateVersion, pos, \"table\", rowKey, op) \n VALUES (@version, -1, @table, @version, @op)\n `);\n\n // oxlint-disable-next-line zero/no-select-star -- Local SQLite replica query; not run through pg prepared statements.\n this.#getRowOpStmt = db.prepare(/*sql*/ `\n SELECT * FROM \"_zero.changeLog2\" WHERE \"table\" = ? AND \"rowKey\" = JSON(?)\n `);\n }\n\n /**\n *\n * @param backfilled The backfilling columns for which values were set. Note\n * that an empty list and the `undefined` value mean different things;\n * * An empty list indicates that a backfill is in progress but no\n * backfilling values were set. In this case, existing\n * backfillingColumnVersions are preserved.\n * * `undefined` indicates that there are no columns being backfilled.\n * In this case, any vestigial `backfillingColumnVersions` value\n * is cleared.\n */\n logSetOp(\n version: LexiVersion,\n pos: number,\n table: string,\n row: LiteRowKey,\n backfilled: string[] | undefined,\n ): string {\n return this.#logRowOp(version, pos, table, row, SET_OP, backfilled);\n }\n\n logDeleteOp(\n version: LexiVersion,\n pos: number,\n table: string,\n row: LiteRowKey,\n ): string {\n // Note: For delete ops, it is always safe to clear the\n // backfillingColumnVersions because the backfill algorithm\n // understands that deletes apply to the whole row.\n return this.#logRowOp(version, pos, table, row, DEL_OP, undefined);\n }\n\n getLatestRowOp(table: string, row: LiteRowKey) {\n const rowKey = stringify(normalizedKeyOrder(row));\n const result = this.#getRowOpStmt.get(table, rowKey);\n return result === undefined\n ? undefined\n : v.parse(result, rawChangeLogEntrySchema, 'passthrough');\n }\n\n #logRowOp(\n version: LexiVersion,\n pos: number,\n table: string,\n row: LiteRowKey,\n op: string,\n backfilled: string[] | undefined,\n ): string {\n const rowKey = stringify(normalizedKeyOrder(row));\n if (backfilled === undefined) {\n this.#logRowOpStmt.run({version, pos, table, rowKey, op});\n } else {\n const versions: Record<string, string> = {};\n for (const col of backfilled) {\n versions[col] = version;\n }\n this.#logRowOpWithBackfillStmt.run({\n version,\n pos,\n table,\n rowKey,\n op,\n backfillingColumnVersions: JSON.stringify(versions),\n });\n }\n return rowKey;\n }\n\n logTruncateOp(version: LexiVersion, table: string) {\n this.#logTableWideOpStmt.run({version, table, op: TRUNCATE_OP});\n }\n\n logResetOp(version: LexiVersion, table: string) {\n this.#logTableWideOpStmt.run({version, table, op: RESET_OP});\n }\n}\n"],"mappings":";;;AAgDA,IAAa,0BAwBX;;;;;;;;;;;;;;;;;;AAmBF,IAAa,uBAAuB,eACjC,OAAO;CACN,cAAc,eAAE,OAAO;CACvB,OAAO,eAAE,OAAO;CAChB,QAAQ,eAAE,OAAO;CACjB,IAAI,aAAA,KAAA,KAAA,KAAA,GAAoD;AAC1D,CAAC,EACA,KAAI,SAAQ;CACX,GAAG;CAEH,QACE,IAAI,OAAO,OAAO,IAAI,OAAO,MACzB,OACA,MAAQ,QAAM,IAAI,MAAM,GAAG,gBAAgB;AACnD,EAAE;AAIJ,IAAM,0BAA0B,eAAE,OAAO;CACvC,cAAc,eAAE,OAAO;CACvB,OAAO,eAAE,OAAO;CAChB,QAAQ,eAAE,OAAO;CACjB,IAAI,aAAA,KAAA,KAAA,KAAA,GAAoD;CACxD,2BAA2B,eACxB,OAAO,EACP,KAAI,QAAO,eAAE,OAAO,eAAE,OAAO,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,CAAC,CAAC;AAC3D,CAAC;AAID,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CACA;CAEA,YAAY,IAAc;EACxB,KAAKA,gBAAgB,GAAG,QAAgB;;;;KAIvC;EAED,KAAKC,4BAA4B,GAAG,QAAgB;;;;;;;;;;;KAWnD;EASD,KAAKC,sBAAsB,GAAG,QAAgB;;;;KAI7C;EAGD,KAAKC,gBAAgB,GAAG,QAAgB;;KAEvC;CACH;;;;;;;;;;;;CAaA,SACE,SACA,KACA,OACA,KACA,YACQ;EACR,OAAO,KAAKC,UAAU,SAAS,KAAK,OAAO,KAAA,KAAa,UAAU;CACpE;CAEA,YACE,SACA,KACA,OACA,KACQ;EAIR,OAAO,KAAKA,UAAU,SAAS,KAAK,OAAO,KAAA,KAAa,KAAA,CAAS;CACnE;CAEA,eAAe,OAAe,KAAiB;EAC7C,MAAM,SAAS,UAAU,mBAAmB,GAAG,CAAC;EAChD,MAAM,SAAS,KAAKD,cAAc,IAAI,OAAO,MAAM;EACnD,OAAO,WAAW,KAAA,IACd,KAAA,IACA,MAAQ,QAAQ,yBAAyB,aAAa;CAC5D;CAEA,UACE,SACA,KACA,OACA,KACA,IACA,YACQ;EACR,MAAM,SAAS,UAAU,mBAAmB,GAAG,CAAC;EAChD,IAAI,eAAe,KAAA,GACjB,KAAKH,cAAc,IAAI;GAAC;GAAS;GAAK;GAAO;GAAQ;EAAE,CAAC;OACnD;GACL,MAAM,WAAmC,CAAC;GAC1C,KAAK,MAAM,OAAO,YAChB,SAAS,OAAO;GAElB,KAAKC,0BAA0B,IAAI;IACjC;IACA;IACA;IACA;IACA;IACA,2BAA2B,KAAK,UAAU,QAAQ;GACpD,CAAC;EACH;EACA,OAAO;CACT;CAEA,cAAc,SAAsB,OAAe;EACjD,KAAKC,oBAAoB,IAAI;GAAC;GAAS;GAAO,IAAA;EAAe,CAAC;CAChE;CAEA,WAAW,SAAsB,OAAe;EAC9C,KAAKA,oBAAoB,IAAI;GAAC;GAAS;GAAO,IAAA;EAAY,CAAC;CAC7D;AACF"}
|
|
1
|
+
{"version":3,"file":"change-log.js","names":["parse","v","v.literalUnion","v.parse"],"sources":["../../../../../../../zero-cache/src/services/replicator/schema/change-log.ts"],"sourcesContent":["import {assert} from '../../../../../shared/src/asserts.ts';\nimport {\n parse,\n stringify,\n type JSONValue,\n} from '../../../../../shared/src/bigint-json.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport type {Database, Statement} from '../../../../../zqlite/src/db.ts';\nimport type {LexiVersion} from '../../../types/lexi-version.ts';\nimport type {LiteRowKey} from '../../../types/lite.ts';\nimport {normalizedKeyOrder} from '../../../types/row-key.ts';\n\n/**\n * The Change Log tracks the last operation (set or delete) for each row in the\n * data base, ordered by state version; in other words, a cross-table\n * index of row changes ordered by version. This facilitates a minimal \"diff\"\n * of row changes needed to advance a pipeline from one state version to another.\n *\n * The Change Log stores identifiers only, i.e. it does not store contents.\n * A database snapshot at the previous version can be used to query a row's\n * old contents, if any, and the current snapshot can be used to query a row's\n * new contents. (In the common case, the new contents will have just been applied\n * and thus has a high likelihood of being in the SQLite cache.)\n *\n * There are two table-wide operations:\n * - `t` corresponds to the postgres `TRUNCATE` operation\n * - `r` represents any schema (i.e. column) change\n *\n * For both operations, the corresponding row changes are not explicitly included\n * in the change log. The consumer has the option of simulating them be reading\n * from pre- and post- snapshots, or resetting their state entirely with the current\n * snapshot.\n *\n * To achieve the desired ordering semantics when processing tables that have been\n * truncated, reset, and modified, the \"rowKey\" is set to `null` for resets and\n * the empty string `\"\"` for truncates. This means that resets will be encountered\n * before truncates, which will be processed before any subsequent row changes.\n *\n * This ordering is chosen because resets are currently the more \"destructive\" op\n * and result in aborting the processing (and starting from scratch); doing this\n * earlier reduces wasted work.\n */\n\nexport const SET_OP = 's';\nexport const DEL_OP = 'd';\nexport const TRUNCATE_OP = 't';\nexport const RESET_OP = 'r';\n\n// Exported for testing (and migrations)\nexport const CREATE_CHANGELOG_SCHEMA =\n // stateVersion : a.k.a. row version\n // pos : order in which to process the change (within the version)\n // table : The table associated with the change\n // rowKey : JSON row key for a row change. For table-wide changes RESET\n // and TRUNCATE, there is no associated row; instead, `pos` is\n // set to -1 and the rowKey is set to the stateVersion,\n // guaranteeing when attempting to process the transaction,\n // the pipeline is reset (and the change log traversal\n // aborted).\n // op : 's' for set (insert/update)\n // : 'd' for delete\n // : 'r' for table reset (schema change)\n // : 't' for table truncation (which also resets the pipeline)\n // backfillingColumnVersions\n // : A JSON mapping from column name to stateVersion tracked\n // for replicated writes of columns that are being backfilled.\n // This is used to prevent backfill data, which is at a\n // fixed snapshot/version outside of the replication stream,\n // from overwriting newer column values.\n //\n // Naming note: To maintain compatibility between a new replication-manager\n // and old view-syncers, the previous _zero.changeLog table is preserved\n // and its replacement given a new name \"changeLog2\".\n `\n CREATE TABLE \"_zero.changeLog2\" (\n \"stateVersion\" TEXT NOT NULL,\n \"pos\" INT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"rowKey\" TEXT NOT NULL,\n \"op\" TEXT NOT NULL,\n \"backfillingColumnVersions\" TEXT DEFAULT '{}',\n PRIMARY KEY(\"stateVersion\", \"pos\"),\n UNIQUE(\"table\", \"rowKey\")\n );\n `;\n\n/**\n * Contains the changeLog fields relevant for computing the diff between\n * two snapshots of a replica. The `pos` and `backfillingColumnVersions`\n * fields are excluded, though the query should be ordered by\n * `<stateVersion, pos>`.\n */\nexport type ChangeLogEntry =\n | {\n readonly stateVersion: string;\n readonly table: string;\n readonly rowKey: Readonly<Record<string, JSONValue>>;\n readonly op: typeof SET_OP | typeof DEL_OP;\n }\n | {\n readonly stateVersion: string;\n readonly table: string;\n // Table-wide ops have no row.\n readonly rowKey: null;\n readonly op: typeof TRUNCATE_OP | typeof RESET_OP;\n };\n\n/**\n * The {@link ChangeLogEntry} for the columns of a change log row. This does\n * not validate what zero-cache itself wrote: the columns are\n * `TEXT NOT NULL`, and `rowKey` is the JSON of an object (see\n * {@link ChangeLog}).\n */\nexport function parseChangeLogEntry(\n stateVersion: string,\n table: string,\n rowKey: string,\n op: string,\n): ChangeLogEntry {\n switch (op) {\n case SET_OP:\n case DEL_OP:\n return {stateVersion, table, rowKey: parseRowKey(rowKey), op};\n case TRUNCATE_OP:\n case RESET_OP:\n return {stateVersion, table, rowKey: null, op};\n default:\n throw new Error(`Invalid change log op \"${op}\" for table \"${table}\"`);\n }\n}\n\nfunction parseRowKey(rowKey: string): Readonly<Record<string, JSONValue>> {\n const key = parse(rowKey);\n assert(\n typeof key === 'object' && key !== null && !Array.isArray(key),\n () => `Invalid change log rowKey ${rowKey}`,\n );\n return key as Readonly<Record<string, JSONValue>>;\n}\n\nconst rawChangeLogEntrySchema = v.object({\n stateVersion: v.string(),\n table: v.string(),\n rowKey: v.string(),\n op: v.literalUnion(SET_OP, DEL_OP, TRUNCATE_OP, RESET_OP),\n backfillingColumnVersions: v\n .string()\n .map(val => v.record(v.string()).parse(JSON.parse(val))),\n});\n\nexport type RawChangeLogEntry = v.Infer<typeof rawChangeLogEntrySchema>;\n\nexport class ChangeLog {\n readonly #logRowOpStmt: Statement;\n readonly #logRowOpWithBackfillStmt: Statement;\n readonly #logTableWideOpStmt;\n readonly #getRowOpStmt: Statement;\n\n constructor(db: Database) {\n this.#logRowOpStmt = db.prepare(/*sql*/ `\n INSERT OR REPLACE INTO \"_zero.changeLog2\" \n (stateVersion, pos, \"table\", rowKey, op)\n VALUES (@version, @pos, @table, JSON(@rowKey), @op)\n `);\n\n this.#logRowOpWithBackfillStmt = db.prepare(/*sql*/ `\n INSERT INTO \"_zero.changeLog2\" \n (stateVersion, pos, \"table\", rowKey, op, backfillingColumnVersions)\n VALUES (@version, @pos, @table, JSON(@rowKey), @op, \n JSON(@backfillingColumnVersions))\n ON CONFLICT (\"table\", rowKey) DO UPDATE \n SET stateVersion = excluded.stateVersion,\n pos = excluded.pos,\n op = excluded.op,\n backfillingColumnVersions = json_patch(\n backfillingColumnVersions, excluded.backfillingColumnVersions)\n `);\n\n // Because table-wide ops result in aborting an incremental update\n // and rehydrating all queries at \"head\", they are assigned pos = -1\n // as an optimization to abort as early as possible to skip unnecessary\n // updates.\n //\n // However, changeLog entries that are destined to be \"skipped\" are\n // nonetheless kept for the purpose of tracking backfillingColumnVersions.\n this.#logTableWideOpStmt = db.prepare(/*sql*/ `\n INSERT OR REPLACE INTO \"_zero.changeLog2\" \n (stateVersion, pos, \"table\", rowKey, op) \n VALUES (@version, -1, @table, @version, @op)\n `);\n\n // oxlint-disable-next-line zero/no-select-star -- Local SQLite replica query; not run through pg prepared statements.\n this.#getRowOpStmt = db.prepare(/*sql*/ `\n SELECT * FROM \"_zero.changeLog2\" WHERE \"table\" = ? AND \"rowKey\" = JSON(?)\n `);\n }\n\n /**\n *\n * @param backfilled The backfilling columns for which values were set. Note\n * that an empty list and the `undefined` value mean different things;\n * * An empty list indicates that a backfill is in progress but no\n * backfilling values were set. In this case, existing\n * backfillingColumnVersions are preserved.\n * * `undefined` indicates that there are no columns being backfilled.\n * In this case, any vestigial `backfillingColumnVersions` value\n * is cleared.\n */\n logSetOp(\n version: LexiVersion,\n pos: number,\n table: string,\n row: LiteRowKey,\n backfilled: string[] | undefined,\n ): string {\n return this.#logRowOp(version, pos, table, row, SET_OP, backfilled);\n }\n\n logDeleteOp(\n version: LexiVersion,\n pos: number,\n table: string,\n row: LiteRowKey,\n ): string {\n // Note: For delete ops, it is always safe to clear the\n // backfillingColumnVersions because the backfill algorithm\n // understands that deletes apply to the whole row.\n return this.#logRowOp(version, pos, table, row, DEL_OP, undefined);\n }\n\n getLatestRowOp(table: string, row: LiteRowKey) {\n const rowKey = stringify(normalizedKeyOrder(row));\n const result = this.#getRowOpStmt.get(table, rowKey);\n return result === undefined\n ? undefined\n : v.parse(result, rawChangeLogEntrySchema, 'passthrough');\n }\n\n #logRowOp(\n version: LexiVersion,\n pos: number,\n table: string,\n row: LiteRowKey,\n op: string,\n backfilled: string[] | undefined,\n ): string {\n const rowKey = stringify(normalizedKeyOrder(row));\n if (backfilled === undefined) {\n this.#logRowOpStmt.run({version, pos, table, rowKey, op});\n } else {\n const versions: Record<string, string> = {};\n for (const col of backfilled) {\n versions[col] = version;\n }\n this.#logRowOpWithBackfillStmt.run({\n version,\n pos,\n table,\n rowKey,\n op,\n backfillingColumnVersions: JSON.stringify(versions),\n });\n }\n return rowKey;\n }\n\n logTruncateOp(version: LexiVersion, table: string) {\n this.#logTableWideOpStmt.run({version, table, op: TRUNCATE_OP});\n }\n\n logResetOp(version: LexiVersion, table: string) {\n this.#logTableWideOpStmt.run({version, table, op: RESET_OP});\n }\n}\n"],"mappings":";;;;AAiDA,IAAa,0BAwBX;;;;;;;;;;;;;;;;;;AAwCF,SAAgB,oBACd,cACA,OACA,QACA,IACgB;CAChB,QAAQ,IAAR;EACE,KAAA;EACA,KAAA,KACE,OAAO;GAAC;GAAc;GAAO,QAAQ,YAAY,MAAM;GAAG;EAAE;EAC9D,KAAA;EACA,KAAA,KACE,OAAO;GAAC;GAAc;GAAO,QAAQ;GAAM;EAAE;EAC/C,SACE,MAAM,IAAI,MAAM,0BAA0B,GAAG,eAAe,MAAM,EAAE;CACxE;AACF;AAEA,SAAS,YAAY,QAAqD;CACxE,MAAM,MAAMA,QAAM,MAAM;CACxB,OACE,OAAO,QAAQ,YAAY,QAAQ,QAAQ,CAAC,MAAM,QAAQ,GAAG,SACvD,6BAA6B,QACrC;CACA,OAAO;AACT;AAEA,IAAM,0BAA0BC,eAAE,OAAO;CACvC,cAAcA,eAAE,OAAO;CACvB,OAAOA,eAAE,OAAO;CAChB,QAAQA,eAAE,OAAO;CACjB,IAAIC,aAAAA,KAAAA,KAAAA,KAAAA,GAAoD;CACxD,2BAA2BD,eACxB,OAAO,CAAC,CACR,KAAI,QAAOA,eAAE,OAAOA,eAAE,OAAO,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,GAAG,CAAC,CAAC;AAC3D,CAAC;AAID,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CACA;CAEA,YAAY,IAAc;EACxB,KAAK,gBAAgB,GAAG,QAAgB;;;;KAIvC;EAED,KAAK,4BAA4B,GAAG,QAAgB;;;;;;;;;;;KAWnD;EASD,KAAK,sBAAsB,GAAG,QAAgB;;;;KAI7C;EAGD,KAAK,gBAAgB,GAAG,QAAgB;;KAEvC;CACH;;;;;;;;;;;;CAaA,SACE,SACA,KACA,OACA,KACA,YACQ;EACR,OAAO,KAAK,UAAU,SAAS,KAAK,OAAO,KAAA,KAAa,UAAU;CACpE;CAEA,YACE,SACA,KACA,OACA,KACQ;EAIR,OAAO,KAAK,UAAU,SAAS,KAAK,OAAO,KAAA,KAAa,KAAA,CAAS;CACnE;CAEA,eAAe,OAAe,KAAiB;EAC7C,MAAM,SAAS,UAAU,mBAAmB,GAAG,CAAC;EAChD,MAAM,SAAS,KAAK,cAAc,IAAI,OAAO,MAAM;EACnD,OAAO,WAAW,KAAA,IACd,KAAA,IACAE,MAAQ,QAAQ,yBAAyB,aAAa;CAC5D;CAEA,UACE,SACA,KACA,OACA,KACA,IACA,YACQ;EACR,MAAM,SAAS,UAAU,mBAAmB,GAAG,CAAC;EAChD,IAAI,eAAe,KAAA,GACjB,KAAK,cAAc,IAAI;GAAC;GAAS;GAAK;GAAO;GAAQ;EAAE,CAAC;OACnD;GACL,MAAM,WAAmC,CAAC;GAC1C,KAAK,MAAM,OAAO,YAChB,SAAS,OAAO;GAElB,KAAK,0BAA0B,IAAI;IACjC;IACA;IACA;IACA;IACA;IACA,2BAA2B,KAAK,UAAU,QAAQ;GACpD,CAAC;EACH;EACA,OAAO;CACT;CAEA,cAAc,SAAsB,OAAe;EACjD,KAAK,oBAAoB,IAAI;GAAC;GAAS;GAAO,IAAA;EAAe,CAAC;CAChE;CAEA,WAAW,SAAsB,OAAe;EAC9C,KAAK,oBAAoB,IAAI;GAAC;GAAS;GAAO,IAAA;EAAY,CAAC;CAC7D;AACF"}
|
|
@@ -202,6 +202,6 @@ function pgColumnSpecToMetadata(spec) {
|
|
|
202
202
|
};
|
|
203
203
|
}
|
|
204
204
|
//#endregion
|
|
205
|
-
export { CREATE_COLUMN_METADATA_TABLE, ColumnMetadataStore, metadataToLiteTypeString, populateFromExistingTables };
|
|
205
|
+
export { CREATE_COLUMN_METADATA_TABLE, ColumnMetadataStore, liteTypeStringToMetadata, metadataToLiteTypeString, pgColumnSpecToMetadata, populateFromExistingTables };
|
|
206
206
|
|
|
207
207
|
//# sourceMappingURL=column-metadata.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-metadata.js","names":["#instances","#insertStmt","#updateStmt","#clearBackfillStmt","#deleteColumnStmt","#deleteTableStmt","#renameTableStmt","#getColumnStmt","#getTableStmt","#hasTableStmt","#insertMetadata"],"sources":["../../../../../../../zero-cache/src/services/replicator/schema/column-metadata.ts"],"sourcesContent":["/**\n * Column metadata table for storing upstream PostgreSQL schema information.\n *\n * Previously, upstream type metadata was embedded in SQLite column type strings\n * using pipe-delimited notation (e.g., \"int8|NOT_NULL|TEXT_ENUM\"). This caused\n * issues with SQLite type affinity and made schema inspection difficult.\n *\n * This table stores that metadata separately, allowing SQLite columns to use\n * plain type names while preserving all necessary upstream type information.\n */\n\nimport type {Database, Statement} from '../../../../../zqlite/src/db.ts';\nimport {isArrayColumn, isEnumColumn} from '../../../db/pg-to-lite.ts';\nimport type {ColumnSpec, LiteTableSpec} from '../../../db/specs.ts';\nimport {\n isArray as checkIsArray,\n isEnum as checkIsEnum,\n liteTypeString,\n nullableUpstream,\n upstreamDataType,\n} from '../../../types/lite.ts';\nimport type {BackfillID} from '../../change-source/protocol/current.ts';\n\n/**\n * Structured column metadata, replacing the old pipe-delimited string format.\n */\nexport interface ColumnMetadata {\n /** PostgreSQL type name, e.g., 'int8', 'varchar', 'text[]', 'user_role' */\n upstreamType: string;\n isNotNull: boolean;\n isEnum: boolean;\n isArray: boolean;\n /** Maximum character length for varchar/char types */\n characterMaxLength?: number | null;\n isBackfilling: boolean;\n}\n\ntype ColumnMetadataRow = {\n upstream_type: string;\n is_not_null: number;\n is_enum: number;\n is_array: number;\n character_max_length: number | null;\n backfill: string | null;\n};\n\nexport const CREATE_COLUMN_METADATA_TABLE = `\n CREATE TABLE \"_zero.column_metadata\" (\n table_name TEXT NOT NULL,\n column_name TEXT NOT NULL,\n upstream_type TEXT NOT NULL,\n is_not_null INTEGER NOT NULL,\n is_enum INTEGER NOT NULL,\n is_array INTEGER NOT NULL,\n character_max_length INTEGER,\n backfill TEXT,\n PRIMARY KEY (table_name, column_name)\n );\n`;\n\n/**\n * Efficient column metadata store that prepares all statements upfront.\n * Use this class to avoid re-preparing statements on every operation.\n *\n * Access via `ColumnMetadataStore.getInstance(db)`, which returns `undefined`\n * if the metadata table doesn't exist yet.\n */\nexport class ColumnMetadataStore {\n static #instances = new WeakMap<Database, ColumnMetadataStore>();\n\n readonly #insertStmt: Statement;\n readonly #updateStmt: Statement;\n readonly #clearBackfillStmt: Statement;\n readonly #deleteColumnStmt: Statement;\n readonly #deleteTableStmt: Statement;\n readonly #renameTableStmt: Statement;\n readonly #getColumnStmt: Statement;\n readonly #getTableStmt: Statement;\n readonly #hasTableStmt: Statement;\n\n private constructor(db: Database) {\n this.#insertStmt = db.prepare(`\n INSERT INTO \"_zero.column_metadata\"\n (table_name, column_name, upstream_type, is_not_null, is_enum, is_array, character_max_length, backfill)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n this.#updateStmt = db.prepare(`\n UPDATE \"_zero.column_metadata\"\n SET column_name = ?,\n upstream_type = ?,\n is_not_null = ?,\n is_enum = ?,\n is_array = ?,\n character_max_length = ?\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#clearBackfillStmt = db.prepare(/*sql*/ `\n UPDATE \"_zero.column_metadata\"\n SET backfill = NULL\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#deleteColumnStmt = db.prepare(`\n DELETE FROM \"_zero.column_metadata\"\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#deleteTableStmt = db.prepare(`\n DELETE FROM \"_zero.column_metadata\"\n WHERE table_name = ?\n `);\n\n this.#renameTableStmt = db.prepare(`\n UPDATE \"_zero.column_metadata\"\n SET table_name = ?\n WHERE table_name = ?\n `);\n\n this.#getColumnStmt = db.prepare(`\n SELECT upstream_type, is_not_null, is_enum, is_array, character_max_length, backfill\n FROM \"_zero.column_metadata\"\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#getTableStmt = db.prepare(`\n SELECT column_name, upstream_type, is_not_null, is_enum, is_array, character_max_length, backfill\n FROM \"_zero.column_metadata\"\n WHERE table_name = ?\n ORDER BY column_name\n `);\n\n this.#hasTableStmt = db.prepare(`\n SELECT 1 FROM sqlite_master\n WHERE type = 'table' AND name = '_zero.column_metadata'\n `);\n }\n\n /**\n * Gets the singleton instance of ColumnMetadataStore for the given database.\n * Returns `undefined` if the metadata table doesn't exist yet.\n */\n static getInstance(db: Database): ColumnMetadataStore | undefined {\n let instance = ColumnMetadataStore.#instances.get(db);\n if (!instance) {\n // Check if table exists\n const tableExists = db\n .prepare(\n `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_zero.column_metadata'`,\n )\n .get();\n\n if (!tableExists) {\n return undefined;\n }\n\n instance = new ColumnMetadataStore(db);\n ColumnMetadataStore.#instances.set(db, instance);\n }\n return instance;\n }\n\n insert(\n tableName: string,\n columnName: string,\n spec: ColumnSpec,\n backfill?: BackfillID,\n ): void {\n const metadata = pgColumnSpecToMetadata(spec);\n this.#insertMetadata(tableName, columnName, metadata, backfill);\n }\n\n #insertMetadata(\n tableName: string,\n columnName: string,\n metadata: Omit<ColumnMetadata, 'isBackfilling'>,\n backfill?: BackfillID,\n ): void {\n this.#insertStmt.run(\n tableName,\n columnName,\n metadata.upstreamType,\n metadata.isNotNull ? 1 : 0,\n metadata.isEnum ? 1 : 0,\n metadata.isArray ? 1 : 0,\n metadata.characterMaxLength ?? null,\n backfill ? JSON.stringify(backfill) : null,\n );\n }\n\n update(\n tableName: string,\n oldColumnName: string,\n newColumnName: string,\n spec: ColumnSpec,\n ): void {\n const metadata = pgColumnSpecToMetadata(spec);\n this.#updateStmt.run(\n newColumnName,\n metadata.upstreamType,\n metadata.isNotNull ? 1 : 0,\n metadata.isEnum ? 1 : 0,\n metadata.isArray ? 1 : 0,\n metadata.characterMaxLength ?? null,\n tableName,\n oldColumnName,\n );\n }\n\n clearBackfilling(tableName: string, columnName: string): void {\n this.#clearBackfillStmt.run(tableName, columnName);\n }\n\n deleteColumn(tableName: string, columnName: string): void {\n this.#deleteColumnStmt.run(tableName, columnName);\n }\n\n deleteTable(tableName: string): void {\n this.#deleteTableStmt.run(tableName);\n }\n\n renameTable(oldTableName: string, newTableName: string): void {\n this.#renameTableStmt.run(newTableName, oldTableName);\n }\n\n getColumn(tableName: string, columnName: string): ColumnMetadata | undefined {\n const row = this.#getColumnStmt.get(tableName, columnName) as\n | ColumnMetadataRow\n | undefined;\n\n if (!row) {\n return undefined;\n }\n\n return {\n upstreamType: row.upstream_type,\n isNotNull: row.is_not_null !== 0,\n isEnum: row.is_enum !== 0,\n isArray: row.is_array !== 0,\n characterMaxLength: row.character_max_length,\n isBackfilling: row.backfill !== null,\n };\n }\n\n getTable(tableName: string): Map<string, ColumnMetadata> {\n const rows = this.#getTableStmt.all(tableName) as Array<\n ColumnMetadataRow & {column_name: string}\n >;\n\n const metadata = new Map<string, ColumnMetadata>();\n for (const row of rows) {\n metadata.set(row.column_name, {\n upstreamType: row.upstream_type,\n isNotNull: row.is_not_null !== 0,\n isEnum: row.is_enum !== 0,\n isArray: row.is_array !== 0,\n characterMaxLength: row.character_max_length,\n isBackfilling: row.backfill !== null,\n });\n }\n\n return metadata;\n }\n\n hasTable(): boolean {\n const result = this.#hasTableStmt.get();\n return result !== undefined;\n }\n}\n\n/**\n * Populates metadata table from existing tables that use pipe notation.\n * This is used during migration v8 to backfill the metadata table.\n */\nexport function populateFromExistingTables(\n db: Database,\n tables: LiteTableSpec[],\n): void {\n // The backfill column is not relevant here, and does not exist on\n // older versions of the replica.\n const legacyInsertStmt = db.prepare(`\n INSERT INTO \"_zero.column_metadata\"\n (table_name, column_name, upstream_type, is_not_null, is_enum, is_array, character_max_length)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n for (const table of tables) {\n for (const [columnName, columnSpec] of Object.entries(table.columns)) {\n const metadata = liteTypeStringToMetadata(\n columnSpec.dataType,\n columnSpec.characterMaximumLength,\n );\n legacyInsertStmt.run(\n table.name,\n columnName,\n metadata.upstreamType,\n metadata.isNotNull ? 1 : 0,\n metadata.isEnum ? 1 : 0,\n metadata.isArray ? 1 : 0,\n metadata.characterMaxLength ?? null,\n );\n }\n }\n}\n\n/**\n * Converts pipe-delimited LiteTypeString to structured ColumnMetadata.\n * This is a compatibility helper for the migration period.\n */\nexport function liteTypeStringToMetadata(\n liteTypeString: string,\n characterMaxLength?: number | null,\n): ColumnMetadata {\n const baseType = upstreamDataType(liteTypeString);\n const isArrayType = checkIsArray(liteTypeString);\n\n // Reconstruct the full upstream type including array notation\n // For new-style arrays like 'text[]', upstreamDataType returns 'text[]'\n // For old-style arrays like 'int4|NOT_NULL[]', upstreamDataType returns 'int4', so we append '[]'\n const fullUpstreamType =\n isArrayType && !baseType.includes('[]') ? `${baseType}[]` : baseType;\n\n return {\n upstreamType: fullUpstreamType,\n isNotNull: !nullableUpstream(liteTypeString),\n isEnum: checkIsEnum(liteTypeString),\n isArray: isArrayType,\n characterMaxLength: characterMaxLength ?? null,\n isBackfilling: false,\n };\n}\n\n/**\n * Converts structured ColumnMetadata back to pipe-delimited LiteTypeString.\n * This is a compatibility helper for the migration period.\n */\nexport function metadataToLiteTypeString(metadata: ColumnMetadata): string {\n return liteTypeString(\n metadata.upstreamType,\n metadata.isNotNull,\n metadata.isEnum,\n metadata.isArray,\n );\n}\n\n/**\n * Converts PostgreSQL ColumnSpec to structured ColumnMetadata.\n * Used during replication to populate the metadata table from upstream schema.\n *\n * Uses the same logic as liteTypeString() and mapPostgresToLiteColumn() via shared helpers.\n */\nexport function pgColumnSpecToMetadata(spec: ColumnSpec): ColumnMetadata {\n return {\n upstreamType: spec.dataType,\n isNotNull: spec.notNull ?? false,\n isEnum: isEnumColumn(spec),\n isArray: isArrayColumn(spec),\n characterMaxLength: spec.characterMaximumLength ?? null,\n isBackfilling: false,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"column-metadata.js","names":["checkIsArray","checkIsEnum"],"sources":["../../../../../../../zero-cache/src/services/replicator/schema/column-metadata.ts"],"sourcesContent":["/**\n * Column metadata table for storing upstream PostgreSQL schema information.\n *\n * Previously, upstream type metadata was embedded in SQLite column type strings\n * using pipe-delimited notation (e.g., \"int8|NOT_NULL|TEXT_ENUM\"). This caused\n * issues with SQLite type affinity and made schema inspection difficult.\n *\n * This table stores that metadata separately, allowing SQLite columns to use\n * plain type names while preserving all necessary upstream type information.\n */\n\nimport type {Database, Statement} from '../../../../../zqlite/src/db.ts';\nimport {isArrayColumn, isEnumColumn} from '../../../db/pg-to-lite.ts';\nimport type {ColumnSpec, LiteTableSpec} from '../../../db/specs.ts';\nimport {\n isArray as checkIsArray,\n isEnum as checkIsEnum,\n liteTypeString,\n nullableUpstream,\n upstreamDataType,\n} from '../../../types/lite.ts';\nimport type {BackfillID} from '../../change-source/protocol/current.ts';\n\n/**\n * Structured column metadata, replacing the old pipe-delimited string format.\n */\nexport interface ColumnMetadata {\n /** PostgreSQL type name, e.g., 'int8', 'varchar', 'text[]', 'user_role' */\n upstreamType: string;\n isNotNull: boolean;\n isEnum: boolean;\n isArray: boolean;\n /** Maximum character length for varchar/char types */\n characterMaxLength?: number | null;\n isBackfilling: boolean;\n}\n\ntype ColumnMetadataRow = {\n upstream_type: string;\n is_not_null: number;\n is_enum: number;\n is_array: number;\n character_max_length: number | null;\n backfill: string | null;\n};\n\nexport const CREATE_COLUMN_METADATA_TABLE = `\n CREATE TABLE \"_zero.column_metadata\" (\n table_name TEXT NOT NULL,\n column_name TEXT NOT NULL,\n upstream_type TEXT NOT NULL,\n is_not_null INTEGER NOT NULL,\n is_enum INTEGER NOT NULL,\n is_array INTEGER NOT NULL,\n character_max_length INTEGER,\n backfill TEXT,\n PRIMARY KEY (table_name, column_name)\n );\n`;\n\n/**\n * Efficient column metadata store that prepares all statements upfront.\n * Use this class to avoid re-preparing statements on every operation.\n *\n * Access via `ColumnMetadataStore.getInstance(db)`, which returns `undefined`\n * if the metadata table doesn't exist yet.\n */\nexport class ColumnMetadataStore {\n static #instances = new WeakMap<Database, ColumnMetadataStore>();\n\n readonly #insertStmt: Statement;\n readonly #updateStmt: Statement;\n readonly #clearBackfillStmt: Statement;\n readonly #deleteColumnStmt: Statement;\n readonly #deleteTableStmt: Statement;\n readonly #renameTableStmt: Statement;\n readonly #getColumnStmt: Statement;\n readonly #getTableStmt: Statement;\n readonly #hasTableStmt: Statement;\n\n private constructor(db: Database) {\n this.#insertStmt = db.prepare(`\n INSERT INTO \"_zero.column_metadata\"\n (table_name, column_name, upstream_type, is_not_null, is_enum, is_array, character_max_length, backfill)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n this.#updateStmt = db.prepare(`\n UPDATE \"_zero.column_metadata\"\n SET column_name = ?,\n upstream_type = ?,\n is_not_null = ?,\n is_enum = ?,\n is_array = ?,\n character_max_length = ?\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#clearBackfillStmt = db.prepare(/*sql*/ `\n UPDATE \"_zero.column_metadata\"\n SET backfill = NULL\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#deleteColumnStmt = db.prepare(`\n DELETE FROM \"_zero.column_metadata\"\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#deleteTableStmt = db.prepare(`\n DELETE FROM \"_zero.column_metadata\"\n WHERE table_name = ?\n `);\n\n this.#renameTableStmt = db.prepare(`\n UPDATE \"_zero.column_metadata\"\n SET table_name = ?\n WHERE table_name = ?\n `);\n\n this.#getColumnStmt = db.prepare(`\n SELECT upstream_type, is_not_null, is_enum, is_array, character_max_length, backfill\n FROM \"_zero.column_metadata\"\n WHERE table_name = ? AND column_name = ?\n `);\n\n this.#getTableStmt = db.prepare(`\n SELECT column_name, upstream_type, is_not_null, is_enum, is_array, character_max_length, backfill\n FROM \"_zero.column_metadata\"\n WHERE table_name = ?\n ORDER BY column_name\n `);\n\n this.#hasTableStmt = db.prepare(`\n SELECT 1 FROM sqlite_master\n WHERE type = 'table' AND name = '_zero.column_metadata'\n `);\n }\n\n /**\n * Gets the singleton instance of ColumnMetadataStore for the given database.\n * Returns `undefined` if the metadata table doesn't exist yet.\n */\n static getInstance(db: Database): ColumnMetadataStore | undefined {\n let instance = ColumnMetadataStore.#instances.get(db);\n if (!instance) {\n // Check if table exists\n const tableExists = db\n .prepare(\n `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_zero.column_metadata'`,\n )\n .get();\n\n if (!tableExists) {\n return undefined;\n }\n\n instance = new ColumnMetadataStore(db);\n ColumnMetadataStore.#instances.set(db, instance);\n }\n return instance;\n }\n\n insert(\n tableName: string,\n columnName: string,\n spec: ColumnSpec,\n backfill?: BackfillID,\n ): void {\n const metadata = pgColumnSpecToMetadata(spec);\n this.#insertMetadata(tableName, columnName, metadata, backfill);\n }\n\n #insertMetadata(\n tableName: string,\n columnName: string,\n metadata: Omit<ColumnMetadata, 'isBackfilling'>,\n backfill?: BackfillID,\n ): void {\n this.#insertStmt.run(\n tableName,\n columnName,\n metadata.upstreamType,\n metadata.isNotNull ? 1 : 0,\n metadata.isEnum ? 1 : 0,\n metadata.isArray ? 1 : 0,\n metadata.characterMaxLength ?? null,\n backfill ? JSON.stringify(backfill) : null,\n );\n }\n\n update(\n tableName: string,\n oldColumnName: string,\n newColumnName: string,\n spec: ColumnSpec,\n ): void {\n const metadata = pgColumnSpecToMetadata(spec);\n this.#updateStmt.run(\n newColumnName,\n metadata.upstreamType,\n metadata.isNotNull ? 1 : 0,\n metadata.isEnum ? 1 : 0,\n metadata.isArray ? 1 : 0,\n metadata.characterMaxLength ?? null,\n tableName,\n oldColumnName,\n );\n }\n\n clearBackfilling(tableName: string, columnName: string): void {\n this.#clearBackfillStmt.run(tableName, columnName);\n }\n\n deleteColumn(tableName: string, columnName: string): void {\n this.#deleteColumnStmt.run(tableName, columnName);\n }\n\n deleteTable(tableName: string): void {\n this.#deleteTableStmt.run(tableName);\n }\n\n renameTable(oldTableName: string, newTableName: string): void {\n this.#renameTableStmt.run(newTableName, oldTableName);\n }\n\n getColumn(tableName: string, columnName: string): ColumnMetadata | undefined {\n const row = this.#getColumnStmt.get(tableName, columnName) as\n | ColumnMetadataRow\n | undefined;\n\n if (!row) {\n return undefined;\n }\n\n return {\n upstreamType: row.upstream_type,\n isNotNull: row.is_not_null !== 0,\n isEnum: row.is_enum !== 0,\n isArray: row.is_array !== 0,\n characterMaxLength: row.character_max_length,\n isBackfilling: row.backfill !== null,\n };\n }\n\n getTable(tableName: string): Map<string, ColumnMetadata> {\n const rows = this.#getTableStmt.all(tableName) as Array<\n ColumnMetadataRow & {column_name: string}\n >;\n\n const metadata = new Map<string, ColumnMetadata>();\n for (const row of rows) {\n metadata.set(row.column_name, {\n upstreamType: row.upstream_type,\n isNotNull: row.is_not_null !== 0,\n isEnum: row.is_enum !== 0,\n isArray: row.is_array !== 0,\n characterMaxLength: row.character_max_length,\n isBackfilling: row.backfill !== null,\n });\n }\n\n return metadata;\n }\n\n hasTable(): boolean {\n const result = this.#hasTableStmt.get();\n return result !== undefined;\n }\n}\n\n/**\n * Populates metadata table from existing tables that use pipe notation.\n * This is used during migration v8 to backfill the metadata table.\n */\nexport function populateFromExistingTables(\n db: Database,\n tables: LiteTableSpec[],\n): void {\n // The backfill column is not relevant here, and does not exist on\n // older versions of the replica.\n const legacyInsertStmt = db.prepare(`\n INSERT INTO \"_zero.column_metadata\"\n (table_name, column_name, upstream_type, is_not_null, is_enum, is_array, character_max_length)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n for (const table of tables) {\n for (const [columnName, columnSpec] of Object.entries(table.columns)) {\n const metadata = liteTypeStringToMetadata(\n columnSpec.dataType,\n columnSpec.characterMaximumLength,\n );\n legacyInsertStmt.run(\n table.name,\n columnName,\n metadata.upstreamType,\n metadata.isNotNull ? 1 : 0,\n metadata.isEnum ? 1 : 0,\n metadata.isArray ? 1 : 0,\n metadata.characterMaxLength ?? null,\n );\n }\n }\n}\n\n/**\n * Converts pipe-delimited LiteTypeString to structured ColumnMetadata.\n * This is a compatibility helper for the migration period.\n */\nexport function liteTypeStringToMetadata(\n liteTypeString: string,\n characterMaxLength?: number | null,\n): ColumnMetadata {\n const baseType = upstreamDataType(liteTypeString);\n const isArrayType = checkIsArray(liteTypeString);\n\n // Reconstruct the full upstream type including array notation\n // For new-style arrays like 'text[]', upstreamDataType returns 'text[]'\n // For old-style arrays like 'int4|NOT_NULL[]', upstreamDataType returns 'int4', so we append '[]'\n const fullUpstreamType =\n isArrayType && !baseType.includes('[]') ? `${baseType}[]` : baseType;\n\n return {\n upstreamType: fullUpstreamType,\n isNotNull: !nullableUpstream(liteTypeString),\n isEnum: checkIsEnum(liteTypeString),\n isArray: isArrayType,\n characterMaxLength: characterMaxLength ?? null,\n isBackfilling: false,\n };\n}\n\n/**\n * Converts structured ColumnMetadata back to pipe-delimited LiteTypeString.\n * This is a compatibility helper for the migration period.\n */\nexport function metadataToLiteTypeString(metadata: ColumnMetadata): string {\n return liteTypeString(\n metadata.upstreamType,\n metadata.isNotNull,\n metadata.isEnum,\n metadata.isArray,\n );\n}\n\n/**\n * Converts PostgreSQL ColumnSpec to structured ColumnMetadata.\n * Used during replication to populate the metadata table from upstream schema.\n *\n * Uses the same logic as liteTypeString() and mapPostgresToLiteColumn() via shared helpers.\n */\nexport function pgColumnSpecToMetadata(spec: ColumnSpec): ColumnMetadata {\n return {\n upstreamType: spec.dataType,\n isNotNull: spec.notNull ?? false,\n isEnum: isEnumColumn(spec),\n isArray: isArrayColumn(spec),\n characterMaxLength: spec.characterMaximumLength ?? null,\n isBackfilling: false,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse, valita_exports } from "../../../../../shared/src/valita.js";
|
|
2
2
|
import { jsonObjectSchema, stringify } from "../../../../../shared/src/bigint-json.js";
|
|
3
|
-
import "./constants.js";
|
|
3
|
+
import { ZERO_VERSION_COLUMN_NAME } from "./constants.js";
|
|
4
4
|
import { CREATE_COLUMN_METADATA_TABLE } from "./column-metadata.js";
|
|
5
5
|
import { CREATE_TABLE_METADATA_TABLE } from "./table-metadata.js";
|
|
6
6
|
import { CREATE_BACKFILLING_TABLE } from "./backfilling.js";
|
|
@@ -73,21 +73,23 @@ function getAscendingEvents(db) {
|
|
|
73
73
|
}));
|
|
74
74
|
}
|
|
75
75
|
function getSubscriptionState(db) {
|
|
76
|
-
|
|
76
|
+
const result = db.get(`
|
|
77
77
|
SELECT c.replicaVersion, c.publications, s.stateVersion as watermark
|
|
78
78
|
FROM "_zero.replicationConfig" as c
|
|
79
79
|
JOIN "_zero.replicationState" as s
|
|
80
80
|
ON c.lock = s.lock
|
|
81
|
-
`)
|
|
81
|
+
`);
|
|
82
|
+
return parse(result, subscriptionStateSchema);
|
|
82
83
|
}
|
|
83
84
|
function getSubscriptionStateAndContext(db) {
|
|
84
|
-
|
|
85
|
+
const result = db.get(`
|
|
85
86
|
SELECT c.replicaVersion, c.publications, c.initialSyncContext,
|
|
86
87
|
s.stateVersion as watermark
|
|
87
88
|
FROM "_zero.replicationConfig" as c
|
|
88
89
|
JOIN "_zero.replicationState" as s
|
|
89
90
|
ON c.lock = s.lock
|
|
90
|
-
`)
|
|
91
|
+
`);
|
|
92
|
+
return parse(result, subscriptionStateAndContextSchema);
|
|
91
93
|
}
|
|
92
94
|
function updateReplicationWatermark(db, watermark, writeTimeMs) {
|
|
93
95
|
if (writeTimeMs === void 0) db.run(`
|
|
@@ -98,9 +100,10 @@ function updateReplicationWatermark(db, watermark, writeTimeMs) {
|
|
|
98
100
|
SET stateVersion=?, writeTimeMs=?`, watermark, writeTimeMs);
|
|
99
101
|
}
|
|
100
102
|
function getReplicationState(db) {
|
|
101
|
-
|
|
103
|
+
const result = db.get(`SELECT stateVersion FROM "_zero.replicationState"`);
|
|
104
|
+
return parse(result, replicationStateSchema);
|
|
102
105
|
}
|
|
103
106
|
//#endregion
|
|
104
|
-
export { CREATE_RUNTIME_EVENTS_TABLE, createReplicationStateTables, getAscendingEvents, getReplicationState, getSubscriptionState, getSubscriptionStateAndContext, initReplicationState, recordEvent, updateReplicationWatermark };
|
|
107
|
+
export { CREATE_RUNTIME_EVENTS_TABLE, ZERO_VERSION_COLUMN_NAME, createReplicationStateTables, getAscendingEvents, getReplicationState, getSubscriptionState, getSubscriptionStateAndContext, initReplicationState, recordEvent, updateReplicationWatermark };
|
|
105
108
|
|
|
106
109
|
//# sourceMappingURL=replication-state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replication-state.js","names":[],"sources":["../../../../../../../zero-cache/src/services/replicator/schema/replication-state.ts"],"sourcesContent":["/**\n * Replication metadata, used for incremental view maintenance and catchup.\n *\n * These tables are created atomically in {@link setupReplicationTables}\n * after the logical replication handoff when initial data synchronization has completed.\n */\n\nimport {\n jsonObjectSchema,\n stringify,\n type JSONObject,\n} from '../../../../../shared/src/bigint-json.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport type {Database} from '../../../../../zqlite/src/db.ts';\nimport type {StatementRunner} from '../../../db/statements.ts';\nimport {CREATE_BACKFILLING_TABLE} from './backfilling.ts';\nimport {CREATE_CHANGELOG_SCHEMA} from './change-log.ts';\nimport {CREATE_COLUMN_METADATA_TABLE} from './column-metadata.ts';\nimport {ZERO_VERSION_COLUMN_NAME} from './constants.ts';\nimport {CREATE_TABLE_METADATA_TABLE} from './table-metadata.ts';\n\nexport {ZERO_VERSION_COLUMN_NAME};\n\nexport type RuntimeEvent = 'sync' | 'upgrade' | 'vacuum';\n\n// event : The RuntimeEvent. Only one row per event is tracked.\n// Inserting an event will REPLACE any row for the same event.\n// timestamp : SQLite timestamp string, e.g. \"2024-04-12 11:37:46\".\n// Append a `Z` when parsing with `new Date(...)`;\nexport const CREATE_RUNTIME_EVENTS_TABLE = `\n CREATE TABLE \"_zero.runtimeEvents\" (\n event TEXT PRIMARY KEY ON CONFLICT REPLACE,\n timestamp TEXT NOT NULL DEFAULT (current_timestamp)\n );\n`;\n\nconst CREATE_REPLICATION_STATE_SCHEMA =\n // replicaVersion : A value identifying the version at which the initial sync happened, i.e.\n // the version at which all rows were copied, and to `_0_version` was set.\n // This value is used to distinguish data from other replicas (e.g. if a\n // replica is reset or if there are ever multiple replicas).\n // publications : JSON stringified array of publication names\n // initialSyncContext : Metadata related to the context of when and how the replica was initially\n // synced. This corresponds with the same column stored in upstream and is\n // used for debugging replica version mismatches, which can arise from a number\n // of misconfigurations, such as dueling replication-managers, or restores of\n // stale litestream backups.\n // lock : Auto-magic column for enforcing single-row semantics.\n /*sql*/ `\n CREATE TABLE \"_zero.replicationConfig\" (\n replicaVersion TEXT NOT NULL,\n publications TEXT NOT NULL,\n initialSyncContext TEXT DEFAULT '{}',\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n ` +\n // stateVersion : The latest version replicated from upstream, starting with the initial\n // `replicaVersion` and moving forward to each subsequent commit watermark\n // (e.g. corresponding to a Postgres LSN). Versions are represented as\n // lexicographically sortable watermarks (e.g. LexiVersions).\n // writeTimeMs : The millisecond epoch at which this version was written to the replica.\n //\n /*sql*/ `\n CREATE TABLE \"_zero.replicationState\" (\n stateVersion TEXT NOT NULL,\n writeTimeMs INTEGER NOT NULL,\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n ` +\n CREATE_CHANGELOG_SCHEMA +\n CREATE_RUNTIME_EVENTS_TABLE +\n CREATE_COLUMN_METADATA_TABLE +\n CREATE_TABLE_METADATA_TABLE +\n CREATE_BACKFILLING_TABLE;\n\nconst stringArray = v.array(v.string());\n\nconst subscriptionStateSchema = v\n .object({\n replicaVersion: v.string(),\n publications: v.string(),\n watermark: v.string(),\n })\n .map(s => ({\n ...s,\n publications: v.parse(JSON.parse(s.publications), stringArray),\n }));\n\nexport type SubscriptionState = v.Infer<typeof subscriptionStateSchema>;\n\nconst subscriptionStateAndContextSchema = v\n .object({\n replicaVersion: v.string(),\n publications: v.string(),\n initialSyncContext: v.string(),\n watermark: v.string(),\n })\n .map(s => ({\n ...s,\n publications: v.parse(JSON.parse(s.publications), stringArray),\n initialSyncContext: v.parse(\n JSON.parse(s.initialSyncContext),\n jsonObjectSchema,\n ),\n }));\n\nexport type SubscriptionStateAndContext = v.Infer<\n typeof subscriptionStateAndContextSchema\n>;\n\nconst replicationStateSchema = v.object({\n stateVersion: v.string(),\n});\n\nexport type ReplicationState = v.Infer<typeof replicationStateSchema>;\n\nexport function initReplicationState(\n db: Database,\n publications: string[],\n watermark: string,\n initialSyncContext: JSONObject = {},\n createTables = true,\n) {\n if (createTables) {\n createReplicationStateTables(db);\n }\n db.prepare(\n `\n INSERT INTO \"_zero.replicationConfig\" \n (replicaVersion, publications, initialSyncContext) VALUES (?, ?, ?)\n `,\n ).run(\n watermark,\n JSON.stringify(publications.sort()),\n stringify(initialSyncContext),\n );\n db.prepare(/*sql*/ `\n INSERT INTO \"_zero.replicationState\" (stateVersion, writeTimeMs) \n VALUES (?, unixepoch('subsec') * 1000)\n `).run(watermark);\n recordEvent(db, 'sync');\n}\n\n/**\n * Exposed as a separate function for the custom change source,\n * which needs the tables to be created in order to construct\n * ChangeProcessor before it knows the initial watermark.\n */\nexport function createReplicationStateTables(db: Database) {\n db.exec(CREATE_REPLICATION_STATE_SCHEMA);\n}\n\nexport function recordEvent(db: Database, event: RuntimeEvent) {\n db.prepare(\n `\n INSERT INTO \"_zero.runtimeEvents\" (event) VALUES (?) \n `,\n ).run(event);\n}\n\nexport function getAscendingEvents(db: Database) {\n const result = db\n .prepare(\n `SELECT event, timestamp FROM \"_zero.runtimeEvents\" \n ORDER BY timestamp ASC\n `,\n )\n .all<{event: string; timestamp: string}>();\n return result.map(({event, timestamp}) => ({\n event,\n timestamp: new Date(timestamp + 'Z'),\n }));\n}\n\nexport function getSubscriptionState(db: StatementRunner): SubscriptionState {\n const result = db.get(/*sql*/ `\n SELECT c.replicaVersion, c.publications, s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock\n `);\n return v.parse(result, subscriptionStateSchema);\n}\n\nexport function getSubscriptionStateAndContext(\n db: StatementRunner,\n): SubscriptionStateAndContext {\n const result = db.get(/*sql*/ `\n SELECT c.replicaVersion, c.publications, c.initialSyncContext,\n s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock\n `);\n return v.parse(result, subscriptionStateAndContextSchema);\n}\n\nexport function updateReplicationWatermark(\n db: StatementRunner,\n watermark: string,\n writeTimeMs?: number | undefined,\n) {\n if (writeTimeMs === undefined) {\n db.run(\n /*sql*/ `\n UPDATE \"_zero.replicationState\"\n SET stateVersion=?, writeTimeMs=unixepoch('subsec') * 1000`,\n watermark,\n );\n } else {\n db.run(\n /*sql*/ `\n UPDATE \"_zero.replicationState\"\n SET stateVersion=?, writeTimeMs=?`,\n watermark,\n writeTimeMs,\n );\n }\n}\n\nexport function getReplicationState(db: StatementRunner): ReplicationState {\n const result = db.get(`SELECT stateVersion FROM \"_zero.replicationState\"`);\n return v.parse(result, replicationStateSchema);\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,IAAa,8BAA8B;;;;;;AAO3C,IAAM,kCAYI,qZAqBR,0BACA,8BACA,+BACA,8BACA;AAEF,IAAM,
|
|
1
|
+
{"version":3,"file":"replication-state.js","names":["v","v.parse"],"sources":["../../../../../../../zero-cache/src/services/replicator/schema/replication-state.ts"],"sourcesContent":["/**\n * Replication metadata, used for incremental view maintenance and catchup.\n *\n * These tables are created atomically in {@link setupReplicationTables}\n * after the logical replication handoff when initial data synchronization has completed.\n */\n\nimport {\n jsonObjectSchema,\n stringify,\n type JSONObject,\n} from '../../../../../shared/src/bigint-json.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport type {Database} from '../../../../../zqlite/src/db.ts';\nimport type {StatementRunner} from '../../../db/statements.ts';\nimport {CREATE_BACKFILLING_TABLE} from './backfilling.ts';\nimport {CREATE_CHANGELOG_SCHEMA} from './change-log.ts';\nimport {CREATE_COLUMN_METADATA_TABLE} from './column-metadata.ts';\nimport {ZERO_VERSION_COLUMN_NAME} from './constants.ts';\nimport {CREATE_TABLE_METADATA_TABLE} from './table-metadata.ts';\n\nexport {ZERO_VERSION_COLUMN_NAME};\n\nexport type RuntimeEvent = 'sync' | 'upgrade' | 'vacuum';\n\n// event : The RuntimeEvent. Only one row per event is tracked.\n// Inserting an event will REPLACE any row for the same event.\n// timestamp : SQLite timestamp string, e.g. \"2024-04-12 11:37:46\".\n// Append a `Z` when parsing with `new Date(...)`;\nexport const CREATE_RUNTIME_EVENTS_TABLE = `\n CREATE TABLE \"_zero.runtimeEvents\" (\n event TEXT PRIMARY KEY ON CONFLICT REPLACE,\n timestamp TEXT NOT NULL DEFAULT (current_timestamp)\n );\n`;\n\nconst CREATE_REPLICATION_STATE_SCHEMA =\n // replicaVersion : A value identifying the version at which the initial sync happened, i.e.\n // the version at which all rows were copied, and to `_0_version` was set.\n // This value is used to distinguish data from other replicas (e.g. if a\n // replica is reset or if there are ever multiple replicas).\n // publications : JSON stringified array of publication names\n // initialSyncContext : Metadata related to the context of when and how the replica was initially\n // synced. This corresponds with the same column stored in upstream and is\n // used for debugging replica version mismatches, which can arise from a number\n // of misconfigurations, such as dueling replication-managers, or restores of\n // stale litestream backups.\n // lock : Auto-magic column for enforcing single-row semantics.\n /*sql*/ `\n CREATE TABLE \"_zero.replicationConfig\" (\n replicaVersion TEXT NOT NULL,\n publications TEXT NOT NULL,\n initialSyncContext TEXT DEFAULT '{}',\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n ` +\n // stateVersion : The latest version replicated from upstream, starting with the initial\n // `replicaVersion` and moving forward to each subsequent commit watermark\n // (e.g. corresponding to a Postgres LSN). Versions are represented as\n // lexicographically sortable watermarks (e.g. LexiVersions).\n // writeTimeMs : The millisecond epoch at which this version was written to the replica.\n //\n /*sql*/ `\n CREATE TABLE \"_zero.replicationState\" (\n stateVersion TEXT NOT NULL,\n writeTimeMs INTEGER NOT NULL,\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n ` +\n CREATE_CHANGELOG_SCHEMA +\n CREATE_RUNTIME_EVENTS_TABLE +\n CREATE_COLUMN_METADATA_TABLE +\n CREATE_TABLE_METADATA_TABLE +\n CREATE_BACKFILLING_TABLE;\n\nconst stringArray = v.array(v.string());\n\nconst subscriptionStateSchema = v\n .object({\n replicaVersion: v.string(),\n publications: v.string(),\n watermark: v.string(),\n })\n .map(s => ({\n ...s,\n publications: v.parse(JSON.parse(s.publications), stringArray),\n }));\n\nexport type SubscriptionState = v.Infer<typeof subscriptionStateSchema>;\n\nconst subscriptionStateAndContextSchema = v\n .object({\n replicaVersion: v.string(),\n publications: v.string(),\n initialSyncContext: v.string(),\n watermark: v.string(),\n })\n .map(s => ({\n ...s,\n publications: v.parse(JSON.parse(s.publications), stringArray),\n initialSyncContext: v.parse(\n JSON.parse(s.initialSyncContext),\n jsonObjectSchema,\n ),\n }));\n\nexport type SubscriptionStateAndContext = v.Infer<\n typeof subscriptionStateAndContextSchema\n>;\n\nconst replicationStateSchema = v.object({\n stateVersion: v.string(),\n});\n\nexport type ReplicationState = v.Infer<typeof replicationStateSchema>;\n\nexport function initReplicationState(\n db: Database,\n publications: string[],\n watermark: string,\n initialSyncContext: JSONObject = {},\n createTables = true,\n) {\n if (createTables) {\n createReplicationStateTables(db);\n }\n db.prepare(\n `\n INSERT INTO \"_zero.replicationConfig\" \n (replicaVersion, publications, initialSyncContext) VALUES (?, ?, ?)\n `,\n ).run(\n watermark,\n JSON.stringify(publications.sort()),\n stringify(initialSyncContext),\n );\n db.prepare(/*sql*/ `\n INSERT INTO \"_zero.replicationState\" (stateVersion, writeTimeMs) \n VALUES (?, unixepoch('subsec') * 1000)\n `).run(watermark);\n recordEvent(db, 'sync');\n}\n\n/**\n * Exposed as a separate function for the custom change source,\n * which needs the tables to be created in order to construct\n * ChangeProcessor before it knows the initial watermark.\n */\nexport function createReplicationStateTables(db: Database) {\n db.exec(CREATE_REPLICATION_STATE_SCHEMA);\n}\n\nexport function recordEvent(db: Database, event: RuntimeEvent) {\n db.prepare(\n `\n INSERT INTO \"_zero.runtimeEvents\" (event) VALUES (?) \n `,\n ).run(event);\n}\n\nexport function getAscendingEvents(db: Database) {\n const result = db\n .prepare(\n `SELECT event, timestamp FROM \"_zero.runtimeEvents\" \n ORDER BY timestamp ASC\n `,\n )\n .all<{event: string; timestamp: string}>();\n return result.map(({event, timestamp}) => ({\n event,\n timestamp: new Date(timestamp + 'Z'),\n }));\n}\n\nexport function getSubscriptionState(db: StatementRunner): SubscriptionState {\n const result = db.get(/*sql*/ `\n SELECT c.replicaVersion, c.publications, s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock\n `);\n return v.parse(result, subscriptionStateSchema);\n}\n\nexport function getSubscriptionStateAndContext(\n db: StatementRunner,\n): SubscriptionStateAndContext {\n const result = db.get(/*sql*/ `\n SELECT c.replicaVersion, c.publications, c.initialSyncContext,\n s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock\n `);\n return v.parse(result, subscriptionStateAndContextSchema);\n}\n\nexport function updateReplicationWatermark(\n db: StatementRunner,\n watermark: string,\n writeTimeMs?: number | undefined,\n) {\n if (writeTimeMs === undefined) {\n db.run(\n /*sql*/ `\n UPDATE \"_zero.replicationState\"\n SET stateVersion=?, writeTimeMs=unixepoch('subsec') * 1000`,\n watermark,\n );\n } else {\n db.run(\n /*sql*/ `\n UPDATE \"_zero.replicationState\"\n SET stateVersion=?, writeTimeMs=?`,\n watermark,\n writeTimeMs,\n );\n }\n}\n\nexport function getReplicationState(db: StatementRunner): ReplicationState {\n const result = db.get(`SELECT stateVersion FROM \"_zero.replicationState\"`);\n return v.parse(result, replicationStateSchema);\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,IAAa,8BAA8B;;;;;;AAO3C,IAAM,kCAYI,qZAqBR,0BACA,8BACA,+BACA,8BACA;AAEF,IAAM,cAAcA,eAAE,MAAMA,eAAE,OAAO,CAAC;AAEtC,IAAM,0BAA0BA,eAC7B,OAAO;CACN,gBAAgBA,eAAE,OAAO;CACzB,cAAcA,eAAE,OAAO;CACvB,WAAWA,eAAE,OAAO;AACtB,CAAC,CAAC,CACD,KAAI,OAAM;CACT,GAAG;CACH,cAAcC,MAAQ,KAAK,MAAM,EAAE,YAAY,GAAG,WAAW;AAC/D,EAAE;AAIJ,IAAM,oCAAoCD,eACvC,OAAO;CACN,gBAAgBA,eAAE,OAAO;CACzB,cAAcA,eAAE,OAAO;CACvB,oBAAoBA,eAAE,OAAO;CAC7B,WAAWA,eAAE,OAAO;AACtB,CAAC,CAAC,CACD,KAAI,OAAM;CACT,GAAG;CACH,cAAcC,MAAQ,KAAK,MAAM,EAAE,YAAY,GAAG,WAAW;CAC7D,oBAAoBA,MAClB,KAAK,MAAM,EAAE,kBAAkB,GAC/B,gBACF;AACF,EAAE;AAMJ,IAAM,yBAAyBD,eAAE,OAAO,EACtC,cAAcA,eAAE,OAAO,EACzB,CAAC;AAID,SAAgB,qBACd,IACA,cACA,WACA,qBAAiC,CAAC,GAClC,eAAe,MACf;CACA,IAAI,cACF,6BAA6B,EAAE;CAEjC,GAAG,QACD;;;KAIF,CAAC,CAAC,IACA,WACA,KAAK,UAAU,aAAa,KAAK,CAAC,GAClC,UAAU,kBAAkB,CAC9B;CACA,GAAG,QAAgB;;;KAGhB,CAAC,CAAC,IAAI,SAAS;CAClB,YAAY,IAAI,MAAM;AACxB;;;;;;AAOA,SAAgB,6BAA6B,IAAc;CACzD,GAAG,KAAK,+BAA+B;AACzC;AAEA,SAAgB,YAAY,IAAc,OAAqB;CAC7D,GAAG,QACD;;KAGF,CAAC,CAAC,IAAI,KAAK;AACb;AAEA,SAAgB,mBAAmB,IAAc;CAQ/C,OAPe,GACZ,QACC;;KAGF,CAAC,CACA,IACI,CAAA,CAAO,KAAK,EAAC,OAAO,iBAAgB;EACzC;EACA,2BAAW,IAAI,KAAK,YAAY,GAAG;CACrC,EAAE;AACJ;AAEA,SAAgB,qBAAqB,IAAwC;CAC3E,MAAM,SAAS,GAAG,IAAY;;;;;KAK3B;CACH,OAAOC,MAAQ,QAAQ,uBAAuB;AAChD;AAEA,SAAgB,+BACd,IAC6B;CAC7B,MAAM,SAAS,GAAG,IAAY;;;;;;KAM3B;CACH,OAAOA,MAAQ,QAAQ,iCAAiC;AAC1D;AAEA,SAAgB,2BACd,IACA,WACA,aACA;CACA,IAAI,gBAAgB,KAAA,GAClB,GAAG,IACO;;qEAGR,SACF;MAEA,GAAG,IACO;;4CAGR,WACA,WACF;AAEJ;AAEA,SAAgB,oBAAoB,IAAuC;CACzE,MAAM,SAAS,GAAG,IAAI,mDAAmD;CACzE,OAAOA,MAAQ,QAAQ,sBAAsB;AAC/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-metadata.js","names":[
|
|
1
|
+
{"version":3,"file":"table-metadata.js","names":[],"sources":["../../../../../../../zero-cache/src/services/replicator/schema/table-metadata.ts"],"sourcesContent":["import type {Database, Statement} from '../../../../../zqlite/src/db.ts';\nimport {liteTableName} from '../../../types/names.ts';\nimport type {\n Identifier,\n TableMetadata,\n} from '../../change-source/protocol/current.ts';\n\n/**\n * Table-level controls for handling replicated data.\n *\n * ### Columns\n *\n * `minRowVersion`: the minimum `_0_version` value to apply to\n * all rows in the table. This overrides any per-row\n * `_0_version` value that is smaller (i.e. earlier).\n * The `minRowVersion` column is used to force a re-download\n * of all rows after a table-wide schema change (by giving\n * each row a version that's newer than what's in any CVR).\n * The naive, brute-force method of updating all of the rows\n * requires re-writing the entire table into the WAL as one\n * SQLite operation, which is too costly from both latency\n * and storage space.\n *\n * `upstreamMetadata`: the replica-level analog of tableMetadata in\n * change-streamer/schema. Per the requirement of the backfill\n * protocol, backfill metadata must be tracked outside of the\n * change source (otherwise the change source would have to be\n * able to compute the state of the metadata at arbitrary points\n * in the past).\n *\n * `metadata`: the previous name of the `upstreamMetadata` column,\n * kept for backwards compatibility.\n *\n * This tracking is done:\n * 1. at the Change DB level, by the change-streamer\n * 2. at the replica level, in order to support the eventual configuration\n * of ephemeral Change DBs (on SQLite) that are initialized from data\n * in the replica.\n */\nexport const CREATE_TABLE_METADATA_TABLE = /*sql*/ `\n CREATE TABLE \"_zero.tableMetadata\" (\n \"schema\" TEXT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"minRowVersion\" TEXT NOT NULL DEFAULT \"00\",\n \"upstreamMetadata\" TEXT,\n \"metadata\" TEXT, -- deprecated\n PRIMARY KEY (\"schema\", \"table\")\n );\n`;\n\nexport class TableMetadataTracker {\n readonly #db: Database;\n\n // All statements are lazily created.\n #setUpstreamMetadata: Statement | undefined;\n #setMinRowVersion: Statement | undefined;\n #getMinRowVersions: Statement | undefined;\n #rename: Statement | undefined;\n #drop: Statement | undefined;\n\n constructor(db: Database) {\n this.#db = db;\n }\n\n setUpstreamMetadata({schema, name}: Identifier, metadata: TableMetadata) {\n (this.#setUpstreamMetadata ??= this.#db.prepare(/*sql*/ `\n INSERT INTO \"_zero.tableMetadata\" (\"schema\", \"table\", \"upstreamMetadata\") \n VALUES (@schema, @name, @metadata)\n ON CONFLICT (\"schema\", \"table\")\n DO UPDATE SET \"upstreamMetadata\" = @metadata\n `)).run({\n schema,\n name,\n metadata: JSON.stringify(metadata),\n });\n }\n\n setMinRowVersion({schema, name}: Identifier, version: string) {\n (this.#setMinRowVersion ??= this.#db.prepare(/*sql*/ `\n INSERT INTO \"_zero.tableMetadata\" (\"schema\", \"table\", \"minRowVersion\") \n VALUES (@schema, @name, @version)\n ON CONFLICT (\"schema\", \"table\")\n DO UPDATE SET \"minRowVersion\" = @version;\n `)).run({schema, name, version});\n }\n\n getMinRowVersions(): Map<string, string> {\n const results = (this.#getMinRowVersions ??= this.#db.prepare(/*sql*/ `\n SELECT \"schema\", \"table\" as \"name\", \"minRowVersion\" FROM \"_zero.tableMetadata\"\n `)).all<{schema: string; name: string; minRowVersion: string}>();\n return new Map(\n results.map(({schema, name, minRowVersion}) => [\n liteTableName({schema, name}),\n minRowVersion,\n ]),\n );\n }\n\n rename(oldTable: Identifier, newTable: Identifier) {\n (this.#rename ??= this.#db.prepare(/*sql*/ `\n UPDATE \"_zero.tableMetadata\" SET \"schema\" = ?, \"table\" = ?\n WHERE \"schema\" = ? AND \"table\" = ?\n `)).run(newTable.schema, newTable.name, oldTable.schema, oldTable.name);\n }\n\n drop({schema, name}: Identifier) {\n (this.#drop ??= this.#db.prepare(/*sql*/ `\n DELETE FROM \"_zero.tableMetadata\" WHERE \"schema\" = ? AND \"table\" = ?\n `)).run(schema, name);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,8BAAsC;;;;;;;;;;AAWnD,IAAa,uBAAb,MAAkC;CAChC;CAGA;CACA;CACA;CACA;CACA;CAEA,YAAY,IAAc;EACxB,KAAK,MAAM;CACb;CAEA,oBAAoB,EAAC,QAAQ,QAAmB,UAAyB;EACvE,CAAC,KAAK,yBAAyB,KAAK,IAAI,QAAgB;;;;;KAKvD,EAAA,CAAG,IAAI;GACN;GACA;GACA,UAAU,KAAK,UAAU,QAAQ;EACnC,CAAC;CACH;CAEA,iBAAiB,EAAC,QAAQ,QAAmB,SAAiB;EAC5D,CAAC,KAAK,sBAAsB,KAAK,IAAI,QAAgB;;;;;KAKpD,EAAA,CAAG,IAAI;GAAC;GAAQ;GAAM;EAAO,CAAC;CACjC;CAEA,oBAAyC;EACvC,MAAM,WAAW,KAAK,uBAAuB,KAAK,IAAI,QAAgB;;KAErE,EAAA,CAAG,IAA2D;EAC/D,OAAO,IAAI,IACT,QAAQ,KAAK,EAAC,QAAQ,MAAM,oBAAmB,CAC7C,cAAc;GAAC;GAAQ;EAAI,CAAC,GAC5B,aACF,CAAC,CACH;CACF;CAEA,OAAO,UAAsB,UAAsB;EACjD,CAAC,KAAK,YAAY,KAAK,IAAI,QAAgB;;;KAG1C,EAAA,CAAG,IAAI,SAAS,QAAQ,SAAS,MAAM,SAAS,QAAQ,SAAS,IAAI;CACxE;CAEA,KAAK,EAAC,QAAQ,QAAmB;EAC/B,CAAC,KAAK,UAAU,KAAK,IAAI,QAAgB;;KAExC,EAAA,CAAG,IAAI,QAAQ,IAAI;CACtB;AACF"}
|
|
@@ -415,6 +415,6 @@ function watermarkDistance(receivedHead, sqliteHead) {
|
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
//#endregion
|
|
418
|
-
export { SQLiteChangeLogObserver, getSQLiteChangeLogInfo, logSQLiteChangeLogStartup, recordSQLiteChangeLogReconcile, sqliteFileBytes };
|
|
418
|
+
export { SQLiteChangeLogObserver, getSQLiteChangeLogInfo, getSQLiteChangeLogStartupInfo, logSQLiteChangeLogStartup, recordSQLiteChangeLogReconcile, sqliteFileBytes };
|
|
419
419
|
|
|
420
420
|
//# sourceMappingURL=sqlite-change-log-observability.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-change-log-observability.js","names":["#lc","#messageProcessing","#logCommitDuration","#transactionRows","#rollbackCounter","#invariantFailureCounter","#hashComparisonCounter","#hashUnpairedCounter","#cookieMutationCounter","#receivedHead","#sqliteHead","#rows","#estimatedBytes","#cookieRows","#sourceHasher","#invariantFailure","#sourcePos","#sourceCommitHash","#resetSourceHash","#transactionWatermark","#recordRollback","#recordUnpaired","#compareHashes","#cookieMutations","#rollbacks","#invariantFailures","#hashMatches","#hashMismatches","#hashUnpaired"],"sources":["../../../../../../zero-cache/src/services/replicator/sqlite-change-log-observability.ts"],"sourcesContent":["/**\n * Observability for the SQLite change log.\n *\n * Everything here now runs in the **change-streamer** process, which is where\n * the writer lives. The module stays beside the rest of the change-log code\n * under `services/replicator/` for the same reason `change-log-db.ts` and\n * `change-log-stream-writer.ts` do: moving files would obscure the topology\n * diff, and the writer's home is a property of who calls it rather than of\n * where it sits. Metric names are unchanged from 7H — only their emitting\n * process, and therefore their resource attributes, moved.\n */\n\nimport {stat} from 'node:fs/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../../shared/src/asserts.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateLatencyHistogram,\n getOrCreateValueHistogram,\n} from '../../observability/metrics.ts';\nimport {versionFromLexi} from '../../types/lexi-version.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport {extractChangeSubstring} from '../change-streamer/change-log-codec.ts';\nimport {ChangeLogTransactionHasher} from '../change-streamer/change-log-transaction-hash.ts';\nimport {\n readCookieRowCounts,\n type CookieOpTag,\n type CookieRowCounts,\n} from './change-log-cookies.ts';\nimport {\n CHANGE_LOG_STREAM_TABLE,\n readChangeLogBounds,\n readChangeLogMeta,\n type ChangeLogMeta,\n type ReconcileResult,\n} from './change-log-db.ts';\nimport type {ChangeLogStreamTransactionStats} from './change-log-stream-writer.ts';\n\n/**\n * Reports the reconciliation of the change-log database against the watermark\n * the stream connection resumes from. This runs once per stream connection, so\n * a reconnect storm is visible here as well as a restart.\n *\n * A wipe is not an error — it is how every abnormal change-log state is\n * collapsed into a correct one — but each wipe costs the retention window, so\n * every reconnecting subscriber below the head gets `too-old`. The `gap` reason\n * in particular means the log neither held the resume watermark nor could be\n * truncated to it, so a nonzero rate in steady state is an alert, not a\n * statistic.\n */\nexport function recordSQLiteChangeLogReconcile(\n lc: LogContext,\n result: ReconcileResult,\n): void {\n switch (result.action) {\n case 'truncated':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_truncated_rows',\n 'Phantom change-log rows removed at reconciliation, i.e. rows above ' +\n 'the watermark the change stream resumed from.',\n ).add(result.rows);\n break;\n case 'reseeded':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_wipes',\n 'Change-log wipes at reconciliation, by reason. Each one resets the ' +\n 'retention window, so subscribers below the head get `too-old`.',\n ).add(1, {reason: result.reason});\n break;\n case 'none':\n break;\n default:\n unreachable(result);\n }\n if (result.cookiesStale) {\n // The invariant-17 counter: after any reconcile that moved the head, the\n // log's cookie set has to be replaced with the one that belongs to the\n // resume watermark. A nonzero `truncated` series in steady state means\n // phantom truncation is happening on a shard where it should not.\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_reseed',\n 'Change-log cookie sets invalidated at reconciliation, by the ' +\n 'reconcile action that invalidated them.',\n ).add(1, {action: result.action});\n }\n lc.debug?.('SQLite change-log reconciliation', {\n sqliteChangeLogReconcile: result,\n });\n}\n\n/**\n * What the log says about itself. Nothing here reads the replica: after the\n * writer moved to the change-streamer, the replica's state version is a\n * consequence of this log rather than a fact about it.\n */\nexport type SQLiteChangeLogStartupInfo = ChangeLogMeta & {\n readonly minWatermark: string;\n readonly headWatermark: string;\n};\n\nexport type SQLiteChangeLogInfo = SQLiteChangeLogStartupInfo & {\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n};\n\n/**\n * Reads the meta row and covered range. Unlike {@link getSQLiteChangeLogInfo}\n * this does not scan the log; see {@link readChangeLogBounds}.\n *\n * Call this after reconciliation, which is the only point at which the head is\n * known to be the resume watermark.\n */\nexport function getSQLiteChangeLogStartupInfo(\n changeLog: Database,\n): SQLiteChangeLogStartupInfo {\n const meta = readChangeLogMeta(changeLog);\n const {minWatermark, headWatermark} = readChangeLogBounds(changeLog);\n if (minWatermark === null || headWatermark === null) {\n throw new Error('the SQLite change log must contain its seed transaction');\n }\n return {...meta, minWatermark, headWatermark};\n}\n\n/**\n * Extends {@link getSQLiteChangeLogStartupInfo} with the retained row and\n * estimated byte totals. Computing these scans the entire change-log table, so\n * only call it when the totals are consumed, i.e. when the writer (and thus the\n * observer) is enabled.\n */\nexport function getSQLiteChangeLogInfo(\n changeLog: Database,\n): SQLiteChangeLogInfo {\n const startup = getSQLiteChangeLogStartupInfo(changeLog);\n const aggregate = changeLog\n .prepare(/*sql*/ `\n SELECT count(*) AS \"rows\",\n coalesce(sum(\"estimatedBytes\"), 0) AS \"estimatedBytes\"\n FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n `)\n .get<{rows: number; estimatedBytes: number}>();\n\n return {\n ...startup,\n rows: aggregate.rows,\n estimatedBytes: aggregate.estimatedBytes,\n cookieRows: readCookieRowCounts(changeLog),\n };\n}\n\nexport function logSQLiteChangeLogStartup(\n lc: LogContext,\n file: string,\n info: SQLiteChangeLogStartupInfo,\n): void {\n lc.info?.('SQLite change-log startup', {\n sqliteChangeLog: {\n file,\n epoch: info.epoch,\n generation: info.generation,\n replicaID: info.replicaID,\n schemaVersion: info.schemaVersion,\n seedWatermark: info.seedWatermark,\n seededAtMs: info.seededAtMs,\n minWatermark: info.minWatermark,\n headWatermark: info.headWatermark,\n },\n });\n}\n\n/**\n * The on-disk footprint of a SQLite database: its main file plus its `-wal` and\n * `-wal2` sidecars, whichever exist. `-shm` is excluded — it is a\n * shared-memory index rather than retained bytes.\n *\n * A missing file counts as zero rather than warning. Which sidecars exist\n * depends on the journal mode, and the change-log database is absent entirely\n * until the writer creates it.\n */\nexport async function sqliteFileBytes(\n lc: LogContext,\n file: string,\n): Promise<number> {\n const sizes = await Promise.all(\n [file, `${file}-wal`, `${file}-wal2`].map(async path => {\n try {\n return (await stat(path)).size;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n lc.warn?.(`unable to stat ${path} for size metrics`, e);\n }\n return 0;\n }\n }),\n );\n return sizes.reduce((total, size) => total + size, 0);\n}\n\n/** What the writer reports back for a committed transaction. */\nexport type ChangeLogCommit = {\n readonly watermark: string;\n readonly stats: ChangeLogStreamTransactionStats;\n /**\n * Appending the commit row and committing the log's transaction. This is the\n * cost that sits on the forward path, once per upstream transaction.\n */\n readonly logCommitMs: number;\n};\n\nexport type SQLiteChangeLogObservabilityState = {\n readonly receivedHead: string;\n readonly sqliteHead: string;\n readonly headLag: number | undefined;\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n readonly cookieMutations: number;\n readonly rollbacks: number;\n readonly invariantFailures: number;\n readonly hashMatches: number;\n readonly hashMismatches: number;\n readonly hashUnpaired: number;\n};\n\nconst TRANSACTION_ROW_BUCKETS = [2, 3, 5, 10, 25, 50, 100, 250, 1000, 5000];\n\n/**\n * Records writer metrics in the change-streamer process. State changes only\n * after the corresponding write succeeds, except receivedHead, which advances\n * when a commit message arrives so that transient lag behind the log is\n * observable.\n */\nexport class SQLiteChangeLogObserver {\n readonly #lc: LogContext;\n readonly #messageProcessing = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.message_processing_duration',\n 'Sampled time to write a replication message to the SQLite change log.',\n );\n // The commit that sits on the forward path. There is no second commit to\n // order against any more: the replica's belongs to a subscriber downstream of\n // this loop, so `sqlite_change_log.replica_commit_duration` and the phantom\n // window it measured are gone. Sampled failed commits are still timed by\n // `message_processing_duration` keyed `tag=\"commit\"`.\n readonly #logCommitDuration = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.log_commit_duration',\n 'Time to append the commit row to the SQLite change log and commit its ' +\n 'transaction. This is on the forward path: it precedes the forward of ' +\n \"that transaction's commit message.\",\n );\n readonly #transactionRows = getOrCreateValueHistogram(\n 'replica',\n 'sqlite_change_log.transaction_rows',\n {\n description: 'Rows stored per committed SQLite change-log transaction.',\n unit: '{row}',\n bucketBoundaries: TRANSACTION_ROW_BUCKETS,\n },\n );\n readonly #rollbackCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.rollbacks',\n 'SQLite change-log transactions rolled back before commit.',\n );\n readonly #invariantFailureCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.invariant_failures',\n 'Detected SQLite change-log writer invariant failures.',\n );\n readonly #hashComparisonCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_comparisons',\n 'Sampled ephemeral transaction hash comparisons between the received change stream and SQLite change-log writes.',\n );\n readonly #hashUnpairedCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_unpaired',\n 'Committed change-log transactions without both ephemeral hashes available for comparison.',\n );\n // Near zero at steady state: cookie state only moves on schema changes.\n readonly #cookieMutationCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_mutations',\n 'Change-log cookie mutations folded from schema changes, by operation.',\n );\n\n #receivedHead: string;\n #sqliteHead: string;\n #rows: number;\n #estimatedBytes: number;\n #cookieRows: CookieRowCounts;\n #cookieMutations = 0;\n #transactionWatermark: string | undefined;\n #sourceHasher: ChangeLogTransactionHasher | undefined;\n #sourcePos = 0;\n #sourceCommitHash: string | undefined;\n #rollbacks = 0;\n #invariantFailures = 0;\n #hashMatches = 0;\n #hashMismatches = 0;\n #hashUnpaired = 0;\n\n constructor(lc: LogContext, info: SQLiteChangeLogInfo) {\n this.#lc = lc.withContext('component', 'sqlite-change-log-observer');\n // Reconciliation has just put the head at the resume watermark, which is\n // also the point the stream is about to be received from.\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.rows',\n 'Rows retained in the SQLite change log.',\n ).addCallback(result => result.observe(this.#rows));\n // Nonzero with no backfill in flight is a leaked cookie, i.e. a backfill\n // that will be re-requested forever.\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.cookie_rows',\n 'Cookie rows retained in the SQLite change log, by cookie table.',\n ).addCallback(result => {\n result.observe(this.#cookieRows.tableMetadata, {table: 'tableMetadata'});\n result.observe(this.#cookieRows.backfilling, {table: 'backfilling'});\n });\n getOrCreateGauge('replica', 'sqlite_change_log.retained_bytes', {\n description:\n 'Estimated UTF-8 payload bytes retained in the SQLite change log.',\n unit: 'By',\n }).addCallback(result => result.observe(this.#estimatedBytes));\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head',\n 'SQLite change-log head converted from its lexicographic watermark.',\n ).addCallback(result => {\n const head = watermarkValue(this.#sqliteHead);\n if (head !== undefined) {\n result.observe(head);\n }\n });\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head_lag',\n 'Distance from the latest received commit to the SQLite change-log ' +\n 'head. The log commits before the forward, so this is zero except ' +\n 'within a transaction.',\n ).addCallback(result => {\n const lag = watermarkDistance(this.#receivedHead, this.#sqliteHead);\n if (lag !== undefined) {\n result.observe(lag);\n }\n });\n }\n\n /**\n * Called as each message arrives, before it is written. For a sampled\n * transaction, hashes the *received* stream and compares it with the hash the\n * writer computes over what it *stored*.\n */\n messageReceived(\n data: ChangeStreamData,\n json: string,\n validateHash = true,\n storedChange = extractChangeSubstring(json, data[1].tag),\n ): void {\n const tag = data[1].tag;\n if (data[0] === 'begin') {\n if (this.#sourceHasher !== undefined) {\n this.#invariantFailure(\n 'Received change-log begin while a source hash is already open',\n {receivedWatermark: data[2].commitWatermark},\n );\n }\n const watermark = data[2].commitWatermark;\n this.#sourceHasher = validateHash\n ? new ChangeLogTransactionHasher()\n : undefined;\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n this.#sourceHasher?.add({\n watermark,\n pos: this.#sourcePos,\n tag,\n change: storedChange,\n precommit: null,\n });\n return;\n }\n\n if (tag === 'rollback') {\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] === 'commit') {\n this.#receivedHead = data[2].watermark;\n }\n\n if (!validateHash) {\n return;\n }\n\n const watermark = this.#transactionWatermark;\n if (this.#sourceHasher === undefined || watermark === undefined) {\n this.#invariantFailure(\n 'Received change-log message without an observed transaction',\n {tag},\n );\n return;\n }\n\n const commitWatermark =\n data[0] === 'commit' ? data[2].watermark : watermark;\n this.#sourceHasher.add({\n watermark: commitWatermark,\n pos: ++this.#sourcePos,\n tag,\n change: storedChange,\n precommit: data[0] === 'commit' ? watermark : null,\n });\n if (data[0] === 'commit') {\n this.#sourceCommitHash = this.#sourceHasher.digest();\n this.#sourceHasher = undefined;\n }\n }\n\n /** Called after the message has been written to the log. */\n messageProcessed(\n data: ChangeStreamData,\n commit: ChangeLogCommit | null,\n durationMs: number | undefined,\n ): void {\n const tag = data[1].tag;\n if (durationMs !== undefined) {\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'success'});\n }\n\n if (data[0] === 'begin') {\n if (this.#transactionWatermark !== undefined) {\n this.#invariantFailure('SQLite change-log transaction already open', {\n openWatermark: this.#transactionWatermark,\n receivedWatermark: data[2].commitWatermark,\n });\n }\n this.#transactionWatermark = data[2].commitWatermark;\n return;\n }\n\n if (tag === 'rollback') {\n this.#recordRollback('upstream');\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] !== 'commit') {\n return;\n }\n\n const watermark = data[2].watermark;\n if (commit !== null) {\n this.#logCommitDuration.recordMs(commit.logCommitMs);\n }\n if (this.#transactionWatermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit does not match the observed begin',\n {openWatermark: this.#transactionWatermark, commitWatermark: watermark},\n );\n }\n if (commit === null) {\n this.#invariantFailure(\n 'SQLite change-log commit result is missing writer statistics',\n {commitWatermark: watermark},\n );\n this.#recordUnpaired(watermark, 'missing-sqlite-hash');\n } else {\n if (commit.watermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit result has an unexpected watermark',\n {commitWatermark: watermark, resultWatermark: commit.watermark},\n );\n }\n this.#rows += commit.stats.rows;\n this.#estimatedBytes += commit.stats.estimatedBytes;\n this.#transactionRows.record(commit.stats.rows);\n if (\n this.#sourceCommitHash !== undefined ||\n commit.stats.hash !== undefined\n ) {\n this.#compareHashes(\n watermark,\n this.#sourceCommitHash,\n commit.stats.hash,\n );\n }\n }\n if (watermark < this.#sqliteHead) {\n this.#invariantFailure('SQLite change-log head regressed', {\n previousHead: this.#sqliteHead,\n commitWatermark: watermark,\n });\n }\n this.#sqliteHead = watermark;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n messageFailed(\n data: ChangeStreamData,\n error: unknown,\n durationMs: number | undefined,\n ): void {\n const tag = data[1].tag;\n if (durationMs !== undefined) {\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'error'});\n }\n if (data[0] === 'commit' && this.#sourceCommitHash !== undefined) {\n // No commit duration here: a failed commit produces no statistics, so\n // there is nothing to attribute. The attempt is timed by\n // `message_processing_duration{tag=\"commit\",outcome=\"error\"}` above.\n this.#recordUnpaired(data[2].watermark, 'sqlite-commit-failed');\n }\n if (\n error instanceof Error &&\n error.name === 'ChangeLogStreamInvariantError'\n ) {\n this.#invariantFailure(error.message);\n }\n if (this.#transactionWatermark !== undefined || data[0] === 'begin') {\n this.#recordRollback('processing-error');\n }\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n /**\n * A committed transaction folded schema changes into the cookie jar. Reported\n * separately from {@link messageProcessed} because it costs a read of the\n * cookie tables, which is only worth taking when they actually moved.\n */\n cookiesMutated(ops: readonly CookieOpTag[], rows: CookieRowCounts): void {\n for (const op of ops) {\n this.#cookieMutations++;\n this.#cookieMutationCounter.add(1, {op});\n }\n this.#cookieRows = rows;\n }\n\n /**\n * A missed truncate-above surfaces as a constraint violation on the writer's\n * plain `INSERT`, which the fail-soft policy would otherwise absorb quietly.\n * Classifying it as an invariant failure is what keeps a reconciliation bug\n * from looking like a lost cache.\n */\n invariantFailure(message: string, details?: Record<string, unknown>): void {\n this.#invariantFailure(message, details);\n }\n\n /** The source interrupted the stream mid-transaction. */\n abort(): void {\n if (this.#transactionWatermark !== undefined) {\n this.#recordRollback('source-interruption');\n this.#transactionWatermark = undefined;\n }\n this.#resetSourceHash();\n }\n\n /** Reconciliation moved the head, so the observer's totals are restated. */\n reconciled(info: SQLiteChangeLogInfo): void {\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n state(): SQLiteChangeLogObservabilityState {\n return {\n receivedHead: this.#receivedHead,\n sqliteHead: this.#sqliteHead,\n headLag: watermarkDistance(this.#receivedHead, this.#sqliteHead),\n rows: this.#rows,\n estimatedBytes: this.#estimatedBytes,\n cookieRows: this.#cookieRows,\n cookieMutations: this.#cookieMutations,\n rollbacks: this.#rollbacks,\n invariantFailures: this.#invariantFailures,\n hashMatches: this.#hashMatches,\n hashMismatches: this.#hashMismatches,\n hashUnpaired: this.#hashUnpaired,\n };\n }\n\n #recordRollback(\n reason: 'upstream' | 'processing-error' | 'source-interruption',\n ) {\n this.#rollbacks++;\n this.#rollbackCounter.add(1, {reason});\n }\n\n #invariantFailure(message: string, details?: Record<string, unknown>) {\n this.#invariantFailures++;\n this.#invariantFailureCounter.add(1);\n this.#lc.error?.(message, details);\n }\n\n #compareHashes(\n watermark: string,\n sourceHash: string | undefined,\n sqliteHash: string | undefined,\n ): void {\n if (sourceHash === undefined) {\n this.#recordUnpaired(watermark, 'missing-source-hash');\n return;\n }\n if (sqliteHash === undefined) {\n this.#recordUnpaired(watermark, 'missing-sqlite-hash');\n return;\n }\n if (sourceHash === sqliteHash) {\n this.#hashMatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'match'});\n return;\n }\n this.#hashMismatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'mismatch'});\n this.#lc.error?.('SQLite change-log transaction hash mismatch', {\n sqliteChangeLogHashComparison: {\n watermark,\n sourceHash: sourceHash.slice(0, 16),\n sqliteHash: sqliteHash.slice(0, 16),\n },\n });\n }\n\n #recordUnpaired(\n watermark: string,\n reason:\n | 'missing-source-hash'\n | 'missing-sqlite-hash'\n | 'sqlite-commit-failed',\n ): void {\n this.#hashUnpaired++;\n this.#hashUnpairedCounter.add(1, {reason});\n this.#lc.warn?.('SQLite change-log transaction hash was not compared', {\n sqliteChangeLogHashComparison: {watermark, reason},\n });\n }\n\n #resetSourceHash(): void {\n this.#sourceHasher = undefined;\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n }\n}\n\nfunction watermarkValue(watermark: string): number | undefined {\n try {\n const value = Number(versionFromLexi(watermark));\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction watermarkDistance(\n receivedHead: string,\n sqliteHead: string,\n): number | undefined {\n if (receivedHead <= sqliteHead) {\n return 0;\n }\n try {\n const distance =\n versionFromLexi(receivedHead) - versionFromLexi(sqliteHead);\n const value = Number(distance);\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite-change-log-observability.js","names":[],"sources":["../../../../../../zero-cache/src/services/replicator/sqlite-change-log-observability.ts"],"sourcesContent":["/**\n * Observability for the SQLite change log.\n *\n * Everything here now runs in the **change-streamer** process, which is where\n * the writer lives. The module stays beside the rest of the change-log code\n * under `services/replicator/` for the same reason `change-log-db.ts` and\n * `change-log-stream-writer.ts` do: moving files would obscure the topology\n * diff, and the writer's home is a property of who calls it rather than of\n * where it sits. Metric names are unchanged from 7H — only their emitting\n * process, and therefore their resource attributes, moved.\n */\n\nimport {stat} from 'node:fs/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../../shared/src/asserts.ts';\nimport type {Database} from '../../../../zqlite/src/db.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateLatencyHistogram,\n getOrCreateValueHistogram,\n} from '../../observability/metrics.ts';\nimport {versionFromLexi} from '../../types/lexi-version.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport {extractChangeSubstring} from '../change-streamer/change-log-codec.ts';\nimport {ChangeLogTransactionHasher} from '../change-streamer/change-log-transaction-hash.ts';\nimport {\n readCookieRowCounts,\n type CookieOpTag,\n type CookieRowCounts,\n} from './change-log-cookies.ts';\nimport {\n CHANGE_LOG_STREAM_TABLE,\n readChangeLogBounds,\n readChangeLogMeta,\n type ChangeLogMeta,\n type ReconcileResult,\n} from './change-log-db.ts';\nimport type {ChangeLogStreamTransactionStats} from './change-log-stream-writer.ts';\n\n/**\n * Reports the reconciliation of the change-log database against the watermark\n * the stream connection resumes from. This runs once per stream connection, so\n * a reconnect storm is visible here as well as a restart.\n *\n * A wipe is not an error — it is how every abnormal change-log state is\n * collapsed into a correct one — but each wipe costs the retention window, so\n * every reconnecting subscriber below the head gets `too-old`. The `gap` reason\n * in particular means the log neither held the resume watermark nor could be\n * truncated to it, so a nonzero rate in steady state is an alert, not a\n * statistic.\n */\nexport function recordSQLiteChangeLogReconcile(\n lc: LogContext,\n result: ReconcileResult,\n): void {\n switch (result.action) {\n case 'truncated':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_truncated_rows',\n 'Phantom change-log rows removed at reconciliation, i.e. rows above ' +\n 'the watermark the change stream resumed from.',\n ).add(result.rows);\n break;\n case 'reseeded':\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.reconcile_wipes',\n 'Change-log wipes at reconciliation, by reason. Each one resets the ' +\n 'retention window, so subscribers below the head get `too-old`.',\n ).add(1, {reason: result.reason});\n break;\n case 'none':\n break;\n default:\n unreachable(result);\n }\n if (result.cookiesStale) {\n // The invariant-17 counter: after any reconcile that moved the head, the\n // log's cookie set has to be replaced with the one that belongs to the\n // resume watermark. A nonzero `truncated` series in steady state means\n // phantom truncation is happening on a shard where it should not.\n getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_reseed',\n 'Change-log cookie sets invalidated at reconciliation, by the ' +\n 'reconcile action that invalidated them.',\n ).add(1, {action: result.action});\n }\n lc.debug?.('SQLite change-log reconciliation', {\n sqliteChangeLogReconcile: result,\n });\n}\n\n/**\n * What the log says about itself. Nothing here reads the replica: after the\n * writer moved to the change-streamer, the replica's state version is a\n * consequence of this log rather than a fact about it.\n */\nexport type SQLiteChangeLogStartupInfo = ChangeLogMeta & {\n readonly minWatermark: string;\n readonly headWatermark: string;\n};\n\nexport type SQLiteChangeLogInfo = SQLiteChangeLogStartupInfo & {\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n};\n\n/**\n * Reads the meta row and covered range. Unlike {@link getSQLiteChangeLogInfo}\n * this does not scan the log; see {@link readChangeLogBounds}.\n *\n * Call this after reconciliation, which is the only point at which the head is\n * known to be the resume watermark.\n */\nexport function getSQLiteChangeLogStartupInfo(\n changeLog: Database,\n): SQLiteChangeLogStartupInfo {\n const meta = readChangeLogMeta(changeLog);\n const {minWatermark, headWatermark} = readChangeLogBounds(changeLog);\n if (minWatermark === null || headWatermark === null) {\n throw new Error('the SQLite change log must contain its seed transaction');\n }\n return {...meta, minWatermark, headWatermark};\n}\n\n/**\n * Extends {@link getSQLiteChangeLogStartupInfo} with the retained row and\n * estimated byte totals. Computing these scans the entire change-log table, so\n * only call it when the totals are consumed, i.e. when the writer (and thus the\n * observer) is enabled.\n */\nexport function getSQLiteChangeLogInfo(\n changeLog: Database,\n): SQLiteChangeLogInfo {\n const startup = getSQLiteChangeLogStartupInfo(changeLog);\n const aggregate = changeLog\n .prepare(/*sql*/ `\n SELECT count(*) AS \"rows\",\n coalesce(sum(\"estimatedBytes\"), 0) AS \"estimatedBytes\"\n FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n `)\n .get<{rows: number; estimatedBytes: number}>();\n\n return {\n ...startup,\n rows: aggregate.rows,\n estimatedBytes: aggregate.estimatedBytes,\n cookieRows: readCookieRowCounts(changeLog),\n };\n}\n\nexport function logSQLiteChangeLogStartup(\n lc: LogContext,\n file: string,\n info: SQLiteChangeLogStartupInfo,\n): void {\n lc.info?.('SQLite change-log startup', {\n sqliteChangeLog: {\n file,\n epoch: info.epoch,\n generation: info.generation,\n replicaID: info.replicaID,\n schemaVersion: info.schemaVersion,\n seedWatermark: info.seedWatermark,\n seededAtMs: info.seededAtMs,\n minWatermark: info.minWatermark,\n headWatermark: info.headWatermark,\n },\n });\n}\n\n/**\n * The on-disk footprint of a SQLite database: its main file plus its `-wal` and\n * `-wal2` sidecars, whichever exist. `-shm` is excluded — it is a\n * shared-memory index rather than retained bytes.\n *\n * A missing file counts as zero rather than warning. Which sidecars exist\n * depends on the journal mode, and the change-log database is absent entirely\n * until the writer creates it.\n */\nexport async function sqliteFileBytes(\n lc: LogContext,\n file: string,\n): Promise<number> {\n const sizes = await Promise.all(\n [file, `${file}-wal`, `${file}-wal2`].map(async path => {\n try {\n return (await stat(path)).size;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n lc.warn?.(`unable to stat ${path} for size metrics`, e);\n }\n return 0;\n }\n }),\n );\n return sizes.reduce((total, size) => total + size, 0);\n}\n\n/** What the writer reports back for a committed transaction. */\nexport type ChangeLogCommit = {\n readonly watermark: string;\n readonly stats: ChangeLogStreamTransactionStats;\n /**\n * Appending the commit row and committing the log's transaction. This is the\n * cost that sits on the forward path, once per upstream transaction.\n */\n readonly logCommitMs: number;\n};\n\nexport type SQLiteChangeLogObservabilityState = {\n readonly receivedHead: string;\n readonly sqliteHead: string;\n readonly headLag: number | undefined;\n readonly rows: number;\n readonly estimatedBytes: number;\n readonly cookieRows: CookieRowCounts;\n readonly cookieMutations: number;\n readonly rollbacks: number;\n readonly invariantFailures: number;\n readonly hashMatches: number;\n readonly hashMismatches: number;\n readonly hashUnpaired: number;\n};\n\nconst TRANSACTION_ROW_BUCKETS = [2, 3, 5, 10, 25, 50, 100, 250, 1000, 5000];\n\n/**\n * Records writer metrics in the change-streamer process. State changes only\n * after the corresponding write succeeds, except receivedHead, which advances\n * when a commit message arrives so that transient lag behind the log is\n * observable.\n */\nexport class SQLiteChangeLogObserver {\n readonly #lc: LogContext;\n readonly #messageProcessing = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.message_processing_duration',\n 'Sampled time to write a replication message to the SQLite change log.',\n );\n // The commit that sits on the forward path. There is no second commit to\n // order against any more: the replica's belongs to a subscriber downstream of\n // this loop, so `sqlite_change_log.replica_commit_duration` and the phantom\n // window it measured are gone. Sampled failed commits are still timed by\n // `message_processing_duration` keyed `tag=\"commit\"`.\n readonly #logCommitDuration = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.log_commit_duration',\n 'Time to append the commit row to the SQLite change log and commit its ' +\n 'transaction. This is on the forward path: it precedes the forward of ' +\n \"that transaction's commit message.\",\n );\n readonly #transactionRows = getOrCreateValueHistogram(\n 'replica',\n 'sqlite_change_log.transaction_rows',\n {\n description: 'Rows stored per committed SQLite change-log transaction.',\n unit: '{row}',\n bucketBoundaries: TRANSACTION_ROW_BUCKETS,\n },\n );\n readonly #rollbackCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.rollbacks',\n 'SQLite change-log transactions rolled back before commit.',\n );\n readonly #invariantFailureCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.invariant_failures',\n 'Detected SQLite change-log writer invariant failures.',\n );\n readonly #hashComparisonCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_comparisons',\n 'Sampled ephemeral transaction hash comparisons between the received change stream and SQLite change-log writes.',\n );\n readonly #hashUnpairedCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.hash_unpaired',\n 'Committed change-log transactions without both ephemeral hashes available for comparison.',\n );\n // Near zero at steady state: cookie state only moves on schema changes.\n readonly #cookieMutationCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.cookie_mutations',\n 'Change-log cookie mutations folded from schema changes, by operation.',\n );\n\n #receivedHead: string;\n #sqliteHead: string;\n #rows: number;\n #estimatedBytes: number;\n #cookieRows: CookieRowCounts;\n #cookieMutations = 0;\n #transactionWatermark: string | undefined;\n #sourceHasher: ChangeLogTransactionHasher | undefined;\n #sourcePos = 0;\n #sourceCommitHash: string | undefined;\n #rollbacks = 0;\n #invariantFailures = 0;\n #hashMatches = 0;\n #hashMismatches = 0;\n #hashUnpaired = 0;\n\n constructor(lc: LogContext, info: SQLiteChangeLogInfo) {\n this.#lc = lc.withContext('component', 'sqlite-change-log-observer');\n // Reconciliation has just put the head at the resume watermark, which is\n // also the point the stream is about to be received from.\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.rows',\n 'Rows retained in the SQLite change log.',\n ).addCallback(result => result.observe(this.#rows));\n // Nonzero with no backfill in flight is a leaked cookie, i.e. a backfill\n // that will be re-requested forever.\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.cookie_rows',\n 'Cookie rows retained in the SQLite change log, by cookie table.',\n ).addCallback(result => {\n result.observe(this.#cookieRows.tableMetadata, {table: 'tableMetadata'});\n result.observe(this.#cookieRows.backfilling, {table: 'backfilling'});\n });\n getOrCreateGauge('replica', 'sqlite_change_log.retained_bytes', {\n description:\n 'Estimated UTF-8 payload bytes retained in the SQLite change log.',\n unit: 'By',\n }).addCallback(result => result.observe(this.#estimatedBytes));\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head',\n 'SQLite change-log head converted from its lexicographic watermark.',\n ).addCallback(result => {\n const head = watermarkValue(this.#sqliteHead);\n if (head !== undefined) {\n result.observe(head);\n }\n });\n getOrCreateGauge(\n 'replica',\n 'sqlite_change_log.head_lag',\n 'Distance from the latest received commit to the SQLite change-log ' +\n 'head. The log commits before the forward, so this is zero except ' +\n 'within a transaction.',\n ).addCallback(result => {\n const lag = watermarkDistance(this.#receivedHead, this.#sqliteHead);\n if (lag !== undefined) {\n result.observe(lag);\n }\n });\n }\n\n /**\n * Called as each message arrives, before it is written. For a sampled\n * transaction, hashes the *received* stream and compares it with the hash the\n * writer computes over what it *stored*.\n */\n messageReceived(\n data: ChangeStreamData,\n json: string,\n validateHash = true,\n storedChange = extractChangeSubstring(json, data[1].tag),\n ): void {\n const tag = data[1].tag;\n if (data[0] === 'begin') {\n if (this.#sourceHasher !== undefined) {\n this.#invariantFailure(\n 'Received change-log begin while a source hash is already open',\n {receivedWatermark: data[2].commitWatermark},\n );\n }\n const watermark = data[2].commitWatermark;\n this.#sourceHasher = validateHash\n ? new ChangeLogTransactionHasher()\n : undefined;\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n this.#sourceHasher?.add({\n watermark,\n pos: this.#sourcePos,\n tag,\n change: storedChange,\n precommit: null,\n });\n return;\n }\n\n if (tag === 'rollback') {\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] === 'commit') {\n this.#receivedHead = data[2].watermark;\n }\n\n if (!validateHash) {\n return;\n }\n\n const watermark = this.#transactionWatermark;\n if (this.#sourceHasher === undefined || watermark === undefined) {\n this.#invariantFailure(\n 'Received change-log message without an observed transaction',\n {tag},\n );\n return;\n }\n\n const commitWatermark =\n data[0] === 'commit' ? data[2].watermark : watermark;\n this.#sourceHasher.add({\n watermark: commitWatermark,\n pos: ++this.#sourcePos,\n tag,\n change: storedChange,\n precommit: data[0] === 'commit' ? watermark : null,\n });\n if (data[0] === 'commit') {\n this.#sourceCommitHash = this.#sourceHasher.digest();\n this.#sourceHasher = undefined;\n }\n }\n\n /** Called after the message has been written to the log. */\n messageProcessed(\n data: ChangeStreamData,\n commit: ChangeLogCommit | null,\n durationMs: number | undefined,\n ): void {\n const tag = data[1].tag;\n if (durationMs !== undefined) {\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'success'});\n }\n\n if (data[0] === 'begin') {\n if (this.#transactionWatermark !== undefined) {\n this.#invariantFailure('SQLite change-log transaction already open', {\n openWatermark: this.#transactionWatermark,\n receivedWatermark: data[2].commitWatermark,\n });\n }\n this.#transactionWatermark = data[2].commitWatermark;\n return;\n }\n\n if (tag === 'rollback') {\n this.#recordRollback('upstream');\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n return;\n }\n\n if (data[0] !== 'commit') {\n return;\n }\n\n const watermark = data[2].watermark;\n if (commit !== null) {\n this.#logCommitDuration.recordMs(commit.logCommitMs);\n }\n if (this.#transactionWatermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit does not match the observed begin',\n {openWatermark: this.#transactionWatermark, commitWatermark: watermark},\n );\n }\n if (commit === null) {\n this.#invariantFailure(\n 'SQLite change-log commit result is missing writer statistics',\n {commitWatermark: watermark},\n );\n this.#recordUnpaired(watermark, 'missing-sqlite-hash');\n } else {\n if (commit.watermark !== watermark) {\n this.#invariantFailure(\n 'SQLite change-log commit result has an unexpected watermark',\n {commitWatermark: watermark, resultWatermark: commit.watermark},\n );\n }\n this.#rows += commit.stats.rows;\n this.#estimatedBytes += commit.stats.estimatedBytes;\n this.#transactionRows.record(commit.stats.rows);\n if (\n this.#sourceCommitHash !== undefined ||\n commit.stats.hash !== undefined\n ) {\n this.#compareHashes(\n watermark,\n this.#sourceCommitHash,\n commit.stats.hash,\n );\n }\n }\n if (watermark < this.#sqliteHead) {\n this.#invariantFailure('SQLite change-log head regressed', {\n previousHead: this.#sqliteHead,\n commitWatermark: watermark,\n });\n }\n this.#sqliteHead = watermark;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n messageFailed(\n data: ChangeStreamData,\n error: unknown,\n durationMs: number | undefined,\n ): void {\n const tag = data[1].tag;\n if (durationMs !== undefined) {\n this.#messageProcessing.recordMs(durationMs, {tag, outcome: 'error'});\n }\n if (data[0] === 'commit' && this.#sourceCommitHash !== undefined) {\n // No commit duration here: a failed commit produces no statistics, so\n // there is nothing to attribute. The attempt is timed by\n // `message_processing_duration{tag=\"commit\",outcome=\"error\"}` above.\n this.#recordUnpaired(data[2].watermark, 'sqlite-commit-failed');\n }\n if (\n error instanceof Error &&\n error.name === 'ChangeLogStreamInvariantError'\n ) {\n this.#invariantFailure(error.message);\n }\n if (this.#transactionWatermark !== undefined || data[0] === 'begin') {\n this.#recordRollback('processing-error');\n }\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n /**\n * A committed transaction folded schema changes into the cookie jar. Reported\n * separately from {@link messageProcessed} because it costs a read of the\n * cookie tables, which is only worth taking when they actually moved.\n */\n cookiesMutated(ops: readonly CookieOpTag[], rows: CookieRowCounts): void {\n for (const op of ops) {\n this.#cookieMutations++;\n this.#cookieMutationCounter.add(1, {op});\n }\n this.#cookieRows = rows;\n }\n\n /**\n * A missed truncate-above surfaces as a constraint violation on the writer's\n * plain `INSERT`, which the fail-soft policy would otherwise absorb quietly.\n * Classifying it as an invariant failure is what keeps a reconciliation bug\n * from looking like a lost cache.\n */\n invariantFailure(message: string, details?: Record<string, unknown>): void {\n this.#invariantFailure(message, details);\n }\n\n /** The source interrupted the stream mid-transaction. */\n abort(): void {\n if (this.#transactionWatermark !== undefined) {\n this.#recordRollback('source-interruption');\n this.#transactionWatermark = undefined;\n }\n this.#resetSourceHash();\n }\n\n /** Reconciliation moved the head, so the observer's totals are restated. */\n reconciled(info: SQLiteChangeLogInfo): void {\n this.#receivedHead = info.headWatermark;\n this.#sqliteHead = info.headWatermark;\n this.#rows = info.rows;\n this.#estimatedBytes = info.estimatedBytes;\n this.#cookieRows = info.cookieRows;\n this.#transactionWatermark = undefined;\n this.#resetSourceHash();\n }\n\n state(): SQLiteChangeLogObservabilityState {\n return {\n receivedHead: this.#receivedHead,\n sqliteHead: this.#sqliteHead,\n headLag: watermarkDistance(this.#receivedHead, this.#sqliteHead),\n rows: this.#rows,\n estimatedBytes: this.#estimatedBytes,\n cookieRows: this.#cookieRows,\n cookieMutations: this.#cookieMutations,\n rollbacks: this.#rollbacks,\n invariantFailures: this.#invariantFailures,\n hashMatches: this.#hashMatches,\n hashMismatches: this.#hashMismatches,\n hashUnpaired: this.#hashUnpaired,\n };\n }\n\n #recordRollback(\n reason: 'upstream' | 'processing-error' | 'source-interruption',\n ) {\n this.#rollbacks++;\n this.#rollbackCounter.add(1, {reason});\n }\n\n #invariantFailure(message: string, details?: Record<string, unknown>) {\n this.#invariantFailures++;\n this.#invariantFailureCounter.add(1);\n this.#lc.error?.(message, details);\n }\n\n #compareHashes(\n watermark: string,\n sourceHash: string | undefined,\n sqliteHash: string | undefined,\n ): void {\n if (sourceHash === undefined) {\n this.#recordUnpaired(watermark, 'missing-source-hash');\n return;\n }\n if (sqliteHash === undefined) {\n this.#recordUnpaired(watermark, 'missing-sqlite-hash');\n return;\n }\n if (sourceHash === sqliteHash) {\n this.#hashMatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'match'});\n return;\n }\n this.#hashMismatches++;\n this.#hashComparisonCounter.add(1, {outcome: 'mismatch'});\n this.#lc.error?.('SQLite change-log transaction hash mismatch', {\n sqliteChangeLogHashComparison: {\n watermark,\n sourceHash: sourceHash.slice(0, 16),\n sqliteHash: sqliteHash.slice(0, 16),\n },\n });\n }\n\n #recordUnpaired(\n watermark: string,\n reason:\n | 'missing-source-hash'\n | 'missing-sqlite-hash'\n | 'sqlite-commit-failed',\n ): void {\n this.#hashUnpaired++;\n this.#hashUnpairedCounter.add(1, {reason});\n this.#lc.warn?.('SQLite change-log transaction hash was not compared', {\n sqliteChangeLogHashComparison: {watermark, reason},\n });\n }\n\n #resetSourceHash(): void {\n this.#sourceHasher = undefined;\n this.#sourcePos = 0;\n this.#sourceCommitHash = undefined;\n }\n}\n\nfunction watermarkValue(watermark: string): number | undefined {\n try {\n const value = Number(versionFromLexi(watermark));\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction watermarkDistance(\n receivedHead: string,\n sqliteHead: string,\n): number | undefined {\n if (receivedHead <= sqliteHead) {\n return 0;\n }\n try {\n const distance =\n versionFromLexi(receivedHead) - versionFromLexi(sqliteHead);\n const value = Number(distance);\n return Number.isFinite(value) ? value : undefined;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
|
|
@@ -278,6 +278,6 @@ var SQLiteChangeLogPurger = class {
|
|
|
278
278
|
}
|
|
279
279
|
};
|
|
280
280
|
//#endregion
|
|
281
|
-
export { SQLiteChangeLogPurger };
|
|
281
|
+
export { SQLITE_CHANGE_LOG_COUNT_TRANSACTION_SQL, SQLITE_CHANGE_LOG_ELIGIBLE_COMMITS_SQL, SQLiteChangeLogPurger };
|
|
282
282
|
|
|
283
283
|
//# sourceMappingURL=sqlite-change-log-purger.js.map
|