@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,417 @@
|
|
|
1
|
+
import { Database } from "../../../../zqlite/src/db.js";
|
|
2
|
+
import { getOrCreateCounter, getOrCreateLatencyHistogram } from "../../observability/metrics.js";
|
|
3
|
+
import { CHANGE_LOG_STREAM_TABLE, readChangeLogMeta } from "../replicator/change-log-db.js";
|
|
4
|
+
import { reconstructWatermarkedChange } from "./change-log-codec.js";
|
|
5
|
+
import { SQLiteChangeLogReader } from "./sqlite-change-log-reader.js";
|
|
6
|
+
import { digestCatchupRange } from "./change-log-compare-digest.js";
|
|
7
|
+
import { isSampledForShard } from "./shard-sampling.js";
|
|
8
|
+
//#region ../zero-cache/src/services/change-streamer/sqlite-change-log-comparator.ts
|
|
9
|
+
/** Interval between comparison cycles. */
|
|
10
|
+
var DEFAULT_COMPARE_INTERVAL_MS = 3e4;
|
|
11
|
+
/** Maximum transactions enumerated per cycle. */
|
|
12
|
+
var DEFAULT_MAX_TRANSACTIONS_PER_CYCLE = 256;
|
|
13
|
+
/** Maximum catchup rows read from each store per cycle. */
|
|
14
|
+
var DEFAULT_MAX_ROWS_PER_SOURCE_PER_CYCLE = 1e3;
|
|
15
|
+
/**
|
|
16
|
+
* Maximum catchup bytes read from each store per cycle.
|
|
17
|
+
*
|
|
18
|
+
* Rows bound the per-row overhead. Bytes bound the read and hash work,
|
|
19
|
+
* which scales with payload size instead of row count. Normal traffic
|
|
20
|
+
* reaches the row limit first. Wide payloads reach this one.
|
|
21
|
+
*/
|
|
22
|
+
var DEFAULT_MAX_BYTES_PER_SOURCE_PER_CYCLE = 32 * 1024 * 1024;
|
|
23
|
+
/** Rows per SQLite catchup batch. */
|
|
24
|
+
var DEFAULT_READ_BATCH_ROWS = 1e3;
|
|
25
|
+
/** Uses separate aggregates so SQLite can use an index for each value. */
|
|
26
|
+
var SQLITE_BOUNDS_SQL = `
|
|
27
|
+
SELECT
|
|
28
|
+
(SELECT min("watermark") FROM "${CHANGE_LOG_STREAM_TABLE}")
|
|
29
|
+
AS "minWatermark",
|
|
30
|
+
(SELECT max("watermark") FROM "${CHANGE_LOG_STREAM_TABLE}")
|
|
31
|
+
AS "headWatermark"
|
|
32
|
+
`;
|
|
33
|
+
/** Lists committed transaction watermarks in `(@after, @through]`. */
|
|
34
|
+
var SQLITE_COMMITS_SQL = `
|
|
35
|
+
SELECT "watermark" FROM "${CHANGE_LOG_STREAM_TABLE}"
|
|
36
|
+
WHERE "precommit" IS NOT NULL
|
|
37
|
+
AND "watermark" > @after
|
|
38
|
+
AND "watermark" <= @through
|
|
39
|
+
ORDER BY "watermark"
|
|
40
|
+
LIMIT @limit
|
|
41
|
+
`;
|
|
42
|
+
/**
|
|
43
|
+
* Compares SQLite and Postgres catchup output without affecting subscribers.
|
|
44
|
+
*
|
|
45
|
+
* Each cycle compares complete ranges through the lower store head.
|
|
46
|
+
* It checks every transaction for presence and samples payloads.
|
|
47
|
+
* A bounds change makes the affected result `inconclusive`.
|
|
48
|
+
* Postgres remains authoritative.
|
|
49
|
+
*/
|
|
50
|
+
var SQLiteChangeLogComparator = class {
|
|
51
|
+
#lc;
|
|
52
|
+
#shard;
|
|
53
|
+
#changeLogFile;
|
|
54
|
+
#identity;
|
|
55
|
+
#pg;
|
|
56
|
+
#opts;
|
|
57
|
+
#now;
|
|
58
|
+
#setTimeoutFn;
|
|
59
|
+
#clearTimeoutFn;
|
|
60
|
+
#yield;
|
|
61
|
+
#compareResults = getOrCreateCounter("replica", "sqlite_change_log.compare_result", "Outcomes of the dark comparison between what SQLite catchup serves and what PG catchup serves, per committed transaction. `inconclusive` means a purge or reseed moved the pinned bounds mid-comparison.");
|
|
62
|
+
#compareCycles = getOrCreateCounter("replica", "sqlite_change_log.compare_cycles", "SQLite change-log comparison cycles, by result.");
|
|
63
|
+
#cycleDuration = getOrCreateLatencyHistogram("replica", "sqlite_change_log.compare_cycle_duration", "Duration of one SQLite change-log comparison cycle.");
|
|
64
|
+
/** Last handled commit watermark. A restart derives it from current bounds. */
|
|
65
|
+
#cursor;
|
|
66
|
+
#running;
|
|
67
|
+
#timer;
|
|
68
|
+
#stopped = false;
|
|
69
|
+
#continueWithoutDelay = false;
|
|
70
|
+
#fileGeneration = 0;
|
|
71
|
+
constructor(lc, shard, changeLogFile, identity, pg, opts) {
|
|
72
|
+
this.#lc = lc.withContext("component", "sqlite-change-log-compare");
|
|
73
|
+
this.#shard = shard;
|
|
74
|
+
this.#changeLogFile = changeLogFile;
|
|
75
|
+
this.#identity = identity;
|
|
76
|
+
this.#pg = pg;
|
|
77
|
+
this.#opts = opts;
|
|
78
|
+
this.#now = opts.now ?? Date.now;
|
|
79
|
+
this.#setTimeoutFn = opts.setTimeoutFn ?? setTimeout;
|
|
80
|
+
this.#clearTimeoutFn = opts.clearTimeoutFn ?? clearTimeout;
|
|
81
|
+
this.#yield = opts.yieldFn ?? (() => new Promise((resolve) => setImmediate(resolve)));
|
|
82
|
+
this.#scheduleNext();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Runs one cycle. Concurrent calls share the same promise.
|
|
86
|
+
* Errors produce `skipped: 'error'`.
|
|
87
|
+
*/
|
|
88
|
+
compareOnce() {
|
|
89
|
+
if (this.#stopped) return Promise.resolve({
|
|
90
|
+
kind: "skipped",
|
|
91
|
+
reason: "stopped"
|
|
92
|
+
});
|
|
93
|
+
if (this.#running === void 0) this.#running = this.#runCycle().then((result) => {
|
|
94
|
+
if (this.#continueWithoutDelay) this.#rescheduleNext(0);
|
|
95
|
+
return result;
|
|
96
|
+
}).finally(() => {
|
|
97
|
+
this.#running = void 0;
|
|
98
|
+
});
|
|
99
|
+
return this.#running;
|
|
100
|
+
}
|
|
101
|
+
stop() {
|
|
102
|
+
this.#stopped = true;
|
|
103
|
+
if (this.#timer !== void 0) {
|
|
104
|
+
this.#clearTimeoutFn(this.#timer);
|
|
105
|
+
this.#timer = void 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Invalidates cycles that can still read a replaced file. */
|
|
109
|
+
invalidate() {
|
|
110
|
+
this.#fileGeneration++;
|
|
111
|
+
this.#cursor = void 0;
|
|
112
|
+
this.#continueWithoutDelay = false;
|
|
113
|
+
}
|
|
114
|
+
#scheduleNext(delayMs = this.#opts.intervalMs ?? DEFAULT_COMPARE_INTERVAL_MS) {
|
|
115
|
+
if (this.#stopped || this.#timer !== void 0) return;
|
|
116
|
+
this.#timer = this.#setTimeoutFn(() => {
|
|
117
|
+
this.#timer = void 0;
|
|
118
|
+
this.compareOnce().finally(() => this.#scheduleNext());
|
|
119
|
+
}, delayMs);
|
|
120
|
+
}
|
|
121
|
+
#rescheduleNext(delayMs) {
|
|
122
|
+
if (this.#timer !== void 0) {
|
|
123
|
+
this.#clearTimeoutFn(this.#timer);
|
|
124
|
+
this.#timer = void 0;
|
|
125
|
+
}
|
|
126
|
+
this.#scheduleNext(delayMs);
|
|
127
|
+
}
|
|
128
|
+
async #runCycle() {
|
|
129
|
+
const startedAt = performance.now();
|
|
130
|
+
const fileGeneration = this.#fileGeneration;
|
|
131
|
+
this.#continueWithoutDelay = false;
|
|
132
|
+
let db;
|
|
133
|
+
let reader;
|
|
134
|
+
try {
|
|
135
|
+
try {
|
|
136
|
+
db = new Database(this.#lc, this.#changeLogFile, { readonly: true });
|
|
137
|
+
} catch {
|
|
138
|
+
return this.#skipped("log-unavailable");
|
|
139
|
+
}
|
|
140
|
+
const pinned = await this.#pinBounds(db);
|
|
141
|
+
if (fileGeneration !== this.#fileGeneration) return this.#skipped("log-rebuilt");
|
|
142
|
+
if (typeof pinned === "string") return this.#skipped(pinned);
|
|
143
|
+
const { meta } = pinned;
|
|
144
|
+
const from = maxWatermark(this.#cursor ?? meta.seedWatermark, meta.seedWatermark, pinned.pgMinWatermark, pinned.sqlite.minWatermark);
|
|
145
|
+
const through = minWatermark(pinned.pgLastWatermark, pinned.sqlite.headWatermark);
|
|
146
|
+
if (through <= from) return this.#skipped("nothing-to-compare");
|
|
147
|
+
let transactions = 0;
|
|
148
|
+
let sampled = 0;
|
|
149
|
+
const outcomes = {
|
|
150
|
+
match: 0,
|
|
151
|
+
mismatch: 0,
|
|
152
|
+
inconclusive: 0,
|
|
153
|
+
deferred: 0,
|
|
154
|
+
oversized: 0
|
|
155
|
+
};
|
|
156
|
+
const record = (watermark, outcome) => {
|
|
157
|
+
this.#compareResults.add(1, { outcome });
|
|
158
|
+
outcomes[outcome]++;
|
|
159
|
+
if (outcome === "mismatch") this.#lc.error?.("SQLite change-log catchup output diverged from Postgres", { sqliteChangeLogCompare: { watermark } });
|
|
160
|
+
};
|
|
161
|
+
reader = new SQLiteChangeLogReader(this.#lc, this.#changeLogFile);
|
|
162
|
+
const plan = reader.plan(from);
|
|
163
|
+
if (plan.kind === "not-ready") return this.#skipped("log-unavailable");
|
|
164
|
+
if (plan.kind === "ahead") return this.#skipped("nothing-to-compare");
|
|
165
|
+
if (plan.kind === "too-old") {
|
|
166
|
+
const reconfirmed = await this.#reconfirm(db, pinned, from);
|
|
167
|
+
const outcome = fileGeneration === this.#fileGeneration ? reconfirmed : "inconclusive";
|
|
168
|
+
record(from, outcome);
|
|
169
|
+
if (outcome === "inconclusive") return this.#compared(from, from, transactions, sampled, outcomes);
|
|
170
|
+
}
|
|
171
|
+
const limit = Math.max(1, this.#opts.maxTransactionsPerCycle ?? DEFAULT_MAX_TRANSACTIONS_PER_CYCLE);
|
|
172
|
+
const union = await this.#enumerateCommits(db, from, through, limit);
|
|
173
|
+
if (fileGeneration !== this.#fileGeneration) return this.#skipped("log-rebuilt");
|
|
174
|
+
const maxRowsPerSource = Math.max(1, this.#opts.maxRowsPerSourcePerCycle ?? DEFAULT_MAX_ROWS_PER_SOURCE_PER_CYCLE);
|
|
175
|
+
const maxBytesPerSource = Math.max(1, this.#opts.maxBytesPerSourcePerCycle ?? DEFAULT_MAX_BYTES_PER_SOURCE_PER_CYCLE);
|
|
176
|
+
let sqliteRowsRead = 0;
|
|
177
|
+
let pgRowsRead = 0;
|
|
178
|
+
let sqliteBytesRead = 0;
|
|
179
|
+
let pgBytesRead = 0;
|
|
180
|
+
let previousBoundary = from;
|
|
181
|
+
let handledThrough = from;
|
|
182
|
+
for (const { watermark, inSqlite, inPg } of union) {
|
|
183
|
+
if (this.#stopped) break;
|
|
184
|
+
let stopAfterTransaction = false;
|
|
185
|
+
if (!inSqlite || !inPg) {
|
|
186
|
+
const outcome = await this.#reconfirm(db, pinned, watermark);
|
|
187
|
+
record(watermark, fileGeneration === this.#fileGeneration ? outcome : "inconclusive");
|
|
188
|
+
} else if (isSampledForShard(this.#shard, watermark, this.#opts.comparePercent)) {
|
|
189
|
+
const sqliteRowsRemaining = maxRowsPerSource - sqliteRowsRead;
|
|
190
|
+
const pgRowsRemaining = maxRowsPerSource - pgRowsRead;
|
|
191
|
+
const sqliteBytesRemaining = maxBytesPerSource - sqliteBytesRead;
|
|
192
|
+
const pgBytesRemaining = maxBytesPerSource - pgBytesRead;
|
|
193
|
+
if (sqliteRowsRemaining === 0 || pgRowsRemaining === 0 || sqliteBytesRemaining <= 0 || pgBytesRemaining <= 0) {
|
|
194
|
+
record(watermark, "deferred");
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
sampled++;
|
|
198
|
+
const comparison = await this.#compareTransaction(db, reader, pinned, previousBoundary, watermark, sqliteRowsRemaining, pgRowsRemaining, sqliteBytesRemaining, pgBytesRemaining, maxRowsPerSource, maxBytesPerSource);
|
|
199
|
+
sqliteRowsRead += comparison.sqliteRowsRead;
|
|
200
|
+
pgRowsRead += comparison.pgRowsRead;
|
|
201
|
+
sqliteBytesRead += comparison.sqliteBytesRead;
|
|
202
|
+
pgBytesRead += comparison.pgBytesRead;
|
|
203
|
+
const outcome = fileGeneration === this.#fileGeneration ? comparison.outcome : "inconclusive";
|
|
204
|
+
record(watermark, outcome);
|
|
205
|
+
if (outcome === "inconclusive") break;
|
|
206
|
+
if (outcome === "deferred") break;
|
|
207
|
+
if (outcome === "oversized") stopAfterTransaction = true;
|
|
208
|
+
await this.#yield();
|
|
209
|
+
}
|
|
210
|
+
previousBoundary = watermark;
|
|
211
|
+
handledThrough = watermark;
|
|
212
|
+
transactions++;
|
|
213
|
+
if (stopAfterTransaction) break;
|
|
214
|
+
}
|
|
215
|
+
if (!this.#stopped && fileGeneration === this.#fileGeneration && outcomes.inconclusive === 0) {
|
|
216
|
+
this.#cursor = handledThrough;
|
|
217
|
+
this.#continueWithoutDelay = handledThrough < through;
|
|
218
|
+
}
|
|
219
|
+
return this.#compared(from, handledThrough, transactions, sampled, outcomes);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
this.#lc.warn?.("error comparing the SQLite change log", e);
|
|
222
|
+
return this.#skipped("error");
|
|
223
|
+
} finally {
|
|
224
|
+
reader?.close();
|
|
225
|
+
db?.close();
|
|
226
|
+
this.#cycleDuration.recordMs(performance.now() - startedAt);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Checks log eligibility and reads the bounds from both stores. */
|
|
230
|
+
async #pinBounds(db) {
|
|
231
|
+
let meta;
|
|
232
|
+
try {
|
|
233
|
+
meta = readChangeLogMeta(db);
|
|
234
|
+
} catch {
|
|
235
|
+
return "log-unavailable";
|
|
236
|
+
}
|
|
237
|
+
if (meta.schemaVersion !== 4) return "ineligible-schema";
|
|
238
|
+
const { epoch, generation, replicaID } = this.#identity;
|
|
239
|
+
if (meta.epoch !== epoch || meta.generation !== generation || meta.replicaID !== replicaID) return "ineligible-identity";
|
|
240
|
+
if (this.#now() - meta.seededAtMs < this.#opts.retentionMs) return "cold-log";
|
|
241
|
+
const sqlite = this.#readSQLiteBounds(db);
|
|
242
|
+
if (sqlite === void 0) return "log-unavailable";
|
|
243
|
+
const { minWatermark, lastWatermark } = await this.#pg.getCatchupBounds();
|
|
244
|
+
if (minWatermark === null) return "nothing-to-compare";
|
|
245
|
+
return {
|
|
246
|
+
meta,
|
|
247
|
+
sqlite,
|
|
248
|
+
pgMinWatermark: minWatermark,
|
|
249
|
+
pgLastWatermark: lastWatermark
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
#readSQLiteBounds(db) {
|
|
253
|
+
const bounds = db.prepare(SQLITE_BOUNDS_SQL).get();
|
|
254
|
+
return bounds.minWatermark === null || bounds.headWatermark === null ? void 0 : {
|
|
255
|
+
minWatermark: bounds.minWatermark,
|
|
256
|
+
headWatermark: bounds.headWatermark
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Merges committed transaction watermarks from both stores.
|
|
261
|
+
* If a list reaches the limit, the result ends at the last complete common range.
|
|
262
|
+
*/
|
|
263
|
+
async #enumerateCommits(db, from, through, limit) {
|
|
264
|
+
let sqlite = db.prepare(SQLITE_COMMITS_SQL).all({
|
|
265
|
+
after: from,
|
|
266
|
+
through,
|
|
267
|
+
limit: limit + 1
|
|
268
|
+
}).map(({ watermark }) => watermark);
|
|
269
|
+
let pg = await this.#pg.listCommitWatermarks(from, through, limit + 1);
|
|
270
|
+
let effectiveThrough = through;
|
|
271
|
+
if (sqlite.length > limit) {
|
|
272
|
+
sqlite.length = limit;
|
|
273
|
+
effectiveThrough = minWatermark(effectiveThrough, sqlite[limit - 1]);
|
|
274
|
+
}
|
|
275
|
+
if (pg.length > limit) {
|
|
276
|
+
pg.length = limit;
|
|
277
|
+
effectiveThrough = minWatermark(effectiveThrough, pg[limit - 1]);
|
|
278
|
+
}
|
|
279
|
+
if (effectiveThrough !== through) {
|
|
280
|
+
sqlite = sqlite.filter((w) => w <= effectiveThrough);
|
|
281
|
+
pg = pg.filter((w) => w <= effectiveThrough);
|
|
282
|
+
}
|
|
283
|
+
const union = [];
|
|
284
|
+
let s = 0;
|
|
285
|
+
let p = 0;
|
|
286
|
+
while (s < sqlite.length || p < pg.length) {
|
|
287
|
+
const sw = s < sqlite.length ? sqlite[s] : void 0;
|
|
288
|
+
const pw = p < pg.length ? pg[p] : void 0;
|
|
289
|
+
if (sw !== void 0 && (pw === void 0 || sw < pw)) {
|
|
290
|
+
union.push({
|
|
291
|
+
watermark: sw,
|
|
292
|
+
inSqlite: true,
|
|
293
|
+
inPg: false
|
|
294
|
+
});
|
|
295
|
+
s++;
|
|
296
|
+
} else if (pw !== void 0 && (sw === void 0 || pw < sw)) {
|
|
297
|
+
union.push({
|
|
298
|
+
watermark: pw,
|
|
299
|
+
inSqlite: false,
|
|
300
|
+
inPg: true
|
|
301
|
+
});
|
|
302
|
+
p++;
|
|
303
|
+
} else if (sw !== void 0) {
|
|
304
|
+
union.push({
|
|
305
|
+
watermark: sw,
|
|
306
|
+
inSqlite: true,
|
|
307
|
+
inPg: true
|
|
308
|
+
});
|
|
309
|
+
s++;
|
|
310
|
+
p++;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return union;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Compares the output in `(previousBoundary, watermark]`.
|
|
317
|
+
* Positional reads do not require either store to contain the boundary row.
|
|
318
|
+
*/
|
|
319
|
+
async #compareTransaction(db, reader, pinned, previousBoundary, watermark, sqliteMaxRows, pgMaxRows, sqliteMaxBytes, pgMaxBytes, maxRowsPerSource, maxBytesPerSource) {
|
|
320
|
+
let sqlite;
|
|
321
|
+
let pg;
|
|
322
|
+
try {
|
|
323
|
+
const readBatchRows = this.#opts.readBatchRows ?? DEFAULT_READ_BATCH_ROWS;
|
|
324
|
+
sqlite = await digestCatchupRange(reader.read(previousBoundary, watermark, Math.min(readBatchRows, sqliteMaxRows), void 0, sqliteMaxRows), watermark, sqliteMaxRows, sqliteMaxBytes);
|
|
325
|
+
pg = await digestCatchupRange(reconstructBatches(this.#pg.readCatchupRange(previousBoundary, watermark, pgMaxRows)), watermark, pgMaxRows, pgMaxBytes);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
this.#lc.warn?.(`error reading transaction ${watermark} for comparison`, e);
|
|
328
|
+
return {
|
|
329
|
+
outcome: await this.#reconfirm(db, pinned, watermark),
|
|
330
|
+
sqliteRowsRead: 0,
|
|
331
|
+
pgRowsRead: 0,
|
|
332
|
+
sqliteBytesRead: 0,
|
|
333
|
+
pgBytesRead: 0
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
const read = {
|
|
337
|
+
sqliteRowsRead: sqlite.rows,
|
|
338
|
+
pgRowsRead: pg.rows,
|
|
339
|
+
sqliteBytesRead: sqlite.bytes,
|
|
340
|
+
pgBytesRead: pg.bytes
|
|
341
|
+
};
|
|
342
|
+
if (sqlite.limitReached || pg.limitReached) {
|
|
343
|
+
const sqliteFresh = sqliteMaxRows === maxRowsPerSource && sqliteMaxBytes === maxBytesPerSource;
|
|
344
|
+
const pgFresh = pgMaxRows === maxRowsPerSource && pgMaxBytes === maxBytesPerSource;
|
|
345
|
+
return {
|
|
346
|
+
outcome: sqlite.limitReached && sqliteFresh || pg.limitReached && pgFresh ? "oversized" : "deferred",
|
|
347
|
+
...read
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (sqlite.digest === pg.digest) return {
|
|
351
|
+
outcome: "match",
|
|
352
|
+
...read
|
|
353
|
+
};
|
|
354
|
+
return {
|
|
355
|
+
outcome: await this.#reconfirm(db, pinned, watermark),
|
|
356
|
+
...read
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Rechecks a suspect result against current store bounds.
|
|
361
|
+
* A moved bound, changed seed, failed read, or lower head makes the result inconclusive.
|
|
362
|
+
*/
|
|
363
|
+
async #reconfirm(db, pinned, watermark) {
|
|
364
|
+
try {
|
|
365
|
+
const meta = readChangeLogMeta(db);
|
|
366
|
+
if (meta.seedWatermark !== pinned.meta.seedWatermark || meta.seededAtMs !== pinned.meta.seededAtMs) return "inconclusive";
|
|
367
|
+
const sqlite = this.#readSQLiteBounds(db);
|
|
368
|
+
const { minWatermark, lastWatermark } = await this.#pg.getCatchupBounds();
|
|
369
|
+
if (sqlite === void 0 || minWatermark === null) return "inconclusive";
|
|
370
|
+
if (sqlite.minWatermark > pinned.sqlite.minWatermark && watermark <= sqlite.minWatermark || minWatermark > pinned.pgMinWatermark && watermark <= minWatermark || watermark > sqlite.headWatermark || watermark > lastWatermark) return "inconclusive";
|
|
371
|
+
return "mismatch";
|
|
372
|
+
} catch {
|
|
373
|
+
return "inconclusive";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
#skipped(reason) {
|
|
377
|
+
this.#compareCycles.add(1, { result: reason });
|
|
378
|
+
if (reason === "error") {} else if (reason !== "nothing-to-compare") this.#lc.debug?.(`skipping SQLite change-log comparison: ${reason}`);
|
|
379
|
+
return {
|
|
380
|
+
kind: "skipped",
|
|
381
|
+
reason
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
#compared(fromWatermark, throughWatermark, transactions, sampled, outcomes) {
|
|
385
|
+
this.#compareCycles.add(1, { result: "compared" });
|
|
386
|
+
const result = {
|
|
387
|
+
kind: "compared",
|
|
388
|
+
fromWatermark,
|
|
389
|
+
throughWatermark,
|
|
390
|
+
transactions,
|
|
391
|
+
sampled,
|
|
392
|
+
matched: outcomes.match,
|
|
393
|
+
mismatched: outcomes.mismatch,
|
|
394
|
+
inconclusive: outcomes.inconclusive,
|
|
395
|
+
deferred: outcomes.deferred,
|
|
396
|
+
oversized: outcomes.oversized
|
|
397
|
+
};
|
|
398
|
+
this.#lc.debug?.("SQLite change-log comparison cycle", { sqliteChangeLogCompare: result });
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
/** Applies the same reconstruction as Postgres catchup. */
|
|
403
|
+
async function* reconstructBatches(batches) {
|
|
404
|
+
for await (const batch of batches) yield batch.map(reconstructWatermarkedChange);
|
|
405
|
+
}
|
|
406
|
+
function minWatermark(a, b) {
|
|
407
|
+
return a < b ? a : b;
|
|
408
|
+
}
|
|
409
|
+
function maxWatermark(...watermarks) {
|
|
410
|
+
let max = watermarks[0];
|
|
411
|
+
for (const w of watermarks) if (w > max) max = w;
|
|
412
|
+
return max;
|
|
413
|
+
}
|
|
414
|
+
//#endregion
|
|
415
|
+
export { SQLiteChangeLogComparator };
|
|
416
|
+
|
|
417
|
+
//# sourceMappingURL=sqlite-change-log-comparator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-change-log-comparator.js","names":["#lc","#shard","#changeLogFile","#identity","#pg","#opts","#now","#setTimeoutFn","#clearTimeoutFn","#yield","#compareResults","#compareCycles","#cycleDuration","#scheduleNext","#stopped","#running","#runCycle","#continueWithoutDelay","#rescheduleNext","#timer","#fileGeneration","#cursor","#skipped","#pinBounds","#reconfirm","#compared","#enumerateCommits","#compareTransaction","#readSQLiteBounds"],"sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-comparator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {\n getOrCreateCounter,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport type {ShardID} from '../../types/shards.ts';\nimport {\n CHANGE_LOG_DB_SCHEMA_VERSION,\n CHANGE_LOG_STREAM_TABLE,\n readChangeLogMeta,\n type ChangeLogIdentity,\n type ChangeLogMeta,\n} from '../replicator/change-log-db.ts';\nimport {\n reconstructWatermarkedChange,\n type ChangeLogEntry,\n} from './change-log-codec.ts';\nimport {\n digestCatchupRange,\n type CatchupRangeDigest,\n} from './change-log-compare-digest.ts';\nimport type {WatermarkedChange} from './change-streamer.ts';\nimport {isSampledForShard} from './shard-sampling.ts';\nimport {SQLiteChangeLogReader} from './sqlite-change-log-reader.ts';\n\n/** Interval between comparison cycles. */\nconst DEFAULT_COMPARE_INTERVAL_MS = 30_000;\n\n/** Maximum transactions enumerated per cycle. */\nconst DEFAULT_MAX_TRANSACTIONS_PER_CYCLE = 256;\n\n/** Maximum catchup rows read from each store per cycle. */\nconst DEFAULT_MAX_ROWS_PER_SOURCE_PER_CYCLE = 1000;\n\n/**\n * Maximum catchup bytes read from each store per cycle.\n *\n * Rows bound the per-row overhead. Bytes bound the read and hash work,\n * which scales with payload size instead of row count. Normal traffic\n * reaches the row limit first. Wide payloads reach this one.\n */\nconst DEFAULT_MAX_BYTES_PER_SOURCE_PER_CYCLE = 32 * 1024 * 1024;\n\n/** Rows per SQLite catchup batch. */\nconst DEFAULT_READ_BATCH_ROWS = 1000;\n\n/**\n * Result for one committed transaction.\n *\n * `mismatch` covers missing, extra, changed, or reordered rows.\n * `inconclusive` means that store bounds changed during the comparison.\n */\nexport type CompareOutcome =\n | 'match'\n | 'mismatch'\n | 'inconclusive'\n | 'deferred'\n | 'oversized';\n\nexport type CompareSkipReason =\n // The comparator was stopped.\n | 'stopped'\n // The writer replaced the file during this cycle.\n | 'log-rebuilt'\n // The file is absent, unreadable, or uninitialized.\n | 'log-unavailable'\n // The schema version does not match this build.\n | 'ineligible-schema'\n // The log belongs to a different replica.\n | 'ineligible-identity'\n // The log is younger than the warm-up period.\n | 'cold-log'\n // The stores have no complete committed range in common.\n | 'nothing-to-compare'\n // The cycle failed. The error was logged and counted.\n | 'error';\n\nexport type CompareCycleResult =\n | {readonly kind: 'skipped'; readonly reason: CompareSkipReason}\n | {\n readonly kind: 'compared';\n /** Exclusive lower bound of the compared range. */\n readonly fromWatermark: string;\n /** Inclusive upper bound handled. The next cycle resumes from here. */\n readonly throughWatermark: string;\n /** Committed transactions handled through `throughWatermark`. */\n readonly transactions: number;\n /** Transactions with compared catchup output. */\n readonly sampled: number;\n readonly matched: number;\n readonly mismatched: number;\n /** Suspects rejected because store bounds changed. */\n readonly inconclusive: number;\n /** Sampled transactions deferred to a fresh budget. */\n readonly deferred: number;\n /** Sampled transactions that exceed a fresh budget. */\n readonly oversized: number;\n };\n\n/** Provides the Postgres side of the catchup comparison. */\nexport interface PGChangeLogRangeReader {\n getCatchupBounds(): Promise<{\n minWatermark: string | null;\n lastWatermark: string;\n }>;\n listCommitWatermarks(\n afterWatermark: string,\n throughWatermark: string,\n limit: number,\n ): Promise<string[]>;\n readCatchupRange(\n afterWatermark: string,\n throughWatermark: string,\n batchRows?: number | undefined,\n ): AsyncIterable<ChangeLogEntry[]>;\n}\n\nexport type SQLiteChangeLogCompareOptions = {\n /** Stable percentage of transactions selected for payload comparison. */\n readonly comparePercent: number;\n /** Warm-up time after a seed. The comparator skips a younger log. */\n readonly retentionMs: number;\n readonly intervalMs?: number | undefined;\n readonly maxTransactionsPerCycle?: number | undefined;\n /** Maximum catchup rows read from each store in one cycle. */\n readonly maxRowsPerSourcePerCycle?: number | undefined;\n /** Maximum catchup bytes read from each store in one cycle. */\n readonly maxBytesPerSourcePerCycle?: number | undefined;\n readonly readBatchRows?: number | undefined;\n /** Clock for the warm-up period. Defaults to `Date.now`. */\n readonly now?: (() => number) | undefined;\n readonly setTimeoutFn?: typeof setTimeout | undefined;\n readonly clearTimeoutFn?: typeof clearTimeout | undefined;\n /** Yields between sampled transactions. Defaults to `setImmediate`. */\n readonly yieldFn?: (() => Promise<void>) | undefined;\n};\n\n/** Uses separate aggregates so SQLite can use an index for each value. */\nconst SQLITE_BOUNDS_SQL = /*sql*/ `\n SELECT\n (SELECT min(\"watermark\") FROM \"${CHANGE_LOG_STREAM_TABLE}\")\n AS \"minWatermark\",\n (SELECT max(\"watermark\") FROM \"${CHANGE_LOG_STREAM_TABLE}\")\n AS \"headWatermark\"\n`;\n\n/** Lists committed transaction watermarks in `(@after, @through]`. */\nconst SQLITE_COMMITS_SQL = /*sql*/ `\n SELECT \"watermark\" FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n WHERE \"precommit\" IS NOT NULL\n AND \"watermark\" > @after\n AND \"watermark\" <= @through\n ORDER BY \"watermark\"\n LIMIT @limit\n`;\n\ntype SQLiteBounds = {\n readonly minWatermark: string;\n readonly headWatermark: string;\n};\n\ntype PinnedBounds = {\n readonly meta: ChangeLogMeta;\n readonly sqlite: SQLiteBounds;\n readonly pgMinWatermark: string;\n readonly pgLastWatermark: string;\n};\n\ntype SampledComparison = {\n readonly outcome: CompareOutcome;\n readonly sqliteRowsRead: number;\n readonly pgRowsRead: number;\n readonly sqliteBytesRead: number;\n readonly pgBytesRead: number;\n};\n\n/**\n * Compares SQLite and Postgres catchup output without affecting subscribers.\n *\n * Each cycle compares complete ranges through the lower store head.\n * It checks every transaction for presence and samples payloads.\n * A bounds change makes the affected result `inconclusive`.\n * Postgres remains authoritative.\n */\nexport class SQLiteChangeLogComparator {\n readonly #lc: LogContext;\n readonly #shard: ShardID;\n readonly #changeLogFile: string;\n readonly #identity: ChangeLogIdentity;\n readonly #pg: PGChangeLogRangeReader;\n readonly #opts: SQLiteChangeLogCompareOptions;\n readonly #now: () => number;\n readonly #setTimeoutFn: typeof setTimeout;\n readonly #clearTimeoutFn: typeof clearTimeout;\n readonly #yield: () => Promise<void>;\n\n readonly #compareResults = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.compare_result',\n 'Outcomes of the dark comparison between what SQLite catchup serves and ' +\n 'what PG catchup serves, per committed transaction. `inconclusive` ' +\n 'means a purge or reseed moved the pinned bounds mid-comparison.',\n );\n readonly #compareCycles = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.compare_cycles',\n 'SQLite change-log comparison cycles, by result.',\n );\n readonly #cycleDuration = getOrCreateLatencyHistogram(\n 'replica',\n 'sqlite_change_log.compare_cycle_duration',\n 'Duration of one SQLite change-log comparison cycle.',\n );\n\n /** Last handled commit watermark. A restart derives it from current bounds. */\n #cursor: string | undefined;\n #running: Promise<CompareCycleResult> | undefined;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #stopped = false;\n #continueWithoutDelay = false;\n #fileGeneration = 0;\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n changeLogFile: string,\n identity: ChangeLogIdentity,\n pg: PGChangeLogRangeReader,\n opts: SQLiteChangeLogCompareOptions,\n ) {\n this.#lc = lc.withContext('component', 'sqlite-change-log-compare');\n this.#shard = shard;\n this.#changeLogFile = changeLogFile;\n this.#identity = identity;\n this.#pg = pg;\n this.#opts = opts;\n this.#now = opts.now ?? Date.now;\n this.#setTimeoutFn = opts.setTimeoutFn ?? setTimeout;\n this.#clearTimeoutFn = opts.clearTimeoutFn ?? clearTimeout;\n this.#yield =\n opts.yieldFn ??\n (() => new Promise<void>(resolve => setImmediate(resolve)));\n this.#scheduleNext();\n }\n\n /**\n * Runs one cycle. Concurrent calls share the same promise.\n * Errors produce `skipped: 'error'`.\n */\n compareOnce(): Promise<CompareCycleResult> {\n if (this.#stopped) {\n return Promise.resolve({kind: 'skipped', reason: 'stopped'});\n }\n if (this.#running === undefined) {\n this.#running = this.#runCycle()\n .then(result => {\n if (this.#continueWithoutDelay) {\n this.#rescheduleNext(0);\n }\n return result;\n })\n .finally(() => {\n this.#running = undefined;\n });\n }\n return this.#running;\n }\n\n stop(): void {\n this.#stopped = true;\n if (this.#timer !== undefined) {\n this.#clearTimeoutFn(this.#timer);\n this.#timer = undefined;\n }\n }\n\n /** Invalidates cycles that can still read a replaced file. */\n invalidate(): void {\n this.#fileGeneration++;\n this.#cursor = undefined;\n this.#continueWithoutDelay = false;\n }\n\n #scheduleNext(\n delayMs = this.#opts.intervalMs ?? DEFAULT_COMPARE_INTERVAL_MS,\n ): void {\n if (this.#stopped || this.#timer !== undefined) {\n return;\n }\n this.#timer = this.#setTimeoutFn(() => {\n this.#timer = undefined;\n void this.compareOnce().finally(() => this.#scheduleNext());\n }, delayMs);\n }\n\n #rescheduleNext(delayMs: number): void {\n if (this.#timer !== undefined) {\n this.#clearTimeoutFn(this.#timer);\n this.#timer = undefined;\n }\n this.#scheduleNext(delayMs);\n }\n\n async #runCycle(): Promise<CompareCycleResult> {\n const startedAt = performance.now();\n const fileGeneration = this.#fileGeneration;\n this.#continueWithoutDelay = false;\n let db: Database | undefined;\n let reader: SQLiteChangeLogReader | undefined;\n try {\n try {\n // A readonly handle does not create a missing file.\n db = new Database(this.#lc, this.#changeLogFile, {readonly: true});\n } catch {\n return this.#skipped('log-unavailable');\n }\n\n const pinned = await this.#pinBounds(db);\n if (fileGeneration !== this.#fileGeneration) {\n return this.#skipped('log-rebuilt');\n }\n if (typeof pinned === 'string') {\n return this.#skipped(pinned);\n }\n const {meta} = pinned;\n\n const from = maxWatermark(\n this.#cursor ?? meta.seedWatermark,\n // Exclude the synthetic SQLite seed transaction.\n // The Postgres seed has no `precommit`, so enumeration also excludes it.\n meta.seedWatermark,\n // A store cannot serve the transaction at its minimum watermark.\n pinned.pgMinWatermark,\n pinned.sqlite.minWatermark,\n );\n const through = minWatermark(\n pinned.pgLastWatermark,\n pinned.sqlite.headWatermark,\n );\n if (through <= from) {\n return this.#skipped('nothing-to-compare');\n }\n\n let transactions = 0;\n let sampled = 0;\n const outcomes: Record<CompareOutcome, number> = {\n match: 0,\n mismatch: 0,\n inconclusive: 0,\n deferred: 0,\n oversized: 0,\n };\n const record = (watermark: string, outcome: CompareOutcome) => {\n this.#compareResults.add(1, {outcome});\n outcomes[outcome]++;\n if (outcome === 'mismatch') {\n // Log only the watermark because payloads contain customer data.\n this.#lc.error?.(\n 'SQLite change-log catchup output diverged from Postgres',\n {sqliteChangeLogCompare: {watermark}},\n );\n }\n };\n\n reader = new SQLiteChangeLogReader(this.#lc, this.#changeLogFile);\n const plan = reader.plan(from);\n if (plan.kind === 'not-ready') {\n return this.#skipped('log-unavailable');\n }\n if (plan.kind === 'ahead') {\n return this.#skipped('nothing-to-compare');\n }\n if (plan.kind === 'too-old') {\n // A fixed lower bound makes this result a mismatch.\n // A changed lower bound makes it inconclusive.\n const reconfirmed = await this.#reconfirm(db, pinned, from);\n const outcome =\n fileGeneration === this.#fileGeneration\n ? reconfirmed\n : 'inconclusive';\n record(from, outcome);\n if (outcome === 'inconclusive') {\n // Keep the cursor unchanged because the bounds moved.\n return this.#compared(from, from, transactions, sampled, outcomes);\n }\n // Positional reads can continue after a missing boundary.\n // Advancing avoids reporting the same boundary in every cycle.\n }\n\n const limit = Math.max(\n 1,\n this.#opts.maxTransactionsPerCycle ??\n DEFAULT_MAX_TRANSACTIONS_PER_CYCLE,\n );\n const union = await this.#enumerateCommits(db, from, through, limit);\n if (fileGeneration !== this.#fileGeneration) {\n return this.#skipped('log-rebuilt');\n }\n const maxRowsPerSource = Math.max(\n 1,\n this.#opts.maxRowsPerSourcePerCycle ??\n DEFAULT_MAX_ROWS_PER_SOURCE_PER_CYCLE,\n );\n const maxBytesPerSource = Math.max(\n 1,\n this.#opts.maxBytesPerSourcePerCycle ??\n DEFAULT_MAX_BYTES_PER_SOURCE_PER_CYCLE,\n );\n\n let sqliteRowsRead = 0;\n let pgRowsRead = 0;\n let sqliteBytesRead = 0;\n let pgBytesRead = 0;\n let previousBoundary = from;\n let handledThrough = from;\n for (const {watermark, inSqlite, inPg} of union) {\n if (this.#stopped) {\n break;\n }\n let stopAfterTransaction = false;\n if (!inSqlite || !inPg) {\n // Check every transaction for presence because this needs no payload read.\n const outcome = await this.#reconfirm(db, pinned, watermark);\n record(\n watermark,\n fileGeneration === this.#fileGeneration ? outcome : 'inconclusive',\n );\n } else if (\n isSampledForShard(this.#shard, watermark, this.#opts.comparePercent)\n ) {\n const sqliteRowsRemaining = maxRowsPerSource - sqliteRowsRead;\n const pgRowsRemaining = maxRowsPerSource - pgRowsRead;\n // A read may pass the byte budget by its last row, so this\n // compares against zero rather than checking for equality.\n const sqliteBytesRemaining = maxBytesPerSource - sqliteBytesRead;\n const pgBytesRemaining = maxBytesPerSource - pgBytesRead;\n if (\n sqliteRowsRemaining === 0 ||\n pgRowsRemaining === 0 ||\n sqliteBytesRemaining <= 0 ||\n pgBytesRemaining <= 0\n ) {\n record(watermark, 'deferred');\n break;\n }\n sampled++;\n const comparison = await this.#compareTransaction(\n db,\n reader,\n pinned,\n previousBoundary,\n watermark,\n sqliteRowsRemaining,\n pgRowsRemaining,\n sqliteBytesRemaining,\n pgBytesRemaining,\n maxRowsPerSource,\n maxBytesPerSource,\n );\n sqliteRowsRead += comparison.sqliteRowsRead;\n pgRowsRead += comparison.pgRowsRead;\n sqliteBytesRead += comparison.sqliteBytesRead;\n pgBytesRead += comparison.pgBytesRead;\n const outcome =\n fileGeneration === this.#fileGeneration\n ? comparison.outcome\n : 'inconclusive';\n record(watermark, outcome);\n if (outcome === 'inconclusive') {\n break;\n }\n if (outcome === 'deferred') {\n break;\n }\n if (outcome === 'oversized') {\n // Advance past a transaction that cannot fit in a fresh budget.\n stopAfterTransaction = true;\n }\n // Yield between sampled reads so other stream work can continue.\n await this.#yield();\n }\n previousBoundary = watermark;\n handledThrough = watermark;\n transactions++;\n if (stopAfterTransaction) {\n break;\n }\n }\n\n if (\n !this.#stopped &&\n fileGeneration === this.#fileGeneration &&\n outcomes.inconclusive === 0\n ) {\n this.#cursor = handledThrough;\n this.#continueWithoutDelay = handledThrough < through;\n }\n return this.#compared(\n from,\n handledThrough,\n transactions,\n sampled,\n outcomes,\n );\n } catch (e) {\n this.#lc.warn?.('error comparing the SQLite change log', e);\n return this.#skipped('error');\n } finally {\n reader?.close();\n db?.close();\n this.#cycleDuration.recordMs(performance.now() - startedAt);\n }\n }\n\n /** Checks log eligibility and reads the bounds from both stores. */\n async #pinBounds(db: Database): Promise<PinnedBounds | CompareSkipReason> {\n let meta: ChangeLogMeta;\n try {\n meta = readChangeLogMeta(db);\n } catch {\n // The writer has not initialized the log.\n return 'log-unavailable';\n }\n if (meta.schemaVersion !== CHANGE_LOG_DB_SCHEMA_VERSION) {\n return 'ineligible-schema';\n }\n const {epoch, generation, replicaID} = this.#identity;\n if (\n meta.epoch !== epoch ||\n meta.generation !== generation ||\n meta.replicaID !== replicaID\n ) {\n return 'ineligible-identity';\n }\n if (this.#now() - meta.seededAtMs < this.#opts.retentionMs) {\n return 'cold-log';\n }\n const sqlite = this.#readSQLiteBounds(db);\n if (sqlite === undefined) {\n return 'log-unavailable';\n }\n const {minWatermark, lastWatermark} = await this.#pg.getCatchupBounds();\n if (minWatermark === null) {\n // Only a new replica has an empty Postgres change log.\n return 'nothing-to-compare';\n }\n return {\n meta,\n sqlite,\n pgMinWatermark: minWatermark,\n pgLastWatermark: lastWatermark,\n };\n }\n\n #readSQLiteBounds(db: Database): SQLiteBounds | undefined {\n const bounds = db\n .prepare(SQLITE_BOUNDS_SQL)\n .get<{minWatermark: string | null; headWatermark: string | null}>();\n return bounds.minWatermark === null || bounds.headWatermark === null\n ? undefined\n : {\n minWatermark: bounds.minWatermark,\n headWatermark: bounds.headWatermark,\n };\n }\n\n /**\n * Merges committed transaction watermarks from both stores.\n * If a list reaches the limit, the result ends at the last complete common range.\n */\n async #enumerateCommits(\n db: Database,\n from: string,\n through: string,\n limit: number,\n ): Promise<{watermark: string; inSqlite: boolean; inPg: boolean}[]> {\n let sqlite: string[] = db\n .prepare(SQLITE_COMMITS_SQL)\n .all<{watermark: string}>({after: from, through, limit: limit + 1})\n .map(({watermark}) => watermark);\n let pg = await this.#pg.listCommitWatermarks(from, through, limit + 1);\n\n let effectiveThrough = through;\n if (sqlite.length > limit) {\n sqlite.length = limit;\n effectiveThrough = minWatermark(effectiveThrough, sqlite[limit - 1]);\n }\n if (pg.length > limit) {\n pg.length = limit;\n effectiveThrough = minWatermark(effectiveThrough, pg[limit - 1]);\n }\n if (effectiveThrough !== through) {\n sqlite = sqlite.filter(w => w <= effectiveThrough);\n pg = pg.filter(w => w <= effectiveThrough);\n }\n\n const union: {watermark: string; inSqlite: boolean; inPg: boolean}[] = [];\n let s = 0;\n let p = 0;\n while (s < sqlite.length || p < pg.length) {\n const sw = s < sqlite.length ? sqlite[s] : undefined;\n const pw = p < pg.length ? pg[p] : undefined;\n if (sw !== undefined && (pw === undefined || sw < pw)) {\n union.push({watermark: sw, inSqlite: true, inPg: false});\n s++;\n } else if (pw !== undefined && (sw === undefined || pw < sw)) {\n union.push({watermark: pw, inSqlite: false, inPg: true});\n p++;\n } else if (sw !== undefined) {\n union.push({watermark: sw, inSqlite: true, inPg: true});\n s++;\n p++;\n }\n }\n return union;\n }\n\n /**\n * Compares the output in `(previousBoundary, watermark]`.\n * Positional reads do not require either store to contain the boundary row.\n */\n async #compareTransaction(\n db: Database,\n reader: SQLiteChangeLogReader,\n pinned: PinnedBounds,\n previousBoundary: string,\n watermark: string,\n sqliteMaxRows: number,\n pgMaxRows: number,\n sqliteMaxBytes: number,\n pgMaxBytes: number,\n maxRowsPerSource: number,\n maxBytesPerSource: number,\n ): Promise<SampledComparison> {\n let sqlite: CatchupRangeDigest;\n let pg: CatchupRangeDigest;\n try {\n const readBatchRows = this.#opts.readBatchRows ?? DEFAULT_READ_BATCH_ROWS;\n sqlite = await digestCatchupRange(\n reader.read(\n previousBoundary,\n watermark,\n Math.min(readBatchRows, sqliteMaxRows),\n undefined,\n sqliteMaxRows,\n ),\n watermark,\n sqliteMaxRows,\n sqliteMaxBytes,\n );\n pg = await digestCatchupRange(\n reconstructBatches(\n this.#pg.readCatchupRange(previousBoundary, watermark, pgMaxRows),\n ),\n watermark,\n pgMaxRows,\n pgMaxBytes,\n );\n } catch (e) {\n // A failed catchup read makes the transaction suspect.\n this.#lc.warn?.(\n `error reading transaction ${watermark} for comparison`,\n e,\n );\n return {\n outcome: await this.#reconfirm(db, pinned, watermark),\n sqliteRowsRead: 0,\n pgRowsRead: 0,\n sqliteBytesRead: 0,\n pgBytesRead: 0,\n };\n }\n const read = {\n sqliteRowsRead: sqlite.rows,\n pgRowsRead: pg.rows,\n sqliteBytesRead: sqlite.bytes,\n pgBytesRead: pg.bytes,\n };\n if (sqlite.limitReached || pg.limitReached) {\n // Skip a transaction that cannot fit in a fresh budget.\n // Defer one that does not fit only in the remaining budget.\n // A budget is fresh when neither dimension has been spent.\n const sqliteFresh =\n sqliteMaxRows === maxRowsPerSource &&\n sqliteMaxBytes === maxBytesPerSource;\n const pgFresh =\n pgMaxRows === maxRowsPerSource && pgMaxBytes === maxBytesPerSource;\n const outcome =\n (sqlite.limitReached && sqliteFresh) || (pg.limitReached && pgFresh)\n ? 'oversized'\n : 'deferred';\n return {outcome, ...read};\n }\n if (sqlite.digest === pg.digest) {\n return {outcome: 'match', ...read};\n }\n return {\n outcome: await this.#reconfirm(db, pinned, watermark),\n ...read,\n };\n }\n\n /**\n * Rechecks a suspect result against current store bounds.\n * A moved bound, changed seed, failed read, or lower head makes the result inconclusive.\n */\n async #reconfirm(\n db: Database,\n pinned: PinnedBounds,\n watermark: string,\n ): Promise<'mismatch' | 'inconclusive'> {\n try {\n const meta = readChangeLogMeta(db);\n if (\n meta.seedWatermark !== pinned.meta.seedWatermark ||\n meta.seededAtMs !== pinned.meta.seededAtMs\n ) {\n return 'inconclusive';\n }\n const sqlite = this.#readSQLiteBounds(db);\n const {minWatermark, lastWatermark} = await this.#pg.getCatchupBounds();\n if (sqlite === undefined || minWatermark === null) {\n return 'inconclusive';\n }\n if (\n (sqlite.minWatermark > pinned.sqlite.minWatermark &&\n watermark <= sqlite.minWatermark) ||\n (minWatermark > pinned.pgMinWatermark && watermark <= minWatermark) ||\n watermark > sqlite.headWatermark ||\n watermark > lastWatermark\n ) {\n return 'inconclusive';\n }\n return 'mismatch';\n } catch {\n return 'inconclusive';\n }\n }\n\n #skipped(reason: CompareSkipReason): CompareCycleResult {\n this.#compareCycles.add(1, {result: reason});\n if (reason === 'error') {\n // The caller logged the error.\n } else if (reason !== 'nothing-to-compare') {\n this.#lc.debug?.(`skipping SQLite change-log comparison: ${reason}`);\n }\n return {kind: 'skipped', reason};\n }\n\n #compared(\n fromWatermark: string,\n throughWatermark: string,\n transactions: number,\n sampled: number,\n outcomes: Record<CompareOutcome, number>,\n ): CompareCycleResult {\n this.#compareCycles.add(1, {result: 'compared'});\n const result = {\n kind: 'compared',\n fromWatermark,\n throughWatermark,\n transactions,\n sampled,\n matched: outcomes.match,\n mismatched: outcomes.mismatch,\n inconclusive: outcomes.inconclusive,\n deferred: outcomes.deferred,\n oversized: outcomes.oversized,\n } as const;\n this.#lc.debug?.('SQLite change-log comparison cycle', {\n sqliteChangeLogCompare: result,\n });\n return result;\n }\n}\n\n/** Applies the same reconstruction as Postgres catchup. */\nasync function* reconstructBatches(\n batches: AsyncIterable<ChangeLogEntry[]>,\n): AsyncIterable<WatermarkedChange[]> {\n for await (const batch of batches) {\n yield batch.map(reconstructWatermarkedChange);\n }\n}\n\nfunction minWatermark(a: string, b: string): string {\n return a < b ? a : b;\n}\n\nfunction maxWatermark(...watermarks: string[]): string {\n let max = watermarks[0];\n for (const w of watermarks) {\n if (w > max) {\n max = w;\n }\n }\n return max;\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAM,8BAA8B;;AAGpC,IAAM,qCAAqC;;AAG3C,IAAM,wCAAwC;;;;;;;;AAS9C,IAAM,yCAAyC,KAAK,OAAO;;AAG3D,IAAM,0BAA0B;;AA8FhC,IAAM,oBAA4B;;qCAEG,wBAAwB;;qCAExB,wBAAwB;;;;AAK7D,IAAM,qBAA6B;6BACN,wBAAwB;;;;;;;;;;;;;;;AAoCrD,IAAa,4BAAb,MAAuC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,kBAA2B,mBACzB,WACA,oCACA,0MAGF;CACA,iBAA0B,mBACxB,WACA,oCACA,iDACF;CACA,iBAA0B,4BACxB,WACA,4CACA,qDACF;;CAGA;CACA;CACA;CACA,WAAW;CACX,wBAAwB;CACxB,kBAAkB;CAElB,YACE,IACA,OACA,eACA,UACA,IACA,MACA;EACA,KAAKA,MAAM,GAAG,YAAY,aAAa,2BAA2B;EAClE,KAAKC,SAAS;EACd,KAAKC,iBAAiB;EACtB,KAAKC,YAAY;EACjB,KAAKC,MAAM;EACX,KAAKC,QAAQ;EACb,KAAKC,OAAO,KAAK,OAAO,KAAK;EAC7B,KAAKC,gBAAgB,KAAK,gBAAgB;EAC1C,KAAKC,kBAAkB,KAAK,kBAAkB;EAC9C,KAAKC,SACH,KAAK,kBACE,IAAI,SAAc,YAAW,aAAa,OAAO,CAAC;EAC3D,KAAKI,cAAc;CACrB;;;;;CAMA,cAA2C;EACzC,IAAI,KAAKC,UACP,OAAO,QAAQ,QAAQ;GAAC,MAAM;GAAW,QAAQ;EAAS,CAAC;EAE7D,IAAI,KAAKC,aAAa,KAAA,GACpB,KAAKA,WAAW,KAAKC,UAAU,EAC5B,MAAK,WAAU;GACd,IAAI,KAAKC,uBACP,KAAKC,gBAAgB,CAAC;GAExB,OAAO;EACT,CAAC,EACA,cAAc;GACb,KAAKH,WAAW,KAAA;EAClB,CAAC;EAEL,OAAO,KAAKA;CACd;CAEA,OAAa;EACX,KAAKD,WAAW;EAChB,IAAI,KAAKK,WAAW,KAAA,GAAW;GAC7B,KAAKX,gBAAgB,KAAKW,MAAM;GAChC,KAAKA,SAAS,KAAA;EAChB;CACF;;CAGA,aAAmB;EACjB,KAAKC;EACL,KAAKC,UAAU,KAAA;EACf,KAAKJ,wBAAwB;CAC/B;CAEA,cACE,UAAU,KAAKZ,MAAM,cAAc,6BAC7B;EACN,IAAI,KAAKS,YAAY,KAAKK,WAAW,KAAA,GACnC;EAEF,KAAKA,SAAS,KAAKZ,oBAAoB;GACrC,KAAKY,SAAS,KAAA;GACd,KAAU,YAAY,EAAE,cAAc,KAAKN,cAAc,CAAC;EAC5D,GAAG,OAAO;CACZ;CAEA,gBAAgB,SAAuB;EACrC,IAAI,KAAKM,WAAW,KAAA,GAAW;GAC7B,KAAKX,gBAAgB,KAAKW,MAAM;GAChC,KAAKA,SAAS,KAAA;EAChB;EACA,KAAKN,cAAc,OAAO;CAC5B;CAEA,MAAMG,YAAyC;EAC7C,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,iBAAiB,KAAKI;EAC5B,KAAKH,wBAAwB;EAC7B,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,IAAI;IAEF,KAAK,IAAI,SAAS,KAAKjB,KAAK,KAAKE,gBAAgB,EAAC,UAAU,KAAI,CAAC;GACnE,QAAQ;IACN,OAAO,KAAKoB,SAAS,iBAAiB;GACxC;GAEA,MAAM,SAAS,MAAM,KAAKC,WAAW,EAAE;GACvC,IAAI,mBAAmB,KAAKH,iBAC1B,OAAO,KAAKE,SAAS,aAAa;GAEpC,IAAI,OAAO,WAAW,UACpB,OAAO,KAAKA,SAAS,MAAM;GAE7B,MAAM,EAAC,SAAQ;GAEf,MAAM,OAAO,aACX,KAAKD,WAAW,KAAK,eAGrB,KAAK,eAEL,OAAO,gBACP,OAAO,OAAO,YAChB;GACA,MAAM,UAAU,aACd,OAAO,iBACP,OAAO,OAAO,aAChB;GACA,IAAI,WAAW,MACb,OAAO,KAAKC,SAAS,oBAAoB;GAG3C,IAAI,eAAe;GACnB,IAAI,UAAU;GACd,MAAM,WAA2C;IAC/C,OAAO;IACP,UAAU;IACV,cAAc;IACd,UAAU;IACV,WAAW;GACb;GACA,MAAM,UAAU,WAAmB,YAA4B;IAC7D,KAAKZ,gBAAgB,IAAI,GAAG,EAAC,QAAO,CAAC;IACrC,SAAS;IACT,IAAI,YAAY,YAEd,KAAKV,IAAI,QACP,2DACA,EAAC,wBAAwB,EAAC,UAAS,EAAC,CACtC;GAEJ;GAEA,SAAS,IAAI,sBAAsB,KAAKA,KAAK,KAAKE,cAAc;GAChE,MAAM,OAAO,OAAO,KAAK,IAAI;GAC7B,IAAI,KAAK,SAAS,aAChB,OAAO,KAAKoB,SAAS,iBAAiB;GAExC,IAAI,KAAK,SAAS,SAChB,OAAO,KAAKA,SAAS,oBAAoB;GAE3C,IAAI,KAAK,SAAS,WAAW;IAG3B,MAAM,cAAc,MAAM,KAAKE,WAAW,IAAI,QAAQ,IAAI;IAC1D,MAAM,UACJ,mBAAmB,KAAKJ,kBACpB,cACA;IACN,OAAO,MAAM,OAAO;IACpB,IAAI,YAAY,gBAEd,OAAO,KAAKK,UAAU,MAAM,MAAM,cAAc,SAAS,QAAQ;GAIrE;GAEA,MAAM,QAAQ,KAAK,IACjB,GACA,KAAKpB,MAAM,2BACT,kCACJ;GACA,MAAM,QAAQ,MAAM,KAAKqB,kBAAkB,IAAI,MAAM,SAAS,KAAK;GACnE,IAAI,mBAAmB,KAAKN,iBAC1B,OAAO,KAAKE,SAAS,aAAa;GAEpC,MAAM,mBAAmB,KAAK,IAC5B,GACA,KAAKjB,MAAM,4BACT,qCACJ;GACA,MAAM,oBAAoB,KAAK,IAC7B,GACA,KAAKA,MAAM,6BACT,sCACJ;GAEA,IAAI,iBAAiB;GACrB,IAAI,aAAa;GACjB,IAAI,kBAAkB;GACtB,IAAI,cAAc;GAClB,IAAI,mBAAmB;GACvB,IAAI,iBAAiB;GACrB,KAAK,MAAM,EAAC,WAAW,UAAU,UAAS,OAAO;IAC/C,IAAI,KAAKS,UACP;IAEF,IAAI,uBAAuB;IAC3B,IAAI,CAAC,YAAY,CAAC,MAAM;KAEtB,MAAM,UAAU,MAAM,KAAKU,WAAW,IAAI,QAAQ,SAAS;KAC3D,OACE,WACA,mBAAmB,KAAKJ,kBAAkB,UAAU,cACtD;IACF,OAAO,IACL,kBAAkB,KAAKnB,QAAQ,WAAW,KAAKI,MAAM,cAAc,GACnE;KACA,MAAM,sBAAsB,mBAAmB;KAC/C,MAAM,kBAAkB,mBAAmB;KAG3C,MAAM,uBAAuB,oBAAoB;KACjD,MAAM,mBAAmB,oBAAoB;KAC7C,IACE,wBAAwB,KACxB,oBAAoB,KACpB,wBAAwB,KACxB,oBAAoB,GACpB;MACA,OAAO,WAAW,UAAU;MAC5B;KACF;KACA;KACA,MAAM,aAAa,MAAM,KAAKsB,oBAC5B,IACA,QACA,QACA,kBACA,WACA,qBACA,iBACA,sBACA,kBACA,kBACA,iBACF;KACA,kBAAkB,WAAW;KAC7B,cAAc,WAAW;KACzB,mBAAmB,WAAW;KAC9B,eAAe,WAAW;KAC1B,MAAM,UACJ,mBAAmB,KAAKP,kBACpB,WAAW,UACX;KACN,OAAO,WAAW,OAAO;KACzB,IAAI,YAAY,gBACd;KAEF,IAAI,YAAY,YACd;KAEF,IAAI,YAAY,aAEd,uBAAuB;KAGzB,MAAM,KAAKX,OAAO;IACpB;IACA,mBAAmB;IACnB,iBAAiB;IACjB;IACA,IAAI,sBACF;GAEJ;GAEA,IACE,CAAC,KAAKK,YACN,mBAAmB,KAAKM,mBACxB,SAAS,iBAAiB,GAC1B;IACA,KAAKC,UAAU;IACf,KAAKJ,wBAAwB,iBAAiB;GAChD;GACA,OAAO,KAAKQ,UACV,MACA,gBACA,cACA,SACA,QACF;EACF,SAAS,GAAG;GACV,KAAKzB,IAAI,OAAO,yCAAyC,CAAC;GAC1D,OAAO,KAAKsB,SAAS,OAAO;EAC9B,UAAU;GACR,QAAQ,MAAM;GACd,IAAI,MAAM;GACV,KAAKV,eAAe,SAAS,YAAY,IAAI,IAAI,SAAS;EAC5D;CACF;;CAGA,MAAMW,WAAW,IAAyD;EACxE,IAAI;EACJ,IAAI;GACF,OAAO,kBAAkB,EAAE;EAC7B,QAAQ;GAEN,OAAO;EACT;EACA,IAAI,KAAK,kBAAA,GACP,OAAO;EAET,MAAM,EAAC,OAAO,YAAY,cAAa,KAAKpB;EAC5C,IACE,KAAK,UAAU,SACf,KAAK,eAAe,cACpB,KAAK,cAAc,WAEnB,OAAO;EAET,IAAI,KAAKG,KAAK,IAAI,KAAK,aAAa,KAAKD,MAAM,aAC7C,OAAO;EAET,MAAM,SAAS,KAAKuB,kBAAkB,EAAE;EACxC,IAAI,WAAW,KAAA,GACb,OAAO;EAET,MAAM,EAAC,cAAc,kBAAiB,MAAM,KAAKxB,IAAI,iBAAiB;EACtE,IAAI,iBAAiB,MAEnB,OAAO;EAET,OAAO;GACL;GACA;GACA,gBAAgB;GAChB,iBAAiB;EACnB;CACF;CAEA,kBAAkB,IAAwC;EACxD,MAAM,SAAS,GACZ,QAAQ,iBAAiB,EACzB,IAAiE;EACpE,OAAO,OAAO,iBAAiB,QAAQ,OAAO,kBAAkB,OAC5D,KAAA,IACA;GACE,cAAc,OAAO;GACrB,eAAe,OAAO;EACxB;CACN;;;;;CAMA,MAAMsB,kBACJ,IACA,MACA,SACA,OACkE;EAClE,IAAI,SAAmB,GACpB,QAAQ,kBAAkB,EAC1B,IAAyB;GAAC,OAAO;GAAM;GAAS,OAAO,QAAQ;EAAC,CAAC,EACjE,KAAK,EAAC,gBAAe,SAAS;EACjC,IAAI,KAAK,MAAM,KAAKtB,IAAI,qBAAqB,MAAM,SAAS,QAAQ,CAAC;EAErE,IAAI,mBAAmB;EACvB,IAAI,OAAO,SAAS,OAAO;GACzB,OAAO,SAAS;GAChB,mBAAmB,aAAa,kBAAkB,OAAO,QAAQ,EAAE;EACrE;EACA,IAAI,GAAG,SAAS,OAAO;GACrB,GAAG,SAAS;GACZ,mBAAmB,aAAa,kBAAkB,GAAG,QAAQ,EAAE;EACjE;EACA,IAAI,qBAAqB,SAAS;GAChC,SAAS,OAAO,QAAO,MAAK,KAAK,gBAAgB;GACjD,KAAK,GAAG,QAAO,MAAK,KAAK,gBAAgB;EAC3C;EAEA,MAAM,QAAiE,CAAC;EACxE,IAAI,IAAI;EACR,IAAI,IAAI;EACR,OAAO,IAAI,OAAO,UAAU,IAAI,GAAG,QAAQ;GACzC,MAAM,KAAK,IAAI,OAAO,SAAS,OAAO,KAAK,KAAA;GAC3C,MAAM,KAAK,IAAI,GAAG,SAAS,GAAG,KAAK,KAAA;GACnC,IAAI,OAAO,KAAA,MAAc,OAAO,KAAA,KAAa,KAAK,KAAK;IACrD,MAAM,KAAK;KAAC,WAAW;KAAI,UAAU;KAAM,MAAM;IAAK,CAAC;IACvD;GACF,OAAO,IAAI,OAAO,KAAA,MAAc,OAAO,KAAA,KAAa,KAAK,KAAK;IAC5D,MAAM,KAAK;KAAC,WAAW;KAAI,UAAU;KAAO,MAAM;IAAI,CAAC;IACvD;GACF,OAAO,IAAI,OAAO,KAAA,GAAW;IAC3B,MAAM,KAAK;KAAC,WAAW;KAAI,UAAU;KAAM,MAAM;IAAI,CAAC;IACtD;IACA;GACF;EACF;EACA,OAAO;CACT;;;;;CAMA,MAAMuB,oBACJ,IACA,QACA,QACA,kBACA,WACA,eACA,WACA,gBACA,YACA,kBACA,mBAC4B;EAC5B,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,MAAM,gBAAgB,KAAKtB,MAAM,iBAAiB;GAClD,SAAS,MAAM,mBACb,OAAO,KACL,kBACA,WACA,KAAK,IAAI,eAAe,aAAa,GACrC,KAAA,GACA,aACF,GACA,WACA,eACA,cACF;GACA,KAAK,MAAM,mBACT,mBACE,KAAKD,IAAI,iBAAiB,kBAAkB,WAAW,SAAS,CAClE,GACA,WACA,WACA,UACF;EACF,SAAS,GAAG;GAEV,KAAKJ,IAAI,OACP,6BAA6B,UAAU,kBACvC,CACF;GACA,OAAO;IACL,SAAS,MAAM,KAAKwB,WAAW,IAAI,QAAQ,SAAS;IACpD,gBAAgB;IAChB,YAAY;IACZ,iBAAiB;IACjB,aAAa;GACf;EACF;EACA,MAAM,OAAO;GACX,gBAAgB,OAAO;GACvB,YAAY,GAAG;GACf,iBAAiB,OAAO;GACxB,aAAa,GAAG;EAClB;EACA,IAAI,OAAO,gBAAgB,GAAG,cAAc;GAI1C,MAAM,cACJ,kBAAkB,oBAClB,mBAAmB;GACrB,MAAM,UACJ,cAAc,oBAAoB,eAAe;GAKnD,OAAO;IAAC,SAHL,OAAO,gBAAgB,eAAiB,GAAG,gBAAgB,UACxD,cACA;IACW,GAAG;GAAI;EAC1B;EACA,IAAI,OAAO,WAAW,GAAG,QACvB,OAAO;GAAC,SAAS;GAAS,GAAG;EAAI;EAEnC,OAAO;GACL,SAAS,MAAM,KAAKA,WAAW,IAAI,QAAQ,SAAS;GACpD,GAAG;EACL;CACF;;;;;CAMA,MAAMA,WACJ,IACA,QACA,WACsC;EACtC,IAAI;GACF,MAAM,OAAO,kBAAkB,EAAE;GACjC,IACE,KAAK,kBAAkB,OAAO,KAAK,iBACnC,KAAK,eAAe,OAAO,KAAK,YAEhC,OAAO;GAET,MAAM,SAAS,KAAKI,kBAAkB,EAAE;GACxC,MAAM,EAAC,cAAc,kBAAiB,MAAM,KAAKxB,IAAI,iBAAiB;GACtE,IAAI,WAAW,KAAA,KAAa,iBAAiB,MAC3C,OAAO;GAET,IACG,OAAO,eAAe,OAAO,OAAO,gBACnC,aAAa,OAAO,gBACrB,eAAe,OAAO,kBAAkB,aAAa,gBACtD,YAAY,OAAO,iBACnB,YAAY,eAEZ,OAAO;GAET,OAAO;EACT,QAAQ;GACN,OAAO;EACT;CACF;CAEA,SAAS,QAA+C;EACtD,KAAKO,eAAe,IAAI,GAAG,EAAC,QAAQ,OAAM,CAAC;EAC3C,IAAI,WAAW,SAAS,CAExB,OAAO,IAAI,WAAW,sBACpB,KAAKX,IAAI,QAAQ,0CAA0C,QAAQ;EAErE,OAAO;GAAC,MAAM;GAAW;EAAM;CACjC;CAEA,UACE,eACA,kBACA,cACA,SACA,UACoB;EACpB,KAAKW,eAAe,IAAI,GAAG,EAAC,QAAQ,WAAU,CAAC;EAC/C,MAAM,SAAS;GACb,MAAM;GACN;GACA;GACA;GACA;GACA,SAAS,SAAS;GAClB,YAAY,SAAS;GACrB,cAAc,SAAS;GACvB,UAAU,SAAS;GACnB,WAAW,SAAS;EACtB;EACA,KAAKX,IAAI,QAAQ,sCAAsC,EACrD,wBAAwB,OAC1B,CAAC;EACD,OAAO;CACT;AACF;;AAGA,gBAAgB,mBACd,SACoC;CACpC,WAAW,MAAM,SAAS,SACxB,MAAM,MAAM,IAAI,4BAA4B;AAEhD;AAEA,SAAS,aAAa,GAAW,GAAmB;CAClD,OAAO,IAAI,IAAI,IAAI;AACrB;AAEA,SAAS,aAAa,GAAG,YAA8B;CACrD,IAAI,MAAM,WAAW;CACrB,KAAK,MAAM,KAAK,YACd,IAAI,IAAI,KACN,MAAM;CAGV,OAAO;AACT"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { Database } from '../../../../zqlite/src/db.ts';
|
|
3
|
+
import type { SQLiteChangeLogCleanupGuard } from './sqlite-change-log-catchup.ts';
|
|
4
|
+
/** Why a pass stopped before draining the eligible set. */
|
|
5
|
+
export type PurgeStopReason = 'stopped' | 'paused' | 'writer-unavailable' | 'subscriber-behind' | 'batch-limit' | 'error';
|
|
6
|
+
/**
|
|
7
|
+
* The invariant-14 probe's classification of `plan(floor)` after a pass.
|
|
8
|
+
*
|
|
9
|
+
* `too-old` at the floor is not by itself a violation: a log that never held
|
|
10
|
+
* that history — routine at every fork, restore, and rolling restart — returns
|
|
11
|
+
* `too-old` without having purged anything. Only the seed point distinguishes
|
|
12
|
+
* the two, which is why `seedWatermark` is in the v2 meta row:
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* violation ⇔ plan(floor) is `too-old` AND seedWatermark <= floor
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export type FloorProbeOutcome = 'retained' | 'ahead' | 'cold-log' | 'violation';
|
|
19
|
+
/** When the cleanup coordinator should run another pass. */
|
|
20
|
+
export type PurgeContinuation = 'immediate' | 'deferred';
|
|
21
|
+
export type PurgePassResult = {
|
|
22
|
+
/** The external floor the pass used. */
|
|
23
|
+
readonly floor: string;
|
|
24
|
+
readonly batches: number;
|
|
25
|
+
readonly deletedRows: number;
|
|
26
|
+
/** Why the pass ended before draining, if it did. */
|
|
27
|
+
readonly stopped: PurgeStopReason | undefined;
|
|
28
|
+
/** The invariant-14 probe's outcome, when the log was available to probe. */
|
|
29
|
+
readonly probe: FloorProbeOutcome | undefined;
|
|
30
|
+
/** Whether, and how soon, the cleanup coordinator should run another pass. */
|
|
31
|
+
readonly continuation: PurgeContinuation | undefined;
|
|
32
|
+
};
|
|
33
|
+
export type SQLiteChangeLogPurgeSchedulerOptions = {
|
|
34
|
+
/**
|
|
35
|
+
* `sqliteChangeLogRetentionMs`: the minimum retention window, ANDed on top
|
|
36
|
+
* of the external floor. It can only make purge more conservative — the
|
|
37
|
+
* purger never accepts it as a standalone bound.
|
|
38
|
+
*/
|
|
39
|
+
readonly retentionMs: number;
|
|
40
|
+
/**
|
|
41
|
+
* `sqliteChangeLogPurgeBatchRows`: the starting chunk size per batch. Not
|
|
42
|
+
* the bound — the statement duration target is.
|
|
43
|
+
*/
|
|
44
|
+
readonly batchRows: number;
|
|
45
|
+
readonly statementTargetMs?: number | undefined;
|
|
46
|
+
readonly maxBatchesPerPass?: number | undefined;
|
|
47
|
+
readonly inTransactionPollMs?: number | undefined;
|
|
48
|
+
/** The clock the retention cutoff is measured against. Defaults to `Date.now`. */
|
|
49
|
+
readonly now?: (() => number) | undefined;
|
|
50
|
+
readonly setTimeoutFn?: typeof setTimeout | undefined;
|
|
51
|
+
readonly clearTimeoutFn?: typeof clearTimeout | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Awaited between batches so fan-out and catchup make progress. Defaults to
|
|
54
|
+
* a macrotask (`setImmediate`), so pending I/O runs before the next batch.
|
|
55
|
+
*/
|
|
56
|
+
readonly yieldFn?: (() => Promise<void>) | undefined;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Runs bounded SQLite change-log purge passes on the writer's own connection.
|
|
60
|
+
*
|
|
61
|
+
* The caller supplies the current durable cleanup floor independently of any
|
|
62
|
+
* other change-log implementation and owns the timing of subsequent passes.
|
|
63
|
+
* Within a pass this class drains bounded batches, re-checking live subscriber
|
|
64
|
+
* ACKs before each one and releasing the cleanup guard between them, so a
|
|
65
|
+
* catchup registration or snapshot reservation never waits longer than one
|
|
66
|
+
* batch.
|
|
67
|
+
*
|
|
68
|
+
* Purge windows exist only between the writer's commits: purge shares the
|
|
69
|
+
* writer's connection, so it cannot run inside the writer's open transaction,
|
|
70
|
+
* and a batch that finds one waits for the commit notification instead.
|
|
71
|
+
*
|
|
72
|
+
* The result tells the cleanup coordinator whether the next pass should be
|
|
73
|
+
* immediate or deferred. This class deliberately owns no cleanup timer: the
|
|
74
|
+
* same level-triggered coordinator continues a large drain, advances the
|
|
75
|
+
* retention clock on a quiet source, and retries changing external state.
|
|
76
|
+
*/
|
|
77
|
+
export declare class SQLiteChangeLogPurgeScheduler {
|
|
78
|
+
#private;
|
|
79
|
+
/**
|
|
80
|
+
* The guard that brackets catchup registration: required-head capture and
|
|
81
|
+
* `Forwarder.add()` run under the same mutex as purge batches, so the
|
|
82
|
+
* registered ACK is visible to `Forwarder.getAcks()` before the next batch
|
|
83
|
+
* evaluates its gate.
|
|
84
|
+
*/
|
|
85
|
+
readonly cleanupGuard: SQLiteChangeLogCleanupGuard;
|
|
86
|
+
/**
|
|
87
|
+
* @param connection the writer's own connection (§3.3). `undefined` until
|
|
88
|
+
* the writer's first reconcile creates the file, and again after the
|
|
89
|
+
* writer fails soft and deletes it — both of which defer the pass rather
|
|
90
|
+
* than fail it, so a later coordinator pass picks up a late-appearing
|
|
91
|
+
* file without a restart.
|
|
92
|
+
* @param subscriberAcks live subscriber ACKs, re-read before every batch so
|
|
93
|
+
* the supplied floor remains safe throughout an asynchronous drain.
|
|
94
|
+
*/
|
|
95
|
+
constructor(lc: LogContext, connection: () => Database | undefined, subscriberAcks: () => Iterable<string>, opts: SQLiteChangeLogPurgeSchedulerOptions);
|
|
96
|
+
/**
|
|
97
|
+
* Runs one purge pass for the current durable cleanup `floor`.
|
|
98
|
+
* Never rejects; a failed pass is logged and reported as `stopped: 'error'`.
|
|
99
|
+
*/
|
|
100
|
+
purge(floor: string): Promise<PurgePassResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Pauses purging while `taskID` holds a snapshot reservation. The returned
|
|
103
|
+
* promise resolves once no purge batch is in flight, i.e. once the bounds a
|
|
104
|
+
* backup monitor is about to advertise cannot be invalidated by a batch
|
|
105
|
+
* that had already been dispatched.
|
|
106
|
+
*/
|
|
107
|
+
pause(taskID: string): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Ends one of `taskID`'s pauses. A later coordinator pass can purge once
|
|
110
|
+
* every reservation has ended. Unknown `taskID`s are ignored.
|
|
111
|
+
*/
|
|
112
|
+
resume(taskID: string): void;
|
|
113
|
+
/**
|
|
114
|
+
* Notes that the writer's open transaction has ended, waking any batch
|
|
115
|
+
* waiting for a purge window. A missed notification costs one poll
|
|
116
|
+
* interval, never correctness.
|
|
117
|
+
*/
|
|
118
|
+
onWriterIdle(): void;
|
|
119
|
+
stop(): void;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=sqlite-change-log-purge-scheduler.d.ts.map
|
package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-change-log-purge-scheduler.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,8BAA8B,CAAC;AAWtE,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,gCAAgC,CAAC;AA2BhF,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GAEvB,SAAS,GAET,QAAQ,GAGR,oBAAoB,GAIpB,mBAAmB,GAEnB,aAAa,GAEb,OAAO,CAAC;AAEZ;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GAGzB,UAAU,GAGV,OAAO,GAIP,UAAU,GAIV,WAAW,CAAC;AAEhB,4DAA4D;AAC5D,MAAM,MAAM,iBAAiB,GAEzB,WAAW,GAGX,UAAU,CAAC;AAEf,MAAM,MAAM,eAAe,GAAG;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAAC;IAC9C,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC9C,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG;IACjD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClD,kFAAkF;IAClF,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,UAAU,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,YAAY,GAAG,SAAS,CAAC;IAC1D;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;CACtD,CAAC;AA0CF;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,6BAA6B;;IAkExC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,2BAA2B,CAEhD;IAEF;;;;;;;;OAQG;gBAED,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,MAAM,QAAQ,GAAG,SAAS,EACtC,cAAc,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,EACtC,IAAI,EAAE,oCAAoC;IAsB5C;;;OAGG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAapD;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpC;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAY5B;;;;OAIG;IACH,YAAY,IAAI,IAAI;IAOpB,IAAI,IAAI,IAAI;CAiRb"}
|