@rocicorp/zero 1.11.0-canary.3 → 1.11.0-canary.30
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/analyze-query/src/analyze-cli.js +4 -4
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/analyze-query/src/bin-analyze.js +13 -12
- package/out/analyze-query/src/bin-analyze.js.map +1 -1
- package/out/analyze-query/src/bin-transform.js +1 -1
- package/out/analyze-query/src/bin-transform.js.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js +25 -25
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/otel/src/log-options.d.ts +16 -0
- package/out/otel/src/log-options.d.ts.map +1 -1
- package/out/otel/src/log-options.js +60 -6
- package/out/otel/src/log-options.js.map +1 -1
- package/out/replicache/src/bg-interval.d.ts +2 -1
- package/out/replicache/src/bg-interval.d.ts.map +1 -1
- package/out/replicache/src/bg-interval.js +13 -4
- package/out/replicache/src/bg-interval.js.map +1 -1
- package/out/replicache/src/btree/node.d.ts.map +1 -1
- package/out/replicache/src/btree/node.js +3 -6
- package/out/replicache/src/btree/node.js.map +1 -1
- package/out/replicache/src/dag/gc.d.ts.map +1 -1
- package/out/replicache/src/dag/gc.js +8 -11
- package/out/replicache/src/dag/gc.js.map +1 -1
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts +12 -0
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts.map +1 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js +19 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js.map +1 -0
- package/out/replicache/src/dag/lazy-store.d.ts +17 -4
- package/out/replicache/src/dag/lazy-store.d.ts.map +1 -1
- package/out/replicache/src/dag/lazy-store.js +35 -9
- package/out/replicache/src/dag/lazy-store.js.map +1 -1
- package/out/replicache/src/dag/store-impl.d.ts +12 -2
- package/out/replicache/src/dag/store-impl.d.ts.map +1 -1
- package/out/replicache/src/dag/store-impl.js +29 -9
- package/out/replicache/src/dag/store-impl.js.map +1 -1
- package/out/replicache/src/db/rebase.d.ts +14 -2
- package/out/replicache/src/db/rebase.d.ts.map +1 -1
- package/out/replicache/src/db/rebase.js +27 -5
- package/out/replicache/src/db/rebase.js.map +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
- package/out/replicache/src/get-kv-store-provider.js +4 -4
- package/out/replicache/src/get-kv-store-provider.js.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js +28 -5
- package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/idb-store.d.ts +18 -0
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store.js +30 -2
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
- package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
- package/out/replicache/src/kv/mem-fallback-store.js +177 -0
- package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
- package/out/replicache/src/kv/mem-store.d.ts +1 -0
- package/out/replicache/src/kv/mem-store.d.ts.map +1 -1
- package/out/replicache/src/kv/mem-store.js +3 -0
- package/out/replicache/src/kv/mem-store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js +4 -4
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +22 -1
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +159 -14
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/kv/store.d.ts +6 -0
- package/out/replicache/src/kv/store.d.ts.map +1 -1
- package/out/replicache/src/new-client-channel.js.map +1 -1
- package/out/replicache/src/persist/client-gc.d.ts +2 -1
- package/out/replicache/src/persist/client-gc.d.ts.map +1 -1
- package/out/replicache/src/persist/client-gc.js +2 -2
- package/out/replicache/src/persist/client-gc.js.map +1 -1
- package/out/replicache/src/persist/client-group-gc.d.ts +2 -1
- package/out/replicache/src/persist/client-group-gc.d.ts.map +1 -1
- package/out/replicache/src/persist/client-group-gc.js +2 -2
- package/out/replicache/src/persist/client-group-gc.js.map +1 -1
- package/out/replicache/src/persist/clients.d.ts +2 -2
- package/out/replicache/src/persist/collect-idb-databases.d.ts +28 -8
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +97 -20
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/heartbeat.d.ts +2 -1
- package/out/replicache/src/persist/heartbeat.d.ts.map +1 -1
- package/out/replicache/src/persist/heartbeat.js +2 -2
- package/out/replicache/src/persist/heartbeat.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
- package/out/replicache/src/persist/persist.d.ts.map +1 -1
- package/out/replicache/src/persist/persist.js +18 -8
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/persist/refresh.js +6 -2
- package/out/replicache/src/persist/refresh.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +85 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +321 -29
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/replicache-options.d.ts +9 -0
- package/out/replicache/src/replicache-options.d.ts.map +1 -1
- package/out/replicache/src/storage-failure.d.ts +37 -0
- package/out/replicache/src/storage-failure.d.ts.map +1 -0
- package/out/replicache/src/storage-failure.js +31 -0
- package/out/replicache/src/storage-failure.js.map +1 -0
- package/out/shared/src/arrays.d.ts +5 -0
- package/out/shared/src/arrays.d.ts.map +1 -1
- package/out/shared/src/arrays.js +8 -10
- package/out/shared/src/arrays.js.map +1 -1
- package/out/shared/src/base64.d.ts +19 -0
- package/out/shared/src/base64.d.ts.map +1 -0
- package/out/shared/src/base64.js +40 -0
- package/out/shared/src/base64.js.map +1 -0
- package/out/shared/src/centroid.d.ts +0 -1
- package/out/shared/src/centroid.d.ts.map +1 -1
- package/out/shared/src/centroid.js +1 -4
- package/out/shared/src/centroid.js.map +1 -1
- package/out/shared/src/error.d.ts +11 -0
- package/out/shared/src/error.d.ts.map +1 -1
- package/out/shared/src/error.js +35 -1
- package/out/shared/src/error.js.map +1 -1
- package/out/shared/src/hash.d.ts.map +1 -1
- package/out/shared/src/hash.js +32 -7
- package/out/shared/src/hash.js.map +1 -1
- package/out/shared/src/iterables.d.ts +2 -0
- package/out/shared/src/iterables.d.ts.map +1 -1
- package/out/shared/src/iterables.js +18 -1
- package/out/shared/src/iterables.js.map +1 -1
- package/out/shared/src/map.d.ts +24 -2
- package/out/shared/src/map.d.ts.map +1 -1
- package/out/shared/src/map.js +18 -4
- package/out/shared/src/map.js.map +1 -1
- package/out/shared/src/options-types.d.ts +2 -2
- package/out/shared/src/options.d.ts.map +1 -1
- package/out/shared/src/options.js +10 -7
- package/out/shared/src/options.js.map +1 -1
- package/out/shared/src/packed-boolean-map.d.ts +7 -0
- package/out/shared/src/packed-boolean-map.d.ts.map +1 -0
- package/out/shared/src/packed-boolean-map.js +36 -0
- package/out/shared/src/packed-boolean-map.js.map +1 -0
- package/out/shared/src/random-values.js +12 -1
- package/out/shared/src/random-values.js.map +1 -1
- package/out/shared/src/ring-buffer.d.ts +10 -0
- package/out/shared/src/ring-buffer.d.ts.map +1 -1
- package/out/shared/src/ring-buffer.js +26 -1
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/shared/src/tdigest.d.ts +10 -0
- package/out/shared/src/tdigest.d.ts.map +1 -1
- package/out/shared/src/tdigest.js +147 -63
- package/out/shared/src/tdigest.js.map +1 -1
- package/out/shared/src/valita.d.ts +13 -0
- package/out/shared/src/valita.d.ts.map +1 -1
- package/out/shared/src/valita.js +23 -23
- package/out/shared/src/valita.js.map +1 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +10 -6
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/z2s/src/sql.d.ts.map +1 -1
- package/out/z2s/src/sql.js +9 -5
- package/out/z2s/src/sql.js.map +1 -1
- package/out/zero/package.js +4 -5
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/zero.js +2 -1
- package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +1 -0
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/normalize.d.ts +1 -0
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +46 -4
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +116 -24
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +409 -157
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.d.ts +1 -1
- package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
- package/out/zero-cache/src/db/lite-tables.js +3 -4
- package/out/zero-cache/src/db/lite-tables.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 +5 -3
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/observability/events.js +4 -1
- package/out/zero-cache/src/observability/events.js.map +1 -1
- package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
- package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
- package/out/zero-cache/src/observability/log-throttle.js +76 -0
- package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +2 -7
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
- package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
- package/out/zero-cache/src/scripts/permissions.js +3 -3
- package/out/zero-cache/src/scripts/permissions.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 +117 -77
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.d.ts +28 -2
- package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js +37 -17
- package/out/zero-cache/src/server/inspector-delegate.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 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/mutator.js +2 -0
- package/out/zero-cache/src/server/mutator.js.map +1 -1
- package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
- package/out/zero-cache/src/server/reaper.js +2 -0
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts +3 -0
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +95 -21
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js +5 -0
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/syncer-assigner.d.ts +14 -0
- package/out/zero-cache/src/server/syncer-assigner.d.ts.map +1 -0
- package/out/zero-cache/src/server/syncer-assigner.js +72 -0
- package/out/zero-cache/src/server/syncer-assigner.js.map +1 -0
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +24 -2
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.js +21 -4
- package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
- package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
- package/out/zero-cache/src/services/analyze.js +11 -9
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +198 -24
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +16 -4
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/init-cleanup.d.ts +24 -0
- package/out/zero-cache/src/services/change-source/common/init-cleanup.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/common/init-cleanup.js +44 -0
- package/out/zero-cache/src/services/change-source/common/init-cleanup.js.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +92 -2
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-restore.js +27 -10
- package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -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 +15 -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 +1 -1
- 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 +7 -3
- 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 +9 -0
- 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 +206 -19
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts +55 -0
- package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js +305 -0
- package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +31 -18
- 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 +130 -157
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +38 -2
- 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 +55 -21
- 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 +31 -0
- 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 +102 -3
- 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-slot-cleanup-monitor.d.ts +16 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js +113 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-cleanup-monitor.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +40 -15
- 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 +149 -27
- 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/index-predicate.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +68 -8
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +32 -1
- 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/replica-stage-enum.d.ts +10 -0
- package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js +6 -0
- package/out/zero-cache/src/services/change-source/pg/schema/replica-stage-enum.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +50 -27
- 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 +98 -34
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +45 -2
- 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 +10 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +60 -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/upstream.d.ts +23 -10
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +72 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +162 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
- 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 +6 -17
- 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.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js +59 -8
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +8 -2
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +24 -4
- 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 -4
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js +28 -8
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts +17 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +44 -1
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +48 -6
- 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 +131 -34
- 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 +22 -0
- 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 +149 -78
- 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 +38 -16
- 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/forwarder.d.ts +3 -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 +36 -5
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts +53 -0
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js +156 -0
- package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +58 -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 +107 -16
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts +92 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js +42 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-message.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +16 -4
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +7 -69
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +21 -53
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +6 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +27 -9
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +95 -4
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +293 -83
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +446 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.js +76 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +58 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +123 -12
- 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 +19 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +23 -3
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -1
- package/out/zero-cache/src/services/http-service.d.ts +1 -1
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +3 -1
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts +34 -3
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +121 -35
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +6 -7
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +51 -24
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +10 -2
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/litestream-controller.js +3 -2
- package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -1
- package/out/zero-cache/src/services/litestream/restore-progress.d.ts +24 -0
- package/out/zero-cache/src/services/litestream/restore-progress.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/restore-progress.js +91 -0
- package/out/zero-cache/src/services/litestream/restore-progress.js.map +1 -0
- package/out/zero-cache/src/services/litestream/wal-keeper.d.ts +33 -0
- package/out/zero-cache/src/services/litestream/wal-keeper.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/wal-keeper.js +49 -0
- package/out/zero-cache/src/services/litestream/wal-keeper.js.map +1 -0
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts +5 -0
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +7 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +5 -6
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.d.ts +163 -0
- package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
- package/out/zero-cache/src/services/plannerz.js +267 -0
- package/out/zero-cache/src/services/plannerz.js.map +1 -0
- package/out/zero-cache/src/services/profz.d.ts +30 -0
- package/out/zero-cache/src/services/profz.d.ts.map +1 -0
- package/out/zero-cache/src/services/profz.js +163 -0
- package/out/zero-cache/src/services/profz.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +5 -4
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +20 -22
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +51 -5
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +5 -2
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +54 -21
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replication-status.d.ts +35 -1
- package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replication-status.js +99 -8
- package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +4 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +5 -2
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +16 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js +70 -27
- package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +6 -6
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +19 -14
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.d.ts +12 -0
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +42 -22
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/runner.d.ts +2 -1
- package/out/zero-cache/src/services/runner.d.ts.map +1 -1
- package/out/zero-cache/src/services/runner.js +10 -2
- package/out/zero-cache/src/services/runner.js.map +1 -1
- package/out/zero-cache/src/services/statz.d.ts +4 -0
- package/out/zero-cache/src/services/statz.d.ts.map +1 -1
- package/out/zero-cache/src/services/statz.js +22 -5
- package/out/zero-cache/src/services/statz.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/advancement-metrics.d.ts +26 -0
- package/out/zero-cache/src/services/view-syncer/advancement-metrics.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/advancement-metrics.js +26 -0
- package/out/zero-cache/src/services/view-syncer/advancement-metrics.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +19 -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 +42 -30
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +2 -1
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +1 -0
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +27 -2
- 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 +85 -20
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +76 -11
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +214 -40
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +14 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +30 -4
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts +33 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js +62 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +51 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +10 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +18 -4
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +80 -6
- 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 +658 -195
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.js +2 -6
- package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +82 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js +214 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/schema/types.d.ts +2 -107
- package/out/zero-cache/src/services/view-syncer/schema/types.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.js +5 -20
- package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts +95 -0
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js +155 -0
- package/out/zero-cache/src/services/view-syncer/snapshot-row-cache.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +96 -14
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +214 -30
- 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 +11 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +566 -107
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/pg-data-type.d.ts +2 -0
- package/out/zero-cache/src/types/pg-data-type.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg-data-type.js +6 -1
- package/out/zero-cache/src/types/pg-data-type.js.map +1 -1
- package/out/zero-cache/src/types/pg.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg.js +1 -1
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +39 -0
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +43 -4
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/profiler.d.ts +12 -0
- package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
- package/out/zero-cache/src/types/profiler.js +46 -1
- package/out/zero-cache/src/types/profiler.js.map +1 -1
- package/out/zero-cache/src/types/row-key.d.ts +1 -15
- package/out/zero-cache/src/types/row-key.d.ts.map +1 -1
- package/out/zero-cache/src/types/row-key.js +6 -8
- package/out/zero-cache/src/types/row-key.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +49 -7
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +190 -58
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +15 -0
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +123 -31
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/types/websocket-handoff.d.ts +1 -1
- package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
- package/out/zero-cache/src/types/websocket-handoff.js +18 -9
- package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
- package/out/zero-cache/src/types/ws.d.ts.map +1 -1
- package/out/zero-cache/src/types/ws.js +1 -16
- package/out/zero-cache/src/types/ws.js.map +1 -1
- package/out/zero-cache/src/workers/connect-params.d.ts +18 -1
- package/out/zero-cache/src/workers/connect-params.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connect-params.js +25 -1
- package/out/zero-cache/src/workers/connect-params.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +4 -4
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +8 -8
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +15 -7
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +21 -16
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +55 -4
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/active-clients-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/active-clients-manager.js +4 -1
- package/out/zero-client/src/client/active-clients-manager.js.map +1 -1
- package/out/zero-client/src/client/connection-manager.d.ts +20 -0
- package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-manager.js +77 -13
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.d.ts +2 -0
- package/out/zero-client/src/client/connection-status-enum.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.js +3 -1
- package/out/zero-client/src/client/connection-status-enum.js.map +1 -1
- package/out/zero-client/src/client/connection.d.ts +6 -0
- package/out/zero-client/src/client/connection.d.ts.map +1 -1
- package/out/zero-client/src/client/connection.js +2 -1
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +23 -13
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +94 -26
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
- package/out/zero-client/src/client/crud-impl.js +1 -1
- package/out/zero-client/src/client/crud-impl.js.map +1 -1
- package/out/zero-client/src/client/custom.d.ts +5 -4
- package/out/zero-client/src/client/custom.d.ts.map +1 -1
- package/out/zero-client/src/client/custom.js +3 -3
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +1 -1
- package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +20 -3
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/keys.d.ts.map +1 -1
- package/out/zero-client/src/client/keys.js +19 -1
- package/out/zero-client/src/client/keys.js.map +1 -1
- package/out/zero-client/src/client/make-mutate-property.d.ts +1 -1
- package/out/zero-client/src/client/make-mutate-property.d.ts.map +1 -1
- package/out/zero-client/src/client/make-mutate-property.js.map +1 -1
- package/out/zero-client/src/client/make-replicache-mutators.d.ts.map +1 -1
- package/out/zero-client/src/client/make-replicache-mutators.js +1 -1
- package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -1
- package/out/zero-client/src/client/mutation-tracker.d.ts +2 -2
- package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
- package/out/zero-client/src/client/mutation-tracker.js +3 -7
- package/out/zero-client/src/client/mutation-tracker.js.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.js +9 -4
- package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +33 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/query-manager.d.ts +9 -0
- package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/query-manager.js +47 -14
- package/out/zero-client/src/client/query-manager.js.map +1 -1
- package/out/zero-client/src/client/reload-error-handler.js +2 -2
- package/out/zero-client/src/client/reload-error-handler.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts +1 -0
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +8 -6
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts +18 -3
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +89 -50
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.d.ts +2 -1
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-client/src/mod.js +1 -0
- package/out/zero-client/src/types/query-result.d.ts +2 -0
- package/out/zero-client/src/types/query-result.d.ts.map +1 -1
- package/out/zero-events/src/status.d.ts +64 -0
- package/out/zero-events/src/status.d.ts.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.d.ts +5 -0
- package/out/zero-protocol/src/analyze-query-result.d.ts.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
- package/out/zero-protocol/src/ast.d.ts.map +1 -1
- package/out/zero-protocol/src/ast.js +13 -14
- package/out/zero-protocol/src/ast.js.map +1 -1
- package/out/zero-protocol/src/connect.d.ts +16 -0
- package/out/zero-protocol/src/connect.d.ts.map +1 -1
- package/out/zero-protocol/src/connect.js +27 -1
- package/out/zero-protocol/src/connect.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +31 -31
- package/out/zero-protocol/src/feature-flag-enum.d.ts +14 -0
- package/out/zero-protocol/src/feature-flag-enum.d.ts.map +1 -0
- package/out/zero-protocol/src/feature-flag-enum.js +10 -0
- package/out/zero-protocol/src/feature-flag-enum.js.map +1 -0
- package/out/zero-protocol/src/feature-flag.d.ts +5 -0
- package/out/zero-protocol/src/feature-flag.d.ts.map +1 -0
- package/out/zero-protocol/src/inspect-down.d.ts +16 -16
- package/out/zero-protocol/src/poke.d.ts +17 -10
- package/out/zero-protocol/src/poke.d.ts.map +1 -1
- package/out/zero-protocol/src/poke.js +3 -2
- package/out/zero-protocol/src/poke.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts +30 -6
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/query-hash.d.ts.map +1 -1
- package/out/zero-protocol/src/query-hash.js +2 -5
- package/out/zero-protocol/src/query-hash.js.map +1 -1
- package/out/zero-react/src/use-query.d.ts +5 -4
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +74 -7
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-react/src/zero.js +1 -0
- package/out/zero-server/src/adapters/drizzle.d.ts +2 -1
- package/out/zero-server/src/adapters/drizzle.d.ts.map +1 -1
- package/out/zero-server/src/adapters/drizzle.js +12 -7
- package/out/zero-server/src/adapters/drizzle.js.map +1 -1
- package/out/zero-server/src/adapters/kysely.d.ts +2 -1
- package/out/zero-server/src/adapters/kysely.d.ts.map +1 -1
- package/out/zero-server/src/adapters/kysely.js +8 -2
- package/out/zero-server/src/adapters/kysely.js.map +1 -1
- package/out/zero-server/src/adapters/pg.d.ts +5 -1
- package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
- package/out/zero-server/src/adapters/pg.js +28 -7
- package/out/zero-server/src/adapters/pg.js.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.d.ts +1 -0
- package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.js +7 -1
- package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
- package/out/zero-server/src/adapters/prisma.d.ts +3 -2
- package/out/zero-server/src/adapters/prisma.d.ts.map +1 -1
- package/out/zero-server/src/adapters/prisma.js +8 -2
- package/out/zero-server/src/adapters/prisma.js.map +1 -1
- package/out/zero-server/src/closable-db-transaction.d.ts +16 -0
- package/out/zero-server/src/closable-db-transaction.d.ts.map +1 -0
- package/out/zero-server/src/closable-db-transaction.js +31 -0
- package/out/zero-server/src/closable-db-transaction.js.map +1 -0
- package/out/zero-server/src/custom.d.ts +10 -4
- package/out/zero-server/src/custom.d.ts.map +1 -1
- package/out/zero-server/src/custom.js +15 -6
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/logging.d.ts +2 -2
- package/out/zero-server/src/logging.d.ts.map +1 -1
- package/out/zero-server/src/logging.js +2 -2
- package/out/zero-server/src/logging.js.map +1 -1
- package/out/zero-server/src/mod.d.ts +1 -1
- package/out/zero-server/src/mod.d.ts.map +1 -1
- package/out/zero-server/src/pg-query-executor.d.ts +2 -2
- package/out/zero-server/src/pg-query-executor.d.ts.map +1 -1
- package/out/zero-server/src/pg-query-executor.js +2 -2
- package/out/zero-server/src/pg-query-executor.js.map +1 -1
- package/out/zero-server/src/process-mutations.d.ts +25 -4
- package/out/zero-server/src/process-mutations.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.js +103 -52
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zero-server/src/push-processor.d.ts +8 -2
- package/out/zero-server/src/push-processor.d.ts.map +1 -1
- package/out/zero-server/src/push-processor.js +12 -4
- package/out/zero-server/src/push-processor.js.map +1 -1
- package/out/zero-server/src/schema.d.ts +2 -2
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +3 -2
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-server/src/zql-database.d.ts +21 -1
- package/out/zero-server/src/zql-database.d.ts.map +1 -1
- package/out/zero-server/src/zql-database.js +61 -24
- package/out/zero-server/src/zql-database.js.map +1 -1
- package/out/zero-solid/src/solid-view.d.ts +17 -0
- package/out/zero-solid/src/solid-view.d.ts.map +1 -1
- package/out/zero-solid/src/solid-view.js +39 -1
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zero-solid/src/use-query.js +1 -2
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zero-solid/src/zero.js +1 -0
- package/out/zql/src/builder/builder.d.ts +25 -0
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +69 -19
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/builder/correlated-predicate-pushdown.d.ts +43 -0
- package/out/zql/src/builder/correlated-predicate-pushdown.d.ts.map +1 -0
- package/out/zql/src/builder/correlated-predicate-pushdown.js +161 -0
- package/out/zql/src/builder/correlated-predicate-pushdown.js.map +1 -0
- package/out/zql/src/builder/debug-delegate.d.ts +6 -1
- package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
- package/out/zql/src/builder/debug-delegate.js +14 -2
- package/out/zql/src/builder/debug-delegate.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts +29 -0
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +74 -16
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts +2 -1
- package/out/zql/src/ivm/cap.d.ts.map +1 -1
- package/out/zql/src/ivm/cap.js +14 -4
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/data.d.ts.map +1 -1
- package/out/zql/src/ivm/data.js +8 -3
- package/out/zql/src/ivm/data.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -1
- package/out/zql/src/ivm/deferred-input.js +3 -2
- package/out/zql/src/ivm/deferred-input.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts +3 -1
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +4 -1
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.d.ts +6 -2
- package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-in.js +13 -5
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.d.ts +5 -2
- package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-out.js +4 -0
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +8 -6
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +19 -8
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter.d.ts +5 -2
- package/out/zql/src/ivm/filter.d.ts.map +1 -1
- package/out/zql/src/ivm/filter.js +10 -4
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.d.ts +9 -4
- package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +97 -46
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/join-index-tap.d.ts +33 -0
- package/out/zql/src/ivm/join-index-tap.d.ts.map +1 -0
- package/out/zql/src/ivm/join-index-tap.js +70 -0
- package/out/zql/src/ivm/join-index-tap.js.map +1 -0
- package/out/zql/src/ivm/join-index.d.ts +35 -0
- package/out/zql/src/ivm/join-index.d.ts.map +1 -0
- package/out/zql/src/ivm/join-index.js +141 -0
- package/out/zql/src/ivm/join-index.js.map +1 -0
- package/out/zql/src/ivm/join-utils.d.ts +6 -0
- package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
- package/out/zql/src/ivm/join-utils.js +22 -1
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/join.d.ts +19 -1
- package/out/zql/src/ivm/join.d.ts.map +1 -1
- package/out/zql/src/ivm/join.js +107 -15
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +6 -4
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +29 -4
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +70 -34
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/memory-storage.d.ts +1 -0
- package/out/zql/src/ivm/memory-storage.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-storage.js +1 -0
- package/out/zql/src/ivm/memory-storage.js.map +1 -1
- package/out/zql/src/ivm/operator.d.ts +45 -10
- package/out/zql/src/ivm/operator.d.ts.map +1 -1
- package/out/zql/src/ivm/operator.js +9 -4
- package/out/zql/src/ivm/operator.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.d.ts +6 -1
- package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +47 -2
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/out/zql/src/ivm/skip.d.ts +2 -1
- package/out/zql/src/ivm/skip.d.ts.map +1 -1
- package/out/zql/src/ivm/skip.js +3 -0
- package/out/zql/src/ivm/skip.js.map +1 -1
- package/out/zql/src/ivm/take-gate.d.ts +57 -0
- package/out/zql/src/ivm/take-gate.d.ts.map +1 -0
- package/out/zql/src/ivm/take-gate.js +106 -0
- package/out/zql/src/ivm/take-gate.js.map +1 -0
- package/out/zql/src/ivm/take.d.ts +8 -2
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +139 -103
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts +3 -0
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +63 -10
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/union-fan-out.d.ts +2 -1
- package/out/zql/src/ivm/union-fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-out.js +5 -0
- package/out/zql/src/ivm/union-fan-out.js.map +1 -1
- package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +9 -4
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/ivm/view.d.ts +15 -0
- package/out/zql/src/ivm/view.d.ts.map +1 -1
- package/out/zql/src/mutate/custom.d.ts +53 -1
- package/out/zql/src/mutate/custom.d.ts.map +1 -1
- package/out/zql/src/mutate/custom.js.map +1 -1
- package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
- package/out/zql/src/mutate/mutator-registry.js +3 -3
- package/out/zql/src/mutate/mutator-registry.js.map +1 -1
- package/out/zql/src/mutate/mutator.d.ts +2 -2
- package/out/zql/src/mutate/mutator.d.ts.map +1 -1
- package/out/zql/src/mutate/mutator.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts +23 -3
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +34 -17
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +58 -2
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +103 -11
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js +3 -8
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/planner/planner-graph.d.ts +10 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +14 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-source.d.ts +2 -2
- package/out/zql/src/planner/planner-source.d.ts.map +1 -1
- package/out/zql/src/planner/planner-source.js +2 -2
- package/out/zql/src/planner/planner-source.js.map +1 -1
- package/out/zql/src/planner/planner-warnings.d.ts +83 -0
- package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
- package/out/zql/src/planner/planner-warnings.js +78 -0
- package/out/zql/src/planner/planner-warnings.js.map +1 -0
- package/out/zql/src/query/measure-push-operator.d.ts +9 -2
- package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +52 -5
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +4 -2
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +66 -33
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +33 -6
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +1 -0
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +6 -23
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-transitions.d.ts.map +1 -1
- package/out/zql/src/query/query-transitions.js +2 -5
- package/out/zql/src/query/query-transitions.js.map +1 -1
- package/out/zql/src/query/query.d.ts +22 -5
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.d.ts +1 -0
- package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +2 -10
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zql/src/query/typed-view.d.ts +1 -1
- package/out/zql/src/query/typed-view.d.ts.map +1 -1
- package/out/zqlite/src/database-storage.d.ts.map +1 -1
- package/out/zqlite/src/database-storage.js +9 -1
- package/out/zqlite/src/database-storage.js.map +1 -1
- package/out/zqlite/src/db.d.ts +9 -1
- package/out/zqlite/src/db.d.ts.map +1 -1
- package/out/zqlite/src/db.js +62 -30
- package/out/zqlite/src/db.js.map +1 -1
- package/out/zqlite/src/index-usage.d.ts +19 -0
- package/out/zqlite/src/index-usage.d.ts.map +1 -0
- package/out/zqlite/src/index-usage.js +49 -0
- package/out/zqlite/src/index-usage.js.map +1 -0
- package/out/zqlite/src/pending-delta.d.ts +110 -0
- package/out/zqlite/src/pending-delta.d.ts.map +1 -0
- package/out/zqlite/src/pending-delta.js +278 -0
- package/out/zqlite/src/pending-delta.js.map +1 -0
- package/out/zqlite/src/query-builder.d.ts +17 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +111 -20
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/query-delegate.d.ts.map +1 -1
- package/out/zqlite/src/query-delegate.js +10 -7
- package/out/zqlite/src/query-delegate.js.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts +3 -1
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +1 -0
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +52 -1
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js +5 -31
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
- package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
- package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
- package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
- package/out/zqlite/src/table-source.d.ts +87 -2
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +276 -42
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +4 -5
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -25
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts +0 -29
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +0 -154
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-log-initializer.js","names":["#lc","#initFromPgChangeLog","#initFromReplica","#sources","#resultCounter","#lastComparison","#readReplica","#compare","#classify","#report"],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-log-initializer.ts"],"sourcesContent":["/**\n * Where a stream connection's initialization parameters come from.\n *\n * A change-streamer starts a stream from three things read at one position: the\n * watermark to resume at, the {@link BackfillRequest}s to hand the change\n * source, and the cookie set those requests were derived from. Postgres has\n * always supplied all three (`Storer.getStartStreamInitializationParameters()`).\n * The point of this workstream is that a restored replica can supply them\n * instead, which is what lets the `cdc` schema be decommissioned.\n *\n * This is the two-source stage. Both are computed on every stream connection,\n * compared, and Postgres's is used — so the replica-derived path runs in\n * production, at full rate, before anything depends on it. The flip is\n * `initFromPgChangeLog: false`, not a rewrite.\n *\n * The shape is {@link UpstreamAcker}'s, deliberately: two stores, one output, a\n * flag per store, and the Postgres flag carrying the marker that retires it.\n * That class solved the identical problem for upstream ACKs earlier in this\n * workstream.\n *\n * ### Why the comparison is a fold and not an equality\n *\n * The replica is a subscriber downstream of the change-streamer's write loop,\n * so its state version trails the log's head by however long the replicator is\n * behind. A raw difference between the two cookie sets is therefore the normal\n * case and says nothing. What is checked instead is that the replica's set,\n * carried forward over exactly the schema changes the log holds in the interval,\n * equals Postgres's set at Postgres's watermark. The log holds precisely those\n * transactions, schema changes are rare, and the check is exact rather than\n * approximate — so it also happens to be the strongest available test of the\n * premise that three interpreters of one fold agree.\n */\n\nimport type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {BigIntJSON} from '../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {StatementRunner} from '../../db/statements.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport type {SchemaChange} from '../change-source/protocol/current/data.ts';\nimport {schemaChangeTags} from '../change-source/protocol/current/schema-change-tags.ts';\nimport type {BackfillRequest} from '../change-source/protocol/current/upstream.ts';\nimport {\n backfillRequestsFrom,\n foldCookies,\n type CookieSet,\n} from '../replicator/change-log-cookies.ts';\nimport {\n CHANGE_LOG_STREAM_TABLE,\n type ChangeLogResumePoint,\n} from '../replicator/change-log-db.ts';\nimport {\n readBackfillRequests,\n readReplicaCookies,\n} from '../replicator/schema/backfilling.ts';\nimport {getReplicationState} from '../replicator/schema/replication-state.ts';\nimport {SQLITE_CHANGE_LOG_BOUNDARY_SQL} from './sqlite-change-log-reader.ts';\n\n/** Everything one stream connection starts from, read at one position. */\nexport type InitializationParameters = {\n readonly lastWatermark: string;\n readonly backfillRequests: BackfillRequest[];\n readonly cookies: CookieSet;\n};\n\n/**\n * - `equal` — the two stores were at the same watermark and held the same\n * cookies. Nothing had to be folded, which is the strongest of the four.\n * - `equal-after-fold` — the replica trailed, and carrying its set forward over\n * the log's schema changes in the interval reproduced Postgres's exactly.\n * The expected steady-state result.\n * - `divergent` — the two disagree at the same position. Alert; investigate\n * individually.\n * - `inconclusive` — the log does not span the interval, so the fold cannot be\n * performed. Routine on a cold log, and not a finding.\n * - `error` — the comparison itself failed: the replica could not be read, or\n * the fold threw. Not one of the four classifications, because it is a\n * statement about this process rather than about the two stores. Kept\n * separate rather than folded into `inconclusive` so that a change-streamer\n * that has silently stopped being able to read its replica is visible before\n * that read becomes the only source.\n */\nexport type InitComparisonResult =\n | 'equal'\n | 'equal-after-fold'\n | 'divergent'\n | 'inconclusive'\n | 'error';\n\nexport type ChangeLogInitializerSources = {\n /** `Storer.getStartStreamInitializationParameters()`. */\n readonly pgChangeLog: () => Promise<InitializationParameters>;\n /** Typically {@link replicaInitializationSource}. */\n readonly replica: () => InitializationParameters;\n /**\n * Reconciles the SQLite change log and returns its current resume point.\n *\n * When `resumeFrom` is present, it supplies the resume point. Otherwise, a\n * valid log uses its own head. A new or invalid log uses `seed`.\n *\n * Returns `undefined` when the writer is disabled or unavailable.\n */\n readonly reconcileChangeLog: (\n resumeFrom: ChangeLogResumePoint | undefined,\n seed: () => ChangeLogResumePoint,\n ) => ChangeLogResumePoint | undefined;\n /**\n * The change log, for the fold. `undefined` before the stream loop's first\n * reconcile has created it, and again once the writer has failed soft and\n * deleted it — both of which make the comparison `inconclusive` rather than\n * an error, since neither says anything about the two stores.\n */\n readonly changeLog: () => Database | undefined;\n};\n\ntype Opts = {\n /**\n * When true, Postgres supplies the resume point and the cookies for a new\n * change log.\n *\n * When false, a valid log resumes from its own head. The replica supplies the\n * resume point for a new or invalid log.\n */\n // TODO: set false when retiring PG\n readonly initFromPgChangeLog: boolean;\n /**\n * Derive the parameters from the replica. When Postgres is enabled, these\n * parameters are used only for comparison.\n *\n * When Postgres is disabled, the replica supplies the seed and the fallback\n * resume point.\n */\n readonly initFromReplica: boolean;\n};\n\n/**\n * The most change-log rows one comparison will scan.\n *\n * The interval is `(replicaWatermark, pgWatermark]`, so it is normally a\n * handful of transactions — but a replicator that has been stalled can leave it\n * arbitrarily large, and this runs on the stream loop between reconciliation and\n * `startStream`, where latency delays the resumption of replication. Over the\n * cap the comparison declines rather than pays: a chronically lagging replicator\n * shows up as `inconclusive`, which is charted, and not as a stall.\n */\nconst MAX_FOLD_SCAN_ROWS = 10_000;\n\nexport class ChangeLogInitializer {\n readonly #lc: LogContext;\n readonly #initFromPgChangeLog: boolean;\n readonly #initFromReplica: boolean;\n readonly #sources: ChangeLogInitializerSources;\n\n readonly #resultCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.init_compare_result',\n 'Comparisons of the replica-derived stream initialization parameters ' +\n 'against the Postgres-derived ones, by classification.',\n );\n\n #lastComparison: InitComparisonResult | undefined;\n\n constructor(\n lc: LogContext,\n {initFromPgChangeLog, initFromReplica}: Opts,\n sources: ChangeLogInitializerSources,\n ) {\n assert(\n initFromPgChangeLog || initFromReplica,\n `At least one of initFromPgChangeLog or initFromReplica must be true`,\n );\n this.#lc = lc.withContext('component', 'change-log-initializer');\n this.#initFromPgChangeLog = initFromPgChangeLog;\n this.#initFromReplica = initFromReplica;\n this.#sources = sources;\n }\n\n /**\n * Returns the comparison result from the most recent {@link initialize}\n * call. Returns `undefined` when that call used only one source.\n *\n * Tests and metrics use this result. It does not affect initialization.\n */\n lastComparison(): InitComparisonResult | undefined {\n return this.#lastComparison;\n }\n\n /**\n * Returns the initialization parameters for one stream connection after it\n * reconciles the change log.\n *\n * The selected source determines how reconciliation works. Keeping both\n * operations here hides that choice from the caller.\n */\n async initialize(): Promise<InitializationParameters> {\n this.#lastComparison = undefined;\n const pg = this.#initFromPgChangeLog\n ? await this.#sources.pgChangeLog()\n : undefined;\n // The replica is required when Postgres is disabled. It seeds a new log and\n // supplies a resume point when the writer is unavailable.\n const replica = this.#initFromReplica\n ? this.#readReplica(pg === undefined)\n : undefined;\n\n const reconciled = this.#sources.reconcileChangeLog(\n pg && {resumeWatermark: pg.lastWatermark, cookies: pg.cookies},\n () => resumePointOf(must(replica, 'no seed for the SQLite change log')),\n );\n\n if (pg) {\n if (replica) {\n // Compare only after reconciliation. The fold must read the schema\n // changes from the replica watermark through the Postgres watermark.\n // Reconciliation makes sure that the log contains that interval.\n this.#lastComparison = this.#compare(pg, replica);\n }\n return pg;\n }\n\n // Use the reconciled log position. If the writer is unavailable, use the\n // replica position.\n const resumePoint =\n reconciled ?? resumePointOf(must(replica, 'no initialization source'));\n return {\n lastWatermark: resumePoint.resumeWatermark,\n cookies: resumePoint.cookies,\n // Derive the requests from these cookies so that the requests and the\n // watermark describe the same position.\n backfillRequests: backfillRequestsFrom(resumePoint.cookies),\n };\n }\n\n /**\n * Compares both sources and records the result. Postgres remains\n * authoritative when both sources are enabled.\n */\n #compare(\n pg: InitializationParameters,\n replica: InitializationParameters,\n ): InitComparisonResult {\n let result: InitComparisonResult;\n try {\n result = this.#classify(pg, replica);\n } catch (e) {\n this.#lc.warn?.('failed to compare change-log initialization sources', e);\n result = 'error';\n }\n this.#resultCounter.add(1, {result});\n this.#lc.debug?.('compared change-log initialization sources', {\n changeLogInitCompare: {\n result,\n pgWatermark: pg.lastWatermark,\n replicaWatermark: replica.lastWatermark,\n },\n });\n return result;\n }\n\n #readReplica(required: boolean): InitializationParameters | undefined {\n try {\n return this.#sources.replica();\n } catch (e) {\n if (required) {\n throw e;\n }\n // Observational until the flip, so a replica that cannot be read must not\n // stop replication -- but it must not be quiet either, since the flip\n // makes this read the only source.\n this.#lc.warn?.(\n 'failed to derive change-log initialization from the replica',\n e,\n );\n this.#resultCounter.add(1, {result: 'error'});\n return undefined;\n }\n }\n\n #classify(\n pg: InitializationParameters,\n replica: InitializationParameters,\n ): InitComparisonResult {\n if (replica.lastWatermark > pg.lastWatermark) {\n // Transactions reach subscribers before the storer's commit, so a replica\n // read taken while one is in flight can be a transaction ahead. The fold\n // only runs forward, so there is nothing to be said either way.\n return 'inconclusive';\n }\n if (replica.lastWatermark === pg.lastWatermark) {\n return this.#report(pg, replica, replica.cookies, 'equal');\n }\n\n const db = this.#sources.changeLog();\n if (db === undefined || !spansInterval(db, replica.lastWatermark)) {\n return 'inconclusive';\n }\n const changes = readSchemaChanges(\n db,\n replica.lastWatermark,\n pg.lastWatermark,\n );\n if (changes === undefined) {\n return 'inconclusive'; // over MAX_FOLD_SCAN_ROWS\n }\n return this.#report(\n pg,\n replica,\n foldCookies(replica.cookies, changes),\n 'equal-after-fold',\n changes.length,\n );\n }\n\n #report(\n pg: InitializationParameters,\n replica: InitializationParameters,\n derived: CookieSet,\n whenEqual: InitComparisonResult,\n foldedChanges = 0,\n ): InitComparisonResult {\n const expected = canonicalizeCookies(pg.cookies);\n const actual = canonicalizeCookies(derived);\n if (expected === actual) {\n return whenEqual;\n }\n // Both sets are normally empty and are at most a handful of rows, so they\n // are logged whole: a divergence is investigated individually (`P§:10`)\n // and the two renderings are the whole of what there is to look at.\n this.#lc.error?.(\n 'the replica-derived change-log cookies do not match Postgres',\n {\n changeLogInitCompare: {\n pgWatermark: pg.lastWatermark,\n replicaWatermark: replica.lastWatermark,\n foldedChanges,\n expected,\n actual,\n },\n },\n );\n return 'divergent';\n }\n}\n\nconst resumePointOf = ({\n lastWatermark,\n cookies,\n}: InitializationParameters): ChangeLogResumePoint => ({\n resumeWatermark: lastWatermark,\n cookies,\n});\n\n/**\n * Reads the replica's initialization parameters in one snapshot, which is\n * invariant 15 on the replica side: the state version and the cookies are only\n * meaningful together.\n */\nexport function readReplicaInitializationParameters(\n db: Database,\n): InitializationParameters {\n const runner = new StatementRunner(db);\n runner.begin(); // deferred, i.e. one read snapshot for the three statements\n try {\n return {\n lastWatermark: getReplicationState(runner).stateVersion,\n backfillRequests: readBackfillRequests(db),\n cookies: readReplicaCookies(db),\n };\n } finally {\n if (db.inTransaction) {\n runner.rollback();\n }\n }\n}\n\n/**\n * The replica source for {@link ChangeLogInitializer}, opening and closing the\n * file per stream connection rather than holding a handle on a database another\n * process owns.\n *\n * The handle is read-only, which is invariant 19 stated in the API: the\n * replica's cookie tables are written by the change-processor and by nothing\n * else. Note that a read-only SQLite connection still needs to attach the\n * WAL's `-shm`, so this can fail if it opens at a moment when no writer holds\n * the replica; that surfaces as the `error` classification, which is the point\n * of running the path under observation before anything depends on it.\n */\nexport function replicaInitializationSource(\n lc: LogContext,\n replicaFile: string,\n): () => InitializationParameters {\n return () => {\n const db = new Database(\n lc.withContext('component', 'change-log-initializer'),\n replicaFile,\n {readonly: true},\n );\n try {\n return readReplicaInitializationParameters(db);\n } finally {\n db.close();\n }\n };\n}\n\n/**\n * Whether the log holds the complete interval above `fromWatermark`, using the\n * same predicate catchup uses to decide `too-old`: the log both reaches back\n * that far and contains that transaction's `commit` row, so the next row after\n * it is the first change of the interval rather than the middle of a\n * transaction the log only partly holds.\n */\nfunction spansInterval(db: Database, fromWatermark: string): boolean {\n if (!streamTableExists(db)) {\n return false;\n }\n const row = db\n .prepare(/*sql*/ `\n SELECT\n (SELECT min(\"watermark\") FROM \"${CHANGE_LOG_STREAM_TABLE}\")\n AS \"minWatermark\",\n coalesce((${SQLITE_CHANGE_LOG_BOUNDARY_SQL}), 0) AS \"boundaryExists\"\n `)\n .get<{minWatermark: string | null; boundaryExists: number}>({\n fromWatermark,\n });\n return (\n row.minWatermark !== null &&\n fromWatermark >= row.minWatermark &&\n row.boundaryExists === 1\n );\n}\n\nconst SCHEMA_CHANGE_TAG_LIST = schemaChangeTags\n .map(tag => `'${tag}'`)\n .join(', ');\n\n/**\n * The log's schema changes over `(after, through]`, in stream order, or\n * `undefined` if the interval is too large to scan (see\n * {@link MAX_FOLD_SCAN_ROWS}).\n *\n * The tag is stored beside the verbatim change so this scan never parses the\n * payloads of ordinary data changes. The row count is still checked first so a\n * stalled replicator cannot make the range scan unbounded.\n *\n * The count is itself capped, at one row past the cap it is deciding. A bare\n * `count(*)` over the interval is `O(interval)`, so the unbounded interval this\n * exists to decline would still be walked in full before being declined —\n * which is the latency the cap is here to avoid, not a cheaper form of it. The\n * `LIMIT` makes the decision `O(MAX_FOLD_SCAN_ROWS)`, and the comparison is\n * unaffected: the subquery returns the true count whenever it is within the\n * cap, and `cap + 1` whenever it is not.\n */\nfunction readSchemaChanges(\n db: Database,\n after: string,\n through: string,\n): SchemaChange[] | undefined {\n const {rows} = db\n .prepare(/*sql*/ `\n SELECT count(*) AS \"rows\" FROM (\n SELECT 1 FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n WHERE \"watermark\" > ? AND \"watermark\" <= ?\n LIMIT ${MAX_FOLD_SCAN_ROWS + 1}\n )\n `)\n .get<{rows: number}>(after, through);\n if (rows > MAX_FOLD_SCAN_ROWS) {\n return undefined;\n }\n return db\n .prepare(/*sql*/ `\n SELECT \"change\" FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n WHERE \"watermark\" > ? AND \"watermark\" <= ?\n AND \"tag\" IN (${SCHEMA_CHANGE_TAG_LIST})\n ORDER BY \"watermark\", \"pos\"\n `)\n .all<{change: string}>(after, through)\n .map(({change}) => BigIntJSON.parse(change) as SchemaChange);\n}\n\nfunction streamTableExists(db: Database): boolean {\n return (\n db\n .prepare(/*sql*/ `\n SELECT 1 FROM \"sqlite_master\" WHERE \"type\" = 'table' AND \"name\" = ?\n `)\n .get<{1: number} | undefined>(CHANGE_LOG_STREAM_TABLE) !== undefined\n );\n}\n\n/**\n * A canonical rendering of a whole cookie set: rows sorted by primary key,\n * object keys sorted at every depth.\n *\n * Both the equality check and the `divergent` log line fall out of this one\n * value, and neither can be defeated by a difference that is not a difference.\n * Postgres stores the cookie documents as `jsonb`, which does not preserve key\n * order, and the two readers order their rows in SQL in two engines under two\n * collations -- `storer.ts` pins `COLLATE \"C\"` for exactly that reason. Sorting\n * again here means an ordering or key-order regression can never be reported as\n * a divergence, which is a class of false alert that would cost more than it\n * caught.\n */\nfunction canonicalizeCookies(cookies: CookieSet): string {\n return BigIntJSON.stringify({\n tableMetadata: byKey(cookies.tableMetadata, c => [c.schema, c.table]).map(\n c => [c.schema, c.table, sortKeys(c.metadata)],\n ),\n backfilling: byKey(cookies.backfilling, c => [\n c.schema,\n c.table,\n c.column,\n ]).map(c => [c.schema, c.table, c.column, sortKeys(c.backfill)]),\n });\n}\n\nfunction byKey<T>(rows: readonly T[], key: (row: T) => string[]): T[] {\n return rows\n .map(row => [JSON.stringify(key(row)), row] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([, row]) => row);\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n if (value === null || typeof value !== 'object') {\n return value;\n }\n return Object.fromEntries(\n Object.entries(value)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => [k, sortKeys(v)]),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"change-log-initializer.js","names":["#lc","#initFromPgChangeLog","#initFromReplica","#sources","#resultCounter","#lastComparison","#pgChangeLog","#readReplica","#compare","#restoredReplica","#classify","#report"],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-log-initializer.ts"],"sourcesContent":["/**\n * Where a stream connection's initialization parameters come from.\n *\n * A change-streamer starts a stream from three things read at one position: the\n * watermark to resume at, the {@link BackfillRequest}s to hand the change\n * source, and the cookie set those requests were derived from. Postgres has\n * always supplied all three (`Storer.getStartStreamInitializationParameters()`).\n * The point of this workstream is that a restored replica can supply them\n * instead, which is what lets the `cdc` schema be decommissioned.\n *\n * This is the two-source stage. Both are computed on every stream connection,\n * compared, and Postgres's is used — so the replica-derived path runs in\n * production, at full rate, before anything depends on it. The flip is\n * `initFromPgChangeLog: false`, not a rewrite.\n *\n * The shape is {@link UpstreamAcker}'s, deliberately: two stores, one output, a\n * flag per store, and the Postgres flag carrying the marker that retires it.\n * That class solved the identical problem for upstream ACKs earlier in this\n * workstream.\n *\n * ### Why the comparison is a fold and not an equality\n *\n * The replica is a subscriber downstream of the change-streamer's write loop,\n * so its state version trails the log's head by however long the replicator is\n * behind. A raw difference between the two cookie sets is therefore the normal\n * case and says nothing. What is checked instead is that the replica's set,\n * carried forward over exactly the schema changes the log holds in the interval,\n * equals Postgres's set at Postgres's watermark. The log holds precisely those\n * transactions, schema changes are rare, and the check is exact rather than\n * approximate — so it also happens to be the strongest available test of the\n * premise that three interpreters of one fold agree.\n */\n\nimport type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {BigIntJSON} from '../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {StatementRunner} from '../../db/statements.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport type {SchemaChange} from '../change-source/protocol/current/data.ts';\nimport {schemaChangeTags} from '../change-source/protocol/current/schema-change-tags.ts';\nimport type {BackfillRequest} from '../change-source/protocol/current/upstream.ts';\nimport {\n backfillRequestsFrom,\n foldCookies,\n type CookieSet,\n} from '../replicator/change-log-cookies.ts';\nimport {\n CHANGE_LOG_STREAM_TABLE,\n type ChangeLogResumePoint,\n} from '../replicator/change-log-db.ts';\nimport {\n readBackfillRequests,\n readReplicaCookies,\n} from '../replicator/schema/backfilling.ts';\nimport {getReplicationState} from '../replicator/schema/replication-state.ts';\nimport {SQLITE_CHANGE_LOG_BOUNDARY_SQL} from './sqlite-change-log-reader.ts';\n\n/** Everything one stream connection starts from, read at one position. */\nexport type InitializationParameters = {\n readonly lastWatermark: string;\n readonly backfillRequests: BackfillRequest[];\n readonly cookies: CookieSet;\n};\n\n/**\n * - `equal` — the two stores were at the same watermark and held the same\n * cookies. Nothing had to be folded, which is the strongest of the four.\n * - `equal-after-fold` — the replica trailed, and carrying its set forward over\n * the log's schema changes in the interval reproduced Postgres's exactly.\n * The expected steady-state result.\n * - `divergent` — the two disagree at the same position. Alert; investigate\n * individually.\n * - `inconclusive` — the log does not span the interval, so the fold cannot be\n * performed. Routine on a cold log, and not a finding.\n * - `error` — the comparison itself failed: the replica could not be read, or\n * the fold threw. Not one of the four classifications, because it is a\n * statement about this process rather than about the two stores. Kept\n * separate rather than folded into `inconclusive` so that a change-streamer\n * that has silently stopped being able to read its replica is visible before\n * that read becomes the only source.\n */\nexport type InitComparisonResult =\n | 'equal'\n | 'equal-after-fold'\n | 'divergent'\n | 'inconclusive'\n | 'error';\n\nexport type ChangeLogInitializerSources = {\n /** `Storer.getStartStreamInitializationParameters()`. */\n readonly pgChangeLog: () => Promise<InitializationParameters>;\n /** Typically {@link replicaInitializationSource}. */\n readonly replica: () => InitializationParameters;\n /**\n * Reconciles the SQLite change log and returns its current resume point.\n *\n * When `resumeFrom` is present, it supplies the resume point. Otherwise, a\n * valid log uses its own head. A new or invalid log uses `seed`.\n *\n * Returns `undefined` when the writer is disabled or unavailable.\n */\n readonly reconcileChangeLog: (\n resumeFrom: ChangeLogResumePoint | undefined,\n seed: () => ChangeLogResumePoint,\n ) => ChangeLogResumePoint | undefined;\n /**\n * The change log, for the fold. `undefined` before the stream loop's first\n * reconcile has created it, and again once the writer has failed soft and\n * deleted it — both of which make the comparison `inconclusive` rather than\n * an error, since neither says anything about the two stores.\n */\n readonly changeLog: () => Database | undefined;\n /**\n * Re-initializes the Postgres change log from the replica's resume point\n * (taking ownership of it) if it does not contain all of the replica's\n * changes, re-checking under lock. Typically\n * `Storer.reinitializeFromReplica()`.\n *\n * Supplied only when the change log's head is behind the position of the\n * replication slot from which replication resumes, i.e. when it may be\n * missing transactions that the slot will not stream. Otherwise, a replica\n * that is ahead of the change log (as changes are forwarded before they are\n * stored) is caught up by the change stream, which resumes from the change\n * log's head.\n */\n readonly reinitializePgChangeLog?:\n | ((params: {\n watermark: string;\n cookies: CookieSet;\n }) => Promise<'reinitialized' | 'maintained'>)\n | undefined;\n};\n\ntype Opts = {\n /**\n * When true, Postgres supplies the resume point and the cookies for a new\n * change log.\n *\n * When false, a valid log resumes from its own head. The replica supplies the\n * resume point for a new or invalid log.\n */\n // TODO: set false when retiring PG\n readonly initFromPgChangeLog: boolean;\n /**\n * Derive the parameters from the replica. When Postgres is enabled, these\n * parameters are used only for comparison.\n *\n * When Postgres is disabled, the replica supplies the seed and the fallback\n * resume point.\n */\n readonly initFromReplica: boolean;\n};\n\n/**\n * The most change-log rows one comparison will scan.\n *\n * The interval is `(replicaWatermark, pgWatermark]`, so it is normally a\n * handful of transactions — but a replicator that has been stalled can leave it\n * arbitrarily large, and this runs on the stream loop between reconciliation and\n * `startStream`, where latency delays the resumption of replication. Over the\n * cap the comparison declines rather than pays: a chronically lagging replicator\n * shows up as `inconclusive`, which is charted, and not as a stall.\n */\nconst MAX_FOLD_SCAN_ROWS = 10_000;\n\nexport class ChangeLogInitializer {\n readonly #lc: LogContext;\n readonly #initFromPgChangeLog: boolean;\n readonly #initFromReplica: boolean;\n readonly #sources: ChangeLogInitializerSources;\n\n readonly #resultCounter = getOrCreateCounter(\n 'replica',\n 'sqlite_change_log.init_compare_result',\n 'Comparisons of the replica-derived stream initialization parameters ' +\n 'against the Postgres-derived ones, by classification.',\n );\n\n #lastComparison: InitComparisonResult | undefined;\n\n /**\n * The (restored) replica's resume point that the Postgres change log must\n * cover, read once. See {@link #pgChangeLog}.\n */\n #restoredReplica: InitializationParameters | undefined;\n\n constructor(\n lc: LogContext,\n {initFromPgChangeLog, initFromReplica}: Opts,\n sources: ChangeLogInitializerSources,\n ) {\n assert(\n initFromPgChangeLog || initFromReplica,\n `At least one of initFromPgChangeLog or initFromReplica must be true`,\n );\n this.#lc = lc.withContext('component', 'change-log-initializer');\n this.#initFromPgChangeLog = initFromPgChangeLog;\n this.#initFromReplica = initFromReplica;\n this.#sources = sources;\n }\n\n /**\n * Returns the comparison result from the most recent {@link initialize}\n * call. Returns `undefined` when that call used only one source.\n *\n * Tests and metrics use this result. It does not affect initialization.\n */\n lastComparison(): InitComparisonResult | undefined {\n return this.#lastComparison;\n }\n\n /**\n * Returns the initialization parameters for one stream connection after it\n * reconciles the change log.\n *\n * The selected source determines how reconciliation works. Keeping both\n * operations here hides that choice from the caller.\n */\n async initialize(): Promise<InitializationParameters> {\n this.#lastComparison = undefined;\n const pg = this.#initFromPgChangeLog\n ? await this.#pgChangeLog()\n : undefined;\n // The replica is required when Postgres is disabled. It seeds a new log and\n // supplies a resume point when the writer is unavailable.\n const replica = this.#initFromReplica\n ? this.#readReplica(pg === undefined)\n : undefined;\n\n const reconciled = this.#sources.reconcileChangeLog(\n pg && {resumeWatermark: pg.lastWatermark, cookies: pg.cookies},\n () => resumePointOf(must(replica, 'no seed for the SQLite change log')),\n );\n\n if (pg) {\n if (replica) {\n // Compare only after reconciliation. The fold must read the schema\n // changes from the replica watermark through the Postgres watermark.\n // Reconciliation makes sure that the log contains that interval.\n this.#lastComparison = this.#compare(pg, replica);\n }\n return pg;\n }\n\n // Use the reconciled log position. If the writer is unavailable, use the\n // replica position.\n const resumePoint =\n reconciled ?? resumePointOf(must(replica, 'no initialization source'));\n return {\n lastWatermark: resumePoint.resumeWatermark,\n cookies: resumePoint.cookies,\n // Derive the requests from these cookies so that the requests and the\n // watermark describe the same position.\n backfillRequests: backfillRequestsFrom(resumePoint.cookies),\n };\n }\n\n /**\n * Reads the Postgres change log's initialization parameters, first\n * re-initializing it from the replica (taking ownership of it) if it may be\n * missing transactions that the replication slot will not stream, e.g. if it\n * is no longer maintained after running in high-availability mode. This is\n * the Postgres counterpart of seeding a new SQLite change log from the\n * replica.\n *\n * This only applies when the change log's head is behind the slot's position\n * (i.e. when {@link ChangeLogInitializerSources.reinitializePgChangeLog} is\n * supplied), and the change log is only re-initialized if its head is also\n * behind the replica's. Otherwise, it contains every transaction before the\n * slot's position: one that was acked after being stored would be in the\n * change log, and one that was acked after being backed up would be in the\n * replica, which the change log's head covers.\n *\n * The replica is read only once, i.e. as it was restored from the backup,\n * before any stream is started. The live replica is no reference: it is\n * caught up from the forwarded changes, which can be ahead of what has been\n * stored, e.g. by the change log's previous owner. Against that fixed\n * resume point, re-initialization is idempotent: afterwards, the change log's\n * head (which only advances) covers it, so subsequent stream connections\n * leave it untouched.\n */\n async #pgChangeLog(): Promise<InitializationParameters> {\n const pg = await this.#sources.pgChangeLog();\n const reinitialize = this.#sources.reinitializePgChangeLog;\n if (!reinitialize) {\n return pg;\n }\n this.#restoredReplica ??= this.#sources.replica();\n const {lastWatermark, cookies} = this.#restoredReplica;\n if (pg.lastWatermark >= lastWatermark) {\n return pg;\n }\n const result = await reinitialize({watermark: lastWatermark, cookies});\n this.#lc.info?.(\n `Postgres change log at ${pg.lastWatermark} is behind the replica at ` +\n `${lastWatermark}: ${result}`,\n );\n // Either way, the change log has changed since it was read.\n return this.#sources.pgChangeLog();\n }\n\n /**\n * Compares both sources and records the result. Postgres remains\n * authoritative when both sources are enabled.\n */\n #compare(\n pg: InitializationParameters,\n replica: InitializationParameters,\n ): InitComparisonResult {\n let result: InitComparisonResult;\n try {\n result = this.#classify(pg, replica);\n } catch (e) {\n this.#lc.warn?.('failed to compare change-log initialization sources', e);\n result = 'error';\n }\n this.#resultCounter.add(1, {result});\n this.#lc.debug?.('compared change-log initialization sources', {\n changeLogInitCompare: {\n result,\n pgWatermark: pg.lastWatermark,\n replicaWatermark: replica.lastWatermark,\n },\n });\n return result;\n }\n\n #readReplica(required: boolean): InitializationParameters | undefined {\n try {\n return this.#sources.replica();\n } catch (e) {\n if (required) {\n throw e;\n }\n // Observational until the flip, so a replica that cannot be read must not\n // stop replication -- but it must not be quiet either, since the flip\n // makes this read the only source.\n this.#lc.warn?.(\n 'failed to derive change-log initialization from the replica',\n e,\n );\n this.#resultCounter.add(1, {result: 'error'});\n return undefined;\n }\n }\n\n #classify(\n pg: InitializationParameters,\n replica: InitializationParameters,\n ): InitComparisonResult {\n if (replica.lastWatermark > pg.lastWatermark) {\n // Transactions reach subscribers before the storer's commit, so a replica\n // read taken while one is in flight can be a transaction ahead. The fold\n // only runs forward, so there is nothing to be said either way.\n return 'inconclusive';\n }\n if (replica.lastWatermark === pg.lastWatermark) {\n return this.#report(pg, replica, replica.cookies, 'equal');\n }\n\n const db = this.#sources.changeLog();\n if (db === undefined || !spansInterval(db, replica.lastWatermark)) {\n return 'inconclusive';\n }\n const changes = readSchemaChanges(\n db,\n replica.lastWatermark,\n pg.lastWatermark,\n );\n if (changes === undefined) {\n return 'inconclusive'; // over MAX_FOLD_SCAN_ROWS\n }\n return this.#report(\n pg,\n replica,\n foldCookies(replica.cookies, changes),\n 'equal-after-fold',\n changes.length,\n );\n }\n\n #report(\n pg: InitializationParameters,\n replica: InitializationParameters,\n derived: CookieSet,\n whenEqual: InitComparisonResult,\n foldedChanges = 0,\n ): InitComparisonResult {\n const expected = canonicalizeCookies(pg.cookies);\n const actual = canonicalizeCookies(derived);\n if (expected === actual) {\n return whenEqual;\n }\n // Both sets are normally empty and are at most a handful of rows, so they\n // are logged whole: a divergence is investigated individually (`P§:10`)\n // and the two renderings are the whole of what there is to look at.\n this.#lc.error?.(\n 'the replica-derived change-log cookies do not match Postgres',\n {\n changeLogInitCompare: {\n pgWatermark: pg.lastWatermark,\n replicaWatermark: replica.lastWatermark,\n foldedChanges,\n expected,\n actual,\n },\n },\n );\n return 'divergent';\n }\n}\n\nconst resumePointOf = ({\n lastWatermark,\n cookies,\n}: InitializationParameters): ChangeLogResumePoint => ({\n resumeWatermark: lastWatermark,\n cookies,\n});\n\n/**\n * Reads the replica's initialization parameters in one snapshot, which is\n * invariant 15 on the replica side: the state version and the cookies are only\n * meaningful together.\n */\nexport function readReplicaInitializationParameters(\n db: Database,\n): InitializationParameters {\n const runner = new StatementRunner(db);\n runner.begin(); // deferred, i.e. one read snapshot for the three statements\n try {\n return {\n lastWatermark: getReplicationState(runner).stateVersion,\n backfillRequests: readBackfillRequests(db),\n cookies: readReplicaCookies(db),\n };\n } finally {\n if (db.inTransaction) {\n runner.rollback();\n }\n }\n}\n\n/**\n * The replica source for {@link ChangeLogInitializer}, opening and closing the\n * file per stream connection rather than holding a handle on a database another\n * process owns.\n *\n * The handle is read-only, which is invariant 19 stated in the API: the\n * replica's cookie tables are written by the change-processor and by nothing\n * else. Note that a read-only SQLite connection still needs to attach the\n * WAL's `-shm`, so this can fail if it opens at a moment when no writer holds\n * the replica; that surfaces as the `error` classification, which is the point\n * of running the path under observation before anything depends on it.\n */\nexport function replicaInitializationSource(\n lc: LogContext,\n replicaFile: string,\n): () => InitializationParameters {\n return () => {\n const db = new Database(\n lc.withContext('component', 'change-log-initializer'),\n replicaFile,\n {readonly: true},\n );\n try {\n return readReplicaInitializationParameters(db);\n } finally {\n db.close();\n }\n };\n}\n\n/**\n * Whether the log holds the complete interval above `fromWatermark`, using the\n * same predicate catchup uses to decide `too-old`: the log both reaches back\n * that far and contains that transaction's `commit` row, so the next row after\n * it is the first change of the interval rather than the middle of a\n * transaction the log only partly holds.\n */\nfunction spansInterval(db: Database, fromWatermark: string): boolean {\n if (!streamTableExists(db)) {\n return false;\n }\n const row = db\n .prepare(/*sql*/ `\n SELECT\n (SELECT min(\"watermark\") FROM \"${CHANGE_LOG_STREAM_TABLE}\")\n AS \"minWatermark\",\n coalesce((${SQLITE_CHANGE_LOG_BOUNDARY_SQL}), 0) AS \"boundaryExists\"\n `)\n .get<{minWatermark: string | null; boundaryExists: number}>({\n fromWatermark,\n });\n return (\n row.minWatermark !== null &&\n fromWatermark >= row.minWatermark &&\n row.boundaryExists === 1\n );\n}\n\nconst SCHEMA_CHANGE_TAG_LIST = schemaChangeTags\n .map(tag => `'${tag}'`)\n .join(', ');\n\n/**\n * The log's schema changes over `(after, through]`, in stream order, or\n * `undefined` if the interval is too large to scan (see\n * {@link MAX_FOLD_SCAN_ROWS}).\n *\n * The tag is stored beside the verbatim change so this scan never parses the\n * payloads of ordinary data changes. The row count is still checked first so a\n * stalled replicator cannot make the range scan unbounded.\n *\n * The count is itself capped, at one row past the cap it is deciding. A bare\n * `count(*)` over the interval is `O(interval)`, so the unbounded interval this\n * exists to decline would still be walked in full before being declined —\n * which is the latency the cap is here to avoid, not a cheaper form of it. The\n * `LIMIT` makes the decision `O(MAX_FOLD_SCAN_ROWS)`, and the comparison is\n * unaffected: the subquery returns the true count whenever it is within the\n * cap, and `cap + 1` whenever it is not.\n */\nfunction readSchemaChanges(\n db: Database,\n after: string,\n through: string,\n): SchemaChange[] | undefined {\n const {rows} = db\n .prepare(/*sql*/ `\n SELECT count(*) AS \"rows\" FROM (\n SELECT 1 FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n WHERE \"watermark\" > ? AND \"watermark\" <= ?\n LIMIT ${MAX_FOLD_SCAN_ROWS + 1}\n )\n `)\n .get<{rows: number}>(after, through);\n if (rows > MAX_FOLD_SCAN_ROWS) {\n return undefined;\n }\n return db\n .prepare(/*sql*/ `\n SELECT \"change\" FROM \"${CHANGE_LOG_STREAM_TABLE}\"\n WHERE \"watermark\" > ? AND \"watermark\" <= ?\n AND \"tag\" IN (${SCHEMA_CHANGE_TAG_LIST})\n ORDER BY \"watermark\", \"pos\"\n `)\n .all<{change: string}>(after, through)\n .map(({change}) => BigIntJSON.parse(change) as SchemaChange);\n}\n\nfunction streamTableExists(db: Database): boolean {\n return (\n db\n .prepare(/*sql*/ `\n SELECT 1 FROM \"sqlite_master\" WHERE \"type\" = 'table' AND \"name\" = ?\n `)\n .get<{1: number} | undefined>(CHANGE_LOG_STREAM_TABLE) !== undefined\n );\n}\n\n/**\n * A canonical rendering of a whole cookie set: rows sorted by primary key,\n * object keys sorted at every depth.\n *\n * Both the equality check and the `divergent` log line fall out of this one\n * value, and neither can be defeated by a difference that is not a difference.\n * Postgres stores the cookie documents as `jsonb`, which does not preserve key\n * order, and the two readers order their rows in SQL in two engines under two\n * collations -- `storer.ts` pins `COLLATE \"C\"` for exactly that reason. Sorting\n * again here means an ordering or key-order regression can never be reported as\n * a divergence, which is a class of false alert that would cost more than it\n * caught.\n */\nfunction canonicalizeCookies(cookies: CookieSet): string {\n return BigIntJSON.stringify({\n tableMetadata: byKey(cookies.tableMetadata, c => [c.schema, c.table]).map(\n c => [c.schema, c.table, sortKeys(c.metadata)],\n ),\n backfilling: byKey(cookies.backfilling, c => [\n c.schema,\n c.table,\n c.column,\n ]).map(c => [c.schema, c.table, c.column, sortKeys(c.backfill)]),\n });\n}\n\nfunction byKey<T>(rows: readonly T[], key: (row: T) => string[]): T[] {\n return rows\n .map(row => [JSON.stringify(key(row)), row] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([, row]) => row);\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortKeys);\n }\n if (value === null || typeof value !== 'object') {\n return value;\n }\n return Object.fromEntries(\n Object.entries(value)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => [k, sortKeys(v)]),\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http';
|
|
2
2
|
import type { LogContext } from '@rocicorp/logger';
|
|
3
|
+
import type { NormalizedZeroConfig } from '../../config/normalize.ts';
|
|
3
4
|
import { type Worker } from '../../types/processes.ts';
|
|
4
5
|
import { type ShardID } from '../../types/shards.ts';
|
|
5
|
-
import { type Source } from '../../types/streams.ts';
|
|
6
|
+
import { type Sized, type Source } from '../../types/streams.ts';
|
|
7
|
+
import { Subscription } from '../../types/subscription.ts';
|
|
6
8
|
import { HttpService, type Options as HttpOptions } from '../http-service.ts';
|
|
7
|
-
import { type ChangeStreamer, type ChangeStreamerService, type
|
|
8
|
-
import { type
|
|
9
|
+
import { type ChangeStreamer, type ChangeStreamerService, type SubscriberContext } from './change-streamer.ts';
|
|
10
|
+
import { type SnapshotStatus, type SubscribeDownstream, type SubscribeUpstream } from './subscribe.ts';
|
|
9
11
|
type Options = HttpOptions & {
|
|
10
12
|
startupDelayMs: number;
|
|
13
|
+
config?: Pick<NormalizedZeroConfig, 'adminPassword' | 'operatorPassword'> | undefined;
|
|
14
|
+
getProfileWorker?: (() => Promise<Worker>) | undefined;
|
|
11
15
|
};
|
|
12
16
|
export declare class ChangeStreamerHttpServer extends HttpService {
|
|
13
17
|
#private;
|
|
@@ -16,11 +20,49 @@ export declare class ChangeStreamerHttpServer extends HttpService {
|
|
|
16
20
|
protected _onStart(): void;
|
|
17
21
|
protected _onStop(): Promise<void>;
|
|
18
22
|
}
|
|
19
|
-
export
|
|
23
|
+
export interface SnapshotReserver {
|
|
24
|
+
reserveSnapshot(taskID: string): Promise<{
|
|
25
|
+
reserved: SnapshotStatus;
|
|
26
|
+
followup: ReservationFollowup;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export interface SubscriptionStarter {
|
|
30
|
+
subscribe(ctx: SubscriberContext): Promise<Source<Sized<SubscribeDownstream>>>;
|
|
31
|
+
}
|
|
32
|
+
export interface ReservationFollowup extends SubscriptionStarter {
|
|
33
|
+
/**
|
|
34
|
+
* A signal to indicate that the followup is no longer valid (i.e. the
|
|
35
|
+
* connection to the reserving task has been severed).
|
|
36
|
+
*/
|
|
37
|
+
readonly signal: AbortSignal;
|
|
38
|
+
/**
|
|
39
|
+
* Abandons the reservation without subscribing, closing the connection that
|
|
40
|
+
* was held open to pin the change-log floor. Used when the caller cannot
|
|
41
|
+
* proceed to `subscribe()` (e.g. process shutdown while a restore is in
|
|
42
|
+
* progress) and must not leave the reservation's connection open forever.
|
|
43
|
+
*/
|
|
44
|
+
cancel(reason?: Error): void;
|
|
45
|
+
}
|
|
46
|
+
export declare class ChangeStreamerHttpClient implements ChangeStreamer, SnapshotReserver, SubscriptionStarter {
|
|
20
47
|
#private;
|
|
21
48
|
constructor(lc: LogContext, shardID: ShardID, changeDB: string, changeStreamerURI: string | undefined);
|
|
22
|
-
reserveSnapshot(taskID: string): Promise<
|
|
23
|
-
|
|
49
|
+
reserveSnapshot(taskID: string): Promise<{
|
|
50
|
+
reserved: SnapshotStatus;
|
|
51
|
+
followup: ReservationFollowup;
|
|
52
|
+
}>;
|
|
53
|
+
subscribe(ctx: SubscriberContext): Promise<Source<Sized<SubscribeDownstream>>>;
|
|
54
|
+
/**
|
|
55
|
+
* Opens a merged v7 `/subscribe` connection (see subscribe.ts). Returns the
|
|
56
|
+
* inbound change/reservation stream and an `outbound` sink into which the
|
|
57
|
+
* caller pushes the `reserve-snapshot` / `start-subscription` control
|
|
58
|
+
* messages that drive the connection's phases. The single connection spans
|
|
59
|
+
* the reservation, backup restore, and subscription, so the reserving
|
|
60
|
+
* replication-manager is the one that serves the subscription.
|
|
61
|
+
*/
|
|
62
|
+
connect(taskID: string): Promise<{
|
|
63
|
+
instream: Source<Sized<SubscribeDownstream>>;
|
|
64
|
+
outbound: Subscription<SubscribeUpstream>;
|
|
65
|
+
}>;
|
|
24
66
|
}
|
|
25
67
|
type RequestHeaders = Pick<IncomingMessage, 'url' | 'headers'>;
|
|
26
68
|
export declare function getSubscriberContext(req: RequestHeaders): SubscriberContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer-http.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,WAAW,CAAC;AAE/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"change-streamer-http.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import { assert } from "../../../../shared/src/asserts.js";
|
|
2
1
|
import { promiseVoid } from "../../../../shared/src/resolved-promises.js";
|
|
2
|
+
import { BigIntJSON } from "../../../../shared/src/bigint-json.js";
|
|
3
3
|
import "../../types/processes.js";
|
|
4
4
|
import { pgClient } from "../../types/pg.js";
|
|
5
5
|
import { HttpService } from "../http-service.js";
|
|
6
|
+
import { URLParams } from "../../types/url-params.js";
|
|
7
|
+
import { handleProfzRequest } from "../profz.js";
|
|
6
8
|
import { PROTOCOL_ERROR, closeWithError } from "../../types/ws.js";
|
|
7
9
|
import { installWebSocketReceiver } from "../../types/websocket-handoff.js";
|
|
8
|
-
import {
|
|
10
|
+
import { Subscription } from "../../types/subscription.js";
|
|
11
|
+
import { readHead, streamInternal, streamInternalStringified, streamInternalWithSize } from "../../types/streams.js";
|
|
9
12
|
import { discoverChangeStreamerAddress } from "./schema/tables.js";
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
+
import "./change-streamer.js";
|
|
14
|
+
import "./snapshot-message.js";
|
|
15
|
+
import { subscribeDownstreamSchema, subscribeUpstreamSchema } from "./subscribe.js";
|
|
13
16
|
import WebSocket$1 from "ws";
|
|
14
17
|
import websocket from "@fastify/websocket";
|
|
15
18
|
//#region ../zero-cache/src/services/change-streamer/change-streamer-http.ts
|
|
16
19
|
var MIN_SUPPORTED_PROTOCOL_VERSION = 4;
|
|
20
|
+
var MIN_SUBSCRIBE_PROTOCOL_VERSION = 7;
|
|
17
21
|
var SNAPSHOT_PATH_PATTERN = "/replication/:version/snapshot";
|
|
18
22
|
var CHANGES_PATH_PATTERN = "/replication/:version/changes";
|
|
19
|
-
var
|
|
20
|
-
var
|
|
21
|
-
var
|
|
23
|
+
var SUBSCRIBE_PATH_PATTERN = "/replication/:version/subscribe";
|
|
24
|
+
var PATH_REGEX = /\/replication\/v(?<version>\d+)\/(changes|snapshot|subscribe)$/;
|
|
25
|
+
var SUBSCRIBE_PATH = `/replication/v8/subscribe`;
|
|
26
|
+
var CONNECT_TIMEOUT_MS = 1e4;
|
|
22
27
|
var ChangeStreamerHttpServer = class extends HttpService {
|
|
23
28
|
id = "change-streamer-http-server";
|
|
24
29
|
#lc;
|
|
@@ -27,8 +32,13 @@ var ChangeStreamerHttpServer = class extends HttpService {
|
|
|
27
32
|
constructor(lc, opts, parent, changeStreamer) {
|
|
28
33
|
super("change-streamer-http-server", lc, opts, async (fastify) => {
|
|
29
34
|
await fastify.register(websocket);
|
|
30
|
-
fastify.get(CHANGES_PATH_PATTERN, { websocket: true }, this.#
|
|
35
|
+
fastify.get(CHANGES_PATH_PATTERN, { websocket: true }, this.#changes);
|
|
31
36
|
fastify.get(SNAPSHOT_PATH_PATTERN, { websocket: true }, this.#reserveSnapshot);
|
|
37
|
+
fastify.get(SUBSCRIBE_PATH_PATTERN, { websocket: true }, this.#subscribe);
|
|
38
|
+
fastify.get("/profz", (req, res) => handleProfzRequest(lc, opts.config ?? {
|
|
39
|
+
adminPassword: void 0,
|
|
40
|
+
operatorPassword: void 0
|
|
41
|
+
}, req, res, opts.getProfileWorker, void 0, "change-streamer"));
|
|
32
42
|
installWebSocketReceiver(lc, fastify.websocketServer, this.#receiveWebsocket, parent);
|
|
33
43
|
});
|
|
34
44
|
this.#lc = lc;
|
|
@@ -38,7 +48,8 @@ var ChangeStreamerHttpServer = class extends HttpService {
|
|
|
38
48
|
#receiveWebsocket = (ws, action, msg) => {
|
|
39
49
|
switch (action) {
|
|
40
50
|
case "snapshot": return this.#reserveSnapshot(ws, msg);
|
|
41
|
-
case "changes": return this.#
|
|
51
|
+
case "changes": return this.#changes(ws, msg);
|
|
52
|
+
case "subscribe": return this.#subscribe(ws, msg);
|
|
42
53
|
default:
|
|
43
54
|
closeWithError(this._lc, ws, `invalid action "${action}" received in handoff`);
|
|
44
55
|
return;
|
|
@@ -52,36 +63,88 @@ var ChangeStreamerHttpServer = class extends HttpService {
|
|
|
52
63
|
const taskID = url.searchParams.get("taskID");
|
|
53
64
|
if (!taskID) throw new Error("Missing taskID in snapshot request");
|
|
54
65
|
const downstream = await this.#changeStreamer.startSnapshotReservation(taskID);
|
|
55
|
-
|
|
66
|
+
streamInternal(this._lc, ws, void 0, downstream);
|
|
56
67
|
} catch (err) {
|
|
57
68
|
closeWithError(this._lc, ws, err, PROTOCOL_ERROR);
|
|
58
69
|
}
|
|
59
70
|
};
|
|
60
|
-
#
|
|
71
|
+
#changes = async (ws, req) => {
|
|
61
72
|
try {
|
|
62
73
|
const ctx = getSubscriberContext(req);
|
|
63
74
|
if (ctx.mode === "serving") this.#ensureChangeStreamerStarted("incoming subscription");
|
|
64
75
|
const downstream = await this.#changeStreamer.subscribe(ctx);
|
|
65
|
-
|
|
76
|
+
streamInternalStringified(this._lc, ws, void 0, downstream, { batched: ctx.wsBatched });
|
|
66
77
|
} catch (err) {
|
|
67
78
|
closeWithError(this._lc, ws, err, PROTOCOL_ERROR);
|
|
68
79
|
}
|
|
69
80
|
};
|
|
81
|
+
/**
|
|
82
|
+
* The merged v7 `/subscribe` handler. A single bidirectional connection
|
|
83
|
+
* carries, in sequence: an optional reservation phase (`reserve-snapshot` →
|
|
84
|
+
* `reserved`) and then the subscription phase (`start-subscription` → change
|
|
85
|
+
* stream). This ensures that the subscriber subscribes to the same task from
|
|
86
|
+
* which it restored the backup.
|
|
87
|
+
*/
|
|
88
|
+
#subscribe = async (ws, req) => {
|
|
89
|
+
let reservation;
|
|
90
|
+
const outbound = Subscription.create();
|
|
91
|
+
try {
|
|
92
|
+
const protocolVersion = checkProtocolVersion(new URL(req.url ?? "", req.headers.origin ?? "http://localhost").pathname);
|
|
93
|
+
if (protocolVersion < MIN_SUBSCRIBE_PROTOCOL_VERSION) throw new Error(`the /subscribe endpoint requires protocol v${MIN_SUBSCRIBE_PROTOCOL_VERSION} (client is at v${protocolVersion})`);
|
|
94
|
+
const instream = await streamInternalStringified(this._lc, ws, subscribeUpstreamSchema, outbound, { batched: true });
|
|
95
|
+
for await (const msg of instream) if (msg[0] === "reserve-snapshot") {
|
|
96
|
+
const { taskID } = msg[1];
|
|
97
|
+
this.#ensureChangeStreamerStarted("incoming snapshot reservation");
|
|
98
|
+
reservation = await this.#changeStreamer.startSnapshotReservation(taskID);
|
|
99
|
+
this.#forwardReservation(reservation, outbound);
|
|
100
|
+
} else if (msg[0] === "start-subscription") {
|
|
101
|
+
const ctx = {
|
|
102
|
+
...msg[1],
|
|
103
|
+
protocolVersion,
|
|
104
|
+
wsBatched: true
|
|
105
|
+
};
|
|
106
|
+
if (ctx.mode === "serving") this.#ensureChangeStreamerStarted("incoming subscription");
|
|
107
|
+
reservation = void 0;
|
|
108
|
+
await this.#changeStreamer.subscribe(ctx, outbound);
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
closeWithError(this._lc, ws, err, PROTOCOL_ERROR);
|
|
112
|
+
} finally {
|
|
113
|
+
reservation?.cancel();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Forwards a snapshot reservation's single `['status', ...]` message onto
|
|
118
|
+
* the merged connection's `outbound` sink as a `['reserved', ...]` message.
|
|
119
|
+
* The loop then blocks on the held-open reservation until it is cancelled,
|
|
120
|
+
* by `subscribe()` at the subscription handoff, or by the reservation-phase
|
|
121
|
+
* teardown on disconnect.
|
|
122
|
+
*/
|
|
123
|
+
async #forwardReservation(reservation, outbound) {
|
|
124
|
+
try {
|
|
125
|
+
for await (const [, status] of reservation) outbound.push(BigIntJSON.stringify(["reserved", {
|
|
126
|
+
...status,
|
|
127
|
+
tag: "snapshot"
|
|
128
|
+
}]));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
this._lc.warn?.(`error forwarding snapshot reservation`, e);
|
|
131
|
+
outbound.fail(e instanceof Error ? e : new Error(String(e)));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
70
134
|
#changeStreamerStarted = false;
|
|
71
135
|
#ensureChangeStreamerStarted(reason) {
|
|
72
136
|
if (!this.#changeStreamerStarted && this._state.shouldRun()) {
|
|
73
|
-
this.#lc.info?.(`starting ChangeStreamerService
|
|
137
|
+
this.#lc.info?.(`starting ChangeStreamerService ${reason ? `(${reason})` : ""}`);
|
|
74
138
|
this.#changeStreamer.run().catch((e) => this.#lc.warn?.(`ChangeStreamerService ended with error`, e)).finally(() => this.stop());
|
|
75
139
|
this.#changeStreamerStarted = true;
|
|
76
140
|
}
|
|
77
141
|
}
|
|
78
142
|
_onStart() {
|
|
79
143
|
const { startupDelayMs, readinessGate = promiseVoid } = this.#opts;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} else this.#ensureChangeStreamerStarted("no startup delay configured");
|
|
144
|
+
if (startupDelayMs === 0) this.#ensureChangeStreamerStarted();
|
|
145
|
+
else readinessGate.then(() => {
|
|
146
|
+
this.#lc.info?.(`waiting ${startupDelayMs}ms before taking over the change log`);
|
|
147
|
+
this._state.setTimeout(() => this.#ensureChangeStreamerStarted(`startup delay elapsed (${startupDelayMs} ms)`), startupDelayMs);
|
|
85
148
|
});
|
|
86
149
|
}
|
|
87
150
|
async _onStop() {
|
|
@@ -114,12 +177,45 @@ var ChangeStreamerHttpClient = class {
|
|
|
114
177
|
return uri;
|
|
115
178
|
}
|
|
116
179
|
async reserveSnapshot(taskID) {
|
|
117
|
-
const
|
|
118
|
-
|
|
180
|
+
const { instream, outbound } = await this.connect(taskID);
|
|
181
|
+
outbound.push(["reserve-snapshot", { taskID }]);
|
|
182
|
+
const { head, rest } = readHead(instream);
|
|
183
|
+
const item = await head.next();
|
|
184
|
+
if (!item.value) throw new Error("stream closed without snapshot reservation");
|
|
185
|
+
const { data: msg } = item.value;
|
|
186
|
+
if (msg[0] !== "reserved") throw new Error(`expected "reserved" message but got ${msg[0]}`);
|
|
187
|
+
return {
|
|
188
|
+
reserved: msg[1],
|
|
189
|
+
followup: {
|
|
190
|
+
subscribe: (ctx) => {
|
|
191
|
+
outbound.push(["start-subscription", toStartSubscriptionContext(ctx)]);
|
|
192
|
+
return Promise.resolve(rest);
|
|
193
|
+
},
|
|
194
|
+
signal: outbound.signal,
|
|
195
|
+
cancel: (reason) => outbound.cancel(reason)
|
|
196
|
+
}
|
|
197
|
+
};
|
|
119
198
|
}
|
|
120
199
|
async subscribe(ctx) {
|
|
121
|
-
const
|
|
122
|
-
|
|
200
|
+
const { instream, outbound } = await this.connect(ctx.taskID);
|
|
201
|
+
outbound.push(["start-subscription", toStartSubscriptionContext(ctx)]);
|
|
202
|
+
return instream;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Opens a merged v7 `/subscribe` connection (see subscribe.ts). Returns the
|
|
206
|
+
* inbound change/reservation stream and an `outbound` sink into which the
|
|
207
|
+
* caller pushes the `reserve-snapshot` / `start-subscription` control
|
|
208
|
+
* messages that drive the connection's phases. The single connection spans
|
|
209
|
+
* the reservation, backup restore, and subscription, so the reserving
|
|
210
|
+
* replication-manager is the one that serves the subscription.
|
|
211
|
+
*/
|
|
212
|
+
async connect(taskID) {
|
|
213
|
+
const ws = new WebSocket$1(await this.#resolveChangeStreamer(SUBSCRIBE_PATH) + `?${new URLSearchParams({ taskID }).toString()}`, { handshakeTimeout: CONNECT_TIMEOUT_MS });
|
|
214
|
+
const outbound = Subscription.create();
|
|
215
|
+
return {
|
|
216
|
+
instream: await streamInternalWithSize(this.#lc, ws, subscribeDownstreamSchema, outbound),
|
|
217
|
+
outbound
|
|
218
|
+
};
|
|
123
219
|
}
|
|
124
220
|
};
|
|
125
221
|
function getSubscriberContext(req) {
|
|
@@ -133,25 +229,26 @@ function getSubscriberContext(req) {
|
|
|
133
229
|
mode: params.get("mode", false) === "backup" ? "backup" : "serving",
|
|
134
230
|
replicaVersion: params.get("replicaVersion", true),
|
|
135
231
|
watermark: params.get("watermark", true),
|
|
136
|
-
|
|
137
|
-
logsChangeStream: params.getBoolean("logsChangeStream")
|
|
232
|
+
wsBatched: params.getBoolean("wsBatched")
|
|
138
233
|
};
|
|
139
234
|
}
|
|
140
235
|
function checkProtocolVersion(pathname) {
|
|
141
236
|
const match = PATH_REGEX.exec(pathname);
|
|
142
237
|
if (!match) throw new Error(`invalid path: ${pathname}`);
|
|
143
238
|
const v = Number(match.groups?.version);
|
|
144
|
-
if (Number.isNaN(v) || v >
|
|
239
|
+
if (Number.isNaN(v) || v > 8 || v < MIN_SUPPORTED_PROTOCOL_VERSION) throw new Error(`Cannot service client at protocol v${v}. Supported protocols: [v${MIN_SUPPORTED_PROTOCOL_VERSION} ... v8]`);
|
|
145
240
|
return v;
|
|
146
241
|
}
|
|
147
|
-
function
|
|
148
|
-
const {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
242
|
+
function toStartSubscriptionContext(ctx) {
|
|
243
|
+
const { taskID, id, mode, replicaVersion, watermark, backfills } = ctx;
|
|
244
|
+
return {
|
|
245
|
+
taskID,
|
|
246
|
+
id,
|
|
247
|
+
mode,
|
|
248
|
+
replicaVersion,
|
|
249
|
+
watermark,
|
|
250
|
+
backfills
|
|
251
|
+
};
|
|
155
252
|
}
|
|
156
253
|
//#endregion
|
|
157
254
|
export { ChangeStreamerHttpClient, ChangeStreamerHttpServer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer-http.js","names":["#lc","#opts","#changeStreamer","#subscribe","#reserveSnapshot","#receiveWebsocket","#ensureChangeStreamerStarted","#changeStreamerStarted","#shardID","#changeDB","#changeStreamerURI","#resolveChangeStreamer"],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"sourcesContent":["import type {IncomingMessage} from 'node:http';\nimport websocket from '@fastify/websocket';\nimport type {LogContext} from '@rocicorp/logger';\nimport WebSocket from 'ws';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport type {IncomingMessageSubset} from '../../types/http.ts';\nimport {pgClient, type PostgresDB} from '../../types/pg.ts';\nimport {type Worker} from '../../types/processes.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {\n streamIn,\n streamInWithSize,\n streamOut,\n streamOutStringified,\n type Source,\n} from '../../types/streams.ts';\nimport {URLParams} from '../../types/url-params.ts';\nimport {installWebSocketReceiver} from '../../types/websocket-handoff.ts';\nimport {closeWithError, PROTOCOL_ERROR} from '../../types/ws.ts';\nimport {HttpService, type Options as HttpOptions} from '../http-service.ts';\nimport {\n downstreamSchema,\n PROTOCOL_VERSION,\n type ChangeStreamer,\n type ChangeStreamerService,\n type SizedDownstream,\n type SubscriberContext,\n} from './change-streamer.ts';\nimport {discoverChangeStreamerAddress} from './schema/tables.ts';\nimport {snapshotMessageSchema, type SnapshotMessage} from './snapshot.ts';\n\nconst MIN_SUPPORTED_PROTOCOL_VERSION = 4;\n\nconst SNAPSHOT_PATH_PATTERN = '/replication/:version/snapshot';\nconst CHANGES_PATH_PATTERN = '/replication/:version/changes';\nconst PATH_REGEX = /\\/replication\\/v(?<version>\\d+)\\/(changes|snapshot)$/;\n\nconst SNAPSHOT_PATH = `/replication/v${PROTOCOL_VERSION}/snapshot`;\nconst CHANGES_PATH = `/replication/v${PROTOCOL_VERSION}/changes`;\n\ntype Options = HttpOptions & {\n startupDelayMs: number;\n};\n\nexport class ChangeStreamerHttpServer extends HttpService {\n readonly id = 'change-streamer-http-server';\n readonly #lc: LogContext;\n readonly #opts: Options;\n readonly #changeStreamer: ChangeStreamerService;\n\n constructor(\n lc: LogContext,\n opts: Options,\n parent: Worker,\n changeStreamer: ChangeStreamerService,\n ) {\n super('change-streamer-http-server', lc, opts, async fastify => {\n await fastify.register(websocket);\n\n fastify.get(CHANGES_PATH_PATTERN, {websocket: true}, this.#subscribe);\n fastify.get(\n SNAPSHOT_PATH_PATTERN,\n {websocket: true},\n this.#reserveSnapshot,\n );\n\n installWebSocketReceiver<'snapshot' | 'changes'>(\n lc,\n fastify.websocketServer,\n this.#receiveWebsocket,\n parent,\n );\n });\n\n this.#lc = lc;\n this.#opts = opts;\n this.#changeStreamer = changeStreamer;\n }\n\n // Called when receiving a web socket via the main dispatcher handoff.\n readonly #receiveWebsocket = (\n ws: WebSocket,\n action: 'changes' | 'snapshot',\n msg: IncomingMessageSubset,\n ) => {\n switch (action) {\n case 'snapshot':\n return this.#reserveSnapshot(ws, msg);\n case 'changes':\n return this.#subscribe(ws, msg);\n default:\n closeWithError(\n this._lc,\n ws,\n `invalid action \"${action}\" received in handoff`,\n );\n return;\n }\n };\n\n readonly #reserveSnapshot = async (ws: WebSocket, req: RequestHeaders) => {\n this.#ensureChangeStreamerStarted('incoming snapshot reservation');\n try {\n const url = new URL(\n req.url ?? '',\n req.headers.origin ?? 'http://localhost',\n );\n checkProtocolVersion(url.pathname);\n const taskID = url.searchParams.get('taskID');\n if (!taskID) {\n throw new Error('Missing taskID in snapshot request');\n }\n const downstream =\n await this.#changeStreamer.startSnapshotReservation(taskID);\n void streamOut(this._lc, downstream, ws);\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n\n readonly #subscribe = async (ws: WebSocket, req: RequestHeaders) => {\n try {\n const ctx = getSubscriberContext(req);\n if (ctx.mode === 'serving') {\n this.#ensureChangeStreamerStarted('incoming subscription');\n }\n\n const downstream = await this.#changeStreamer.subscribe(ctx);\n void streamOutStringified(this._lc, downstream, ws);\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n\n #changeStreamerStarted = false;\n\n #ensureChangeStreamerStarted(reason: string) {\n if (!this.#changeStreamerStarted && this._state.shouldRun()) {\n this.#lc.info?.(`starting ChangeStreamerService: ${reason}`);\n void this.#changeStreamer\n .run()\n .catch(e =>\n this.#lc.warn?.(`ChangeStreamerService ended with error`, e),\n )\n .finally(() => this.stop());\n\n this.#changeStreamerStarted = true;\n }\n }\n\n protected override _onStart(): void {\n const {startupDelayMs, readinessGate = promiseVoid} = this.#opts;\n void readinessGate.then(() => {\n if (startupDelayMs > 0) {\n // In RMv1, starting the change-streamer forcibly shuts down the\n // previous change-streamer, causing view-syncers to reconnect.\n // If this replication-manager has just started, the routing layer may\n // not have registered it with DNS, as that only happens after it\n // confirms health checks. To minimize downtime, the takeover is\n // delayed for the configured startupDelayMs _after_ beginning to\n // advertise readiness.\n this.#lc.info?.(\n `waiting ${startupDelayMs}ms before taking over the change log`,\n );\n this._state.setTimeout(\n () =>\n this.#ensureChangeStreamerStarted(\n `startup delay elapsed (${startupDelayMs} ms)`,\n ),\n startupDelayMs,\n );\n } else {\n // In RMv2, this is not necessary because a new RM does not kill the\n // old one; it is started as soon as possible.\n this.#ensureChangeStreamerStarted('no startup delay configured');\n }\n });\n }\n\n protected override async _onStop(): Promise<void> {\n if (this.#changeStreamerStarted) {\n await this.#changeStreamer.stop();\n }\n }\n}\n\nexport class ChangeStreamerHttpClient implements ChangeStreamer {\n readonly #lc: LogContext;\n readonly #shardID: ShardID;\n readonly #changeDB: PostgresDB;\n readonly #changeStreamerURI: string | undefined;\n\n constructor(\n lc: LogContext,\n shardID: ShardID,\n changeDB: string,\n changeStreamerURI: string | undefined,\n ) {\n this.#lc = lc;\n this.#shardID = shardID;\n // Create a pg client with a single short-lived connection for the purpose\n // of change-streamer discovery (i.e. ChangeDB as DNS).\n this.#changeDB = pgClient(lc, changeDB, 'change-streamer-discovery', {\n max: 1,\n ['idle_timeout']: 15,\n });\n this.#changeStreamerURI = changeStreamerURI;\n }\n\n async #resolveChangeStreamer(path: string) {\n let baseURL = this.#changeStreamerURI;\n if (!baseURL) {\n const address = await discoverChangeStreamerAddress(\n this.#shardID,\n this.#changeDB,\n );\n if (!address) {\n throw new Error(`no change-streamer is running`);\n }\n baseURL = address.includes('://') ? `${address}/` : `ws://${address}/`;\n }\n const uri = new URL(path, baseURL);\n this.#lc.info?.(`connecting to change-streamer@${uri}`);\n return uri;\n }\n\n async reserveSnapshot(taskID: string): Promise<Source<SnapshotMessage>> {\n const uri = await this.#resolveChangeStreamer(SNAPSHOT_PATH);\n\n const params = new URLSearchParams({taskID});\n const ws = new WebSocket(uri + `?${params.toString()}`);\n\n return streamIn(this.#lc, ws, snapshotMessageSchema);\n }\n\n async subscribe(ctx: SubscriberContext): Promise<Source<SizedDownstream>> {\n const uri = await this.#resolveChangeStreamer(CHANGES_PATH);\n\n const params = getParams(ctx);\n const ws = new WebSocket(uri + `?${params.toString()}`);\n\n return streamInWithSize(this.#lc, ws, downstreamSchema);\n }\n}\n\ntype RequestHeaders = Pick<IncomingMessage, 'url' | 'headers'>;\n\nexport function getSubscriberContext(req: RequestHeaders): SubscriberContext {\n const url = new URL(req.url ?? '', req.headers.origin ?? 'http://localhost');\n const protocolVersion = checkProtocolVersion(url.pathname);\n const params = new URLParams(url);\n\n return {\n protocolVersion,\n id: params.get('id', true),\n taskID: params.get('taskID', true),\n mode: params.get('mode', false) === 'backup' ? 'backup' : 'serving',\n replicaVersion: params.get('replicaVersion', true),\n watermark: params.get('watermark', true),\n initial: params.getBoolean('initial'),\n // Absent for subscribers that predate the parameter, which is the safe\n // default: the barrier falls back to polling rather than waiting on an\n // ACK that would never be attributed to a writer.\n logsChangeStream: params.getBoolean('logsChangeStream'),\n };\n}\n\nfunction checkProtocolVersion(pathname: string): number {\n const match = PATH_REGEX.exec(pathname);\n if (!match) {\n throw new Error(`invalid path: ${pathname}`);\n }\n const v = Number(match.groups?.version);\n if (\n Number.isNaN(v) ||\n v > PROTOCOL_VERSION ||\n v < MIN_SUPPORTED_PROTOCOL_VERSION\n ) {\n throw new Error(\n `Cannot service client at protocol v${v}. ` +\n `Supported protocols: [v${MIN_SUPPORTED_PROTOCOL_VERSION} ... v${PROTOCOL_VERSION}]`,\n );\n }\n return v;\n}\n\n// This is called from the client-side (i.e. the replicator).\nfunction getParams(ctx: SubscriberContext): URLSearchParams {\n // The protocolVersion is hard-coded into the CHANGES_PATH.\n const {protocolVersion, ...stringParams} = ctx;\n assert(\n protocolVersion === PROTOCOL_VERSION,\n `replicator should be setting protocolVersion to ${PROTOCOL_VERSION}`,\n );\n return new URLSearchParams({\n ...stringParams,\n initial: ctx.initial ? 'true' : 'false',\n logsChangeStream: ctx.logsChangeStream ? 'true' : 'false',\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"change-streamer-http.js","names":["#lc","#opts","#changeStreamer","#changes","#reserveSnapshot","#subscribe","#receiveWebsocket","#ensureChangeStreamerStarted","#forwardReservation","#changeStreamerStarted","#shardID","#changeDB","#changeStreamerURI","#resolveChangeStreamer"],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"sourcesContent":["import type {IncomingMessage} from 'node:http';\nimport websocket from '@fastify/websocket';\nimport type {LogContext} from '@rocicorp/logger';\nimport WebSocket from 'ws';\nimport {BigIntJSON} from '../../../../shared/src/bigint-json.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport type {NormalizedZeroConfig} from '../../config/normalize.ts';\nimport type {IncomingMessageSubset} from '../../types/http.ts';\nimport {pgClient, type PostgresDB} from '../../types/pg.ts';\nimport {type Worker} from '../../types/processes.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {\n readHead,\n streamInternal,\n streamInternalStringified,\n streamInternalWithSize,\n type Sized,\n type Source,\n} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport {URLParams} from '../../types/url-params.ts';\nimport {installWebSocketReceiver} from '../../types/websocket-handoff.ts';\nimport {closeWithError, PROTOCOL_ERROR} from '../../types/ws.ts';\nimport {HttpService, type Options as HttpOptions} from '../http-service.ts';\nimport {handleProfzRequest} from '../profz.ts';\nimport type {PreSerializedBatch} from './broadcast.ts';\nimport {\n PROTOCOL_VERSION,\n type ChangeStreamer,\n type ChangeStreamerService,\n type SubscriberContext,\n} from './change-streamer.ts';\nimport {discoverChangeStreamerAddress} from './schema/tables.ts';\nimport {type SnapshotMessage} from './snapshot-message.ts';\nimport {\n subscribeDownstreamSchema,\n subscribeUpstreamSchema,\n type ReservedMessage,\n type SnapshotStatus,\n type SubscribeContext,\n type SubscribeDownstream,\n type SubscribeUpstream,\n} from './subscribe.ts';\n\nconst MIN_SUPPORTED_PROTOCOL_VERSION = 4;\n\n// The merged `/subscribe` protocol was introduced at v7.\nconst MIN_SUBSCRIBE_PROTOCOL_VERSION = 7;\n\nconst SNAPSHOT_PATH_PATTERN = '/replication/:version/snapshot';\nconst CHANGES_PATH_PATTERN = '/replication/:version/changes';\nconst SUBSCRIBE_PATH_PATTERN = '/replication/:version/subscribe';\nconst PATH_REGEX =\n /\\/replication\\/v(?<version>\\d+)\\/(changes|snapshot|subscribe)$/;\n\nconst SUBSCRIBE_PATH = `/replication/v${PROTOCOL_VERSION}/subscribe`;\n\n// Max time for a client to establish the /subscribe websocket (TCP connect\n// plus upgrade) before abandoning the attempt.\nconst CONNECT_TIMEOUT_MS = 10_000;\n\ntype Options = HttpOptions & {\n startupDelayMs: number;\n config?:\n | Pick<NormalizedZeroConfig, 'adminPassword' | 'operatorPassword'>\n | undefined;\n getProfileWorker?: (() => Promise<Worker>) | undefined;\n};\n\nexport class ChangeStreamerHttpServer extends HttpService {\n readonly id = 'change-streamer-http-server';\n readonly #lc: LogContext;\n readonly #opts: Options;\n readonly #changeStreamer: ChangeStreamerService;\n\n constructor(\n lc: LogContext,\n opts: Options,\n parent: Worker,\n changeStreamer: ChangeStreamerService,\n ) {\n super('change-streamer-http-server', lc, opts, async fastify => {\n await fastify.register(websocket);\n\n fastify.get(CHANGES_PATH_PATTERN, {websocket: true}, this.#changes);\n fastify.get(\n SNAPSHOT_PATH_PATTERN,\n {websocket: true},\n this.#reserveSnapshot,\n );\n fastify.get(SUBSCRIBE_PATH_PATTERN, {websocket: true}, this.#subscribe);\n\n fastify.get('/profz', (req, res) =>\n handleProfzRequest(\n lc,\n opts.config ?? {\n adminPassword: undefined,\n operatorPassword: undefined,\n },\n req,\n res,\n opts.getProfileWorker,\n undefined,\n 'change-streamer',\n ),\n );\n\n installWebSocketReceiver<'snapshot' | 'changes' | 'subscribe'>(\n lc,\n fastify.websocketServer,\n this.#receiveWebsocket,\n parent,\n );\n });\n\n this.#lc = lc;\n this.#opts = opts;\n this.#changeStreamer = changeStreamer;\n }\n\n // Called when receiving a web socket via the main dispatcher handoff.\n readonly #receiveWebsocket = (\n ws: WebSocket,\n action: 'changes' | 'snapshot' | 'subscribe',\n msg: IncomingMessageSubset,\n ) => {\n switch (action) {\n case 'snapshot':\n return this.#reserveSnapshot(ws, msg);\n case 'changes':\n return this.#changes(ws, msg);\n case 'subscribe':\n return this.#subscribe(ws, msg);\n default:\n closeWithError(\n this._lc,\n ws,\n `invalid action \"${action}\" received in handoff`,\n );\n return;\n }\n };\n\n readonly #reserveSnapshot = async (ws: WebSocket, req: RequestHeaders) => {\n this.#ensureChangeStreamerStarted('incoming snapshot reservation');\n try {\n const url = new URL(\n req.url ?? '',\n req.headers.origin ?? 'http://localhost',\n );\n checkProtocolVersion(url.pathname);\n const taskID = url.searchParams.get('taskID');\n if (!taskID) {\n throw new Error('Missing taskID in snapshot request');\n }\n const downstream =\n await this.#changeStreamer.startSnapshotReservation(taskID);\n // Send-only for now: the reservation carries no inbound application\n // messages (only transport acks, demuxed internally).\n void streamInternal(this._lc, ws, undefined, downstream);\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n\n readonly #changes = async (ws: WebSocket, req: RequestHeaders) => {\n try {\n const ctx = getSubscriberContext(req);\n if (ctx.mode === 'serving') {\n this.#ensureChangeStreamerStarted('incoming subscription');\n }\n\n const downstream = await this.#changeStreamer.subscribe(ctx);\n // Send-only for now: the subscription carries no inbound application\n // messages (only transport acks, demuxed internally).\n void streamInternalStringified(this._lc, ws, undefined, downstream, {\n batched: ctx.wsBatched,\n });\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n\n /**\n * The merged v7 `/subscribe` handler. A single bidirectional connection\n * carries, in sequence: an optional reservation phase (`reserve-snapshot` →\n * `reserved`) and then the subscription phase (`start-subscription` → change\n * stream). This ensures that the subscriber subscribes to the same task from\n * which it restored the backup.\n */\n readonly #subscribe = async (ws: WebSocket, req: RequestHeaders) => {\n let reservation: Source<SnapshotMessage> | undefined;\n const outbound = Subscription.create<string | PreSerializedBatch>();\n\n try {\n const url = new URL(\n req.url ?? '',\n req.headers.origin ?? 'http://localhost',\n );\n const protocolVersion = checkProtocolVersion(url.pathname);\n if (protocolVersion < MIN_SUBSCRIBE_PROTOCOL_VERSION) {\n throw new Error(\n `the /subscribe endpoint requires protocol v${MIN_SUBSCRIBE_PROTOCOL_VERSION} ` +\n `(client is at v${protocolVersion})`,\n );\n }\n\n const instream = await streamInternalStringified(\n this._lc,\n ws,\n subscribeUpstreamSchema,\n outbound,\n {batched: true},\n );\n\n // Keep reading upstream control messages for the life of the connection.\n // Do not manually exit loop, as that would close the socket. The loop\n // exits naturally when the connection closes.\n for await (const msg of instream) {\n if (msg[0] === 'reserve-snapshot') {\n const {taskID} = msg[1];\n this.#ensureChangeStreamerStarted('incoming snapshot reservation');\n reservation =\n await this.#changeStreamer.startSnapshotReservation(taskID);\n void this.#forwardReservation(reservation, outbound);\n } else if (msg[0] === 'start-subscription') {\n const ctx: SubscriberContext = {\n ...msg[1],\n protocolVersion,\n wsBatched: true,\n };\n if (ctx.mode === 'serving') {\n this.#ensureChangeStreamerStarted('incoming subscription');\n }\n // subscribe() registers the subscriber and then releases any\n // reservation for that task.\n reservation = undefined;\n await this.#changeStreamer.subscribe(ctx, outbound);\n }\n }\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n } finally {\n // Covers a reservation-phase disconnect (instream ended before a\n // subscription started); idempotent with the close handler above.\n reservation?.cancel();\n }\n };\n\n /**\n * Forwards a snapshot reservation's single `['status', ...]` message onto\n * the merged connection's `outbound` sink as a `['reserved', ...]` message.\n * The loop then blocks on the held-open reservation until it is cancelled,\n * by `subscribe()` at the subscription handoff, or by the reservation-phase\n * teardown on disconnect.\n */\n async #forwardReservation(\n reservation: Source<SnapshotMessage>,\n outbound: Subscription<string | PreSerializedBatch>,\n ) {\n try {\n for await (const [, status] of reservation) {\n // Translate the /snapshot protocol's ['status', {tag: 'status', ...}]\n // message to the /subscribe protocol's ['reserved', {tag: 'snapshot', ...}].\n outbound.push(\n BigIntJSON.stringify([\n 'reserved',\n {...status, tag: 'snapshot'},\n ] satisfies ReservedMessage),\n );\n }\n } catch (e) {\n this._lc.warn?.(`error forwarding snapshot reservation`, e);\n outbound.fail(e instanceof Error ? e : new Error(String(e)));\n }\n }\n\n #changeStreamerStarted = false;\n\n #ensureChangeStreamerStarted(reason?: string) {\n if (!this.#changeStreamerStarted && this._state.shouldRun()) {\n this.#lc.info?.(\n `starting ChangeStreamerService ${reason ? `(${reason})` : ''}`,\n );\n void this.#changeStreamer\n .run()\n .catch(e =>\n this.#lc.warn?.(`ChangeStreamerService ended with error`, e),\n )\n .finally(() => this.stop());\n\n this.#changeStreamerStarted = true;\n }\n }\n\n protected override _onStart(): void {\n const {startupDelayMs, readinessGate = promiseVoid} = this.#opts;\n if (startupDelayMs === 0) {\n // In RMv2, there is no need to delay starting the change streamer\n // because RM startup is non-disruptive.\n this.#ensureChangeStreamerStarted();\n } else {\n // In RMv1, starting the change-streamer forcibly shuts down the\n // previous change-streamer, causing view-syncers to reconnect.\n // If this replication-manager has just started, the routing layer may\n // not have registered it with DNS, as that only happens after it\n // confirms health checks. To minimize downtime, the takeover is\n // delayed for the configured startupDelayMs _after_ beginning to\n // advertise readiness.\n void readinessGate.then(() => {\n this.#lc.info?.(\n `waiting ${startupDelayMs}ms before taking over the change log`,\n );\n this._state.setTimeout(\n () =>\n this.#ensureChangeStreamerStarted(\n `startup delay elapsed (${startupDelayMs} ms)`,\n ),\n startupDelayMs,\n );\n });\n }\n }\n\n protected override async _onStop(): Promise<void> {\n if (this.#changeStreamerStarted) {\n await this.#changeStreamer.stop();\n }\n }\n}\n\nexport interface SnapshotReserver {\n reserveSnapshot(taskID: string): Promise<{\n reserved: SnapshotStatus;\n followup: ReservationFollowup;\n }>;\n}\n\nexport interface SubscriptionStarter {\n subscribe(\n ctx: SubscriberContext,\n ): Promise<Source<Sized<SubscribeDownstream>>>;\n}\n\nexport interface ReservationFollowup extends SubscriptionStarter {\n /**\n * A signal to indicate that the followup is no longer valid (i.e. the\n * connection to the reserving task has been severed).\n */\n readonly signal: AbortSignal;\n\n /**\n * Abandons the reservation without subscribing, closing the connection that\n * was held open to pin the change-log floor. Used when the caller cannot\n * proceed to `subscribe()` (e.g. process shutdown while a restore is in\n * progress) and must not leave the reservation's connection open forever.\n */\n cancel(reason?: Error): void;\n}\n\nexport class ChangeStreamerHttpClient\n implements ChangeStreamer, SnapshotReserver, SubscriptionStarter\n{\n readonly #lc: LogContext;\n readonly #shardID: ShardID;\n readonly #changeDB: PostgresDB;\n readonly #changeStreamerURI: string | undefined;\n\n constructor(\n lc: LogContext,\n shardID: ShardID,\n changeDB: string,\n changeStreamerURI: string | undefined,\n ) {\n this.#lc = lc;\n this.#shardID = shardID;\n // Create a pg client with a single short-lived connection for the purpose\n // of change-streamer discovery (i.e. ChangeDB as DNS).\n this.#changeDB = pgClient(lc, changeDB, 'change-streamer-discovery', {\n max: 1,\n ['idle_timeout']: 15,\n });\n this.#changeStreamerURI = changeStreamerURI;\n }\n\n async #resolveChangeStreamer(path: string) {\n let baseURL = this.#changeStreamerURI;\n if (!baseURL) {\n const address = await discoverChangeStreamerAddress(\n this.#shardID,\n this.#changeDB,\n );\n if (!address) {\n throw new Error(`no change-streamer is running`);\n }\n baseURL = address.includes('://') ? `${address}/` : `ws://${address}/`;\n }\n const uri = new URL(path, baseURL);\n this.#lc.info?.(`connecting to change-streamer@${uri}`);\n return uri;\n }\n\n async reserveSnapshot(taskID: string): Promise<{\n reserved: SnapshotStatus;\n followup: ReservationFollowup;\n }> {\n const {instream, outbound} = await this.connect(taskID);\n outbound.push(['reserve-snapshot', {taskID}]);\n\n const {head, rest} = readHead(instream);\n const item = await head.next();\n if (!item.value) {\n throw new Error('stream closed without snapshot reservation');\n }\n const {data: msg} = item.value;\n if (msg[0] !== 'reserved') {\n throw new Error(`expected \"reserved\" message but got ${msg[0]}`);\n }\n const reserved: SnapshotStatus = msg[1];\n const followup: ReservationFollowup = {\n subscribe: ctx => {\n outbound.push(['start-subscription', toStartSubscriptionContext(ctx)]);\n return Promise.resolve(rest);\n },\n signal: outbound.signal,\n cancel: reason => outbound.cancel(reason),\n };\n\n return {reserved, followup};\n }\n\n async subscribe(\n ctx: SubscriberContext,\n ): Promise<Source<Sized<SubscribeDownstream>>> {\n const {instream, outbound} = await this.connect(ctx.taskID);\n outbound.push(['start-subscription', toStartSubscriptionContext(ctx)]);\n return instream;\n }\n\n /**\n * Opens a merged v7 `/subscribe` connection (see subscribe.ts). Returns the\n * inbound change/reservation stream and an `outbound` sink into which the\n * caller pushes the `reserve-snapshot` / `start-subscription` control\n * messages that drive the connection's phases. The single connection spans\n * the reservation, backup restore, and subscription, so the reserving\n * replication-manager is the one that serves the subscription.\n */\n // exported for testing\n async connect(taskID: string): Promise<{\n instream: Source<Sized<SubscribeDownstream>>;\n outbound: Subscription<SubscribeUpstream>;\n }> {\n const uri = await this.#resolveChangeStreamer(SUBSCRIBE_PATH);\n\n // taskID is carried in the query for observability/routing; the server\n // reads the authoritative taskID from the control messages themselves.\n const params = new URLSearchParams({taskID});\n // Bound the TCP connect + HTTP upgrade. Without this, a SYN black-holed\n // by a stale Service endpoint (e.g. a just-terminated replication-manager\n // pod) is retransmitted by the kernel for ~2 minutes before ETIMEDOUT.\n // Failing fast lets the caller's retry open a fresh connection that is\n // routed to a live endpoint.\n const ws = new WebSocket(uri + `?${params.toString()}`, {\n handshakeTimeout: CONNECT_TIMEOUT_MS,\n });\n\n const outbound = Subscription.create<SubscribeUpstream>();\n const instream = await streamInternalWithSize(\n this.#lc,\n ws,\n subscribeDownstreamSchema,\n outbound,\n );\n return {instream, outbound};\n }\n}\n\ntype RequestHeaders = Pick<IncomingMessage, 'url' | 'headers'>;\n\nexport function getSubscriberContext(req: RequestHeaders): SubscriberContext {\n const url = new URL(req.url ?? '', req.headers.origin ?? 'http://localhost');\n const protocolVersion = checkProtocolVersion(url.pathname);\n const params = new URLParams(url);\n\n return {\n protocolVersion,\n id: params.get('id', true),\n taskID: params.get('taskID', true),\n mode: params.get('mode', false) === 'backup' ? 'backup' : 'serving',\n replicaVersion: params.get('replicaVersion', true),\n watermark: params.get('watermark', true),\n // Absent for subscribers that predate the parameter, which is the safe\n // default: the barrier falls back to polling rather than waiting on an\n // ACK that would never be attributed to a writer.\n wsBatched: params.getBoolean('wsBatched'),\n };\n}\n\nfunction checkProtocolVersion(pathname: string): number {\n const match = PATH_REGEX.exec(pathname);\n if (!match) {\n throw new Error(`invalid path: ${pathname}`);\n }\n const v = Number(match.groups?.version);\n if (\n Number.isNaN(v) ||\n v > PROTOCOL_VERSION ||\n v < MIN_SUPPORTED_PROTOCOL_VERSION\n ) {\n throw new Error(\n `Cannot service client at protocol v${v}. ` +\n `Supported protocols: [v${MIN_SUPPORTED_PROTOCOL_VERSION} ... v${PROTOCOL_VERSION}]`,\n );\n }\n return v;\n}\n\n// Projects a client-side SubscriberContext onto the start-subscription\n// message payload, dropping the fields that are implicit on the merged\n// connection: protocolVersion (from the request path), wsBatched (always on).\nfunction toStartSubscriptionContext(ctx: SubscriberContext): SubscribeContext {\n const {taskID, id, mode, replicaVersion, watermark, backfills} = ctx;\n return {taskID, id, mode, replicaVersion, watermark, backfills};\n}\n"],"mappings":"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"}
|
|
@@ -15,6 +15,11 @@ import { type PurgeLock, type TuningOptions as StorerOptions } from './storer.ts
|
|
|
15
15
|
export type BackupConfig = {
|
|
16
16
|
backupURL: string;
|
|
17
17
|
litestreamVersion: LitestreamVersion;
|
|
18
|
+
/**
|
|
19
|
+
* The replica being backed up. Its size is sent to subscribers as an
|
|
20
|
+
* estimate of the size of their restored replica.
|
|
21
|
+
*/
|
|
22
|
+
replicaFile?: string | undefined;
|
|
18
23
|
};
|
|
19
24
|
export type SQLiteCatchupOptions = {
|
|
20
25
|
/** The change-log database, i.e. `changeLogFileName(replicaFile)`. */
|
|
@@ -66,6 +71,23 @@ export type SQLiteChangeLogServeOptions = {
|
|
|
66
71
|
inspect?: (() => SQLiteChangeLogCoverage | undefined) | undefined;
|
|
67
72
|
};
|
|
68
73
|
export type TuningOptions = StorerOptions & {
|
|
74
|
+
/**
|
|
75
|
+
* Keeps the legacy Postgres change log on the initialization, persistence,
|
|
76
|
+
* catchup, purge, and ACK paths. Disable only after SQLite and the replica
|
|
77
|
+
* backup are authoritative for all of those responsibilities.
|
|
78
|
+
*/
|
|
79
|
+
pgChangeLogEnabled: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the PG change log's head was behind the position of the
|
|
82
|
+
* replication slot from which replication resumes, in which case the
|
|
83
|
+
* change log is re-initialized from the (restored) replica if it does not
|
|
84
|
+
* contain all of the replica's changes (see `PgChangeLogPurgeLocker`).
|
|
85
|
+
*
|
|
86
|
+
* Required so that a caller cannot silently drop it: without it, the
|
|
87
|
+
* stream resumes from a head that the slot cannot stream from, skipping
|
|
88
|
+
* transactions.
|
|
89
|
+
*/
|
|
90
|
+
pgChangeLogBehindSlot: boolean;
|
|
69
91
|
flowControlConsensusTimeoutProportion: number;
|
|
70
92
|
flowControlSlowSubscriberGracePeriodMs?: number | undefined;
|
|
71
93
|
sqliteCatchup?: SQLiteCatchupOptions | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAcjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,mCAAmC,CAAC;AAM3C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"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"}
|