@rocicorp/zero 1.10.0-canary.0 → 1.10.0-canary.10
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/replicache/src/db/commit.js +1 -4
- package/out/replicache/src/db/commit.js.map +1 -1
- package/out/replicache/src/db/read.d.ts +1 -0
- package/out/replicache/src/db/read.d.ts.map +1 -1
- package/out/replicache/src/db/read.js +1 -4
- package/out/replicache/src/db/read.js.map +1 -1
- package/out/replicache/src/db/write.d.ts +2 -1
- package/out/replicache/src/db/write.d.ts.map +1 -1
- package/out/replicache/src/db/write.js +4 -51
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/persist/clients.d.ts +1 -0
- package/out/replicache/src/persist/clients.d.ts.map +1 -1
- package/out/replicache/src/persist/clients.js +4 -31
- package/out/replicache/src/persist/clients.js.map +1 -1
- package/out/replicache/src/process-env.d.ts +28 -0
- package/out/replicache/src/process-env.d.ts.map +1 -0
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/scan-iterator.js +2 -14
- package/out/replicache/src/scan-iterator.js.map +1 -1
- package/out/replicache/src/subscriptions.d.ts +1 -0
- package/out/replicache/src/subscriptions.d.ts.map +1 -1
- package/out/replicache/src/subscriptions.js +2 -10
- package/out/replicache/src/subscriptions.js.map +1 -1
- package/out/replicache/src/transactions.d.ts +1 -0
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +2 -4
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/shared/src/promise-race.d.ts +14 -0
- package/out/shared/src/promise-race.d.ts.map +1 -1
- package/out/shared/src/promise-race.js +32 -1
- package/out/shared/src/promise-race.js.map +1 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +1 -22
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/zero/package.js +3 -3
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +8 -1
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/normalize.d.ts +6 -0
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +24 -2
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +65 -4
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +202 -35
- 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/db/delete-lite-db.js +2 -1
- package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +10 -5
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/migration.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration.js +4 -2
- package/out/zero-cache/src/db/migration.js.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.d.ts +3 -2
- package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.js +13 -8
- package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.d.ts +5 -15
- package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +8 -72
- package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +6 -1
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +20 -2
- package/out/zero-cache/src/observability/metrics.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 +105 -34
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +8 -18
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +48 -5
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +3 -2
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +1 -1
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.d.ts +0 -1
- package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
- package/out/zero-cache/src/server/worker-urls.js +1 -2
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/change-source/change-source.d.ts +4 -2
- package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +35 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js +34 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +2 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +81 -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.d.ts +2 -6
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +14 -5
- 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-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +22 -11
- 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.d.ts +7 -7
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +90 -31
- 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.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/decommission.js +20 -11
- 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 +9 -1
- 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 +61 -76
- 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/stream.d.ts +65 -2
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +101 -10
- 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/replication-slots.d.ts +27 -2
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +41 -8
- 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/init.d.ts +0 -5
- 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 +29 -41
- 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/shard.d.ts +35 -16
- 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 +77 -7
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +2 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +5 -13
- 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.d.ts +6 -3
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts +6 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js +21 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +3 -7
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +26 -10
- 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.d.ts +15 -5
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js +71 -0
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +68 -78
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +84 -92
- 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 +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js +50 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts +34 -0
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js +67 -0
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts +141 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +270 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js +39 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +5 -8
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +20 -19
- 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.d.ts +90 -24
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +456 -66
- 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.d.ts +50 -13
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts +8 -0
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +14 -3
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +42 -12
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.d.ts → litestream3-prometheus-poller.d.ts} +19 -45
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.js → litestream3-prometheus-poller.js} +68 -176
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts +15 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js +55 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +5 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +2 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts +11 -0
- package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/shard-sampling.js +19 -0
- package/out/zero-cache/src/services/change-streamer/shard-sampling.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +24 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +119 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +4 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +39 -14
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +99 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +344 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts +83 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +417 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts +121 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js +391 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts +73 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js +157 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +62 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +164 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +109 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +258 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +85 -12
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +286 -141
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +61 -10
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +101 -8
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +34 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +86 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts +29 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +154 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/services/http-service.d.ts +1 -0
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js +7 -3
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +4 -3
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts +12 -11
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +115 -152
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +42 -0
- package/out/zero-cache/src/services/litestream/config.yml +3 -2
- package/out/zero-cache/src/services/litestream/metrics.d.ts +5 -4
- package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js +10 -10
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +5 -2
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts +169 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +434 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts +368 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js +491 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +55 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +150 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts +6 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +70 -25
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +19 -6
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.d.ts +0 -22
- package/out/zero-cache/src/services/replicator/notifier.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.js +7 -1
- package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +17 -2
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +9 -3
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +27 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js +32 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +119 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +214 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +3 -3
- 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.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js +9 -5
- package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +132 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +415 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts +120 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +283 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -0
- package/out/zero-cache/src/services/replicator/write-worker.js +23 -12
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +2 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/running-state.d.ts +1 -1
- package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/running-state.js +1 -0
- package/out/zero-cache/src/services/running-state.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +3 -2
- 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 +25 -17
- package/out/zero-cache/src/services/view-syncer/client-handler.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 +14 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +53 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +68 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -0
- 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 +18 -3
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +14 -4
- 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 +70 -8
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/downstream.d.ts +11 -0
- package/out/zero-cache/src/types/downstream.d.ts.map +1 -0
- package/out/zero-cache/src/types/pg.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg.js +14 -4
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/poke-chunk.d.ts +18 -0
- package/out/zero-cache/src/types/poke-chunk.d.ts.map +1 -0
- package/out/zero-cache/src/types/poke-chunk.js +57 -0
- package/out/zero-cache/src/types/poke-chunk.js.map +1 -0
- package/out/zero-cache/src/types/processes.d.ts +1 -2
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +1 -28
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +16 -0
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +25 -4
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +2 -0
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +6 -0
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +23 -4
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +130 -10
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.d.ts +25 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +33 -1
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts +12 -0
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +75 -0
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.d.ts +7 -2
- package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js +52 -2
- package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts +6 -7
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +194 -107
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +1 -1
- package/out/zero-protocol/src/poke.d.ts +89 -7
- package/out/zero-protocol/src/poke.d.ts.map +1 -1
- package/out/zero-protocol/src/poke.js +16 -8
- package/out/zero-protocol/src/poke.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts +12 -7
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-schema/src/builder/table-builder.d.ts +23 -0
- package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
- package/out/zero-schema/src/builder/table-builder.js +18 -0
- package/out/zero-schema/src/builder/table-builder.js.map +1 -1
- package/out/zero-server/src/custom.js +10 -3
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/schema.d.ts +8 -0
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +63 -24
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-types/src/schema.d.ts +11 -0
- package/out/zero-types/src/schema.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +10 -2
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +14 -6
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/stream.d.ts +0 -1
- package/out/zql/src/ivm/stream.d.ts.map +1 -1
- package/out/zql/src/ivm/stream.js +1 -7
- package/out/zql/src/ivm/stream.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +11 -2
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/mutate/crud.d.ts +10 -8
- package/out/zql/src/mutate/crud.d.ts.map +1 -1
- package/out/zql/src/mutate/crud.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +5 -1
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +2 -2
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query.d.ts +83 -14
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/static-query.d.ts +1 -1
- package/out/zql/src/query/static-query.d.ts.map +1 -1
- package/out/zqlite/src/db.js +2 -2
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts +15 -0
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +33 -14
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +5 -5
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +4 -4
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +3 -3
- package/out/replicache/src/db/index.js +0 -91
- package/out/replicache/src/db/index.js.map +0 -1
- package/out/replicache/src/lazy.d.ts +0 -2
- package/out/replicache/src/lazy.d.ts.map +0 -1
- package/out/replicache/src/lazy.js +0 -12
- package/out/replicache/src/lazy.js.map +0 -1
- package/out/zero-cache/src/server/backup-watermark-reader.js +0 -35
- package/out/zero-cache/src/server/backup-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts +0 -16
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js +0 -54
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts +0 -25
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js +0 -159
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts +0 -44
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +0 -65
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts +0 -11
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js +0 -74
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts +0 -42
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js +0 -117
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js.map +0 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Database } from "../../../../zqlite/src/db.js";
|
|
2
|
+
import { readChangeLogBounds, readChangeLogMeta } from "../replicator/change-log-db.js";
|
|
3
|
+
import { isSampledForShard } from "./shard-sampling.js";
|
|
4
|
+
//#region ../zero-cache/src/services/change-streamer/sqlite-change-log-read-router.ts
|
|
5
|
+
var DEFAULT_FAILURE_COOLDOWN_MS = 3e4;
|
|
6
|
+
/**
|
|
7
|
+
* Selects and pins the catchup source shared by `/snapshot` and `/changes`.
|
|
8
|
+
*
|
|
9
|
+
* A post-registration SQLite failure opens a process-local breaker. Requests
|
|
10
|
+
* use PG during the cooldown; the first request after it expires is the
|
|
11
|
+
* bounded probe. A successful readiness inspection closes the breaker, while
|
|
12
|
+
* another unavailable result rearms it. A writer failure opens it permanently
|
|
13
|
+
* because the writer remains disabled and its file stays absent for the life
|
|
14
|
+
* of the process.
|
|
15
|
+
*/
|
|
16
|
+
var SQLiteChangeLogReadRouter = class {
|
|
17
|
+
#shard;
|
|
18
|
+
#readPercent;
|
|
19
|
+
#coldReadPercent;
|
|
20
|
+
#retentionMs;
|
|
21
|
+
#inspect;
|
|
22
|
+
#failureCooldownMs;
|
|
23
|
+
#now;
|
|
24
|
+
#pins = /* @__PURE__ */ new Map();
|
|
25
|
+
#breakerUntilMs;
|
|
26
|
+
#permanentlyDisabled = false;
|
|
27
|
+
constructor(opts) {
|
|
28
|
+
this.#shard = opts.shard;
|
|
29
|
+
this.#readPercent = opts.readPercent;
|
|
30
|
+
this.#coldReadPercent = opts.coldReadPercent ?? 0;
|
|
31
|
+
this.#retentionMs = opts.retentionMs;
|
|
32
|
+
this.#inspect = opts.inspect;
|
|
33
|
+
this.#failureCooldownMs = opts.failureCooldownMs ?? DEFAULT_FAILURE_COOLDOWN_MS;
|
|
34
|
+
this.#now = opts.now ?? Date.now;
|
|
35
|
+
}
|
|
36
|
+
/** Chooses and pins a source for a snapshot reservation. */
|
|
37
|
+
pin(taskID) {
|
|
38
|
+
const route = this.#choose(taskID);
|
|
39
|
+
this.#pins.set(taskID, route);
|
|
40
|
+
return route;
|
|
41
|
+
}
|
|
42
|
+
/** Returns the source pinned by an active snapshot reservation. */
|
|
43
|
+
peek(taskID) {
|
|
44
|
+
const route = this.#pins.get(taskID);
|
|
45
|
+
return route && this.#asPinned(route);
|
|
46
|
+
}
|
|
47
|
+
/** Consumes a snapshot pin, or makes the stable choice for a direct retry. */
|
|
48
|
+
consume(taskID) {
|
|
49
|
+
const route = this.#pins.get(taskID);
|
|
50
|
+
if (route === void 0) return this.#choose(taskID);
|
|
51
|
+
this.#pins.delete(taskID);
|
|
52
|
+
return this.#asPinned(route);
|
|
53
|
+
}
|
|
54
|
+
/** Releases a pin whose snapshot reservation ended before subscribing. */
|
|
55
|
+
release(taskID) {
|
|
56
|
+
this.#pins.delete(taskID);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Replaces a pinned SQLite route with PG, for a reservation whose backup
|
|
60
|
+
* the log cannot cover -- most often a log seeded after that backup.
|
|
61
|
+
*
|
|
62
|
+
* The pin moves, not just the reservation's advertised bounds. Confirming
|
|
63
|
+
* with PG bounds while the task's `/changes` request still resolves to
|
|
64
|
+
* SQLite is exactly the mismatch the reservation path exists to prevent,
|
|
65
|
+
* so the two have to change together.
|
|
66
|
+
*
|
|
67
|
+
* A task with no pin is left alone: `consume` makes it a fresh choice.
|
|
68
|
+
*/
|
|
69
|
+
demote(taskID) {
|
|
70
|
+
const route = {
|
|
71
|
+
source: "pg",
|
|
72
|
+
reason: "backup-uncovered"
|
|
73
|
+
};
|
|
74
|
+
if (this.#pins.has(taskID)) this.#pins.set(taskID, route);
|
|
75
|
+
return route;
|
|
76
|
+
}
|
|
77
|
+
/** Opens the post-registration failure breaker. */
|
|
78
|
+
trip(permanent = false) {
|
|
79
|
+
if (permanent) {
|
|
80
|
+
this.#permanentlyDisabled = true;
|
|
81
|
+
this.#breakerUntilMs = void 0;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!this.#permanentlyDisabled) this.#breakerUntilMs = this.#now() + this.#failureCooldownMs;
|
|
85
|
+
}
|
|
86
|
+
#choose(taskID) {
|
|
87
|
+
const now = this.#now();
|
|
88
|
+
if (this.#permanentlyDisabled || this.#breakerUntilMs !== void 0 && now < this.#breakerUntilMs) return {
|
|
89
|
+
source: "pg",
|
|
90
|
+
reason: "breaker-open"
|
|
91
|
+
};
|
|
92
|
+
let coverage;
|
|
93
|
+
try {
|
|
94
|
+
coverage = this.#inspect();
|
|
95
|
+
} catch {
|
|
96
|
+
coverage = void 0;
|
|
97
|
+
}
|
|
98
|
+
if (coverage === void 0) {
|
|
99
|
+
if (this.#breakerUntilMs !== void 0) this.#breakerUntilMs = now + this.#failureCooldownMs;
|
|
100
|
+
return {
|
|
101
|
+
source: "pg",
|
|
102
|
+
reason: "log-unavailable"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
this.#breakerUntilMs = void 0;
|
|
106
|
+
const cold = now - coverage.seededAtMs < this.#retentionMs;
|
|
107
|
+
if (cold && !isSampledForShard(this.#shard, taskID, this.#coldReadPercent)) return {
|
|
108
|
+
source: "pg",
|
|
109
|
+
reason: "cold-log",
|
|
110
|
+
coverage
|
|
111
|
+
};
|
|
112
|
+
if (!isSampledForShard(this.#shard, taskID, this.#readPercent)) return {
|
|
113
|
+
source: "pg",
|
|
114
|
+
reason: "percentage",
|
|
115
|
+
coverage
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
source: "sqlite",
|
|
119
|
+
reason: cold ? "selected-cold" : "selected",
|
|
120
|
+
coverage
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
#asPinned(route) {
|
|
124
|
+
return {
|
|
125
|
+
...route,
|
|
126
|
+
pinned: true
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Reads the coverage and validates the v2 schema and RMv2 identity in one
|
|
132
|
+
* short-lived readonly connection. It never creates the log.
|
|
133
|
+
*/
|
|
134
|
+
function inspectSQLiteChangeLog(lc, changeLogFile, identity) {
|
|
135
|
+
let db;
|
|
136
|
+
try {
|
|
137
|
+
db = new Database(lc.withContext("component", "sqlite-change-log-read-router"), changeLogFile, { readonly: true });
|
|
138
|
+
const meta = readChangeLogMeta(db);
|
|
139
|
+
if (meta.schemaVersion !== 4 || meta.epoch !== identity.epoch || meta.generation !== identity.generation || meta.replicaID !== identity.replicaID) return;
|
|
140
|
+
const bounds = readChangeLogBounds(db);
|
|
141
|
+
if (bounds.minWatermark === null || bounds.headWatermark === null) return;
|
|
142
|
+
return {
|
|
143
|
+
seededAtMs: meta.seededAtMs,
|
|
144
|
+
seedWatermark: meta.seedWatermark,
|
|
145
|
+
minWatermark: bounds.minWatermark,
|
|
146
|
+
headWatermark: bounds.headWatermark
|
|
147
|
+
};
|
|
148
|
+
} catch {
|
|
149
|
+
return;
|
|
150
|
+
} finally {
|
|
151
|
+
db?.close();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
export { SQLiteChangeLogReadRouter, inspectSQLiteChangeLog };
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=sqlite-change-log-read-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-change-log-read-router.js","names":["#shard","#readPercent","#coldReadPercent","#retentionMs","#inspect","#failureCooldownMs","#now","#pins","#choose","#asPinned","#permanentlyDisabled","#breakerUntilMs"],"sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-read-router.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport type {ShardID} from '../../types/shards.ts';\nimport {\n CHANGE_LOG_DB_SCHEMA_VERSION,\n readChangeLogBounds,\n readChangeLogMeta,\n type ChangeLogIdentity,\n} from '../replicator/change-log-db.ts';\nimport {isSampledForShard} from './shard-sampling.ts';\n\nexport type ChangeLogReadSource = 'pg' | 'sqlite';\n\nexport type SQLiteChangeLogCoverage = {\n readonly seededAtMs: number;\n readonly seedWatermark: string;\n readonly minWatermark: string;\n readonly headWatermark: string;\n};\n\nexport type ChangeLogReadRouteReason =\n | 'selected'\n // Selected while the log was still inside its warm-up window. Kept\n // distinct from 'selected' so bootstrap serving is separable in metrics\n // from steady-state serving.\n | 'selected-cold'\n | 'percentage'\n | 'cold-log'\n | 'log-unavailable'\n | 'breaker-open'\n // Pinned to SQLite, then demoted because the log could not cover the\n // backup the reservation is restoring from. See {@link\n // SQLiteChangeLogReadRouter.demote}.\n | 'backup-uncovered';\n\nexport type ChangeLogReadRoute = {\n readonly source: ChangeLogReadSource;\n readonly reason: ChangeLogReadRouteReason;\n readonly pinned?: boolean | undefined;\n readonly coverage?: SQLiteChangeLogCoverage | undefined;\n};\n\nexport type SQLiteChangeLogReadRouterOptions = {\n readonly shard: ShardID;\n readonly readPercent: number;\n /**\n * Percentage of eligible tasks served from a log that has not yet aged\n * through {@link retentionMs}. Defaults to zero, which keeps every such\n * task on PG.\n */\n readonly coldReadPercent?: number | undefined;\n readonly retentionMs: number;\n readonly inspect: () => SQLiteChangeLogCoverage | undefined;\n readonly failureCooldownMs?: number | undefined;\n readonly now?: (() => number) | undefined;\n};\n\nconst DEFAULT_FAILURE_COOLDOWN_MS = 30_000;\n\n/**\n * Selects and pins the catchup source shared by `/snapshot` and `/changes`.\n *\n * A post-registration SQLite failure opens a process-local breaker. Requests\n * use PG during the cooldown; the first request after it expires is the\n * bounded probe. A successful readiness inspection closes the breaker, while\n * another unavailable result rearms it. A writer failure opens it permanently\n * because the writer remains disabled and its file stays absent for the life\n * of the process.\n */\nexport class SQLiteChangeLogReadRouter {\n readonly #shard: ShardID;\n readonly #readPercent: number;\n readonly #coldReadPercent: number;\n readonly #retentionMs: number;\n readonly #inspect: () => SQLiteChangeLogCoverage | undefined;\n readonly #failureCooldownMs: number;\n readonly #now: () => number;\n readonly #pins = new Map<string, ChangeLogReadRoute>();\n\n #breakerUntilMs: number | undefined;\n #permanentlyDisabled = false;\n\n constructor(opts: SQLiteChangeLogReadRouterOptions) {\n this.#shard = opts.shard;\n this.#readPercent = opts.readPercent;\n this.#coldReadPercent = opts.coldReadPercent ?? 0;\n this.#retentionMs = opts.retentionMs;\n this.#inspect = opts.inspect;\n this.#failureCooldownMs =\n opts.failureCooldownMs ?? DEFAULT_FAILURE_COOLDOWN_MS;\n this.#now = opts.now ?? Date.now;\n }\n\n /** Chooses and pins a source for a snapshot reservation. */\n pin(taskID: string): ChangeLogReadRoute {\n const route = this.#choose(taskID);\n this.#pins.set(taskID, route);\n return route;\n }\n\n /** Returns the source pinned by an active snapshot reservation. */\n peek(taskID: string): ChangeLogReadRoute | undefined {\n const route = this.#pins.get(taskID);\n return route && this.#asPinned(route);\n }\n\n /** Consumes a snapshot pin, or makes the stable choice for a direct retry. */\n consume(taskID: string): ChangeLogReadRoute {\n const route = this.#pins.get(taskID);\n if (route === undefined) {\n return this.#choose(taskID);\n }\n this.#pins.delete(taskID);\n return this.#asPinned(route);\n }\n\n /** Releases a pin whose snapshot reservation ended before subscribing. */\n release(taskID: string): void {\n this.#pins.delete(taskID);\n }\n\n /**\n * Replaces a pinned SQLite route with PG, for a reservation whose backup\n * the log cannot cover -- most often a log seeded after that backup.\n *\n * The pin moves, not just the reservation's advertised bounds. Confirming\n * with PG bounds while the task's `/changes` request still resolves to\n * SQLite is exactly the mismatch the reservation path exists to prevent,\n * so the two have to change together.\n *\n * A task with no pin is left alone: `consume` makes it a fresh choice.\n */\n demote(taskID: string): ChangeLogReadRoute {\n const route: ChangeLogReadRoute = {\n source: 'pg',\n reason: 'backup-uncovered',\n };\n if (this.#pins.has(taskID)) {\n this.#pins.set(taskID, route);\n }\n return route;\n }\n\n /** Opens the post-registration failure breaker. */\n trip(permanent = false): void {\n if (permanent) {\n this.#permanentlyDisabled = true;\n this.#breakerUntilMs = undefined;\n return;\n }\n if (!this.#permanentlyDisabled) {\n this.#breakerUntilMs = this.#now() + this.#failureCooldownMs;\n }\n }\n\n #choose(taskID: string): ChangeLogReadRoute {\n const now = this.#now();\n if (\n this.#permanentlyDisabled ||\n (this.#breakerUntilMs !== undefined && now < this.#breakerUntilMs)\n ) {\n return {source: 'pg', reason: 'breaker-open'};\n }\n\n // Inspect eligibility even at readPercent=0. Slice 11 starts its rollout\n // at zero specifically so warm/cold/unavailable rates are visible before\n // any subscriber is routed to SQLite.\n let coverage: SQLiteChangeLogCoverage | undefined;\n try {\n coverage = this.#inspect();\n } catch {\n coverage = undefined;\n }\n if (coverage === undefined) {\n // This was a breaker probe rather than ordinary startup unavailability.\n // Rearm it so a busy retry loop does not turn into an open/read loop.\n if (this.#breakerUntilMs !== undefined) {\n this.#breakerUntilMs = now + this.#failureCooldownMs;\n }\n return {source: 'pg', reason: 'log-unavailable'};\n }\n // A successful probe restores eligibility. This happens before the gates\n // below so that declining a task -- for a cold log or for the percentage\n // -- does not leave the cooldown armed against the next ordinary\n // unavailability.\n this.#breakerUntilMs = undefined;\n // A log seeded less than a retention window ago holds less history than\n // the purger promises to keep, so it is the case that most often cannot\n // cover a follower. It is also the only path that has no PG to fall back\n // to once PG is retired, which is the argument for exercising it now\n // rather than meeting it at the cutover: `coldReadPercent` is that dial.\n const cold = now - coverage.seededAtMs < this.#retentionMs;\n if (\n cold &&\n !isSampledForShard(this.#shard, taskID, this.#coldReadPercent)\n ) {\n return {source: 'pg', reason: 'cold-log', coverage};\n }\n\n // Keeping the percentage decision after readiness makes zero-percent\n // rollouts emit eligibility metrics without serving reads.\n if (!isSampledForShard(this.#shard, taskID, this.#readPercent)) {\n return {source: 'pg', reason: 'percentage', coverage};\n }\n return {\n source: 'sqlite',\n reason: cold ? 'selected-cold' : 'selected',\n coverage,\n };\n }\n\n #asPinned(route: ChangeLogReadRoute): ChangeLogReadRoute {\n return {...route, pinned: true};\n }\n}\n\n/**\n * Reads the coverage and validates the v2 schema and RMv2 identity in one\n * short-lived readonly connection. It never creates the log.\n */\nexport function inspectSQLiteChangeLog(\n lc: LogContext,\n changeLogFile: string,\n identity: ChangeLogIdentity,\n): SQLiteChangeLogCoverage | undefined {\n let db: Database | undefined;\n try {\n db = new Database(\n lc.withContext('component', 'sqlite-change-log-read-router'),\n changeLogFile,\n {readonly: true},\n );\n const meta = readChangeLogMeta(db);\n if (\n meta.schemaVersion !== CHANGE_LOG_DB_SCHEMA_VERSION ||\n meta.epoch !== identity.epoch ||\n meta.generation !== identity.generation ||\n meta.replicaID !== identity.replicaID\n ) {\n return undefined;\n }\n const bounds = readChangeLogBounds(db);\n if (bounds.minWatermark === null || bounds.headWatermark === null) {\n return undefined;\n }\n return {\n seededAtMs: meta.seededAtMs,\n seedWatermark: meta.seedWatermark,\n minWatermark: bounds.minWatermark,\n headWatermark: bounds.headWatermark,\n };\n } catch {\n return undefined;\n } finally {\n db?.close();\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { WatermarkedChange } from './change-streamer.ts';
|
|
3
|
+
export type CatchupPlan = {
|
|
4
|
+
readonly kind: 'range';
|
|
5
|
+
readonly minWatermark: string;
|
|
6
|
+
readonly headWatermark: string;
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'ahead';
|
|
9
|
+
readonly headWatermark: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'too-old';
|
|
12
|
+
readonly minWatermark: string;
|
|
13
|
+
readonly headWatermark: string;
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: 'not-ready';
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Seeks to the final position so validating a boundary does not scan every row
|
|
19
|
+
* of a large transaction. Exported so the focused query-plan test covers the
|
|
20
|
+
* production subquery.
|
|
21
|
+
*/
|
|
22
|
+
export declare const SQLITE_CHANGE_LOG_BOUNDARY_SQL = "\n SELECT\n \"precommit\" = @fromWatermark AND\n \"tag\" = 'commit' AS \"boundaryExists\"\n FROM \"_zero.changeLogStream\"\n WHERE \"watermark\" = @fromWatermark\n ORDER BY \"pos\" DESC\n LIMIT 1\n";
|
|
23
|
+
/**
|
|
24
|
+
* The log describes its own bounds; nothing here reads the replica.
|
|
25
|
+
* `max("watermark")` is the head because a transaction's rows commit atomically,
|
|
26
|
+
* so the log never ends mid-transaction.
|
|
27
|
+
*
|
|
28
|
+
* `min` and `max` are deliberately kept as *separate* scalar subqueries: SQLite
|
|
29
|
+
* applies its single-aggregate index optimization only when the aggregate is
|
|
30
|
+
* alone in its query, so `SELECT min(x), max(x) FROM t` degrades to a full scan
|
|
31
|
+
* while two scalar subqueries are two index seeks. The query-plan test pins
|
|
32
|
+
* this.
|
|
33
|
+
*
|
|
34
|
+
* Exported so that test can cover the production query.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SQLITE_CHANGE_LOG_PLAN_SQL = "\n SELECT\n (SELECT max(\"watermark\") FROM \"_zero.changeLogStream\")\n AS \"headWatermark\",\n (SELECT min(\"watermark\") FROM \"_zero.changeLogStream\")\n AS \"minWatermark\",\n coalesce((\n SELECT\n \"precommit\" = @fromWatermark AND\n \"tag\" = 'commit' AS \"boundaryExists\"\n FROM \"_zero.changeLogStream\"\n WHERE \"watermark\" = @fromWatermark\n ORDER BY \"pos\" DESC\n LIMIT 1\n), 0) AS \"boundaryExists\"\n";
|
|
37
|
+
/** Exported so the focused query-plan test covers the production query. */
|
|
38
|
+
export declare const SQLITE_CHANGE_LOG_READ_BATCH_SQL = "\n SELECT\n \"watermark\",\n \"pos\",\n \"tag\",\n \"change\"\n FROM \"_zero.changeLogStream\"\n WHERE (\"watermark\", \"pos\") > (?, ?)\n AND \"watermark\" <= ?\n ORDER BY \"watermark\", \"pos\"\n LIMIT ?\n";
|
|
39
|
+
/**
|
|
40
|
+
* Reads the change log without retaining a SQLite snapshot while a consumer
|
|
41
|
+
* processes a batch.
|
|
42
|
+
*
|
|
43
|
+
* The log lives in its own database beside the replica
|
|
44
|
+
* (`changeLogFileName(replicaFile)`), which this reader opens read-only; it
|
|
45
|
+
* never opens the replica. The connection therefore keeps the journal mode
|
|
46
|
+
* configured by the change-log writer. Callers pin a head with {@link plan}
|
|
47
|
+
* before passing it to {@link read}.
|
|
48
|
+
*
|
|
49
|
+
* A change-streamer can start before the replicator has created the log, so the
|
|
50
|
+
* stream table may not exist yet. Statements are prepared on first use rather
|
|
51
|
+
* than in the constructor, and {@link plan} reports `not-ready` until they can
|
|
52
|
+
* be; a reader constructed too early becomes usable once the writer catches up.
|
|
53
|
+
*/
|
|
54
|
+
export declare class SQLiteChangeLogReader implements Disposable {
|
|
55
|
+
#private;
|
|
56
|
+
constructor(lc: LogContext, changeLogFile: string);
|
|
57
|
+
plan(fromWatermark: string): CatchupPlan;
|
|
58
|
+
read(fromWatermark: string, throughWatermark: string, batchSize: number, signal?: AbortSignal | undefined, maxRows?: number | undefined): AsyncIterable<readonly WatermarkedChange[]>;
|
|
59
|
+
close(): void;
|
|
60
|
+
[Symbol.dispose](): void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=sqlite-change-log-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-change-log-reader.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-reader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AASjD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,WAAW,GACnB;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,GACD;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAC,GACxD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,GAKD;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAC,CAAC;AAiBjC;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,uNAQ1C,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,ucAOtC,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,wOAW5C,CAAC;AAMF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,qBAAsB,YAAW,UAAU;;gBAK1C,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM;IAQjD,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW;IAqBjC,IAAI,CACT,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,EAChC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAC3B,aAAa,CAAC,SAAS,iBAAiB,EAAE,CAAC;IA6E9C,KAAK,IAAI,IAAI;IAQb,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAuCzB"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { assert } from "../../../../shared/src/asserts.js";
|
|
2
|
+
import { AbortError } from "../../../../shared/src/abort-error.js";
|
|
3
|
+
import { Database } from "../../../../zqlite/src/db.js";
|
|
4
|
+
import { CHANGE_LOG_STREAM_TABLE } from "../replicator/change-log-db.js";
|
|
5
|
+
import { reconstructWatermarkedChange } from "./change-log-codec.js";
|
|
6
|
+
//#region ../zero-cache/src/services/change-streamer/sqlite-change-log-reader.ts
|
|
7
|
+
/**
|
|
8
|
+
* Seeks to the final position so validating a boundary does not scan every row
|
|
9
|
+
* of a large transaction. Exported so the focused query-plan test covers the
|
|
10
|
+
* production subquery.
|
|
11
|
+
*/
|
|
12
|
+
var SQLITE_CHANGE_LOG_BOUNDARY_SQL = `
|
|
13
|
+
SELECT
|
|
14
|
+
"precommit" = @fromWatermark AND
|
|
15
|
+
"tag" = 'commit' AS "boundaryExists"
|
|
16
|
+
FROM "${CHANGE_LOG_STREAM_TABLE}"
|
|
17
|
+
WHERE "watermark" = @fromWatermark
|
|
18
|
+
ORDER BY "pos" DESC
|
|
19
|
+
LIMIT 1
|
|
20
|
+
`;
|
|
21
|
+
/**
|
|
22
|
+
* The log describes its own bounds; nothing here reads the replica.
|
|
23
|
+
* `max("watermark")` is the head because a transaction's rows commit atomically,
|
|
24
|
+
* so the log never ends mid-transaction.
|
|
25
|
+
*
|
|
26
|
+
* `min` and `max` are deliberately kept as *separate* scalar subqueries: SQLite
|
|
27
|
+
* applies its single-aggregate index optimization only when the aggregate is
|
|
28
|
+
* alone in its query, so `SELECT min(x), max(x) FROM t` degrades to a full scan
|
|
29
|
+
* while two scalar subqueries are two index seeks. The query-plan test pins
|
|
30
|
+
* this.
|
|
31
|
+
*
|
|
32
|
+
* Exported so that test can cover the production query.
|
|
33
|
+
*/
|
|
34
|
+
var SQLITE_CHANGE_LOG_PLAN_SQL = `
|
|
35
|
+
SELECT
|
|
36
|
+
(SELECT max("watermark") FROM "${CHANGE_LOG_STREAM_TABLE}")
|
|
37
|
+
AS "headWatermark",
|
|
38
|
+
(SELECT min("watermark") FROM "${CHANGE_LOG_STREAM_TABLE}")
|
|
39
|
+
AS "minWatermark",
|
|
40
|
+
coalesce((${SQLITE_CHANGE_LOG_BOUNDARY_SQL}), 0) AS "boundaryExists"
|
|
41
|
+
`;
|
|
42
|
+
/** Exported so the focused query-plan test covers the production query. */
|
|
43
|
+
var SQLITE_CHANGE_LOG_READ_BATCH_SQL = `
|
|
44
|
+
SELECT
|
|
45
|
+
"watermark",
|
|
46
|
+
"pos",
|
|
47
|
+
"tag",
|
|
48
|
+
"change"
|
|
49
|
+
FROM "${CHANGE_LOG_STREAM_TABLE}"
|
|
50
|
+
WHERE ("watermark", "pos") > (?, ?)
|
|
51
|
+
AND "watermark" <= ?
|
|
52
|
+
ORDER BY "watermark", "pos"
|
|
53
|
+
LIMIT ?
|
|
54
|
+
`;
|
|
55
|
+
var TABLE_EXISTS_SQL = `
|
|
56
|
+
SELECT 1 FROM "sqlite_master" WHERE "type" = 'table' AND "name" = ?
|
|
57
|
+
`;
|
|
58
|
+
/**
|
|
59
|
+
* Reads the change log without retaining a SQLite snapshot while a consumer
|
|
60
|
+
* processes a batch.
|
|
61
|
+
*
|
|
62
|
+
* The log lives in its own database beside the replica
|
|
63
|
+
* (`changeLogFileName(replicaFile)`), which this reader opens read-only; it
|
|
64
|
+
* never opens the replica. The connection therefore keeps the journal mode
|
|
65
|
+
* configured by the change-log writer. Callers pin a head with {@link plan}
|
|
66
|
+
* before passing it to {@link read}.
|
|
67
|
+
*
|
|
68
|
+
* A change-streamer can start before the replicator has created the log, so the
|
|
69
|
+
* stream table may not exist yet. Statements are prepared on first use rather
|
|
70
|
+
* than in the constructor, and {@link plan} reports `not-ready` until they can
|
|
71
|
+
* be; a reader constructed too early becomes usable once the writer catches up.
|
|
72
|
+
*/
|
|
73
|
+
var SQLiteChangeLogReader = class {
|
|
74
|
+
#db;
|
|
75
|
+
#statements;
|
|
76
|
+
#closed = false;
|
|
77
|
+
constructor(lc, changeLogFile) {
|
|
78
|
+
this.#db = new Database(lc.withContext("component", "sqlite-change-log-reader"), changeLogFile, { readonly: true });
|
|
79
|
+
}
|
|
80
|
+
plan(fromWatermark) {
|
|
81
|
+
this.#assertOpen();
|
|
82
|
+
const statements = this.#prepare();
|
|
83
|
+
if (statements === void 0) return { kind: "not-ready" };
|
|
84
|
+
const row = statements.plan.get({ fromWatermark });
|
|
85
|
+
if (row.headWatermark === null || row.minWatermark === null) return { kind: "not-ready" };
|
|
86
|
+
const { headWatermark, minWatermark } = row;
|
|
87
|
+
if (fromWatermark > headWatermark) return {
|
|
88
|
+
kind: "ahead",
|
|
89
|
+
headWatermark
|
|
90
|
+
};
|
|
91
|
+
if (fromWatermark < minWatermark || row.boundaryExists === 0) return {
|
|
92
|
+
kind: "too-old",
|
|
93
|
+
minWatermark,
|
|
94
|
+
headWatermark
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
kind: "range",
|
|
98
|
+
minWatermark,
|
|
99
|
+
headWatermark
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async *read(fromWatermark, throughWatermark, batchSize, signal, maxRows) {
|
|
103
|
+
assert(Number.isSafeInteger(batchSize) && batchSize > 0, "SQLite change log batch size must be a positive safe integer");
|
|
104
|
+
assert(maxRows === void 0 || Number.isSafeInteger(maxRows) && maxRows > 0, "SQLite change log row limit must be a positive safe integer");
|
|
105
|
+
this.#assertOpen();
|
|
106
|
+
const statements = this.#prepare();
|
|
107
|
+
assert(statements !== void 0, "SQLite change log must be ready before it is read");
|
|
108
|
+
let lastWatermark = fromWatermark;
|
|
109
|
+
let lastPos = Number.MAX_SAFE_INTEGER;
|
|
110
|
+
let lastTag;
|
|
111
|
+
let rowsRead = 0;
|
|
112
|
+
while (true) {
|
|
113
|
+
this.#throwIfAborted(signal);
|
|
114
|
+
const remainingRows = maxRows === void 0 ? batchSize : maxRows - rowsRead;
|
|
115
|
+
if (remainingRows === 0) break;
|
|
116
|
+
const rows = statements.readBatch.all(lastWatermark, lastPos, throughWatermark, Math.min(batchSize, remainingRows));
|
|
117
|
+
this.#throwIfAborted(signal);
|
|
118
|
+
if (rows.length === 0) break;
|
|
119
|
+
const changes = rows.map(reconstructWatermarkedChange);
|
|
120
|
+
const last = rows.at(-1);
|
|
121
|
+
assert(last !== void 0, "non-empty batch must have a final row");
|
|
122
|
+
lastWatermark = last.watermark;
|
|
123
|
+
lastPos = last.pos;
|
|
124
|
+
lastTag = last.tag;
|
|
125
|
+
rowsRead += rows.length;
|
|
126
|
+
yield changes;
|
|
127
|
+
}
|
|
128
|
+
if (fromWatermark !== throughWatermark && (maxRows === void 0 || rowsRead < maxRows)) assert(lastWatermark === throughWatermark && lastTag === "commit", () => `SQLite change log did not end at pinned head ${throughWatermark}`);
|
|
129
|
+
}
|
|
130
|
+
close() {
|
|
131
|
+
if (this.#closed) return;
|
|
132
|
+
this.#closed = true;
|
|
133
|
+
this.#db.close();
|
|
134
|
+
}
|
|
135
|
+
[Symbol.dispose]() {
|
|
136
|
+
this.close();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Prepares the statements once the stream table exists, memoizing them.
|
|
140
|
+
* Returns `undefined` while the log has not been created, which is a normal
|
|
141
|
+
* startup state rather than an error.
|
|
142
|
+
*/
|
|
143
|
+
#prepare() {
|
|
144
|
+
if (this.#statements === void 0 && this.#streamTableExists()) this.#statements = {
|
|
145
|
+
plan: this.#db.prepare(SQLITE_CHANGE_LOG_PLAN_SQL),
|
|
146
|
+
readBatch: this.#db.prepare(SQLITE_CHANGE_LOG_READ_BATCH_SQL)
|
|
147
|
+
};
|
|
148
|
+
return this.#statements;
|
|
149
|
+
}
|
|
150
|
+
#streamTableExists() {
|
|
151
|
+
return this.#db.prepare(TABLE_EXISTS_SQL).get(CHANGE_LOG_STREAM_TABLE) !== void 0;
|
|
152
|
+
}
|
|
153
|
+
#assertOpen() {
|
|
154
|
+
if (this.#closed) throw new AbortError("SQLite change log reader is closed");
|
|
155
|
+
}
|
|
156
|
+
#throwIfAborted(signal) {
|
|
157
|
+
this.#assertOpen();
|
|
158
|
+
if (signal?.aborted) throw new AbortError("SQLite change log read aborted");
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { SQLITE_CHANGE_LOG_BOUNDARY_SQL, SQLITE_CHANGE_LOG_PLAN_SQL, SQLiteChangeLogReader };
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=sqlite-change-log-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-change-log-reader.js","names":["#db","#assertOpen","#prepare","#throwIfAborted","#closed","#statements","#streamTableExists"],"sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-reader.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {Database, type Statement} from '../../../../zqlite/src/db.ts';\nimport {CHANGE_LOG_STREAM_TABLE} from '../replicator/change-log-db.ts';\nimport {\n reconstructWatermarkedChange,\n type ChangeLogEntry,\n} from './change-log-codec.ts';\nimport type {WatermarkedChange} from './change-streamer.ts';\n\nexport type CatchupPlan =\n | {\n readonly kind: 'range';\n readonly minWatermark: string;\n readonly headWatermark: string;\n }\n | {readonly kind: 'ahead'; readonly headWatermark: string}\n | {\n readonly kind: 'too-old';\n readonly minWatermark: string;\n readonly headWatermark: string;\n }\n // The log exists as a file but has no content to serve: the replicator has\n // not created or reconciled it yet. Subscriber selection treats this as\n // \"decline and fall back to PG catchup\", which is why it is a plan rather\n // than an error.\n | {readonly kind: 'not-ready'};\n\ntype PlanRow = {\n readonly headWatermark: string | null;\n readonly minWatermark: string | null;\n readonly boundaryExists: number;\n};\n\ntype ChangeLogRow = ChangeLogEntry & {\n readonly pos: number;\n};\n\ntype PreparedStatements = {\n readonly plan: Statement;\n readonly readBatch: Statement;\n};\n\n/**\n * Seeks to the final position so validating a boundary does not scan every row\n * of a large transaction. Exported so the focused query-plan test covers the\n * production subquery.\n */\nexport const SQLITE_CHANGE_LOG_BOUNDARY_SQL = /*sql*/ `\n SELECT\n \"precommit\" = @fromWatermark AND\n \"tag\" = 'commit' AS \"boundaryExists\"\n FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n WHERE \"watermark\" = @fromWatermark\n ORDER BY \"pos\" DESC\n LIMIT 1\n`;\n\n/**\n * The log describes its own bounds; nothing here reads the replica.\n * `max(\"watermark\")` is the head because a transaction's rows commit atomically,\n * so the log never ends mid-transaction.\n *\n * `min` and `max` are deliberately kept as *separate* scalar subqueries: SQLite\n * applies its single-aggregate index optimization only when the aggregate is\n * alone in its query, so `SELECT min(x), max(x) FROM t` degrades to a full scan\n * while two scalar subqueries are two index seeks. The query-plan test pins\n * this.\n *\n * Exported so that test can cover the production query.\n */\nexport const SQLITE_CHANGE_LOG_PLAN_SQL = /*sql*/ `\n SELECT\n (SELECT max(\"watermark\") FROM \"${CHANGE_LOG_STREAM_TABLE}\")\n AS \"headWatermark\",\n (SELECT min(\"watermark\") FROM \"${CHANGE_LOG_STREAM_TABLE}\")\n AS \"minWatermark\",\n coalesce((${SQLITE_CHANGE_LOG_BOUNDARY_SQL}), 0) AS \"boundaryExists\"\n`;\n\n/** Exported so the focused query-plan test covers the production query. */\nexport const SQLITE_CHANGE_LOG_READ_BATCH_SQL = /*sql*/ `\n SELECT\n \"watermark\",\n \"pos\",\n \"tag\",\n \"change\"\n FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n WHERE (\"watermark\", \"pos\") > (?, ?)\n AND \"watermark\" <= ?\n ORDER BY \"watermark\", \"pos\"\n LIMIT ?\n`;\n\nconst TABLE_EXISTS_SQL = /*sql*/ `\n SELECT 1 FROM \"sqlite_master\" WHERE \"type\" = 'table' AND \"name\" = ?\n`;\n\n/**\n * Reads the change log without retaining a SQLite snapshot while a consumer\n * processes a batch.\n *\n * The log lives in its own database beside the replica\n * (`changeLogFileName(replicaFile)`), which this reader opens read-only; it\n * never opens the replica. The connection therefore keeps the journal mode\n * configured by the change-log writer. Callers pin a head with {@link plan}\n * before passing it to {@link read}.\n *\n * A change-streamer can start before the replicator has created the log, so the\n * stream table may not exist yet. Statements are prepared on first use rather\n * than in the constructor, and {@link plan} reports `not-ready` until they can\n * be; a reader constructed too early becomes usable once the writer catches up.\n */\nexport class SQLiteChangeLogReader implements Disposable {\n readonly #db: Database;\n #statements: PreparedStatements | undefined;\n #closed = false;\n\n constructor(lc: LogContext, changeLogFile: string) {\n this.#db = new Database(\n lc.withContext('component', 'sqlite-change-log-reader'),\n changeLogFile,\n {readonly: true},\n );\n }\n\n plan(fromWatermark: string): CatchupPlan {\n this.#assertOpen();\n const statements = this.#prepare();\n if (statements === undefined) {\n return {kind: 'not-ready'};\n }\n const row = statements.plan.get<PlanRow>({fromWatermark});\n if (row.headWatermark === null || row.minWatermark === null) {\n return {kind: 'not-ready'};\n }\n const {headWatermark, minWatermark} = row;\n\n if (fromWatermark > headWatermark) {\n return {kind: 'ahead', headWatermark};\n }\n if (fromWatermark < minWatermark || row.boundaryExists === 0) {\n return {kind: 'too-old', minWatermark, headWatermark};\n }\n return {kind: 'range', minWatermark, headWatermark};\n }\n\n async *read(\n fromWatermark: string,\n throughWatermark: string,\n batchSize: number,\n signal?: AbortSignal | undefined,\n maxRows?: number | undefined,\n ): AsyncIterable<readonly WatermarkedChange[]> {\n assert(\n Number.isSafeInteger(batchSize) && batchSize > 0,\n 'SQLite change log batch size must be a positive safe integer',\n );\n assert(\n maxRows === undefined || (Number.isSafeInteger(maxRows) && maxRows > 0),\n 'SQLite change log row limit must be a positive safe integer',\n );\n this.#assertOpen();\n const statements = this.#prepare();\n assert(\n statements !== undefined,\n 'SQLite change log must be ready before it is read',\n );\n\n let lastWatermark = fromWatermark;\n // The first query must be strictly after the requested transaction. Every\n // real stream position is far below this sentinel.\n let lastPos = Number.MAX_SAFE_INTEGER;\n let lastTag: string | undefined;\n let rowsRead = 0;\n\n while (true) {\n this.#throwIfAborted(signal);\n const remainingRows =\n maxRows === undefined ? batchSize : maxRows - rowsRead;\n if (remainingRows === 0) {\n break;\n }\n const rows = statements.readBatch.all<ChangeLogRow>(\n lastWatermark,\n lastPos,\n throughWatermark,\n Math.min(batchSize, remainingRows),\n );\n this.#throwIfAborted(signal);\n\n if (rows.length === 0) {\n break;\n }\n\n const changes = rows.map(reconstructWatermarkedChange);\n const last = rows.at(-1);\n assert(last !== undefined, 'non-empty batch must have a final row');\n lastWatermark = last.watermark;\n lastPos = last.pos;\n lastTag = last.tag;\n rowsRead += rows.length;\n\n // all() has exhausted the SELECT and closed its implicit read\n // transaction. This yield therefore cannot pin the WAL while subscriber\n // flow control is pending.\n yield changes;\n }\n\n // `throughWatermark` was pinned from `max(\"watermark\")` of this same table,\n // so reaching it no longer depends on the log agreeing with the replica's\n // `_zero.replicationState`, which is what this assertion used to guard.\n // What remains is a concurrent purge of the head transaction, which the\n // purge policy (never past the latest durable transaction) does not do.\n // A read that stopped at `maxRows` cannot know whether more rows follow,\n // so completeness is only asserted for a read that ran out on its own.\n // Detecting a short bounded read is left to the caller, which set the\n // limit and is the only side that knows what a full range should hold.\n if (\n fromWatermark !== throughWatermark &&\n (maxRows === undefined || rowsRead < maxRows)\n ) {\n assert(\n lastWatermark === throughWatermark && lastTag === 'commit',\n () =>\n `SQLite change log did not end at pinned head ${throughWatermark}`,\n );\n }\n }\n\n close(): void {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#db.close();\n }\n\n [Symbol.dispose](): void {\n this.close();\n }\n\n /**\n * Prepares the statements once the stream table exists, memoizing them.\n * Returns `undefined` while the log has not been created, which is a normal\n * startup state rather than an error.\n */\n #prepare(): PreparedStatements | undefined {\n if (this.#statements === undefined && this.#streamTableExists()) {\n this.#statements = {\n plan: this.#db.prepare(SQLITE_CHANGE_LOG_PLAN_SQL),\n readBatch: this.#db.prepare(SQLITE_CHANGE_LOG_READ_BATCH_SQL),\n };\n }\n return this.#statements;\n }\n\n #streamTableExists(): boolean {\n return (\n this.#db\n .prepare(TABLE_EXISTS_SQL)\n .get<{1: number} | undefined>(CHANGE_LOG_STREAM_TABLE) !== undefined\n );\n }\n\n #assertOpen(): void {\n if (this.#closed) {\n throw new AbortError('SQLite change log reader is closed');\n }\n }\n\n #throwIfAborted(signal: AbortSignal | undefined): void {\n this.#assertOpen();\n if (signal?.aborted) {\n throw new AbortError('SQLite change log read aborted');\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { Database } from '../../../../zqlite/src/db.ts';
|
|
3
|
+
import type { ChangeStreamData } from '../change-source/protocol/current/downstream.ts';
|
|
4
|
+
import { type ChangeLogIdentity, type ChangeLogResumePoint } from '../replicator/change-log-db.ts';
|
|
5
|
+
import { type SQLiteChangeLogObservabilityState } from '../replicator/sqlite-change-log-observability.ts';
|
|
6
|
+
export type SQLiteChangeLogWriterOptions = {
|
|
7
|
+
/** The replica file the log is named after, i.e. `${replicaFile}-change-log`. */
|
|
8
|
+
replicaFile: string;
|
|
9
|
+
/** The replica whose contents this log belongs to. */
|
|
10
|
+
identity: ChangeLogIdentity;
|
|
11
|
+
/**
|
|
12
|
+
* Called after each transaction's log commit, with the log's new head. This
|
|
13
|
+
* is what releases the catchup barrier: after the writer moved in-process,
|
|
14
|
+
* the head advances here rather than at a subscriber's ACK.
|
|
15
|
+
*/
|
|
16
|
+
onCommit?: ((watermark: string) => void) | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Called when the writer disables itself and deletes the file. The reader is
|
|
19
|
+
* cached on the service, so a delete that leaves it open would leave an
|
|
20
|
+
* in-process reader serving an unlinked inode while every new open sees
|
|
21
|
+
* nothing.
|
|
22
|
+
*/
|
|
23
|
+
onDisabled?: (() => void) | undefined;
|
|
24
|
+
/** Called when reconciliation replaces the file, to close cached readers. */
|
|
25
|
+
onRebuilt?: (() => void) | undefined;
|
|
26
|
+
/** Overridable for tests. */
|
|
27
|
+
now?: (() => number) | undefined;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Appends the canonical change stream to the SQLite change log, from the
|
|
31
|
+
* change-streamer's own stream loop.
|
|
32
|
+
*
|
|
33
|
+
* ### Ordering
|
|
34
|
+
*
|
|
35
|
+
* The log's commit for a transaction precedes the forward of that transaction's
|
|
36
|
+
* `commit` message, and nothing `await`s in between. Everything that can
|
|
37
|
+
* advance the watermark the stream would resume from — the storer's Postgres
|
|
38
|
+
* commit, `lastWatermark`, the upstream ACK — runs strictly after it, so the
|
|
39
|
+
* log's head is always at or above the resume point. A *hole* is therefore
|
|
40
|
+
* unreachable at any crash timing, and a *phantom* is the normal state, which is
|
|
41
|
+
* what makes truncate-above the whole of reconciliation.
|
|
42
|
+
*
|
|
43
|
+
* ### Fail-soft
|
|
44
|
+
*
|
|
45
|
+
* The log is a catchup cache: nothing in it is not either already durable in the
|
|
46
|
+
* replica's litestream backup or re-derivable from the replication slot. A write
|
|
47
|
+
* failure must therefore not stop replication for the shard. Corruption is
|
|
48
|
+
* rebuilt once at open; any other write error — a full disk, an I/O error, a
|
|
49
|
+
* permissions problem — logs, disables the writer, deletes the file, closes the
|
|
50
|
+
* reader, and lets the stream continue. The cost is catchup reach, and reach is
|
|
51
|
+
* recoverable: `too-old` sends a subscriber to a restore.
|
|
52
|
+
*
|
|
53
|
+
* One carve-out keeps that from hiding the bug class this topology introduces. A
|
|
54
|
+
* missed truncate-above surfaces as `SQLITE_CONSTRAINT` on the writer's plain
|
|
55
|
+
* `INSERT`, which is "any write error other than corruption" and would be
|
|
56
|
+
* absorbed quietly. It is counted as an invariant failure and alerted on before
|
|
57
|
+
* failing soft.
|
|
58
|
+
*/
|
|
59
|
+
export declare class SQLiteChangeLogWriter {
|
|
60
|
+
#private;
|
|
61
|
+
constructor(lc: LogContext, opts: SQLiteChangeLogWriterOptions);
|
|
62
|
+
/** False once the writer has failed soft, or after {@link close}. */
|
|
63
|
+
get enabled(): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The writer's own connection, which is the one purge runs on (§3.3): one
|
|
66
|
+
* process appends *and* deletes, on one handle, with no second writable
|
|
67
|
+
* connection and no cross-process barrier. `undefined` until the stream
|
|
68
|
+
* loop's first reconcile creates the log, and again once the writer has
|
|
69
|
+
* failed soft and deleted the file — both of which a purge cycle treats as
|
|
70
|
+
* "skip", not as an error.
|
|
71
|
+
*/
|
|
72
|
+
get connection(): Database | undefined;
|
|
73
|
+
/** Exposed for tests and for the integration assertions on the metrics. */
|
|
74
|
+
state(): SQLiteChangeLogObservabilityState | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Opens the log if necessary and reconciles it with the Postgres resume
|
|
77
|
+
* point. Returns the resume point stored in the log.
|
|
78
|
+
*
|
|
79
|
+
* Returns `undefined` when the writer is disabled. The caller must then use
|
|
80
|
+
* another source.
|
|
81
|
+
*
|
|
82
|
+
* This runs for every connection because Postgres supplies a new resume point
|
|
83
|
+
* after each reconnect. The log can contain transactions above that point.
|
|
84
|
+
* Reconciliation removes them before Postgres sends them again.
|
|
85
|
+
*
|
|
86
|
+
* `resumeFrom` includes cookies because the cookies must match the resume
|
|
87
|
+
* watermark.
|
|
88
|
+
*/
|
|
89
|
+
reconcile(resumeFrom: ChangeLogResumePoint): ChangeLogResumePoint | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Reconciles the log without a Postgres resume point.
|
|
92
|
+
*
|
|
93
|
+
* A valid log uses its own head and cookies. A new or invalid log uses the
|
|
94
|
+
* replica values returned by `seed`.
|
|
95
|
+
*/
|
|
96
|
+
reconcileFromLog(seed: () => ChangeLogResumePoint): ChangeLogResumePoint | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Writes one stream message, committing at transaction boundaries. Never
|
|
99
|
+
* throws: a write failure disables the writer instead of failing the stream.
|
|
100
|
+
*/
|
|
101
|
+
write(change: ChangeStreamData, json: string): void;
|
|
102
|
+
/**
|
|
103
|
+
* Discards the open transaction, if any. Called when the change stream is
|
|
104
|
+
* interrupted mid-transaction.
|
|
105
|
+
*/
|
|
106
|
+
abort(): void;
|
|
107
|
+
close(): void;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=sqlite-change-log-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-change-log-writer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-writer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,8BAA8B,CAAC;AAM3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iDAAiD,CAAC;AAKtF,OAAO,EAUL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAE1B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAML,KAAK,iCAAiC,EACvC,MAAM,kDAAkD,CAAC;AAE1D,MAAM,MAAM,4BAA4B,GAAG;IACzC,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACrD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACtC,6EAA6E;IAC7E,SAAS,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACrC,6BAA6B;IAC7B,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,qBAAqB;;gBAUpB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,4BAA4B;IAM9D,qEAAqE;IACrE,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;;;OAOG;IACH,IAAI,UAAU,IAAI,QAAQ,GAAG,SAAS,CAErC;IAED,2EAA2E;IAC3E,KAAK,IAAI,iCAAiC,GAAG,SAAS;IAItD;;;;;;;;;;;;;OAaG;IACH,SAAS,CACP,UAAU,EAAE,oBAAoB,GAC/B,oBAAoB,GAAG,SAAS;IAInC;;;;;OAKG;IACH,gBAAgB,CACd,IAAI,EAAE,MAAM,oBAAoB,GAC/B,oBAAoB,GAAG,SAAS;IA6GnC;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAkEnD;;;OAGG;IACH,KAAK,IAAI,IAAI;IAab,KAAK,IAAI,IAAI;CAwCd"}
|