@rocicorp/zero 1.9.1-canary.1 → 1.10.0-canary.1
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 +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/replicache/src/db/commit.js +1 -4
- package/out/replicache/src/db/commit.js.map +1 -1
- package/out/replicache/src/db/read.d.ts +1 -0
- package/out/replicache/src/db/read.d.ts.map +1 -1
- package/out/replicache/src/db/read.js +1 -4
- package/out/replicache/src/db/read.js.map +1 -1
- package/out/replicache/src/db/write.d.ts +2 -1
- package/out/replicache/src/db/write.d.ts.map +1 -1
- package/out/replicache/src/db/write.js +4 -51
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/persist/clients.d.ts +1 -0
- package/out/replicache/src/persist/clients.d.ts.map +1 -1
- package/out/replicache/src/persist/clients.js +4 -31
- package/out/replicache/src/persist/clients.js.map +1 -1
- package/out/replicache/src/process-env.d.ts +28 -0
- package/out/replicache/src/process-env.d.ts.map +1 -0
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/scan-iterator.js +2 -14
- package/out/replicache/src/scan-iterator.js.map +1 -1
- package/out/replicache/src/subscriptions.d.ts +1 -0
- package/out/replicache/src/subscriptions.d.ts.map +1 -1
- package/out/replicache/src/subscriptions.js +2 -10
- package/out/replicache/src/subscriptions.js.map +1 -1
- package/out/replicache/src/transactions.d.ts +1 -0
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +2 -4
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/zero/package.js +2 -2
- package/out/zero/package.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 +10 -1
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +43 -1
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +139 -20
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/db/migration.js +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +5 -0
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +15 -1
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/server/backup-watermark-poller.js +31 -0
- package/out/zero-cache/src/server/backup-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +90 -30
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +5 -15
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +46 -3
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +2 -1
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.d.ts +1 -1
- package/out/zero-cache/src/server/worker-urls.js +2 -2
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/change-source/change-source.d.ts +4 -2
- package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +35 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js +34 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +2 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +73 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts +2 -6
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +16 -7
- 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.js +4 -7
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +7 -7
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +75 -23
- 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 +9 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +34 -4
- 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.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +4 -2
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +7 -4
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +0 -5
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +29 -41
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +35 -16
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +77 -7
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +1 -12
- 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 +3 -3
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts +6 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js +21 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +5 -6
- 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 +32 -9
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +15 -5
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js +66 -0
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +68 -78
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +84 -92
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js +50 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts +141 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +270 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js +39 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -4
- 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 +15 -20
- 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 +69 -24
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +330 -63
- 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 +55 -13
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts +8 -0
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +14 -3
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +41 -11
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.d.ts → litestream3-prometheus-poller.d.ts} +19 -45
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.js → litestream3-prometheus-poller.js} +57 -171
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts +15 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js +55 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +5 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +2 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +14 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +86 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +4 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +39 -14
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +75 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +261 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts +121 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js +391 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +62 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +159 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +109 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +258 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +22 -12
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +93 -117
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +61 -10
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +101 -8
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +34 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +86 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -0
- package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +3 -2
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts +12 -11
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +50 -140
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +42 -0
- package/out/zero-cache/src/services/litestream/metrics.d.ts +4 -4
- package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js +7 -10
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +57 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +137 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts +169 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +434 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts +358 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js +477 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +55 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +150 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +44 -17
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +11 -4
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +2 -2
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +119 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +214 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +3 -3
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js +9 -5
- package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +131 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +413 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts +120 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +283 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -0
- package/out/zero-cache/src/services/replicator/write-worker.js +9 -6
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +2 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/running-state.d.ts +1 -1
- package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/running-state.js +1 -0
- package/out/zero-cache/src/services/running-state.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +9 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +9 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +18 -3
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.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 +48 -7
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +1 -2
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +1 -25
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +10 -0
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +25 -4
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +1 -0
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +25 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.d.ts +25 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +33 -1
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts +12 -0
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +75 -0
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-schema/src/builder/table-builder.d.ts +23 -0
- package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
- package/out/zero-schema/src/builder/table-builder.js +18 -0
- package/out/zero-schema/src/builder/table-builder.js.map +1 -1
- package/out/zero-types/src/schema.d.ts +11 -0
- package/out/zero-types/src/schema.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +2 -2
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query.d.ts +83 -14
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/static-query.d.ts +1 -1
- package/out/zql/src/query/static-query.d.ts.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts +15 -0
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +20 -13
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/package.json +2 -2
- package/out/replicache/src/db/index.js +0 -91
- package/out/replicache/src/db/index.js.map +0 -1
- package/out/replicache/src/lazy.d.ts +0 -2
- package/out/replicache/src/lazy.d.ts.map +0 -1
- package/out/replicache/src/lazy.js +0 -12
- package/out/replicache/src/lazy.js.map +0 -1
- package/out/zero-cache/src/server/backup-watermark-reader.js +0 -35
- package/out/zero-cache/src/server/backup-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts +0 -16
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js +0 -54
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts +0 -25
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js +0 -159
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts +0 -44
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +0 -65
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts +0 -11
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js +0 -74
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts +0 -42
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js +0 -117
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport type WalMode = 'wal' | 'wal2';\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function prepare(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: WalMode,\n mode: ReplicaFileMode,\n): Promise<{file: string; walMode: WalMode}> {\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n const replica = new Database(lc, file);\n try {\n // Start by folding any (e.g. restored) WAL(2) files into the main db.\n await setJournalMode(lc, replica, 'delete');\n\n const [{page_size: pageSize}] = replica.pragma<{page_size: number}>(\n 'page_size',\n );\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n replica.unsafeMode(false);\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n }\n }\n\n await setJournalMode(lc, replica, walMode);\n\n const pragmas = getPragmaConfig(mode);\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n } finally {\n replica.close();\n }\n return {file, walMode};\n}\n\n// Setting the journal_mode requires an exclusive lock on the replica.\n// Add resilience against random replica reads (for stats, etc.) by\n// retrying if the database is locked. Note that the busy_timeout doesn't\n// work here.\nasync function setJournalMode(\n lc: LogContext,\n replica: Database,\n mode: 'delete' | 'wal' | 'wal2',\n) {\n lc.info?.(`setting ${replica.name} to ${mode} mode`);\n let err: unknown;\n for (let i = 0; i < 5; i++) {\n try {\n replica.pragma(`journal_mode = ${mode}`);\n return;\n } catch (e) {\n lc.warn?.(`error setting journal_mode to ${mode} (attempt ${i + 1})`, e);\n err = e;\n }\n await sleep(500);\n }\n throw err;\n}\n\n/**\n * Returns the PragmaConfig for a given replica file mode.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n */\nexport function getPragmaConfig(mode: ReplicaFileMode): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: mode === 'backup' ? 0 : undefined,\n };\n}\n\nexport function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n) {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return prepare(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return prepare(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return prepare(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(\n _lc: LogContext,\n source: Worker,\n onNotify?: (state: ReplicaState) => void,\n): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg => {\n onNotify?.(msg);\n void notifier.notifySubscribers(msg);\n });\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {deleteChangeLogDB} from '../services/replicator/change-log-db.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport type WalMode = 'wal' | 'wal2';\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\n/**\n * Whether a replicator worker deletes the change-log file beside its replica at\n * startup.\n *\n * The change log belongs to the change-streamer, so this only ever cleans up a\n * file that nothing is writing. The predicate is deliberately the config flag\n * and nothing else: keying it on anything derived per replica -- a file mode, or\n * the retired `logsChangeStream` -- would make every replicator start unlink the\n * change-streamer's live log, because no replicator writes it any more. Paths\n * coincide whenever `fileMode !== 'serving-copy'`, so that hazard is real in the\n * shipped no-`backupURL` configuration.\n *\n * Deleting is safe when nothing writes the log: it is excluded from the\n * litestream backup, only the writer purges it, and re-enabling the writer\n * reseeds at the resume watermark.\n */\nexport function replicatorDeletesStaleChangeLog(\n sqliteChangeLogMode: string,\n): boolean {\n return sqliteChangeLogMode === 'off';\n}\n\n/**\n * The `mode=off` cleanup itself: deletes the change-log file beside `dbPath`\n * when {@link replicatorDeletesStaleChangeLog} says nothing writes it, and\n * reports whether it did. The decision and the delete live in one function so\n * that the guard test covers the call the replicator actually makes, not just\n * the predicate.\n */\nexport function deleteStaleChangeLog(\n sqliteChangeLogMode: string,\n dbPath: string,\n): boolean {\n if (!replicatorDeletesStaleChangeLog(sqliteChangeLogMode)) {\n return false;\n }\n deleteChangeLogDB(dbPath);\n return true;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function prepare(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: WalMode,\n mode: ReplicaFileMode,\n): Promise<{file: string; walMode: WalMode}> {\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n const replica = new Database(lc, file);\n try {\n // Start by folding any (e.g. restored) WAL(2) files into the main db.\n await setJournalMode(lc, replica, 'delete');\n\n const [{page_size: pageSize}] = replica.pragma<{page_size: number}>(\n 'page_size',\n );\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n replica.unsafeMode(false);\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n }\n }\n\n await setJournalMode(lc, replica, walMode);\n\n const pragmas = getPragmaConfig(mode);\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n } finally {\n replica.close();\n }\n return {file, walMode};\n}\n\n// Setting the journal_mode requires an exclusive lock on the replica.\n// Add resilience against random replica reads (for stats, etc.) by\n// retrying if the database is locked. Note that the busy_timeout doesn't\n// work here.\nasync function setJournalMode(\n lc: LogContext,\n replica: Database,\n mode: 'delete' | 'wal' | 'wal2',\n) {\n lc.info?.(`setting ${replica.name} to ${mode} mode`);\n let err: unknown;\n for (let i = 0; i < 5; i++) {\n try {\n replica.pragma(`journal_mode = ${mode}`);\n return;\n } catch (e) {\n lc.warn?.(`error setting journal_mode to ${mode} (attempt ${i + 1})`, e);\n err = e;\n }\n await sleep(500);\n }\n throw err;\n}\n\n/**\n * Returns the PragmaConfig for a given replica file mode.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n */\nexport function getPragmaConfig(mode: ReplicaFileMode): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: mode === 'backup' ? 0 : undefined,\n };\n}\n\nexport function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n) {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return prepare(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return prepare(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return prepare(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(\n _lc: LogContext,\n source: Worker,\n onNotify?: (state: ReplicaState) => void,\n): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg => {\n onNotify?.(msg);\n void notifier.notifySubscribers(msg);\n });\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
|
|
@@ -28,6 +28,18 @@ export type ServingLagStats = {
|
|
|
28
28
|
readonly p99Ms: number;
|
|
29
29
|
readonly maxMs: number;
|
|
30
30
|
};
|
|
31
|
+
export type PipelineDedupViewSyncer = Pick<ViewSyncer, 'pipelineHashes' | 'clientSchemaKey'>;
|
|
32
|
+
export type PipelineDedupStats = {
|
|
33
|
+
readonly clientTotal: number;
|
|
34
|
+
readonly internalTotal: number;
|
|
35
|
+
readonly clientHashes: ReadonlyMap<string, {
|
|
36
|
+
count: number;
|
|
37
|
+
queryName: string | undefined;
|
|
38
|
+
}>;
|
|
39
|
+
readonly internalUnique: number;
|
|
40
|
+
readonly clientSchemas: number;
|
|
41
|
+
};
|
|
42
|
+
export declare function computePipelineDedupStats(viewSyncers: Iterable<PipelineDedupViewSyncer>): PipelineDedupStats;
|
|
31
43
|
export declare const MAX_REPLICA_READY_STATES = 10000;
|
|
32
44
|
export declare function computeServingLagStatsMs(now: number, replicaReadyStates: ReplicaReadyState[], viewSyncers: Iterable<ServingLagViewSyncer>): ServingLagStats;
|
|
33
45
|
export declare function computeMaxServingLagMs(now: number, replicaReadyStates: ReplicaReadyState[], viewSyncers: Iterable<ServingLagViewSyncer>): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAUjD,OAAO,EAAyB,KAAK,iBAAiB,EAAC,MAAM,iBAAiB,CAAC;AAE/E,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAYzD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAC1D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sCAAsC,CAAC;AAEvE,OAAO,KAAK,EACV,oBAAoB,EACpB,OAAO,EACP,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uDAAuD,CAAC;AACpG,OAAO,EAAC,gBAAgB,EAAC,MAAM,8CAA8C,CAAC;AAC9E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAO3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,WAAW,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,UAAU,EACV,aAAa,GAAG,eAAe,GAAG,oBAAoB,CACvD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAMF,eAAO,MAAM,wBAAwB,QAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,8 +17,45 @@ import { resolver } from "@rocicorp/resolver";
|
|
|
17
17
|
import { WebSocketServer } from "ws";
|
|
18
18
|
import { pid } from "node:process";
|
|
19
19
|
//#region ../zero-cache/src/workers/syncer.ts
|
|
20
|
+
function computePipelineDedupStats(viewSyncers) {
|
|
21
|
+
let clientTotal = 0;
|
|
22
|
+
let internalTotal = 0;
|
|
23
|
+
const clientHashes = /* @__PURE__ */ new Map();
|
|
24
|
+
const internalHashes = /* @__PURE__ */ new Set();
|
|
25
|
+
const schemaKeys = /* @__PURE__ */ new Set();
|
|
26
|
+
let vsIndex = 0;
|
|
27
|
+
for (const vs of viewSyncers) {
|
|
28
|
+
const schemaKey = vs.clientSchemaKey;
|
|
29
|
+
if (schemaKey !== void 0) schemaKeys.add(schemaKey);
|
|
30
|
+
const dedupSchemaKey = schemaKey ?? `#vs${vsIndex}`;
|
|
31
|
+
vsIndex++;
|
|
32
|
+
for (const { transformationHash, internal, queryName } of vs.pipelineHashes()) {
|
|
33
|
+
const key = `${dedupSchemaKey}/${transformationHash}`;
|
|
34
|
+
if (internal) {
|
|
35
|
+
internalTotal++;
|
|
36
|
+
internalHashes.add(key);
|
|
37
|
+
} else {
|
|
38
|
+
clientTotal++;
|
|
39
|
+
const entry = clientHashes.get(key);
|
|
40
|
+
if (entry) entry.count++;
|
|
41
|
+
else clientHashes.set(key, {
|
|
42
|
+
count: 1,
|
|
43
|
+
queryName
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
clientTotal,
|
|
50
|
+
internalTotal,
|
|
51
|
+
clientHashes,
|
|
52
|
+
internalUnique: internalHashes.size,
|
|
53
|
+
clientSchemas: schemaKeys.size
|
|
54
|
+
};
|
|
55
|
+
}
|
|
20
56
|
var MAX_REPLICA_READY_STATES = 1e4;
|
|
21
57
|
var VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS = 6e4;
|
|
58
|
+
var SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS = 5 * 6e4;
|
|
22
59
|
var PROTOCOL_VERSION_ATTRIBUTE = "protocol.version";
|
|
23
60
|
var FAILURE_REASON_ATTRIBUTE = "reason";
|
|
24
61
|
function boundReplicaReadyStates(replicaReadyStates) {
|
|
@@ -134,6 +171,7 @@ var Syncer = class {
|
|
|
134
171
|
#servingLagDistributionCache;
|
|
135
172
|
#servingLagDistributionCacheClearQueued = false;
|
|
136
173
|
#viewSyncerLagSampleInterval;
|
|
174
|
+
#eligibilityLogInterval;
|
|
137
175
|
constructor(lc, config, viewSyncerFactory, mutagenFactory, pusherFactory, parent, validateLegacyJWT) {
|
|
138
176
|
this.#config = config;
|
|
139
177
|
this.#validateLegacyJWT = validateLegacyJWT;
|
|
@@ -178,8 +216,23 @@ var Syncer = class {
|
|
|
178
216
|
getOrCreateGauge("sync", "serving_lagging_client_groups", "Number of active client groups with unserved replica changes").addCallback((result) => {
|
|
179
217
|
result.observe(this.#computeServingLagDistribution().laggingClientGroups);
|
|
180
218
|
});
|
|
219
|
+
getOrCreateGauge("sync", "pipelines_total", "Query pipelines across all client groups on this worker, by query type. The ratio to sync.pipelines_unique is the dedup factor available to shared pipeline advancement.").addCallback((result) => {
|
|
220
|
+
const stats = this.#computePipelineDedupStats();
|
|
221
|
+
result.observe(stats.clientTotal, { type: "client" });
|
|
222
|
+
result.observe(stats.internalTotal, { type: "internal" });
|
|
223
|
+
});
|
|
224
|
+
getOrCreateGauge("sync", "pipelines_unique", "Distinct (clientSchema, transformationHash) pipelines across all client groups on this worker, by query type").addCallback((result) => {
|
|
225
|
+
const stats = this.#computePipelineDedupStats();
|
|
226
|
+
result.observe(stats.clientHashes.size, { type: "client" });
|
|
227
|
+
result.observe(stats.internalUnique, { type: "internal" });
|
|
228
|
+
});
|
|
229
|
+
getOrCreateGauge("sync", "client_schemas", "Distinct client schemas across all client groups on this worker").addCallback((result) => {
|
|
230
|
+
result.observe(this.#computePipelineDedupStats().clientSchemas);
|
|
231
|
+
});
|
|
181
232
|
this.#viewSyncerLagSampleInterval = setInterval(() => this.#recordViewSyncerLagSamples(), VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS);
|
|
182
233
|
this.#viewSyncerLagSampleInterval.unref?.();
|
|
234
|
+
this.#eligibilityLogInterval = setInterval(() => this.#logSharedAdvanceEligibility(), SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS);
|
|
235
|
+
this.#eligibilityLogInterval.unref?.();
|
|
183
236
|
}
|
|
184
237
|
#computeServingLagDistribution() {
|
|
185
238
|
if (this.#servingLagDistributionCache) return this.#servingLagDistributionCache;
|
|
@@ -198,6 +251,27 @@ var Syncer = class {
|
|
|
198
251
|
const { lagsMs } = this.#computeServingLagDistribution();
|
|
199
252
|
for (const lagMs of lagsMs) this.#viewSyncerLag.recordMs(lagMs);
|
|
200
253
|
}
|
|
254
|
+
#computePipelineDedupStats() {
|
|
255
|
+
return computePipelineDedupStats(this.#viewSyncers.getServices());
|
|
256
|
+
}
|
|
257
|
+
#logSharedAdvanceEligibility() {
|
|
258
|
+
const stats = this.#computePipelineDedupStats();
|
|
259
|
+
if (stats.clientTotal === 0) return;
|
|
260
|
+
const unique = stats.clientHashes.size;
|
|
261
|
+
const topDuplicated = [...stats.clientHashes.entries()].filter(([, { count }]) => count > 1).sort((a, b) => b[1].count - a[1].count).slice(0, 10).map(([key, { count, queryName }]) => ({
|
|
262
|
+
key,
|
|
263
|
+
count,
|
|
264
|
+
queryName
|
|
265
|
+
}));
|
|
266
|
+
this.#lc.info?.("shared-advance-eligibility", {
|
|
267
|
+
clientPipelines: stats.clientTotal,
|
|
268
|
+
uniqueClientPipelines: unique,
|
|
269
|
+
dedupFactor: Number((stats.clientTotal / Math.max(unique, 1)).toFixed(2)),
|
|
270
|
+
internalPipelines: stats.internalTotal,
|
|
271
|
+
clientSchemas: stats.clientSchemas,
|
|
272
|
+
topDuplicated
|
|
273
|
+
});
|
|
274
|
+
}
|
|
201
275
|
#recordReplicaReadyState(state) {
|
|
202
276
|
if (state.watermark === void 0 || state.replicaReadyTimeMs === void 0) return;
|
|
203
277
|
const last = this.#replicaReadyStates.at(-1);
|
|
@@ -350,6 +424,7 @@ var Syncer = class {
|
|
|
350
424
|
}
|
|
351
425
|
stop() {
|
|
352
426
|
clearInterval(this.#viewSyncerLagSampleInterval);
|
|
427
|
+
clearInterval(this.#eligibilityLogInterval);
|
|
353
428
|
this.#wss.close();
|
|
354
429
|
this.#stopped.resolve();
|
|
355
430
|
return promiseVoid;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.js","names":["#lc","#viewSyncers","#mutagens","#pushers","#connections","#drainCoordinator","#parent","#wss","#stopped","#config","#validateLegacyJWT","#replicaReadyStates","#webSocketOpenConnections","#webSocketConnectionAttempts","#webSocketConnectionSuccesses","#webSocketConnectionFailures","#viewSyncerLag","#recordReplicaReadyState","#createConnection","#computeServingLagDistribution","#viewSyncerLagSampleInterval","#recordViewSyncerLagSamples","#servingLagDistributionCache","#servingLagDistributionCacheClearQueued"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {\n isProtocolError,\n ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {resolveAuth, type Auth, type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateNativeHistogram,\n getOrCreateUpDownCounter,\n} from '../observability/metrics.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport type {ConnectionContextManager} from '../services/view-syncer/connection-context-manager.ts';\nimport {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\n};\n\nexport type ReplicaReadyState = {\n readonly watermark: string;\n readonly replicaReadyTimeMs: number;\n};\n\nexport type ServingLagViewSyncer = Pick<\n ViewSyncer,\n 'createdAtMs' | 'servedVersion' | 'servingLagEligible'\n>;\n\nexport type ServingLagStats = {\n readonly activeClientGroups: number;\n readonly laggingClientGroups: number;\n readonly minMs: number;\n readonly p50Ms: number;\n readonly p75Ms: number;\n readonly p99Ms: number;\n readonly maxMs: number;\n};\n\ntype ServingLagDistribution = ServingLagStats & {\n readonly lagsMs: readonly number[];\n};\n\nexport const MAX_REPLICA_READY_STATES = 10_000;\nconst VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS = 60_000;\n\nconst PROTOCOL_VERSION_ATTRIBUTE = 'protocol.version';\nconst FAILURE_REASON_ATTRIBUTE = 'reason';\n\nfunction boundReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n): void {\n if (replicaReadyStates.length > MAX_REPLICA_READY_STATES) {\n replicaReadyStates.splice(\n 0,\n replicaReadyStates.length - MAX_REPLICA_READY_STATES,\n );\n }\n}\n\nfunction pruneReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n firstNeededIndex: number,\n): void {\n if (firstNeededIndex > 0) {\n replicaReadyStates.splice(0, firstNeededIndex);\n }\n\n boundReplicaReadyStates(replicaReadyStates);\n}\n\nfunction lowerBoundReplicaReadyTimeMs(\n replicaReadyStates: readonly ReplicaReadyState[],\n replicaReadyTimeMs: number,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].replicaReadyTimeMs < replicaReadyTimeMs) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction upperBoundWatermark(\n replicaReadyStates: readonly ReplicaReadyState[],\n watermark: string,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].watermark <= watermark) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction findFirstUnservedIndex(\n replicaReadyStates: readonly ReplicaReadyState[],\n viewSyncer: ServingLagViewSyncer,\n): number {\n const firstReadyAfterCreation = lowerBoundReplicaReadyTimeMs(\n replicaReadyStates,\n viewSyncer.createdAtMs,\n );\n const firstAfterServedVersion =\n viewSyncer.servedVersion === null\n ? 0\n : upperBoundWatermark(replicaReadyStates, viewSyncer.servedVersion);\n\n const firstUnservedIndex = Math.max(\n firstReadyAfterCreation,\n firstAfterServedVersion,\n );\n return firstUnservedIndex < replicaReadyStates.length\n ? firstUnservedIndex\n : -1;\n}\n\nfunction percentileNearestRank(\n sortedValues: readonly number[],\n percentile: number,\n): number {\n if (sortedValues.length === 0) {\n return 0;\n }\n const index = Math.min(\n sortedValues.length - 1,\n Math.max(0, Math.ceil((percentile / 100) * sortedValues.length) - 1),\n );\n return sortedValues[index];\n}\n\nfunction computeServingLagDistributionMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagDistribution {\n const lags: number[] = [];\n let laggingClientGroups = 0;\n let firstNeededIndex = replicaReadyStates.length;\n\n for (const viewSyncer of viewSyncers) {\n if (!viewSyncer.servingLagEligible) {\n continue;\n }\n\n const firstUnservedIndex = findFirstUnservedIndex(\n replicaReadyStates,\n viewSyncer,\n );\n if (firstUnservedIndex === -1) {\n lags.push(0);\n continue;\n }\n\n firstNeededIndex = Math.min(firstNeededIndex, firstUnservedIndex);\n const lagMs = Math.max(\n 0,\n now - replicaReadyStates[firstUnservedIndex].replicaReadyTimeMs,\n );\n lags.push(lagMs);\n if (lagMs > 0) {\n laggingClientGroups++;\n }\n }\n\n pruneReplicaReadyStates(replicaReadyStates, firstNeededIndex);\n\n lags.sort((a, b) => a - b);\n return {\n activeClientGroups: lags.length,\n laggingClientGroups,\n // The percentile fields are for the legacy serving_lag_stats gauge.\n // The native view_syncer_lag histogram records lagsMs and computes\n // percentiles in Prometheus/AMP.\n minMs: lags[0] ?? 0,\n p50Ms: percentileNearestRank(lags, 50),\n p75Ms: percentileNearestRank(lags, 75),\n p99Ms: percentileNearestRank(lags, 99),\n maxMs: lags.at(-1) ?? 0,\n lagsMs: lags,\n };\n}\n\nexport function computeServingLagStatsMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagStats {\n const distribution = computeServingLagDistributionMs(\n now,\n replicaReadyStates,\n viewSyncers,\n );\n return {\n activeClientGroups: distribution.activeClientGroups,\n laggingClientGroups: distribution.laggingClientGroups,\n minMs: distribution.minMs,\n p50Ms: distribution.p50Ms,\n p75Ms: distribution.p75Ms,\n p99Ms: distribution.p99Ms,\n maxMs: distribution.maxMs,\n };\n}\n\nexport function computeMaxServingLagMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): number {\n return computeServingLagStatsMs(now, replicaReadyStates, viewSyncers).maxMs;\n}\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n maxPayload: config.websocketMaxPayloadBytes,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service> | undefined;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n readonly #replicaReadyStates: ReplicaReadyState[] = [];\n readonly #webSocketOpenConnections = getOrCreateUpDownCounter(\n 'sync',\n 'websocket.open_connections',\n 'Open client WebSocket connections.',\n );\n readonly #webSocketConnectionAttempts = getOrCreateCounter(\n 'sync',\n 'websocket.connection_attempts',\n 'Client WebSocket connection attempts.',\n );\n readonly #webSocketConnectionSuccesses = getOrCreateCounter(\n 'sync',\n 'websocket.connection_successes',\n 'Client WebSocket connections successfully initialized.',\n );\n readonly #webSocketConnectionFailures = getOrCreateCounter(\n 'sync',\n 'websocket.connection_failures',\n 'Client WebSocket connection attempts that failed before initialization.',\n );\n readonly #viewSyncerLag = getOrCreateNativeHistogram(\n 'sync',\n 'view_syncer_lag',\n {\n description:\n 'Lag from replica-ready change to ViewSyncer output for active ' +\n 'client groups. A change is output after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 's',\n },\n );\n #servingLagDistributionCache: ServingLagDistribution | undefined;\n #servingLagDistributionCacheClearQueued = false;\n #viewSyncerLagSampleInterval: ReturnType<typeof setInterval> | undefined;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: ((id: string) => Mutagen & Service) | undefined,\n pusherFactory:\n | ((\n id: string,\n connContextManager: ConnectionContextManager,\n ) => Pusher & Service)\n | undefined,\n parent: Worker,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) {\n this.#config = config;\n this.#validateLegacyJWT = validateLegacyJWT;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent, state =>\n this.#recordReplicaReadyState(state),\n );\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator),\n v => v.keepalive(),\n );\n if (mutagenFactory) {\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n }\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(\n lc,\n id =>\n pusherFactory(\n id,\n this.#viewSyncers.getService(id).connContextManager,\n ),\n p => p.hasRefs(),\n );\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n\n getOrCreateGauge(\n 'sync',\n 'active-client-groups',\n 'Number of active client groups',\n ).addCallback(result => result.observe(this.#viewSyncers.size));\n\n getOrCreateGauge(\n 'sync',\n 'queries',\n 'Active queries (pipelines) across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.queryCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge(\n 'sync',\n 'rows',\n 'Tracked rows across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.rowCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge('sync', 'serving_lag', {\n description:\n 'Maximum time active ViewSyncer client groups have had unserved ' +\n 'replica changes. A change is served after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().maxMs);\n });\n\n getOrCreateGauge('sync', 'serving_lag_stats', {\n description:\n 'Distribution of time active ViewSyncer client groups have had ' +\n 'unserved replica changes. A change is served after IVM advancement, ' +\n 'CVR flush, and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n const stats = this.#computeServingLagDistribution();\n result.observe(stats.minMs, {stat: 'min'});\n result.observe(stats.p50Ms, {stat: 'p50'});\n result.observe(stats.p75Ms, {stat: 'p75'});\n result.observe(stats.p99Ms, {stat: 'p99'});\n result.observe(stats.maxMs, {stat: 'max'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'serving_lagging_client_groups',\n 'Number of active client groups with unserved replica changes',\n ).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().laggingClientGroups);\n });\n\n this.#viewSyncerLagSampleInterval = setInterval(\n () => this.#recordViewSyncerLagSamples(),\n VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS,\n );\n this.#viewSyncerLagSampleInterval.unref?.();\n }\n\n #computeServingLagDistribution(): ServingLagDistribution {\n if (this.#servingLagDistributionCache) {\n return this.#servingLagDistributionCache;\n }\n\n const distribution = computeServingLagDistributionMs(\n Date.now(),\n this.#replicaReadyStates,\n this.#viewSyncers.getServices(),\n );\n this.#servingLagDistributionCache = distribution;\n if (!this.#servingLagDistributionCacheClearQueued) {\n this.#servingLagDistributionCacheClearQueued = true;\n queueMicrotask(() => {\n this.#servingLagDistributionCache = undefined;\n this.#servingLagDistributionCacheClearQueued = false;\n });\n }\n return distribution;\n }\n\n #recordViewSyncerLagSamples(): void {\n const {lagsMs} = this.#computeServingLagDistribution();\n for (const lagMs of lagsMs) {\n this.#viewSyncerLag.recordMs(lagMs);\n }\n }\n\n #recordReplicaReadyState(state: ReplicaState): void {\n if (\n state.watermark === undefined ||\n state.replicaReadyTimeMs === undefined\n ) {\n return;\n }\n const last = this.#replicaReadyStates.at(-1);\n if (last && last.watermark >= state.watermark) {\n return;\n }\n this.#replicaReadyStates.push({\n watermark: state.watermark,\n replicaReadyTimeMs: state.replicaReadyTimeMs,\n });\n if (this.#viewSyncers.size === 0) {\n this.#replicaReadyStates.length = 0;\n return;\n }\n boundReplicaReadyStates(this.#replicaReadyStates);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n const metricAttributes = {\n [PROTOCOL_VERSION_ATTRIBUTE]: params.protocolVersion,\n };\n let connectionResultRecorded = false;\n const recordConnectionFailure = (reason: string) => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionFailures.add(1, {\n ...metricAttributes,\n [FAILURE_REASON_ATTRIBUTE]: reason,\n });\n };\n const recordConnectionSuccessMetric = () => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionSuccesses.add(1, metricAttributes);\n };\n\n this.#webSocketConnectionAttempts.add(1, metricAttributes);\n this.#webSocketOpenConnections.add(1, metricAttributes);\n ws.once('close', () => {\n this.#webSocketOpenConnections.add(-1, metricAttributes);\n });\n\n this.#lc.debug?.(\n 'creating connection',\n params.clientGroupID,\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n const hasProvidedAuth = auth !== undefined && auth !== '';\n const incomingUserID = userID ?? null;\n\n if (hasProvidedAuth) {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n\n // must either have one of the token options set or have custom mutations & queries enabled\n const hasExactlyOneTokenOption = tokenOptions.length === 1;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {\n recordConnectionFailure('configuration');\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n }\n\n let initialAuth: Auth | undefined;\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS.\n try {\n initialAuth = await resolveAuth(\n this.#lc\n .withContext('clientGroupID', clientGroupID)\n .withContext('clientID', clientID),\n // no previous auth, since this is a new connection, and resolveAuth is\n // connection scoped, not client group scoped\n undefined,\n incomingUserID,\n auth,\n this.#validateLegacyJWT,\n );\n } catch (e) {\n if (isProtocolError(e)) {\n this.#lc.warn?.(\n 'Rejecting sync connection during initial auth resolution',\n {\n clientGroupID,\n clientID,\n incomingUserID,\n hasProvidedAuth,\n errorKind: e.message,\n },\n );\n recordConnectionFailure('auth');\n sendError(this.#lc, ws, e.errorBody);\n ws.close(3000, e.errorBody.message);\n return;\n }\n recordConnectionFailure('internal');\n throw e;\n }\n\n const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n const connContextManager = viewSyncer.connContextManager;\n const group = connContextManager.getGroupState();\n\n // TODO(0xcadams): we only check for user ID mismatch here if the group is\n // already validated. This prevents wrong-user reconnects from evicting a\n // healthy connection, but it does not protect against same-user reconnects\n // with an invalid opaque token. The long-term fix is to keep the replacement\n // connection pending until its auth is fully validated, and only then replace\n // the existing socket.\n if (\n group.pinnedUser !== undefined &&\n group.pinnedUser.id !== incomingUserID\n ) {\n const error = new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n });\n recordConnectionFailure('user_mismatch');\n sendError(this.#lc, ws, error.errorBody);\n ws.close(3000, error.message);\n return;\n }\n\n // Check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n connContextManager.registerConnection(\n {clientID, wsID: params.wsID},\n params,\n initialAuth,\n );\n\n const mutagen = this.#mutagens?.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen?.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n connContextManager,\n viewSyncer,\n mutagen,\n pusher,\n ),\n () => {\n connContextManager.closeConnection({\n clientID,\n wsID: params.wsID,\n });\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen?.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n recordConnectionFailure('internal');\n connContextManager.closeConnection({clientID, wsID: params.wsID});\n mutagen?.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n if (connection.init()) {\n recordConnectionSuccessMetric();\n recordConnectionSuccess();\n } else {\n recordConnectionFailure('protocol_version');\n }\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n params.clientGroupID,\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(`draining view-syncer ${vs.id} (forced)`);\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n clearInterval(this.#viewSyncerLagSampleInterval);\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"syncer.js","names":["#lc","#viewSyncers","#mutagens","#pushers","#connections","#drainCoordinator","#parent","#wss","#stopped","#config","#validateLegacyJWT","#replicaReadyStates","#webSocketOpenConnections","#webSocketConnectionAttempts","#webSocketConnectionSuccesses","#webSocketConnectionFailures","#viewSyncerLag","#recordReplicaReadyState","#createConnection","#computeServingLagDistribution","#computePipelineDedupStats","#viewSyncerLagSampleInterval","#recordViewSyncerLagSamples","#eligibilityLogInterval","#logSharedAdvanceEligibility","#servingLagDistributionCache","#servingLagDistributionCacheClearQueued"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {\n isProtocolError,\n ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {resolveAuth, type Auth, type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateNativeHistogram,\n getOrCreateUpDownCounter,\n} from '../observability/metrics.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport type {ConnectionContextManager} from '../services/view-syncer/connection-context-manager.ts';\nimport {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\n};\n\nexport type ReplicaReadyState = {\n readonly watermark: string;\n readonly replicaReadyTimeMs: number;\n};\n\nexport type ServingLagViewSyncer = Pick<\n ViewSyncer,\n 'createdAtMs' | 'servedVersion' | 'servingLagEligible'\n>;\n\nexport type ServingLagStats = {\n readonly activeClientGroups: number;\n readonly laggingClientGroups: number;\n readonly minMs: number;\n readonly p50Ms: number;\n readonly p75Ms: number;\n readonly p99Ms: number;\n readonly maxMs: number;\n};\n\ntype ServingLagDistribution = ServingLagStats & {\n readonly lagsMs: readonly number[];\n};\n\nexport type PipelineDedupViewSyncer = Pick<\n ViewSyncer,\n 'pipelineHashes' | 'clientSchemaKey'\n>;\n\nexport type PipelineDedupStats = {\n readonly clientTotal: number;\n readonly internalTotal: number;\n // (clientSchemaKey, transformationHash) -> occurrence count across client\n // groups, for client/custom queries.\n readonly clientHashes: ReadonlyMap<\n string,\n {count: number; queryName: string | undefined}\n >;\n readonly internalUnique: number;\n readonly clientSchemas: number;\n};\n\nexport function computePipelineDedupStats(\n viewSyncers: Iterable<PipelineDedupViewSyncer>,\n): PipelineDedupStats {\n let clientTotal = 0;\n let internalTotal = 0;\n const clientHashes = new Map<\n string,\n {count: number; queryName: string | undefined}\n >();\n const internalHashes = new Set<string>();\n const schemaKeys = new Set<string>();\n let vsIndex = 0;\n for (const vs of viewSyncers) {\n const schemaKey = vs.clientSchemaKey;\n if (schemaKey !== undefined) {\n schemaKeys.add(schemaKey);\n }\n // Without a client schema the pipeline's dedup identity is unknown, so key\n // it uniquely per view-syncer; otherwise schema-less pipelines from\n // unrelated client groups would collapse under a shared `undefined/<hash>`\n // key and overstate deduplication.\n const dedupSchemaKey = schemaKey ?? `#vs${vsIndex}`;\n vsIndex++;\n for (const {\n transformationHash,\n internal,\n queryName,\n } of vs.pipelineHashes()) {\n // Pipelines only share when both the client schema (which determines\n // row keys) and the transformed AST are identical.\n const key = `${dedupSchemaKey}/${transformationHash}`;\n if (internal) {\n internalTotal++;\n internalHashes.add(key);\n } else {\n clientTotal++;\n const entry = clientHashes.get(key);\n if (entry) {\n entry.count++;\n } else {\n clientHashes.set(key, {count: 1, queryName});\n }\n }\n }\n }\n return {\n clientTotal,\n internalTotal,\n clientHashes,\n internalUnique: internalHashes.size,\n clientSchemas: schemaKeys.size,\n };\n}\n\nexport const MAX_REPLICA_READY_STATES = 10_000;\nconst VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS = 60_000;\nconst SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS = 5 * 60_000;\n\nconst PROTOCOL_VERSION_ATTRIBUTE = 'protocol.version';\nconst FAILURE_REASON_ATTRIBUTE = 'reason';\n\nfunction boundReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n): void {\n if (replicaReadyStates.length > MAX_REPLICA_READY_STATES) {\n replicaReadyStates.splice(\n 0,\n replicaReadyStates.length - MAX_REPLICA_READY_STATES,\n );\n }\n}\n\nfunction pruneReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n firstNeededIndex: number,\n): void {\n if (firstNeededIndex > 0) {\n replicaReadyStates.splice(0, firstNeededIndex);\n }\n\n boundReplicaReadyStates(replicaReadyStates);\n}\n\nfunction lowerBoundReplicaReadyTimeMs(\n replicaReadyStates: readonly ReplicaReadyState[],\n replicaReadyTimeMs: number,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].replicaReadyTimeMs < replicaReadyTimeMs) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction upperBoundWatermark(\n replicaReadyStates: readonly ReplicaReadyState[],\n watermark: string,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].watermark <= watermark) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction findFirstUnservedIndex(\n replicaReadyStates: readonly ReplicaReadyState[],\n viewSyncer: ServingLagViewSyncer,\n): number {\n const firstReadyAfterCreation = lowerBoundReplicaReadyTimeMs(\n replicaReadyStates,\n viewSyncer.createdAtMs,\n );\n const firstAfterServedVersion =\n viewSyncer.servedVersion === null\n ? 0\n : upperBoundWatermark(replicaReadyStates, viewSyncer.servedVersion);\n\n const firstUnservedIndex = Math.max(\n firstReadyAfterCreation,\n firstAfterServedVersion,\n );\n return firstUnservedIndex < replicaReadyStates.length\n ? firstUnservedIndex\n : -1;\n}\n\nfunction percentileNearestRank(\n sortedValues: readonly number[],\n percentile: number,\n): number {\n if (sortedValues.length === 0) {\n return 0;\n }\n const index = Math.min(\n sortedValues.length - 1,\n Math.max(0, Math.ceil((percentile / 100) * sortedValues.length) - 1),\n );\n return sortedValues[index];\n}\n\nfunction computeServingLagDistributionMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagDistribution {\n const lags: number[] = [];\n let laggingClientGroups = 0;\n let firstNeededIndex = replicaReadyStates.length;\n\n for (const viewSyncer of viewSyncers) {\n if (!viewSyncer.servingLagEligible) {\n continue;\n }\n\n const firstUnservedIndex = findFirstUnservedIndex(\n replicaReadyStates,\n viewSyncer,\n );\n if (firstUnservedIndex === -1) {\n lags.push(0);\n continue;\n }\n\n firstNeededIndex = Math.min(firstNeededIndex, firstUnservedIndex);\n const lagMs = Math.max(\n 0,\n now - replicaReadyStates[firstUnservedIndex].replicaReadyTimeMs,\n );\n lags.push(lagMs);\n if (lagMs > 0) {\n laggingClientGroups++;\n }\n }\n\n pruneReplicaReadyStates(replicaReadyStates, firstNeededIndex);\n\n lags.sort((a, b) => a - b);\n return {\n activeClientGroups: lags.length,\n laggingClientGroups,\n // The percentile fields are for the legacy serving_lag_stats gauge.\n // The native view_syncer_lag histogram records lagsMs and computes\n // percentiles in Prometheus/AMP.\n minMs: lags[0] ?? 0,\n p50Ms: percentileNearestRank(lags, 50),\n p75Ms: percentileNearestRank(lags, 75),\n p99Ms: percentileNearestRank(lags, 99),\n maxMs: lags.at(-1) ?? 0,\n lagsMs: lags,\n };\n}\n\nexport function computeServingLagStatsMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagStats {\n const distribution = computeServingLagDistributionMs(\n now,\n replicaReadyStates,\n viewSyncers,\n );\n return {\n activeClientGroups: distribution.activeClientGroups,\n laggingClientGroups: distribution.laggingClientGroups,\n minMs: distribution.minMs,\n p50Ms: distribution.p50Ms,\n p75Ms: distribution.p75Ms,\n p99Ms: distribution.p99Ms,\n maxMs: distribution.maxMs,\n };\n}\n\nexport function computeMaxServingLagMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): number {\n return computeServingLagStatsMs(now, replicaReadyStates, viewSyncers).maxMs;\n}\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n maxPayload: config.websocketMaxPayloadBytes,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service> | undefined;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n readonly #replicaReadyStates: ReplicaReadyState[] = [];\n readonly #webSocketOpenConnections = getOrCreateUpDownCounter(\n 'sync',\n 'websocket.open_connections',\n 'Open client WebSocket connections.',\n );\n readonly #webSocketConnectionAttempts = getOrCreateCounter(\n 'sync',\n 'websocket.connection_attempts',\n 'Client WebSocket connection attempts.',\n );\n readonly #webSocketConnectionSuccesses = getOrCreateCounter(\n 'sync',\n 'websocket.connection_successes',\n 'Client WebSocket connections successfully initialized.',\n );\n readonly #webSocketConnectionFailures = getOrCreateCounter(\n 'sync',\n 'websocket.connection_failures',\n 'Client WebSocket connection attempts that failed before initialization.',\n );\n readonly #viewSyncerLag = getOrCreateNativeHistogram(\n 'sync',\n 'view_syncer_lag',\n {\n description:\n 'Lag from replica-ready change to ViewSyncer output for active ' +\n 'client groups. A change is output after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 's',\n },\n );\n #servingLagDistributionCache: ServingLagDistribution | undefined;\n #servingLagDistributionCacheClearQueued = false;\n #viewSyncerLagSampleInterval: ReturnType<typeof setInterval> | undefined;\n #eligibilityLogInterval: ReturnType<typeof setInterval> | undefined;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: ((id: string) => Mutagen & Service) | undefined,\n pusherFactory:\n | ((\n id: string,\n connContextManager: ConnectionContextManager,\n ) => Pusher & Service)\n | undefined,\n parent: Worker,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) {\n this.#config = config;\n this.#validateLegacyJWT = validateLegacyJWT;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent, state =>\n this.#recordReplicaReadyState(state),\n );\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator),\n v => v.keepalive(),\n );\n if (mutagenFactory) {\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n }\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(\n lc,\n id =>\n pusherFactory(\n id,\n this.#viewSyncers.getService(id).connContextManager,\n ),\n p => p.hasRefs(),\n );\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n\n getOrCreateGauge(\n 'sync',\n 'active-client-groups',\n 'Number of active client groups',\n ).addCallback(result => result.observe(this.#viewSyncers.size));\n\n getOrCreateGauge(\n 'sync',\n 'queries',\n 'Active queries (pipelines) across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.queryCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge(\n 'sync',\n 'rows',\n 'Tracked rows across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.rowCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge('sync', 'serving_lag', {\n description:\n 'Maximum time active ViewSyncer client groups have had unserved ' +\n 'replica changes. A change is served after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().maxMs);\n });\n\n getOrCreateGauge('sync', 'serving_lag_stats', {\n description:\n 'Distribution of time active ViewSyncer client groups have had ' +\n 'unserved replica changes. A change is served after IVM advancement, ' +\n 'CVR flush, and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n const stats = this.#computeServingLagDistribution();\n result.observe(stats.minMs, {stat: 'min'});\n result.observe(stats.p50Ms, {stat: 'p50'});\n result.observe(stats.p75Ms, {stat: 'p75'});\n result.observe(stats.p99Ms, {stat: 'p99'});\n result.observe(stats.maxMs, {stat: 'max'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'serving_lagging_client_groups',\n 'Number of active client groups with unserved replica changes',\n ).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().laggingClientGroups);\n });\n\n getOrCreateGauge(\n 'sync',\n 'pipelines_total',\n 'Query pipelines across all client groups on this worker, by query ' +\n 'type. The ratio to sync.pipelines_unique is the dedup factor ' +\n 'available to shared pipeline advancement.',\n ).addCallback(result => {\n const stats = this.#computePipelineDedupStats();\n result.observe(stats.clientTotal, {type: 'client'});\n result.observe(stats.internalTotal, {type: 'internal'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'pipelines_unique',\n 'Distinct (clientSchema, transformationHash) pipelines across all ' +\n 'client groups on this worker, by query type',\n ).addCallback(result => {\n const stats = this.#computePipelineDedupStats();\n result.observe(stats.clientHashes.size, {type: 'client'});\n result.observe(stats.internalUnique, {type: 'internal'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'client_schemas',\n 'Distinct client schemas across all client groups on this worker',\n ).addCallback(result => {\n result.observe(this.#computePipelineDedupStats().clientSchemas);\n });\n\n this.#viewSyncerLagSampleInterval = setInterval(\n () => this.#recordViewSyncerLagSamples(),\n VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS,\n );\n this.#viewSyncerLagSampleInterval.unref?.();\n\n this.#eligibilityLogInterval = setInterval(\n () => this.#logSharedAdvanceEligibility(),\n SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS,\n );\n this.#eligibilityLogInterval.unref?.();\n }\n\n #computeServingLagDistribution(): ServingLagDistribution {\n if (this.#servingLagDistributionCache) {\n return this.#servingLagDistributionCache;\n }\n\n const distribution = computeServingLagDistributionMs(\n Date.now(),\n this.#replicaReadyStates,\n this.#viewSyncers.getServices(),\n );\n this.#servingLagDistributionCache = distribution;\n if (!this.#servingLagDistributionCacheClearQueued) {\n this.#servingLagDistributionCacheClearQueued = true;\n queueMicrotask(() => {\n this.#servingLagDistributionCache = undefined;\n this.#servingLagDistributionCacheClearQueued = false;\n });\n }\n return distribution;\n }\n\n #recordViewSyncerLagSamples(): void {\n const {lagsMs} = this.#computeServingLagDistribution();\n for (const lagMs of lagsMs) {\n this.#viewSyncerLag.recordMs(lagMs);\n }\n }\n\n #computePipelineDedupStats(): PipelineDedupStats {\n return computePipelineDedupStats(this.#viewSyncers.getServices());\n }\n\n #logSharedAdvanceEligibility(): void {\n const stats = this.#computePipelineDedupStats();\n if (stats.clientTotal === 0) {\n return;\n }\n const unique = stats.clientHashes.size;\n const topDuplicated = [...stats.clientHashes.entries()]\n .filter(([, {count}]) => count > 1)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 10)\n .map(([key, {count, queryName}]) => ({key, count, queryName}));\n this.#lc.info?.('shared-advance-eligibility', {\n clientPipelines: stats.clientTotal,\n uniqueClientPipelines: unique,\n dedupFactor: Number((stats.clientTotal / Math.max(unique, 1)).toFixed(2)),\n internalPipelines: stats.internalTotal,\n clientSchemas: stats.clientSchemas,\n topDuplicated,\n });\n }\n\n #recordReplicaReadyState(state: ReplicaState): void {\n if (\n state.watermark === undefined ||\n state.replicaReadyTimeMs === undefined\n ) {\n return;\n }\n const last = this.#replicaReadyStates.at(-1);\n if (last && last.watermark >= state.watermark) {\n return;\n }\n this.#replicaReadyStates.push({\n watermark: state.watermark,\n replicaReadyTimeMs: state.replicaReadyTimeMs,\n });\n if (this.#viewSyncers.size === 0) {\n this.#replicaReadyStates.length = 0;\n return;\n }\n boundReplicaReadyStates(this.#replicaReadyStates);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n const metricAttributes = {\n [PROTOCOL_VERSION_ATTRIBUTE]: params.protocolVersion,\n };\n let connectionResultRecorded = false;\n const recordConnectionFailure = (reason: string) => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionFailures.add(1, {\n ...metricAttributes,\n [FAILURE_REASON_ATTRIBUTE]: reason,\n });\n };\n const recordConnectionSuccessMetric = () => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionSuccesses.add(1, metricAttributes);\n };\n\n this.#webSocketConnectionAttempts.add(1, metricAttributes);\n this.#webSocketOpenConnections.add(1, metricAttributes);\n ws.once('close', () => {\n this.#webSocketOpenConnections.add(-1, metricAttributes);\n });\n\n this.#lc.debug?.(\n 'creating connection',\n params.clientGroupID,\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n const hasProvidedAuth = auth !== undefined && auth !== '';\n const incomingUserID = userID ?? null;\n\n if (hasProvidedAuth) {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n\n // must either have one of the token options set or have custom mutations & queries enabled\n const hasExactlyOneTokenOption = tokenOptions.length === 1;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {\n recordConnectionFailure('configuration');\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n }\n\n let initialAuth: Auth | undefined;\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS.\n try {\n initialAuth = await resolveAuth(\n this.#lc\n .withContext('clientGroupID', clientGroupID)\n .withContext('clientID', clientID),\n // no previous auth, since this is a new connection, and resolveAuth is\n // connection scoped, not client group scoped\n undefined,\n incomingUserID,\n auth,\n this.#validateLegacyJWT,\n );\n } catch (e) {\n if (isProtocolError(e)) {\n this.#lc.warn?.(\n 'Rejecting sync connection during initial auth resolution',\n {\n clientGroupID,\n clientID,\n incomingUserID,\n hasProvidedAuth,\n errorKind: e.message,\n },\n );\n recordConnectionFailure('auth');\n sendError(this.#lc, ws, e.errorBody);\n ws.close(3000, e.errorBody.message);\n return;\n }\n recordConnectionFailure('internal');\n throw e;\n }\n\n const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n const connContextManager = viewSyncer.connContextManager;\n const group = connContextManager.getGroupState();\n\n // TODO(0xcadams): we only check for user ID mismatch here if the group is\n // already validated. This prevents wrong-user reconnects from evicting a\n // healthy connection, but it does not protect against same-user reconnects\n // with an invalid opaque token. The long-term fix is to keep the replacement\n // connection pending until its auth is fully validated, and only then replace\n // the existing socket.\n if (\n group.pinnedUser !== undefined &&\n group.pinnedUser.id !== incomingUserID\n ) {\n const error = new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n });\n recordConnectionFailure('user_mismatch');\n sendError(this.#lc, ws, error.errorBody);\n ws.close(3000, error.message);\n return;\n }\n\n // Check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n connContextManager.registerConnection(\n {clientID, wsID: params.wsID},\n params,\n initialAuth,\n );\n\n const mutagen = this.#mutagens?.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen?.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n connContextManager,\n viewSyncer,\n mutagen,\n pusher,\n ),\n () => {\n connContextManager.closeConnection({\n clientID,\n wsID: params.wsID,\n });\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen?.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n recordConnectionFailure('internal');\n connContextManager.closeConnection({clientID, wsID: params.wsID});\n mutagen?.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n if (connection.init()) {\n recordConnectionSuccessMetric();\n recordConnectionSuccess();\n } else {\n recordConnectionFailure('protocol_version');\n }\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n params.clientGroupID,\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(`draining view-syncer ${vs.id} (forced)`);\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n clearInterval(this.#viewSyncerLagSampleInterval);\n clearInterval(this.#eligibilityLogInterval);\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n}\n"],"mappings":"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"}
|
|
@@ -56,6 +56,29 @@ export declare class TableBuilderWithColumns<TShape extends TableSchema> {
|
|
|
56
56
|
primaryKey<TPKColNames extends (keyof TShape['columns'])[]>(...pkColumnNames: TPKColNames): TableBuilderWithColumns<TShape & {
|
|
57
57
|
primaryKey: TPKColNames;
|
|
58
58
|
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Declares that `columnNames` together are unique — mirroring a unique index
|
|
61
|
+
* that already exists upstream. Call once per index.
|
|
62
|
+
*
|
|
63
|
+
* Zero neither creates nor enforces the constraint; declaring it tells the
|
|
64
|
+
* type checker what the server already knows from the replica, which is what
|
|
65
|
+
* lets a `{scalar: true}` subquery pinned on these columns typecheck.
|
|
66
|
+
*
|
|
67
|
+
* The return type is spelled out so the column names stay literal — spreading
|
|
68
|
+
* the accumulated keys widens them to `PrimaryKey`, which loses exactly the
|
|
69
|
+
* information the check needs.
|
|
70
|
+
*/
|
|
71
|
+
unique<const TUniqueColNames extends [
|
|
72
|
+
keyof TShape['columns'] & string,
|
|
73
|
+
...(keyof TShape['columns'] & string)[]
|
|
74
|
+
]>(...columnNames: TUniqueColNames): TableBuilderWithColumns<Omit<TShape, 'uniqueKeys'> & {
|
|
75
|
+
uniqueKeys: [
|
|
76
|
+
...(TShape extends {
|
|
77
|
+
uniqueKeys: infer TExisting extends readonly PrimaryKey[];
|
|
78
|
+
} ? TExisting : []),
|
|
79
|
+
TUniqueColNames
|
|
80
|
+
];
|
|
81
|
+
}>;
|
|
59
82
|
get schema(): TShape;
|
|
60
83
|
build(): TShape;
|
|
61
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-builder.d.ts","sourceRoot":"","sources":["../../../../../zero-schema/src/builder/table-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAEnE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAC1E,OAAO,KAAK,EAAC,WAAW,EAAE,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAGjE,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,EAAE,KAAK;;;gBAI1B,UAAU;GAEtC;AAED,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;;;gBAIb,CAAC;GAEnC;AAED,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;;;gBAIb,CAAC;GAEnC;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;;;gBAIhB,CAAC;GAEnC;AAED,wBAAgB,IAAI,CAAC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB;;;gBAIjC,CAAC;GAEnC;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM;;;gBAIT,CAAC;GAEnC;AAED,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC;AAEF,qBAAa,YAAY,CAAC,MAAM,SAAS,WAAW;;gBAEtC,MAAM,EAAE,MAAM;IAI1B,IAAI,CAAC,UAAU,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU;IAYtD,OAAO,CAAC,KAAK,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,EACvE,OAAO,EAAE,QAAQ,GAChB,uBAAuB,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,OAAO,EAAE;aAAE,CAAC,IAAI,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;SAAC,CAAC;QACxD,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;KAClC,CAAC;CAOH;AAED,qBAAa,uBAAuB,CAAC,MAAM,SAAS,WAAW;;gBAGjD,MAAM,EAAE,MAAM;IAI1B,UAAU,CAAC,WAAW,SAAS,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,EACxD,GAAG,aAAa,EAAE,WAAW;;;IAQ/B,IAAI,MAAM,WAET;IAED,KAAK;CAqBN;AAED,cAAM,aAAa,CAAC,MAAM,SAAS,WAAW,CAAC,GAAG,CAAC;;gBAErC,MAAM,EAAE,MAAM;IAI1B,IAAI,CAAC,UAAU,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU;oBACL,MAAM;;IAMvD,QAAQ,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAC,CAAC;IAOtE,IAAI,MAAM,WAET;CACF;AAED,YAAY,EAAC,aAAa,EAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"table-builder.d.ts","sourceRoot":"","sources":["../../../../../zero-schema/src/builder/table-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAEnE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAC1E,OAAO,KAAK,EAAC,WAAW,EAAE,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAGjE,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,EAAE,KAAK;;;gBAI1B,UAAU;GAEtC;AAED,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;;;gBAIb,CAAC;GAEnC;AAED,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;;;gBAIb,CAAC;GAEnC;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;;;gBAIhB,CAAC;GAEnC;AAED,wBAAgB,IAAI,CAAC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB;;;gBAIjC,CAAC;GAEnC;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM;;;gBAIT,CAAC;GAEnC;AAED,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC;AAEF,qBAAa,YAAY,CAAC,MAAM,SAAS,WAAW;;gBAEtC,MAAM,EAAE,MAAM;IAI1B,IAAI,CAAC,UAAU,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU;IAYtD,OAAO,CAAC,KAAK,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,EACvE,OAAO,EAAE,QAAQ,GAChB,uBAAuB,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,OAAO,EAAE;aAAE,CAAC,IAAI,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;SAAC,CAAC;QACxD,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;KAClC,CAAC;CAOH;AAED,qBAAa,uBAAuB,CAAC,MAAM,SAAS,WAAW;;gBAGjD,MAAM,EAAE,MAAM;IAI1B,UAAU,CAAC,WAAW,SAAS,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,EACxD,GAAG,aAAa,EAAE,WAAW;;;IAQ/B;;;;;;;;;;;OAWG;IACH,MAAM,CACJ,KAAK,CAAC,eAAe,SAAS;QAC5B,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM;QAChC,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,EAAE;KACxC,EAED,GAAG,WAAW,EAAE,eAAe,GAC9B,uBAAuB,CACxB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG;QAC3B,UAAU,EAAE;YACV,GAAG,CAAC,MAAM,SAAS;gBACjB,UAAU,EAAE,MAAM,SAAS,SAAS,SAAS,UAAU,EAAE,CAAC;aAC3D,GACG,SAAS,GACT,EAAE,CAAC;YACP,eAAe;SAChB,CAAC;KACH,CACF;IAOD,IAAI,MAAM,WAET;IAED,KAAK;CAqBN;AAED,cAAM,aAAa,CAAC,MAAM,SAAS,WAAW,CAAC,GAAG,CAAC;;gBAErC,MAAM,EAAE,MAAM;IAI1B,IAAI,CAAC,UAAU,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU;oBACL,MAAM;;IAMvD,QAAQ,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAC,CAAC;IAOtE,IAAI,MAAM,WAET;CACF;AAED,YAAY,EAAC,aAAa,EAAC,CAAC"}
|
|
@@ -72,6 +72,24 @@ var TableBuilderWithColumns = class TableBuilderWithColumns {
|
|
|
72
72
|
primaryKey: pkColumnNames
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Declares that `columnNames` together are unique — mirroring a unique index
|
|
77
|
+
* that already exists upstream. Call once per index.
|
|
78
|
+
*
|
|
79
|
+
* Zero neither creates nor enforces the constraint; declaring it tells the
|
|
80
|
+
* type checker what the server already knows from the replica, which is what
|
|
81
|
+
* lets a `{scalar: true}` subquery pinned on these columns typecheck.
|
|
82
|
+
*
|
|
83
|
+
* The return type is spelled out so the column names stay literal — spreading
|
|
84
|
+
* the accumulated keys widens them to `PrimaryKey`, which loses exactly the
|
|
85
|
+
* information the check needs.
|
|
86
|
+
*/
|
|
87
|
+
unique(...columnNames) {
|
|
88
|
+
return new TableBuilderWithColumns({
|
|
89
|
+
...this.#schema,
|
|
90
|
+
uniqueKeys: [...this.#schema.uniqueKeys ?? [], columnNames]
|
|
91
|
+
});
|
|
92
|
+
}
|
|
75
93
|
get schema() {
|
|
76
94
|
return this.#schema;
|
|
77
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-builder.js","names":["#schema"],"sources":["../../../../../zero-schema/src/builder/table-builder.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {mapValues} from '../../../shared/src/objects.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue, TableSchema} from '../table-schema.ts';\n\n/* oxlint-disable @typescript-eslint/no-explicit-any */\nexport function table<TName extends string>(name: TName) {\n return new TableBuilder({\n name,\n columns: {},\n primaryKey: [] as any as PrimaryKey,\n });\n}\n\nexport function string<T extends string = string>() {\n return new ColumnBuilder({\n type: 'string',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function number<T extends number = number>() {\n return new ColumnBuilder({\n type: 'number',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function boolean<T extends boolean = boolean>() {\n return new ColumnBuilder({\n type: 'boolean',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function json<T extends ReadonlyJSONValue = ReadonlyJSONValue>() {\n return new ColumnBuilder({\n type: 'json',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function enumeration<T extends string>() {\n return new ColumnBuilder({\n type: 'string',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport const column = {\n string,\n number,\n boolean,\n json,\n enumeration,\n};\n\nexport class TableBuilder<TShape extends TableSchema> {\n readonly #schema: TShape;\n constructor(schema: TShape) {\n this.#schema = schema;\n }\n\n from<ServerName extends string>(serverName: ServerName) {\n return new TableBuilder<TShape>({\n ...this.#schema,\n // Strip the \"public.\" schema if specified, as tables in the upstream\n // \"public\" schema are created without the schema prefix on the replica.\n // See liteTableName() in zero-cache/src/types/names.ts\n serverName: serverName.startsWith('public.')\n ? serverName.substring('public.'.length)\n : serverName,\n });\n }\n\n columns<const TColumns extends Record<string, ColumnBuilder<SchemaValue>>>(\n columns: TColumns,\n ): TableBuilderWithColumns<{\n name: TShape['name'];\n columns: {[K in keyof TColumns]: TColumns[K]['schema']};\n primaryKey: TShape['primaryKey'];\n }> {\n const columnSchemas = mapValues(columns, column => column.schema);\n return new TableBuilderWithColumns({\n ...this.#schema,\n columns: columnSchemas,\n }) as any;\n }\n}\n\nexport class TableBuilderWithColumns<TShape extends TableSchema> {\n readonly #schema: TShape;\n\n constructor(schema: TShape) {\n this.#schema = schema;\n }\n\n primaryKey<TPKColNames extends (keyof TShape['columns'])[]>(\n ...pkColumnNames: TPKColNames\n ) {\n return new TableBuilderWithColumns({\n ...this.#schema,\n primaryKey: pkColumnNames,\n });\n }\n\n get schema() {\n return this.#schema;\n }\n\n build() {\n // We can probably get the type system to throw an error if primaryKey is not called\n // before passing the schema to createSchema\n // Till then --\n if (this.#schema.primaryKey.length === 0) {\n throw new Error(`Table \"${this.#schema.name}\" is missing a primary key`);\n }\n const names = new Set<string>();\n for (const [col, {serverName}] of Object.entries(this.#schema.columns)) {\n const name = serverName ?? col;\n if (names.has(name)) {\n throw new Error(\n `Table \"${\n this.#schema.name\n }\" has multiple columns referencing \"${name}\"`,\n );\n }\n names.add(name);\n }\n return this.#schema;\n }\n}\n\nclass ColumnBuilder<TShape extends SchemaValue<any>> {\n readonly #schema: TShape;\n constructor(schema: TShape) {\n this.#schema = schema;\n }\n\n from<ServerName extends string>(serverName: ServerName) {\n return new ColumnBuilder<TShape & {serverName: string}>({\n ...this.#schema,\n serverName,\n });\n }\n\n optional(): ColumnBuilder<Omit<TShape, 'optional'> & {optional: true}> {\n return new ColumnBuilder({\n ...this.#schema,\n optional: true,\n });\n }\n\n get schema() {\n return this.#schema;\n }\n}\n\nexport type {ColumnBuilder};\n"],"mappings":";;AAMA,SAAgB,MAA4B,MAAa;CACvD,OAAO,IAAI,aAAa;EACtB;EACA,SAAS,CAAC;EACV,YAAY,CAAC;CACf,CAAC;AACH;AAEA,SAAgB,SAAoC;CAClD,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,SAAoC;CAClD,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,UAAuC;CACrD,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,OAAwD;CACtE,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,cAAgC;CAC9C,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAUA,IAAa,eAAb,MAAa,aAAyC;CACpD;CACA,YAAY,QAAgB;EAC1B,KAAKA,UAAU;CACjB;CAEA,KAAgC,YAAwB;EACtD,OAAO,IAAI,aAAqB;GAC9B,GAAG,KAAKA;GAIR,YAAY,WAAW,WAAW,SAAS,IACvC,WAAW,UAAU,CAAgB,IACrC;EACN,CAAC;CACH;CAEA,QACE,SAKC;EACD,MAAM,gBAAgB,UAAU,UAAS,WAAU,OAAO,MAAM;EAChE,OAAO,IAAI,wBAAwB;GACjC,GAAG,KAAKA;GACR,SAAS;EACX,CAAC;CACH;AACF;AAEA,IAAa,0BAAb,MAAa,wBAAoD;CAC/D;CAEA,YAAY,QAAgB;EAC1B,KAAKA,UAAU;CACjB;CAEA,WACE,GAAG,eACH;EACA,OAAO,IAAI,wBAAwB;GACjC,GAAG,KAAKA;GACR,YAAY;EACd,CAAC;CACH;CAEA,IAAI,SAAS;EACX,OAAO,KAAKA;CACd;CAEA,QAAQ;EAIN,IAAI,KAAKA,QAAQ,WAAW,WAAW,GACrC,MAAM,IAAI,MAAM,UAAU,KAAKA,QAAQ,KAAK,2BAA2B;EAEzE,MAAM,wBAAQ,IAAI,IAAY;EAC9B,KAAK,MAAM,CAAC,KAAK,EAAC,iBAAgB,OAAO,QAAQ,KAAKA,QAAQ,OAAO,GAAG;GACtE,MAAM,OAAO,cAAc;GAC3B,IAAI,MAAM,IAAI,IAAI,GAChB,MAAM,IAAI,MACR,UACE,KAAKA,QAAQ,KACd,sCAAsC,KAAK,EAC9C;GAEF,MAAM,IAAI,IAAI;EAChB;EACA,OAAO,KAAKA;CACd;AACF;AAEA,IAAM,gBAAN,MAAM,cAA+C;CACnD;CACA,YAAY,QAAgB;EAC1B,KAAKA,UAAU;CACjB;CAEA,KAAgC,YAAwB;EACtD,OAAO,IAAI,cAA6C;GACtD,GAAG,KAAKA;GACR;EACF,CAAC;CACH;CAEA,WAAuE;EACrE,OAAO,IAAI,cAAc;GACvB,GAAG,KAAKA;GACR,UAAU;EACZ,CAAC;CACH;CAEA,IAAI,SAAS;EACX,OAAO,KAAKA;CACd;AACF"}
|
|
1
|
+
{"version":3,"file":"table-builder.js","names":["#schema"],"sources":["../../../../../zero-schema/src/builder/table-builder.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {mapValues} from '../../../shared/src/objects.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue, TableSchema} from '../table-schema.ts';\n\n/* oxlint-disable @typescript-eslint/no-explicit-any */\nexport function table<TName extends string>(name: TName) {\n return new TableBuilder({\n name,\n columns: {},\n primaryKey: [] as any as PrimaryKey,\n });\n}\n\nexport function string<T extends string = string>() {\n return new ColumnBuilder({\n type: 'string',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function number<T extends number = number>() {\n return new ColumnBuilder({\n type: 'number',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function boolean<T extends boolean = boolean>() {\n return new ColumnBuilder({\n type: 'boolean',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function json<T extends ReadonlyJSONValue = ReadonlyJSONValue>() {\n return new ColumnBuilder({\n type: 'json',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport function enumeration<T extends string>() {\n return new ColumnBuilder({\n type: 'string',\n optional: false,\n customType: null as unknown as T,\n });\n}\n\nexport const column = {\n string,\n number,\n boolean,\n json,\n enumeration,\n};\n\nexport class TableBuilder<TShape extends TableSchema> {\n readonly #schema: TShape;\n constructor(schema: TShape) {\n this.#schema = schema;\n }\n\n from<ServerName extends string>(serverName: ServerName) {\n return new TableBuilder<TShape>({\n ...this.#schema,\n // Strip the \"public.\" schema if specified, as tables in the upstream\n // \"public\" schema are created without the schema prefix on the replica.\n // See liteTableName() in zero-cache/src/types/names.ts\n serverName: serverName.startsWith('public.')\n ? serverName.substring('public.'.length)\n : serverName,\n });\n }\n\n columns<const TColumns extends Record<string, ColumnBuilder<SchemaValue>>>(\n columns: TColumns,\n ): TableBuilderWithColumns<{\n name: TShape['name'];\n columns: {[K in keyof TColumns]: TColumns[K]['schema']};\n primaryKey: TShape['primaryKey'];\n }> {\n const columnSchemas = mapValues(columns, column => column.schema);\n return new TableBuilderWithColumns({\n ...this.#schema,\n columns: columnSchemas,\n }) as any;\n }\n}\n\nexport class TableBuilderWithColumns<TShape extends TableSchema> {\n readonly #schema: TShape;\n\n constructor(schema: TShape) {\n this.#schema = schema;\n }\n\n primaryKey<TPKColNames extends (keyof TShape['columns'])[]>(\n ...pkColumnNames: TPKColNames\n ) {\n return new TableBuilderWithColumns({\n ...this.#schema,\n primaryKey: pkColumnNames,\n });\n }\n\n /**\n * Declares that `columnNames` together are unique — mirroring a unique index\n * that already exists upstream. Call once per index.\n *\n * Zero neither creates nor enforces the constraint; declaring it tells the\n * type checker what the server already knows from the replica, which is what\n * lets a `{scalar: true}` subquery pinned on these columns typecheck.\n *\n * The return type is spelled out so the column names stay literal — spreading\n * the accumulated keys widens them to `PrimaryKey`, which loses exactly the\n * information the check needs.\n */\n unique<\n const TUniqueColNames extends [\n keyof TShape['columns'] & string,\n ...(keyof TShape['columns'] & string)[],\n ],\n >(\n ...columnNames: TUniqueColNames\n ): TableBuilderWithColumns<\n Omit<TShape, 'uniqueKeys'> & {\n uniqueKeys: [\n ...(TShape extends {\n uniqueKeys: infer TExisting extends readonly PrimaryKey[];\n }\n ? TExisting\n : []),\n TUniqueColNames,\n ];\n }\n > {\n return new TableBuilderWithColumns({\n ...this.#schema,\n uniqueKeys: [...(this.#schema.uniqueKeys ?? []), columnNames],\n }) as any;\n }\n\n get schema() {\n return this.#schema;\n }\n\n build() {\n // We can probably get the type system to throw an error if primaryKey is not called\n // before passing the schema to createSchema\n // Till then --\n if (this.#schema.primaryKey.length === 0) {\n throw new Error(`Table \"${this.#schema.name}\" is missing a primary key`);\n }\n const names = new Set<string>();\n for (const [col, {serverName}] of Object.entries(this.#schema.columns)) {\n const name = serverName ?? col;\n if (names.has(name)) {\n throw new Error(\n `Table \"${\n this.#schema.name\n }\" has multiple columns referencing \"${name}\"`,\n );\n }\n names.add(name);\n }\n return this.#schema;\n }\n}\n\nclass ColumnBuilder<TShape extends SchemaValue<any>> {\n readonly #schema: TShape;\n constructor(schema: TShape) {\n this.#schema = schema;\n }\n\n from<ServerName extends string>(serverName: ServerName) {\n return new ColumnBuilder<TShape & {serverName: string}>({\n ...this.#schema,\n serverName,\n });\n }\n\n optional(): ColumnBuilder<Omit<TShape, 'optional'> & {optional: true}> {\n return new ColumnBuilder({\n ...this.#schema,\n optional: true,\n });\n }\n\n get schema() {\n return this.#schema;\n }\n}\n\nexport type {ColumnBuilder};\n"],"mappings":";;AAMA,SAAgB,MAA4B,MAAa;CACvD,OAAO,IAAI,aAAa;EACtB;EACA,SAAS,CAAC;EACV,YAAY,CAAC;CACf,CAAC;AACH;AAEA,SAAgB,SAAoC;CAClD,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,SAAoC;CAClD,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,UAAuC;CACrD,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,OAAwD;CACtE,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAEA,SAAgB,cAAgC;CAC9C,OAAO,IAAI,cAAc;EACvB,MAAM;EACN,UAAU;EACV,YAAY;CACd,CAAC;AACH;AAUA,IAAa,eAAb,MAAa,aAAyC;CACpD;CACA,YAAY,QAAgB;EAC1B,KAAKA,UAAU;CACjB;CAEA,KAAgC,YAAwB;EACtD,OAAO,IAAI,aAAqB;GAC9B,GAAG,KAAKA;GAIR,YAAY,WAAW,WAAW,SAAS,IACvC,WAAW,UAAU,CAAgB,IACrC;EACN,CAAC;CACH;CAEA,QACE,SAKC;EACD,MAAM,gBAAgB,UAAU,UAAS,WAAU,OAAO,MAAM;EAChE,OAAO,IAAI,wBAAwB;GACjC,GAAG,KAAKA;GACR,SAAS;EACX,CAAC;CACH;AACF;AAEA,IAAa,0BAAb,MAAa,wBAAoD;CAC/D;CAEA,YAAY,QAAgB;EAC1B,KAAKA,UAAU;CACjB;CAEA,WACE,GAAG,eACH;EACA,OAAO,IAAI,wBAAwB;GACjC,GAAG,KAAKA;GACR,YAAY;EACd,CAAC;CACH;;;;;;;;;;;;;CAcA,OAME,GAAG,aAYH;EACA,OAAO,IAAI,wBAAwB;GACjC,GAAG,KAAKA;GACR,YAAY,CAAC,GAAI,KAAKA,QAAQ,cAAc,CAAC,GAAI,WAAW;EAC9D,CAAC;CACH;CAEA,IAAI,SAAS;EACX,OAAO,KAAKA;CACd;CAEA,QAAQ;EAIN,IAAI,KAAKA,QAAQ,WAAW,WAAW,GACrC,MAAM,IAAI,MAAM,UAAU,KAAKA,QAAQ,KAAK,2BAA2B;EAEzE,MAAM,wBAAQ,IAAI,IAAY;EAC9B,KAAK,MAAM,CAAC,KAAK,EAAC,iBAAgB,OAAO,QAAQ,KAAKA,QAAQ,OAAO,GAAG;GACtE,MAAM,OAAO,cAAc;GAC3B,IAAI,MAAM,IAAI,IAAI,GAChB,MAAM,IAAI,MACR,UACE,KAAKA,QAAQ,KACd,sCAAsC,KAAK,EAC9C;GAEF,MAAM,IAAI,IAAI;EAChB;EACA,OAAO,KAAKA;CACd;AACF;AAEA,IAAM,gBAAN,MAAM,cAA+C;CACnD;CACA,YAAY,QAAgB;EAC1B,KAAKA,UAAU;CACjB;CAEA,KAAgC,YAAwB;EACtD,OAAO,IAAI,cAA6C;GACtD,GAAG,KAAKA;GACR;EACF,CAAC;CACH;CAEA,WAAuE;EACrE,OAAO,IAAI,cAAc;GACvB,GAAG,KAAKA;GACR,UAAU;EACZ,CAAC;CACH;CAEA,IAAI,SAAS;EACX,OAAO,KAAKA;CACd;AACF"}
|
|
@@ -9,6 +9,17 @@ export type TableSchema = {
|
|
|
9
9
|
readonly serverName?: string | undefined;
|
|
10
10
|
readonly columns: Record<string, SchemaValue>;
|
|
11
11
|
readonly primaryKey: PrimaryKey;
|
|
12
|
+
/**
|
|
13
|
+
* Additional unique keys on the table, beyond the primary key — one entry
|
|
14
|
+
* per unique index, each listing every column the index covers.
|
|
15
|
+
*
|
|
16
|
+
* This is metadata for the type checker, not a constraint Zero creates or
|
|
17
|
+
* enforces: it mirrors unique indexes that already exist upstream. The
|
|
18
|
+
* server reads the real indexes off the replica, so declaring these only
|
|
19
|
+
* affects what the client-side types can prove — notably whether a
|
|
20
|
+
* `{scalar: true}` subquery is provably limited to one row.
|
|
21
|
+
*/
|
|
22
|
+
readonly uniqueKeys?: readonly PrimaryKey[] | undefined;
|
|
12
23
|
};
|
|
13
24
|
export type RelationshipsSchema = {
|
|
14
25
|
readonly [name: string]: Relationship;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../zero-types/src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../zero-types/src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,UAAU,EAAE,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;AAEzC,KAAK,UAAU,GAAG;IAChB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,YAAY,GACpB,SAAS,CAAC,UAAU,CAAC,GACrB,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAEtC,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,YAAY,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,GAC1E,CAAC,GACD,CAAC,SAAS,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,GACnC,CAAC,GACD,CAAC,SAAS,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,GAC5C,CAAC,GACD,KAAK,CAAC;AAEd;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,MAAM,EAAE;QAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAAA;KAAC,CAAC;IACzD,QAAQ,CAAC,aAAa,EAAE;QAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAAA;KAAC,CAAC;IACxE;;;;;;OAMG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnD;;;;;;OAMG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrD,CAAC"}
|
|
@@ -29,14 +29,14 @@ export declare class QueryImpl<TTable extends keyof TSchema['tables'] & string,
|
|
|
29
29
|
one: () => Query<TTable, TSchema, TReturn | undefined>;
|
|
30
30
|
whereExists: (relationship: string, cbOrOptions?: ((q: AnyQuery) => AnyQuery) | ExistsOptions, options?: ExistsOptions) => Query<TTable, TSchema, TReturn>;
|
|
31
31
|
related: (relationship: string, cb?: (q: AnyQuery) => AnyQuery) => Query<TTable, TSchema, any>;
|
|
32
|
-
where: (fieldOrExpressionFactory: string | ExpressionFactory<TTable, TSchema>, opOrValue?: any, value?: any) => Query<TTable, TSchema, TReturn>;
|
|
32
|
+
where: (fieldOrExpressionFactory: string | ExpressionFactory<TTable, TSchema>, opOrValue?: any, value?: any) => Query<TTable, TSchema, TReturn, any>;
|
|
33
33
|
start: (row: Partial<Record<string, ReadonlyJSONValue | undefined>>, opts?: {
|
|
34
34
|
inclusive: boolean;
|
|
35
35
|
}) => Query<TTable, TSchema, TReturn>;
|
|
36
36
|
limit: (limit: number) => Query<TTable, TSchema, TReturn>;
|
|
37
37
|
orderBy: <TSelector extends keyof TSchema["tables"][TTable]["columns"]>(field: TSelector, direction: "asc" | "desc") => Query<TTable, TSchema, TReturn>;
|
|
38
38
|
get ast(): AST;
|
|
39
|
-
expressionBuilder(): ExpressionBuilder<
|
|
39
|
+
expressionBuilder(): ExpressionBuilder<TTable, TSchema>;
|
|
40
40
|
}
|
|
41
41
|
export declare function asQueryImpl<TTable extends keyof TSchema['tables'] & string, TSchema extends Schema, TReturn>(q: Query<TTable, TSchema, TReturn>): QueryImpl<TTable, TSchema, TReturn>;
|
|
42
42
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-impl.d.ts","sourceRoot":"","sources":["../../../../../zql/src/query/query-impl.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"query-impl.d.ts","sourceRoot":"","sources":["../../../../../zql/src/query/query-impl.ts"],"names":[],"mappings":"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"}
|