@rocicorp/zero 1.10.0-canary.2 → 1.10.0-canary.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/replicache/src/btree/read.d.ts +12 -1
- package/out/replicache/src/btree/read.d.ts.map +1 -1
- package/out/replicache/src/btree/read.js +24 -12
- package/out/replicache/src/btree/read.js.map +1 -1
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js +27 -4
- package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +11 -0
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +43 -3
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +10 -0
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +1 -1
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/shared/src/error.d.ts +11 -0
- package/out/shared/src/error.d.ts.map +1 -1
- package/out/shared/src/error.js +35 -1
- package/out/shared/src/error.js.map +1 -1
- package/out/shared/src/hash.d.ts.map +1 -1
- package/out/shared/src/hash.js +105 -10
- package/out/shared/src/hash.js.map +1 -1
- package/out/shared/src/map.d.ts +7 -2
- package/out/shared/src/map.d.ts.map +1 -1
- package/out/shared/src/map.js.map +1 -1
- package/out/shared/src/promise-race.d.ts +14 -0
- package/out/shared/src/promise-race.d.ts.map +1 -1
- package/out/shared/src/promise-race.js +32 -1
- package/out/shared/src/promise-race.js.map +1 -1
- package/out/zero/package.js +2 -3
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +8 -1
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/normalize.d.ts +5 -0
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +16 -3
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +36 -7
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +121 -46
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/db/create.d.ts.map +1 -1
- package/out/zero-cache/src/db/create.js +4 -1
- package/out/zero-cache/src/db/create.js.map +1 -1
- package/out/zero-cache/src/db/index-predicate.d.ts +4 -0
- package/out/zero-cache/src/db/index-predicate.d.ts.map +1 -0
- package/out/zero-cache/src/db/index-predicate.js +46 -0
- package/out/zero-cache/src/db/index-predicate.js.map +1 -0
- package/out/zero-cache/src/db/lite-tables.d.ts +10 -2
- package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
- package/out/zero-cache/src/db/lite-tables.js +8 -3
- package/out/zero-cache/src/db/lite-tables.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +5 -3
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/specs.d.ts +41 -0
- package/out/zero-cache/src/db/specs.d.ts.map +1 -1
- package/out/zero-cache/src/db/specs.js +30 -3
- package/out/zero-cache/src/db/specs.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.d.ts +5 -15
- package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +39 -73
- package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +27 -15
- 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 +4 -4
- 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 +8 -4
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +1 -1
- package/out/zero-cache/src/server/worker-urls.d.ts +0 -1
- package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
- package/out/zero-cache/src/server/worker-urls.js +1 -2
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
- package/out/zero-cache/src/services/analyze.js +1 -0
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts +12 -2
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +14 -3
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- 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 +10 -41
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +3 -2
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +42 -16
- 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 +34 -2
- 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 +47 -19
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/decommission.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/decommission.js +20 -11
- package/out/zero-cache/src/services/change-source/pg/decommission.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +58 -80
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +20 -0
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +43 -3
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +24 -1
- 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 +35 -5
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +1513 -229
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts +19 -0
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +581 -0
- package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +15 -6
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +137 -20
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js +82 -13
- package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +128 -21
- 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 +5 -5
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +2 -0
- 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/downstream.d.ts +3 -0
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +1 -4
- 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 +11 -18
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js +8 -3
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +4 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts +34 -0
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js +67 -0
- package/out/zero-cache/src/services/change-streamer/change-log-compare-digest.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -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 +8 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +34 -13
- 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 +165 -36
- 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 +4 -9
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +12 -3
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js +11 -5
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts +11 -0
- package/out/zero-cache/src/services/change-streamer/shard-sampling.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/shard-sampling.js +19 -0
- package/out/zero-cache/src/services/change-streamer/shard-sampling.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +11 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +38 -5
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +25 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +95 -12
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts +83 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js +417 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-comparator.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts +73 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js +157 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-read-router.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +8 -3
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +63 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +217 -49
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +8 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +10 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts +29 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js +154 -0
- package/out/zero-cache/src/services/change-streamer/vfs-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/services/http-service.d.ts +1 -0
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js +7 -3
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +16 -7
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +15 -1
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +131 -0
- package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts +70 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js +101 -0
- package/out/zero-cache/src/services/litestream/litestream-controller.js.map +1 -0
- package/out/zero-cache/src/services/litestream/metrics.d.ts +1 -0
- package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js +4 -1
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts +10 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-db.js +15 -1
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +18 -21
- 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 +2 -6
- 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/sqlite-change-log-observability.d.ts +3 -2
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +8 -6
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +27 -2
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js +3 -2
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +24 -5
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts +3 -2
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +25 -17
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +14 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +3 -3
- 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 +6 -2
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/downstream.d.ts +11 -0
- package/out/zero-cache/src/types/downstream.d.ts.map +1 -0
- package/out/zero-cache/src/types/pg.d.ts +15 -2
- package/out/zero-cache/src/types/pg.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg.js +21 -8
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/poke-chunk.d.ts +18 -0
- package/out/zero-cache/src/types/poke-chunk.d.ts.map +1 -0
- package/out/zero-cache/src/types/poke-chunk.js +57 -0
- package/out/zero-cache/src/types/poke-chunk.js.map +1 -0
- package/out/zero-cache/src/types/processes.js +1 -4
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +4 -9
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +2 -11
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +4 -25
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +22 -4
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +112 -14
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.d.ts +1 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +4 -2
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-manager.js +46 -0
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +15 -0
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +44 -0
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +19 -12
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.d.ts +7 -2
- package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js +52 -2
- package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +2 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +98 -25
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/custom-queries.d.ts +1 -1
- package/out/zero-protocol/src/down.d.ts +1 -1
- package/out/zero-protocol/src/error.d.ts +4 -4
- package/out/zero-protocol/src/inspect-down.js +1 -1
- package/out/zero-protocol/src/mutate-server.d.ts +2 -2
- package/out/zero-protocol/src/poke.d.ts +89 -7
- package/out/zero-protocol/src/poke.d.ts.map +1 -1
- package/out/zero-protocol/src/poke.js +16 -8
- package/out/zero-protocol/src/poke.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts +12 -7
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/push.d.ts +1 -1
- package/out/zero-protocol/src/query-server.d.ts +2 -2
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +5 -3
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/data.d.ts.map +1 -1
- package/out/zql/src/ivm/data.js +8 -3
- package/out/zql/src/ivm/data.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts +44 -0
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -0
- package/out/zql/src/ivm/deferred-input.js +88 -0
- package/out/zql/src/ivm/deferred-input.js.map +1 -0
- package/out/zql/src/ivm/memory-source.d.ts +10 -2
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +19 -16
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/stream.d.ts +0 -1
- package/out/zql/src/ivm/stream.d.ts.map +1 -1
- package/out/zql/src/ivm/stream.js +1 -7
- package/out/zql/src/ivm/stream.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +11 -2
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +6 -1
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +6 -0
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +73 -10
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +22 -0
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +2 -1
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js +1 -0
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +3 -2
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +2 -3
- package/out/zero-cache/src/server/backup-watermark-poller.js +0 -31
- package/out/zero-cache/src/server/backup-watermark-poller.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +0 -57
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +0 -137
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +0 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Downstream } from '../../../zero-protocol/src/down.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A logical view-syncer message and, when already available, its serialized
|
|
4
|
+
* wire representation. Keeping them together lets the connection reuse the
|
|
5
|
+
* view-syncer's serialization instead of stringifying large poke parts again.
|
|
6
|
+
*/
|
|
7
|
+
export type ViewSyncerDownstream = {
|
|
8
|
+
readonly message: Downstream;
|
|
9
|
+
readonly serialized: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=downstream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downstream.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/downstream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oCAAoC,CAAC;AAEnE;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,CAAC"}
|
|
@@ -97,11 +97,24 @@ type SocketFactoryOptions = {
|
|
|
97
97
|
*
|
|
98
98
|
* Exported for testing.
|
|
99
99
|
*/
|
|
100
|
-
export declare function inactivityTimeoutSocket(lc: LogContext, timeoutMs?: number): (options: SocketFactoryOptions) => Socket;
|
|
100
|
+
export declare function inactivityTimeoutSocket(lc: LogContext, timeoutMs?: number, keepAliveMs?: number): (options: SocketFactoryOptions) => Socket;
|
|
101
101
|
export declare function pgClient(lc: LogContext, connectionURI: string, applicationName: string, options?: postgres.Options<{
|
|
102
102
|
bigint: PostgresType<bigint>;
|
|
103
103
|
json: PostgresType<JSONValue>;
|
|
104
|
-
}
|
|
104
|
+
}> & {
|
|
105
|
+
/**
|
|
106
|
+
* How to detect a dead ("half-open") connection:
|
|
107
|
+
* - `'inactivity-timeout'` (default): reset the socket after
|
|
108
|
+
* {@link SOCKET_INACTIVITY_TIMEOUT_MS} of no read/write activity. Catches
|
|
109
|
+
* proxy-level half-opens, but false-positives on connections that are
|
|
110
|
+
* legitimately idle for long stretches.
|
|
111
|
+
* - `'keepalive'`: disable the inactivity watchdog and rely on OS TCP
|
|
112
|
+
* keepalive probes instead. Use for connections that are expected to sit
|
|
113
|
+
* idle under back-pressure (e.g. the backfill COPY stream), where the
|
|
114
|
+
* watchdog would reset a perfectly healthy connection.
|
|
115
|
+
*/
|
|
116
|
+
liveness?: 'inactivity-timeout' | 'keepalive';
|
|
117
|
+
}, opts?: TypeOptions): PostgresDB;
|
|
105
118
|
export declare function connectPgClient(...args: Parameters<typeof pgClient>): Promise<PostgresDB>;
|
|
106
119
|
/**
|
|
107
120
|
* Disables any statement_timeout for the current transaction. By default,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/pg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,UAAU,CAAC;AAehC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,QAAQ,EAAE,EAAc,KAAK,YAAY,EAAC,MAAM,UAAU,CAAC;AAClE,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAmB9E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAmD7D;AAED,iBAAS,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAuBhD;AAcD,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAoBvE;AAOD,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA8ErE;AAED,iBAAS,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,uBAAoB,WAAgB;;;;;;;;;;;;;;;;;;2BAwBlD,OAAO;;;;;;2BAOP,OAAO;;;;;;2BAQP,MAAM,GAAG,IAAI;;;;;;2BAUb,MAAM;uBACV,MAAM,GAAG,MAAM;;;CAG9B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,cAAc,CAAC;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/pg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,UAAU,CAAC;AAehC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,QAAQ,EAAE,EAAc,KAAK,YAAY,EAAC,MAAM,UAAU,CAAC;AAClE,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAmB9E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAmD7D;AAED,iBAAS,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAuBhD;AAcD,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAoBvE;AAOD,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA8ErE;AAED,iBAAS,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,uBAAoB,WAAgB;;;;;;;;;;;;;;;;;;2BAwBlD,OAAO;;;;;;2BAOP,OAAO;;;;;;2BAQP,MAAM,GAAG,IAAI;;;;;;2BAUb,MAAM;uBACV,MAAM,GAAG,MAAM;;;CAG9B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,cAAc,CAAC;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAkBH,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,UAAU,EACd,SAAS,SAA+B,EAWxC,WAAW,SAAI,IAEP,SAAS,oBAAoB,KAAG,MAAM,CAqD/C;AAED,wBAAgB,QAAQ,CACtB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,MAAM,EAGrB,eAAe,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;CAC/B,CAAC,GAAG;IACH;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,oBAAoB,GAAG,WAAW,CAAC;CAC/C,EACD,IAAI,CAAC,EAAE,WAAW,GACjB,UAAU,CAmFZ;AAED,wBAAsB,eAAe,CACnC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,GACnC,OAAO,CAAC,UAAU,CAAC,CAcrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,QAE/D;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOhD,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,WAE1E;AA8BD,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,WAiB/C"}
|
|
@@ -157,9 +157,10 @@ var SOCKET_INACTIVITY_TIMEOUT_MS = parseInt(process.env.ZERO_PG_SOCKET_INACTIVIT
|
|
|
157
157
|
*
|
|
158
158
|
* Exported for testing.
|
|
159
159
|
*/
|
|
160
|
-
function inactivityTimeoutSocket(lc, timeoutMs = SOCKET_INACTIVITY_TIMEOUT_MS) {
|
|
160
|
+
function inactivityTimeoutSocket(lc, timeoutMs = SOCKET_INACTIVITY_TIMEOUT_MS, keepAliveMs = 0) {
|
|
161
161
|
return (options) => {
|
|
162
162
|
const socket = new Socket();
|
|
163
|
+
if (keepAliveMs > 0) socket.setKeepAlive(true, keepAliveMs);
|
|
163
164
|
const target = options.path ? options.path : `${options.host[0]}:${options.port[0]}`;
|
|
164
165
|
if (timeoutMs > 0) {
|
|
165
166
|
let lastActivity = -1;
|
|
@@ -193,19 +194,29 @@ function inactivityTimeoutSocket(lc, timeoutMs = SOCKET_INACTIVITY_TIMEOUT_MS) {
|
|
|
193
194
|
}
|
|
194
195
|
function pgClient(lc, connectionURI, applicationName, options, opts) {
|
|
195
196
|
applicationName = `zero-${applicationName}`;
|
|
197
|
+
const safeNotice = (n) => ({
|
|
198
|
+
severity: n.severity,
|
|
199
|
+
code: n.code,
|
|
200
|
+
message: n.message,
|
|
201
|
+
schema: n.schema_name,
|
|
202
|
+
table: n.table_name,
|
|
203
|
+
column: n.column_name,
|
|
204
|
+
constraint: n.constraint_name,
|
|
205
|
+
routine: n.routine
|
|
206
|
+
});
|
|
196
207
|
const onnotice = (n) => {
|
|
197
208
|
switch (n.severity) {
|
|
198
209
|
case "NOTICE": return;
|
|
199
210
|
case "DEBUG":
|
|
200
|
-
lc.debug?.(n);
|
|
211
|
+
lc.debug?.("pg notice", safeNotice(n));
|
|
201
212
|
return;
|
|
202
213
|
case "WARNING":
|
|
203
|
-
lc.warn?.(n);
|
|
214
|
+
lc.warn?.("pg notice", safeNotice(n));
|
|
204
215
|
return;
|
|
205
216
|
case "EXCEPTION":
|
|
206
|
-
lc.error?.(n);
|
|
217
|
+
lc.error?.("pg notice", safeNotice(n));
|
|
207
218
|
return;
|
|
208
|
-
default: lc.info?.(n);
|
|
219
|
+
default: lc.info?.("pg notice", safeNotice(n));
|
|
209
220
|
}
|
|
210
221
|
};
|
|
211
222
|
const url = new URL(connectionURI);
|
|
@@ -215,8 +226,10 @@ function pgClient(lc, connectionURI, applicationName, options, opts) {
|
|
|
215
226
|
else if (sslFlag === "no-verify") ssl = { rejectUnauthorized: false };
|
|
216
227
|
else ssl = sslFlag;
|
|
217
228
|
const maxLifetimeSeconds = randInt(300, 600);
|
|
218
|
-
const
|
|
219
|
-
const
|
|
229
|
+
const { liveness = "inactivity-timeout", ...pgOptions } = options ?? {};
|
|
230
|
+
const providedConnection = pgOptions.connection ?? {};
|
|
231
|
+
const socketLc = lc.withContext("appName", applicationName);
|
|
232
|
+
const socketFactory = { socket: liveness === "keepalive" ? inactivityTimeoutSocket(socketLc, 0, 6e4) : inactivityTimeoutSocket(socketLc) };
|
|
220
233
|
return postgres(connectionURI, {
|
|
221
234
|
...postgresTypeConfig(opts),
|
|
222
235
|
onnotice,
|
|
@@ -224,7 +237,7 @@ function pgClient(lc, connectionURI, applicationName, options, opts) {
|
|
|
224
237
|
["idle_timeout"]: 60,
|
|
225
238
|
ssl,
|
|
226
239
|
...socketFactory,
|
|
227
|
-
...
|
|
240
|
+
...pgOptions,
|
|
228
241
|
connection: {
|
|
229
242
|
...providedConnection,
|
|
230
243
|
["application_name"]: applicationName
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg.js","names":[],"sources":["../../../../../zero-cache/src/types/pg.ts"],"sourcesContent":["import {Socket} from 'node:net';\nimport {\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n PG_CONNECTION_DOES_NOT_EXIST,\n PG_CONNECTION_EXCEPTION,\n PG_CONNECTION_FAILURE,\n PG_INSUFFICIENT_PRIVILEGE,\n PG_INVALID_AUTHORIZATION_SPECIFICATION,\n PG_INVALID_CATALOG_NAME,\n PG_INVALID_PASSWORD,\n PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n PG_TOO_MANY_CONNECTIONS,\n} from '@drdgvhbh/postgres-error-codes';\nimport {PreciseDate} from '@google-cloud/precise-date';\nimport {OID} from '@postgresql-typed/oids';\nimport type {LogContext} from '@rocicorp/logger';\nimport postgres, {type Notice, type PostgresType} from 'postgres';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {ConfigurationError} from './configuration-error.ts';\nimport {\n DATE,\n JSON,\n JSONB,\n NUMERIC,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n} from './pg-types.ts';\n\n// Matches: YEAR-MM-DD HH:MM:SS[.fraction][+-TZ[:MM]][ BC]\nconst pgTimestampRe =\n /^(\\d+)-(\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)([+-]\\d{1,2}(?::\\d{2})?)?(?: BC)?$/;\n\n// exported for testing.\nexport function timestampToFpMillis(timestamp: string): number {\n if (timestamp === 'infinity') return Infinity;\n if (timestamp === '-infinity') return -Infinity;\n const match = timestamp.match(pgTimestampRe);\n if (!match) {\n throw new Error(`Error parsing ${timestamp}`);\n }\n\n const [, yearStr, monthDay, time, tz] = match;\n const bc = timestamp.endsWith(' BC');\n\n let year = Number(yearStr);\n if (bc) {\n // Postgres: 1 BC = JS year 0, 2 BC = JS year -1, N BC = -(N-1)\n year = -(year - 1);\n }\n\n // Format year as ISO 8601 expanded year if needed.\n // https://tc39.es/ecma262/#sec-expanded-years\n let isoYear: string;\n if (year >= 0 && year <= 9999) {\n isoYear = String(year).padStart(4, '0');\n } else if (year >= 0) {\n isoYear = '+' + String(year).padStart(6, '0');\n } else {\n isoYear = '-' + String(Math.abs(year)).padStart(6, '0');\n }\n\n // Build a UTC ISO string so PreciseDate returns microsecond precision.\n const utcString = `${isoYear}-${monthDay}T${time}Z`;\n\n try {\n const fullTime = new PreciseDate(utcString).getFullTime();\n const millis = Number(fullTime / 1_000_000n);\n const nanos = Number(fullTime % 1_000_000n);\n const ret = millis + nanos * 1e-6; // floating point milliseconds\n\n // Add back in the timezone offset. We passed local time as UTC,\n // so a positive offset means we need to subtract, and vice versa.\n if (tz) {\n const positiveOffset = tz.startsWith('+');\n const [hours, minutes] = tz.split(':');\n const offset =\n Math.abs(Number(hours)) * 60 + (minutes ? Number(minutes) : 0);\n const offsetMillis = offset * 60 * 1_000;\n return positiveOffset ? ret - offsetMillis : ret + offsetMillis;\n }\n return ret;\n } catch (e) {\n throw new Error(`Error parsing ${timestamp}`, {cause: e});\n }\n}\n\nfunction serializeTimestamp(val: unknown): string {\n switch (typeof val) {\n case 'string':\n return val; // Let Postgres parse it\n case 'number': {\n if (Number.isInteger(val)) {\n return new PreciseDate(val).toISOString();\n }\n // Convert floating point to bigint nanoseconds.\n const nanoseconds =\n 1_000_000n * BigInt(Math.trunc(val)) +\n BigInt(Math.trunc((val % 1) * 1e6));\n return new PreciseDate(nanoseconds).toISOString();\n }\n // Note: Don't support bigint inputs until we decide what the semantics are (e.g. micros vs nanos)\n // case 'bigint':\n // return new PreciseDate(val).toISOString();\n default:\n if (val instanceof Date) {\n return val.toISOString();\n }\n }\n throw new Error(`Unsupported type \"${typeof val}\" for timestamp: ${val}`);\n}\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction serializeTime(x: unknown, type: 'time' | 'timetz'): string {\n switch (typeof x) {\n case 'string':\n return x; // Let Postgres parse it\n case 'number':\n return millisecondsToPostgresTime(x);\n }\n throw new Error(`Unsupported type \"${typeof x}\" for ${type}: ${x}`);\n}\n\nexport function millisecondsToPostgresTime(milliseconds: number): string {\n if (milliseconds < 0) {\n throw new Error('Milliseconds cannot be negative');\n }\n\n if (milliseconds >= MILLISECONDS_PER_DAY) {\n throw new Error(\n `Milliseconds cannot exceed 24 hours (${MILLISECONDS_PER_DAY}ms)`,\n );\n }\n\n milliseconds = Math.floor(milliseconds); // Ensure it's an integer\n\n const totalSeconds = Math.floor(milliseconds / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const ms = milliseconds % 1000;\n\n return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}+00`;\n}\n\n// Regular expression to match HH:MM:SS, HH:MM:SS.mmm, or HH:MM:SS+00 / HH:MM:SS.mmm+00\n// Supports optional timezone offset\nconst timeRegex =\n /^(\\d{1,2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?(?:([+-])(\\d{1,2})(?::(\\d{2}))?)?$/;\n\nexport function postgresTimeToMilliseconds(timeString: string): number {\n // Validate basic format\n if (!timeString || typeof timeString !== 'string') {\n throw new Error('Invalid time string: must be a non-empty string');\n }\n\n const match = timeString.match(timeRegex);\n\n if (!match) {\n throw new Error(\n `Invalid time format: \"${timeString}\". Expected HH:MM:SS[.mmm][+|-HH[:MM]]`,\n );\n }\n\n // Extract components\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const seconds = parseInt(match[3], 10);\n // Handle optional milliseconds, pad right with zeros if needed\n let milliseconds = 0;\n if (match[4]) {\n // Pad microseconds to 6 digits\n const msString = match[4].padEnd(6, '0');\n // slice milliseconds out of the microseconds\n // e.g. 123456 -> 123, 1234 -> 123,\n milliseconds = parseInt(msString.slice(0, 3), 10);\n }\n\n // Validate ranges\n if (hours < 0 || hours > 24) {\n throw new Error(\n `Invalid hours: ${hours}. Must be between 0 and 24 (24 means end of day)`,\n );\n }\n\n if (minutes < 0 || minutes >= 60) {\n throw new Error(`Invalid minutes: ${minutes}. Must be between 0 and 59`);\n }\n\n if (seconds < 0 || seconds >= 60) {\n throw new Error(`Invalid seconds: ${seconds}. Must be between 0 and 59`);\n }\n\n if (milliseconds < 0 || milliseconds >= 1000) {\n throw new Error(\n `Invalid milliseconds: ${milliseconds}. Must be between 0 and 999`,\n );\n }\n\n // Special case: PostgreSQL allows 24:00:00 to represent end of day\n if (hours === 24 && (minutes !== 0 || seconds !== 0 || milliseconds !== 0)) {\n throw new Error(\n 'Invalid time: when hours is 24, minutes, seconds, and milliseconds must be 0',\n );\n }\n\n // Calculate total milliseconds\n let totalMs =\n hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;\n\n // Timezone Offset\n if (match[5]) {\n const sign = match[5] === '+' ? 1 : -1;\n const tzHours = parseInt(match[6], 10);\n const tzMinutes = match[7] ? parseInt(match[7], 10) : 0;\n const offsetMs = sign * (tzHours * 3600000 + tzMinutes * 60000);\n totalMs -= offsetMs;\n }\n\n // Normalize to 0-24h only if outside valid range\n if (totalMs > MILLISECONDS_PER_DAY || totalMs < 0) {\n return (\n ((totalMs % MILLISECONDS_PER_DAY) + MILLISECONDS_PER_DAY) %\n MILLISECONDS_PER_DAY\n );\n }\n\n return totalMs;\n}\n\nfunction dateToUTCMidnight(date: string): number {\n if (date === 'infinity') return Infinity;\n if (date === '-infinity') return -Infinity;\n const d = new Date(date);\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n}\n\n/**\n * The (javascript) types of objects that can be returned by our configured\n * Postgres clients. For initial-sync, these comes from the postgres.js client:\n *\n * https://github.com/porsager/postgres/blob/master/src/types.js\n *\n * and for the replication stream these come from the the node-postgres client:\n *\n * https://github.com/brianc/node-pg-types/blob/master/lib/textParsers.js\n */\nexport type PostgresValueType = JSONValue | Uint8Array;\n\nexport type TypeOptions = {\n /**\n * Sends strings directly as JSON values (i.e. without JSON stringification).\n * The application is responsible for ensuring that string inputs for JSON\n * columns are already stringified. Other data types (e.g. objects) will\n * still be stringified by the pg client.\n */\n sendStringAsJson?: boolean;\n};\n\n/**\n * Configures types for the Postgres.js client library (`postgres`).\n *\n * @param jsonAsString Keep JSON / JSONB values as strings instead of parsing.\n */\nexport const postgresTypeConfig = ({sendStringAsJson}: TypeOptions = {}) => ({\n // Type the type IDs as `number` so that Typescript doesn't complain about\n // referencing external types during type inference.\n types: {\n bigint: postgres.BigInt,\n json: {\n to: JSON,\n from: [JSON, JSONB],\n serialize: sendStringAsJson\n ? (x: unknown) => (typeof x === 'string' ? x : BigIntJSON.stringify(x))\n : BigIntJSON.stringify,\n parse: BigIntJSON.parse,\n },\n // Timestamps are converted to PreciseDate objects.\n timestamp: {\n to: TIMESTAMP,\n from: [TIMESTAMP, TIMESTAMPTZ],\n serialize: serializeTimestamp,\n parse: timestampToFpMillis,\n },\n // Times are converted as strings\n time: {\n to: TIME,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'time'),\n parse: postgresTimeToMilliseconds,\n },\n\n timetz: {\n to: TIMETZ,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'timetz'),\n parse: postgresTimeToMilliseconds,\n },\n\n // The DATE type is stored directly as the PG normalized date string.\n date: {\n to: DATE,\n from: [DATE],\n serialize: (x: string | Date) =>\n (x instanceof Date ? x : new Date(x)).toISOString(),\n parse: dateToUTCMidnight,\n },\n // Returns a `js` number which can lose precision for large numbers.\n // JS number is 53 bits so this should generally not occur.\n // An API will be provided for users to override this type.\n numeric: {\n to: NUMERIC,\n from: [NUMERIC],\n serialize: (x: number) => String(x), // pg expects a string\n parse: (x: string | number) => Number(x),\n },\n },\n});\n\nexport type PostgresDB = postgres.Sql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport type PostgresTransaction = postgres.TransactionSql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\n// Guards against half-open TCP connections, which otherwise hang the pool\n// indefinitely: a proxy can keep the client-facing socket alive (ACKing TCP\n// keepalives) after its backend is gone, so no 'close' or 'error' event ever\n// fires and statements like BEGIN / COMMIT — which are not covered by the\n// TransactionPool's statementResponseTimeout — await forever.\n// See https://github.com/porsager/postgres/issues/1089.\n//\n// If no bytes are read or written for this long, the socket is reset, which\n// rejects all in-flight queries and lets the pool recover. Wire activity\n// resets the timer, so streaming operations (e.g. COPY) are safe, but\n// statements that legitimately compute silently for longer (e.g. index\n// builds in migrations) must raise this. Settable as an emergency measure\n// (0 disables); deliberately not exposed as a server option.\nconst SOCKET_INACTIVITY_TIMEOUT_MS = parseInt(\n process.env.ZERO_PG_SOCKET_INACTIVITY_TIMEOUT ?? '120000',\n);\n\ntype SocketFactoryOptions = {\n host: string[];\n port: number[];\n path?: string | false;\n};\n\n/**\n * Creates the custom socket factory passed to postgres.js via its (untyped)\n * `socket` option. postgres.js does not call `connect()` on custom sockets,\n * and its TLS upgrade reads `socket.host` for the SNI servername, so the\n * factory must handle both itself.\n *\n * Note: unlike postgres.js's internal socket, reconnects do not rotate\n * through multiple hosts; multi-host URIs are not used by the zero-cache.\n *\n * Exported for testing.\n */\nexport function inactivityTimeoutSocket(\n lc: LogContext,\n timeoutMs = SOCKET_INACTIVITY_TIMEOUT_MS,\n) {\n return (options: SocketFactoryOptions): Socket => {\n const socket = new Socket();\n const target = options.path\n ? options.path\n : `${options.host[0]}:${options.port[0]}`;\n if (timeoutMs > 0) {\n // socket.setTimeout() cannot be used here: it registers its callback\n // as a 'timeout' event listener, and postgres.js removes all listeners\n // from the raw socket when upgrading it to TLS (removeAllListeners()\n // in secure()), which would silently disarm the watchdog on every TLS\n // connection. An interval is not a socket listener, so it survives the\n // upgrade, and encrypted traffic still flows through this raw socket,\n // advancing its byte counters.\n //\n // The counters are sampled once per timeout period, so a half-open\n // socket is reset after one to two timeout periods of inactivity.\n let lastActivity = -1;\n const watchdog = setInterval(() => {\n if (socket.destroyed) {\n clearInterval(watchdog);\n return;\n }\n const activity = socket.bytesRead + socket.bytesWritten;\n if (activity === lastActivity) {\n lc.warn?.(\n `resetting connection to ${target} after ${timeoutMs} ms of inactivity ` +\n `(likely half-open); in-flight queries will be rejected`,\n );\n clearInterval(watchdog);\n socket.resetAndDestroy();\n } else {\n lastActivity = activity;\n }\n }, timeoutMs);\n watchdog.unref();\n // Best-effort cleanup; removed on TLS upgrade, in which case the\n // destroyed check above clears the (unref'ed) interval instead.\n socket.once('close', () => clearInterval(watchdog));\n }\n if (options.path) {\n socket.connect(options.path);\n } else {\n const [host] = options.host;\n const [port] = options.port;\n socket.connect(port, host);\n // Read by postgres.js for the TLS SNI servername.\n Object.assign(socket, {host, port});\n }\n return socket;\n };\n}\n\nexport function pgClient(\n lc: LogContext,\n connectionURI: string,\n // A descriptive name for the code that is making the connection, so that we\n // can debug things like deadlocks.\n applicationName: string,\n options?: postgres.Options<{\n bigint: PostgresType<bigint>;\n json: PostgresType<JSONValue>;\n }>,\n opts?: TypeOptions,\n): PostgresDB {\n applicationName = `zero-${applicationName}`;\n\n const onnotice = (n: Notice) => {\n // https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html#PLPGSQL-STATEMENTS-RAISE\n switch (n.severity) {\n case 'NOTICE':\n return; // silenced\n case 'DEBUG':\n lc.debug?.(n);\n return;\n case 'WARNING':\n lc.warn?.(n);\n return;\n case 'EXCEPTION':\n lc.error?.(n);\n return;\n case 'LOG':\n case 'INFO':\n default:\n lc.info?.(n);\n }\n };\n const url = new URL(connectionURI);\n const sslFlag =\n url.searchParams.get('ssl') ?? url.searchParams.get('sslmode') ?? 'prefer';\n\n let ssl: boolean | 'prefer' | {rejectUnauthorized: boolean};\n if (sslFlag === 'disable' || sslFlag === 'false') {\n ssl = false;\n } else if (sslFlag === 'no-verify') {\n ssl = {rejectUnauthorized: false};\n } else {\n ssl = sslFlag as 'prefer';\n }\n\n // Set connections to expire between 5 and 10 minutes to free up state on PG.\n const maxLifetimeSeconds = randInt(5 * 60, 10 * 60);\n const providedConnection = options?.connection ?? {};\n\n // postgres.js supports a custom socket factory via the `socket` option,\n // but it is missing from its type declarations, hence the untyped spread.\n const socketFactory = {\n socket: inactivityTimeoutSocket(lc.withContext('appName', applicationName)),\n };\n\n return postgres(connectionURI, {\n ...postgresTypeConfig(opts),\n onnotice,\n ['max_lifetime']: maxLifetimeSeconds,\n // Close idle connections cleanly before the socket inactivity timer\n // (which cannot distinguish an idle pool connection from a hung query)\n // resets them.\n ['idle_timeout']: 60,\n ssl,\n ...socketFactory,\n ...options,\n connection: {\n ...providedConnection,\n ['application_name']: applicationName,\n },\n });\n}\n\nexport async function connectPgClient(\n ...args: Parameters<typeof pgClient>\n): Promise<PostgresDB> {\n const db = pgClient(...args);\n try {\n await db`SELECT 1`.simple().execute();\n return db;\n } catch (e) {\n if (isPostgresConfigError(e)) {\n throw new ConfigurationError(\n 'Unable to connect to Postgres. Check your database configuration.',\n {cause: e},\n );\n }\n throw e;\n }\n}\n\n/**\n * Disables any statement_timeout for the current transaction. By default,\n * Postgres does not impose a statement timeout, but some users and providers\n * set one at the database level (even though it is explicitly discouraged by\n * the Postgres documentation).\n *\n * Zero logic in particular often does not fit into the category of general\n * application logic; for potentially long-running operations like migrations\n * and background cleanup, the statement timeout should be disabled to prevent\n * these operations from timing out.\n */\nexport function disableStatementTimeout(sql: PostgresTransaction) {\n void sql`SET LOCAL statement_timeout = 0;`.execute().catch(() => {});\n}\n\nexport const typeNameByOID: Record<number, string> = Object.freeze(\n Object.fromEntries(\n Object.entries(OID).map(([name, oid]) => [\n oid,\n name.startsWith('_') ? `${name.substring(1)}[]` : name,\n ]),\n ),\n);\n\nexport function isPostgresError(e: unknown, ...codes: [string, ...string[]]) {\n return e instanceof postgres.PostgresError && codes.includes(e.code);\n}\n\nfunction isPostgresStartupConnectionError(e: unknown) {\n if (!(e instanceof Error) || !('code' in e)) {\n return false;\n }\n const {code} = e;\n // postgres.js throws plain Error instances for connection failures that\n // happen before Postgres can return a SQLSTATE. These are still startup\n // configuration problems for Zero: wrong host/port, unreachable DB, DNS,\n // TLS certificate mismatch, etc.\n return (\n typeof code === 'string' &&\n [\n 'CONNECT_TIMEOUT',\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'EHOSTUNREACH',\n 'ENETUNREACH',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'ETIMEDOUT',\n 'ERR_TLS_CERT_ALTNAME_INVALID',\n 'SELF_SIGNED_CERT_IN_CHAIN',\n 'DEPTH_ZERO_SELF_SIGNED_CERT',\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n ].includes(code)\n );\n}\n\nexport function isPostgresConfigError(e: unknown) {\n return (\n isPostgresStartupConnectionError(e) ||\n isPostgresError(\n e,\n PG_CONNECTION_EXCEPTION,\n PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n PG_CONNECTION_DOES_NOT_EXIST,\n PG_CONNECTION_FAILURE,\n PG_INVALID_AUTHORIZATION_SPECIFICATION,\n PG_INVALID_PASSWORD,\n PG_INVALID_CATALOG_NAME,\n PG_INSUFFICIENT_PRIVILEGE,\n PG_TOO_MANY_CONNECTIONS,\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n )\n );\n}\n"],"mappings":";;;;;;;;;;AAgCA,IAAM,gBACJ;AAGF,SAAgB,oBAAoB,WAA2B;CAC7D,IAAI,cAAc,YAAY,OAAO;CACrC,IAAI,cAAc,aAAa,OAAO;CACtC,MAAM,QAAQ,UAAU,MAAM,aAAa;CAC3C,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,iBAAiB,WAAW;CAG9C,MAAM,GAAG,SAAS,UAAU,MAAM,MAAM;CACxC,MAAM,KAAK,UAAU,SAAS,KAAK;CAEnC,IAAI,OAAO,OAAO,OAAO;CACzB,IAAI,IAEF,OAAO,EAAE,OAAO;CAKlB,IAAI;CACJ,IAAI,QAAQ,KAAK,QAAQ,MACvB,UAAU,OAAO,IAAI,EAAE,SAAS,GAAG,GAAG;MACjC,IAAI,QAAQ,GACjB,UAAU,MAAM,OAAO,IAAI,EAAE,SAAS,GAAG,GAAG;MAE5C,UAAU,MAAM,OAAO,KAAK,IAAI,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;CAIxD,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK;CAEjD,IAAI;EACF,MAAM,WAAW,IAAI,YAAY,SAAS,EAAE,YAAY;EAGxD,MAAM,MAFS,OAAO,WAAW,QAErB,IADE,OAAO,WAAW,QACX,IAAQ;EAI7B,IAAI,IAAI;GACN,MAAM,iBAAiB,GAAG,WAAW,GAAG;GACxC,MAAM,CAAC,OAAO,WAAW,GAAG,MAAM,GAAG;GAGrC,MAAM,gBADJ,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,MAAM,UAAU,OAAO,OAAO,IAAI,MAChC,KAAK;GACnC,OAAO,iBAAiB,MAAM,eAAe,MAAM;EACrD;EACA,OAAO;CACT,SAAS,GAAG;EACV,MAAM,IAAI,MAAM,iBAAiB,aAAa,EAAC,OAAO,EAAC,CAAC;CAC1D;AACF;AAEA,SAAS,mBAAmB,KAAsB;CAChD,QAAQ,OAAO,KAAf;EACE,KAAK,UACH,OAAO;EACT,KAAK;GACH,IAAI,OAAO,UAAU,GAAG,GACtB,OAAO,IAAI,YAAY,GAAG,EAAE,YAAY;GAM1C,OAAO,IAAI,YAFT,WAAa,OAAO,KAAK,MAAM,GAAG,CAAC,IACnC,OAAO,KAAK,MAAO,MAAM,IAAK,GAAG,CAAC,CACF,EAAE,YAAY;EAKlD,SACE,IAAI,eAAe,MACjB,OAAO,IAAI,YAAY;CAE7B;CACA,MAAM,IAAI,MAAM,qBAAqB,OAAO,IAAI,mBAAmB,KAAK;AAC1E;AAEA,IAAM,uBAAuB,OAAU,KAAK;AAE5C,SAAS,cAAc,GAAY,MAAiC;CAClE,QAAQ,OAAO,GAAf;EACE,KAAK,UACH,OAAO;EACT,KAAK,UACH,OAAO,2BAA2B,CAAC;CACvC;CACA,MAAM,IAAI,MAAM,qBAAqB,OAAO,EAAE,QAAQ,KAAK,IAAI,GAAG;AACpE;AAEA,SAAgB,2BAA2B,cAA8B;CACvE,IAAI,eAAe,GACjB,MAAM,IAAI,MAAM,iCAAiC;CAGnD,IAAI,gBAAgB,sBAClB,MAAM,IAAI,MACR,wCAAwC,qBAAqB,IAC/D;CAGF,eAAe,KAAK,MAAM,YAAY;CAEtC,MAAM,eAAe,KAAK,MAAM,eAAe,GAAI;CACnD,MAAM,QAAQ,KAAK,MAAM,eAAe,IAAI;CAC5C,MAAM,UAAU,KAAK,MAAO,eAAe,OAAQ,EAAE;CACrD,MAAM,UAAU,eAAe;CAC/B,MAAM,KAAK,eAAe;CAE1B,OAAO,GAAG,MAAM,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE,GAAG,QAAQ,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE,GAAG,QAAQ,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE,GAAG,GAAG,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAC9J;AAIA,IAAM,YACJ;AAEF,SAAgB,2BAA2B,YAA4B;CAErE,IAAI,CAAC,cAAc,OAAO,eAAe,UACvC,MAAM,IAAI,MAAM,iDAAiD;CAGnE,MAAM,QAAQ,WAAW,MAAM,SAAS;CAExC,IAAI,CAAC,OACH,MAAM,IAAI,MACR,yBAAyB,WAAW,uCACtC;CAIF,MAAM,QAAQ,SAAS,MAAM,IAAI,EAAE;CACnC,MAAM,UAAU,SAAS,MAAM,IAAI,EAAE;CACrC,MAAM,UAAU,SAAS,MAAM,IAAI,EAAE;CAErC,IAAI,eAAe;CACnB,IAAI,MAAM,IAAI;EAEZ,MAAM,WAAW,MAAM,GAAG,OAAO,GAAG,GAAG;EAGvC,eAAe,SAAS,SAAS,MAAM,GAAG,CAAC,GAAG,EAAE;CAClD;CAGA,IAAI,QAAQ,KAAK,QAAQ,IACvB,MAAM,IAAI,MACR,kBAAkB,MAAM,iDAC1B;CAGF,IAAI,UAAU,KAAK,WAAW,IAC5B,MAAM,IAAI,MAAM,oBAAoB,QAAQ,2BAA2B;CAGzE,IAAI,UAAU,KAAK,WAAW,IAC5B,MAAM,IAAI,MAAM,oBAAoB,QAAQ,2BAA2B;CAGzE,IAAI,eAAe,KAAK,gBAAgB,KACtC,MAAM,IAAI,MACR,yBAAyB,aAAa,4BACxC;CAIF,IAAI,UAAU,OAAO,YAAY,KAAK,YAAY,KAAK,iBAAiB,IACtE,MAAM,IAAI,MACR,8EACF;CAIF,IAAI,UACF,QAAQ,OAAU,UAAU,MAAQ,UAAU,MAAO;CAGvD,IAAI,MAAM,IAAI;EACZ,MAAM,OAAO,MAAM,OAAO,MAAM,IAAI;EACpC,MAAM,UAAU,SAAS,MAAM,IAAI,EAAE;EACrC,MAAM,YAAY,MAAM,KAAK,SAAS,MAAM,IAAI,EAAE,IAAI;EACtD,MAAM,WAAW,QAAQ,UAAU,OAAU,YAAY;EACzD,WAAW;CACb;CAGA,IAAI,UAAU,wBAAwB,UAAU,GAC9C,QACI,UAAU,uBAAwB,wBACpC;CAIJ,OAAO;AACT;AAEA,SAAS,kBAAkB,MAAsB;CAC/C,IAAI,SAAS,YAAY,OAAO;CAChC,IAAI,SAAS,aAAa,OAAO;CACjC,MAAM,IAAI,IAAI,KAAK,IAAI;CACvB,OAAO,KAAK,IAAI,EAAE,eAAe,GAAG,EAAE,YAAY,GAAG,EAAE,WAAW,CAAC;AACrE;;;;;;AA6BA,IAAa,sBAAsB,EAAC,qBAAiC,CAAC,OAAO,EAG3E,OAAO;CACL,QAAQ,SAAS;CACjB,MAAM;EACJ,IAAA;EACA,MAAM,CAAA,KAAO,KAAK;EAClB,WAAW,oBACN,MAAgB,OAAO,MAAM,WAAW,IAAI,WAAW,UAAU,CAAC,IACnE,WAAW;EACf,OAAO,WAAW;CACpB;CAEA,WAAW;EACT,IAAI;EACJ,MAAM,CAAC,WAAW,WAAW;EAC7B,WAAW;EACX,OAAO;CACT;CAEA,MAAM;EACJ,IAAI;EACJ,MAAM,CAAC,MAAM,MAAM;EACnB,YAAY,MAAe,cAAc,GAAG,MAAM;EAClD,OAAO;CACT;CAEA,QAAQ;EACN,IAAI;EACJ,MAAM,CAAC,MAAM,MAAM;EACnB,YAAY,MAAe,cAAc,GAAG,QAAQ;EACpD,OAAO;CACT;CAGA,MAAM;EACJ,IAAI;EACJ,MAAM,CAAC,IAAI;EACX,YAAY,OACT,aAAa,OAAO,IAAI,IAAI,KAAK,CAAC,GAAG,YAAY;EACpD,OAAO;CACT;CAIA,SAAS;EACP,IAAI;EACJ,MAAM,CAAC,OAAO;EACd,YAAY,MAAc,OAAO,CAAC;EAClC,QAAQ,MAAuB,OAAO,CAAC;CACzC;AACF,EACF;AAyBA,IAAM,+BAA+B,SACnC,QAAQ,IAAI,qCAAqC,QACnD;;;;;;;;;;;;AAmBA,SAAgB,wBACd,IACA,YAAY,8BACZ;CACA,QAAQ,YAA0C;EAChD,MAAM,SAAS,IAAI,OAAO;EAC1B,MAAM,SAAS,QAAQ,OACnB,QAAQ,OACR,GAAG,QAAQ,KAAK,GAAG,GAAG,QAAQ,KAAK;EACvC,IAAI,YAAY,GAAG;GAWjB,IAAI,eAAe;GACnB,MAAM,WAAW,kBAAkB;IACjC,IAAI,OAAO,WAAW;KACpB,cAAc,QAAQ;KACtB;IACF;IACA,MAAM,WAAW,OAAO,YAAY,OAAO;IAC3C,IAAI,aAAa,cAAc;KAC7B,GAAG,OACD,2BAA2B,OAAO,SAAS,UAAU,yEAEvD;KACA,cAAc,QAAQ;KACtB,OAAO,gBAAgB;IACzB,OACE,eAAe;GAEnB,GAAG,SAAS;GACZ,SAAS,MAAM;GAGf,OAAO,KAAK,eAAe,cAAc,QAAQ,CAAC;EACpD;EACA,IAAI,QAAQ,MACV,OAAO,QAAQ,QAAQ,IAAI;OACtB;GACL,MAAM,CAAC,QAAQ,QAAQ;GACvB,MAAM,CAAC,QAAQ,QAAQ;GACvB,OAAO,QAAQ,MAAM,IAAI;GAEzB,OAAO,OAAO,QAAQ;IAAC;IAAM;GAAI,CAAC;EACpC;EACA,OAAO;CACT;AACF;AAEA,SAAgB,SACd,IACA,eAGA,iBACA,SAIA,MACY;CACZ,kBAAkB,QAAQ;CAE1B,MAAM,YAAY,MAAc;EAE9B,QAAQ,EAAE,UAAV;GACE,KAAK,UACH;GACF,KAAK;IACH,GAAG,QAAQ,CAAC;IACZ;GACF,KAAK;IACH,GAAG,OAAO,CAAC;IACX;GACF,KAAK;IACH,GAAG,QAAQ,CAAC;IACZ;GAGF,SACE,GAAG,OAAO,CAAC;EACf;CACF;CACA,MAAM,MAAM,IAAI,IAAI,aAAa;CACjC,MAAM,UACJ,IAAI,aAAa,IAAI,KAAK,KAAK,IAAI,aAAa,IAAI,SAAS,KAAK;CAEpE,IAAI;CACJ,IAAI,YAAY,aAAa,YAAY,SACvC,MAAM;MACD,IAAI,YAAY,aACrB,MAAM,EAAC,oBAAoB,MAAK;MAEhC,MAAM;CAIR,MAAM,qBAAqB,QAAQ,KAAQ,GAAO;CAClD,MAAM,qBAAqB,SAAS,cAAc,CAAC;CAInD,MAAM,gBAAgB,EACpB,QAAQ,wBAAwB,GAAG,YAAY,WAAW,eAAe,CAAC,EAC5E;CAEA,OAAO,SAAS,eAAe;EAC7B,GAAG,mBAAmB,IAAI;EAC1B;GACC,iBAAiB;GAIjB,iBAAiB;EAClB;EACA,GAAG;EACH,GAAG;EACH,YAAY;GACV,GAAG;IACF,qBAAqB;EACxB;CACF,CAAC;AACH;AAEA,eAAsB,gBACpB,GAAG,MACkB;CACrB,MAAM,KAAK,SAAS,GAAG,IAAI;CAC3B,IAAI;EACF,MAAM,EAAE,WAAW,OAAO,EAAE,QAAQ;EACpC,OAAO;CACT,SAAS,GAAG;EACV,IAAI,sBAAsB,CAAC,GACzB,MAAM,IAAI,mBACR,qEACA,EAAC,OAAO,EAAC,CACX;EAEF,MAAM;CACR;AACF;AAiBqD,OAAO,OAC1D,OAAO,YACL,OAAO,QAAQ,GAAG,EAAE,KAAK,CAAC,MAAM,SAAS,CACvC,KACA,KAAK,WAAW,GAAG,IAAI,GAAG,KAAK,UAAU,CAAC,EAAE,MAAM,IACpD,CAAC,CACH,CACF;AAEA,SAAgB,gBAAgB,GAAY,GAAG,OAA8B;CAC3E,OAAO,aAAa,SAAS,iBAAiB,MAAM,SAAS,EAAE,IAAI;AACrE;AAEA,SAAS,iCAAiC,GAAY;CACpD,IAAI,EAAE,aAAa,UAAU,EAAE,UAAU,IACvC,OAAO;CAET,MAAM,EAAC,SAAQ;CAKf,OACE,OAAO,SAAS,YAChB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EAAE,SAAS,IAAI;AAEnB;AAEA,SAAgB,sBAAsB,GAAY;CAChD,OACE,iCAAiC,CAAC,KAClC,gBACE,GACA,yBACA,gDACA,8BACA,uBACA,wCACA,qBACA,yBACA,2BACA,yBACA,+BACF;AAEJ"}
|
|
1
|
+
{"version":3,"file":"pg.js","names":[],"sources":["../../../../../zero-cache/src/types/pg.ts"],"sourcesContent":["import {Socket} from 'node:net';\nimport {\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n PG_CONNECTION_DOES_NOT_EXIST,\n PG_CONNECTION_EXCEPTION,\n PG_CONNECTION_FAILURE,\n PG_INSUFFICIENT_PRIVILEGE,\n PG_INVALID_AUTHORIZATION_SPECIFICATION,\n PG_INVALID_CATALOG_NAME,\n PG_INVALID_PASSWORD,\n PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n PG_TOO_MANY_CONNECTIONS,\n} from '@drdgvhbh/postgres-error-codes';\nimport {PreciseDate} from '@google-cloud/precise-date';\nimport {OID} from '@postgresql-typed/oids';\nimport type {LogContext} from '@rocicorp/logger';\nimport postgres, {type Notice, type PostgresType} from 'postgres';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {ConfigurationError} from './configuration-error.ts';\nimport {\n DATE,\n JSON,\n JSONB,\n NUMERIC,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n} from './pg-types.ts';\n\n// Matches: YEAR-MM-DD HH:MM:SS[.fraction][+-TZ[:MM]][ BC]\nconst pgTimestampRe =\n /^(\\d+)-(\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)([+-]\\d{1,2}(?::\\d{2})?)?(?: BC)?$/;\n\n// exported for testing.\nexport function timestampToFpMillis(timestamp: string): number {\n if (timestamp === 'infinity') return Infinity;\n if (timestamp === '-infinity') return -Infinity;\n const match = timestamp.match(pgTimestampRe);\n if (!match) {\n throw new Error(`Error parsing ${timestamp}`);\n }\n\n const [, yearStr, monthDay, time, tz] = match;\n const bc = timestamp.endsWith(' BC');\n\n let year = Number(yearStr);\n if (bc) {\n // Postgres: 1 BC = JS year 0, 2 BC = JS year -1, N BC = -(N-1)\n year = -(year - 1);\n }\n\n // Format year as ISO 8601 expanded year if needed.\n // https://tc39.es/ecma262/#sec-expanded-years\n let isoYear: string;\n if (year >= 0 && year <= 9999) {\n isoYear = String(year).padStart(4, '0');\n } else if (year >= 0) {\n isoYear = '+' + String(year).padStart(6, '0');\n } else {\n isoYear = '-' + String(Math.abs(year)).padStart(6, '0');\n }\n\n // Build a UTC ISO string so PreciseDate returns microsecond precision.\n const utcString = `${isoYear}-${monthDay}T${time}Z`;\n\n try {\n const fullTime = new PreciseDate(utcString).getFullTime();\n const millis = Number(fullTime / 1_000_000n);\n const nanos = Number(fullTime % 1_000_000n);\n const ret = millis + nanos * 1e-6; // floating point milliseconds\n\n // Add back in the timezone offset. We passed local time as UTC,\n // so a positive offset means we need to subtract, and vice versa.\n if (tz) {\n const positiveOffset = tz.startsWith('+');\n const [hours, minutes] = tz.split(':');\n const offset =\n Math.abs(Number(hours)) * 60 + (minutes ? Number(minutes) : 0);\n const offsetMillis = offset * 60 * 1_000;\n return positiveOffset ? ret - offsetMillis : ret + offsetMillis;\n }\n return ret;\n } catch (e) {\n throw new Error(`Error parsing ${timestamp}`, {cause: e});\n }\n}\n\nfunction serializeTimestamp(val: unknown): string {\n switch (typeof val) {\n case 'string':\n return val; // Let Postgres parse it\n case 'number': {\n if (Number.isInteger(val)) {\n return new PreciseDate(val).toISOString();\n }\n // Convert floating point to bigint nanoseconds.\n const nanoseconds =\n 1_000_000n * BigInt(Math.trunc(val)) +\n BigInt(Math.trunc((val % 1) * 1e6));\n return new PreciseDate(nanoseconds).toISOString();\n }\n // Note: Don't support bigint inputs until we decide what the semantics are (e.g. micros vs nanos)\n // case 'bigint':\n // return new PreciseDate(val).toISOString();\n default:\n if (val instanceof Date) {\n return val.toISOString();\n }\n }\n throw new Error(`Unsupported type \"${typeof val}\" for timestamp: ${val}`);\n}\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction serializeTime(x: unknown, type: 'time' | 'timetz'): string {\n switch (typeof x) {\n case 'string':\n return x; // Let Postgres parse it\n case 'number':\n return millisecondsToPostgresTime(x);\n }\n throw new Error(`Unsupported type \"${typeof x}\" for ${type}: ${x}`);\n}\n\nexport function millisecondsToPostgresTime(milliseconds: number): string {\n if (milliseconds < 0) {\n throw new Error('Milliseconds cannot be negative');\n }\n\n if (milliseconds >= MILLISECONDS_PER_DAY) {\n throw new Error(\n `Milliseconds cannot exceed 24 hours (${MILLISECONDS_PER_DAY}ms)`,\n );\n }\n\n milliseconds = Math.floor(milliseconds); // Ensure it's an integer\n\n const totalSeconds = Math.floor(milliseconds / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const ms = milliseconds % 1000;\n\n return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}+00`;\n}\n\n// Regular expression to match HH:MM:SS, HH:MM:SS.mmm, or HH:MM:SS+00 / HH:MM:SS.mmm+00\n// Supports optional timezone offset\nconst timeRegex =\n /^(\\d{1,2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?(?:([+-])(\\d{1,2})(?::(\\d{2}))?)?$/;\n\nexport function postgresTimeToMilliseconds(timeString: string): number {\n // Validate basic format\n if (!timeString || typeof timeString !== 'string') {\n throw new Error('Invalid time string: must be a non-empty string');\n }\n\n const match = timeString.match(timeRegex);\n\n if (!match) {\n throw new Error(\n `Invalid time format: \"${timeString}\". Expected HH:MM:SS[.mmm][+|-HH[:MM]]`,\n );\n }\n\n // Extract components\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const seconds = parseInt(match[3], 10);\n // Handle optional milliseconds, pad right with zeros if needed\n let milliseconds = 0;\n if (match[4]) {\n // Pad microseconds to 6 digits\n const msString = match[4].padEnd(6, '0');\n // slice milliseconds out of the microseconds\n // e.g. 123456 -> 123, 1234 -> 123,\n milliseconds = parseInt(msString.slice(0, 3), 10);\n }\n\n // Validate ranges\n if (hours < 0 || hours > 24) {\n throw new Error(\n `Invalid hours: ${hours}. Must be between 0 and 24 (24 means end of day)`,\n );\n }\n\n if (minutes < 0 || minutes >= 60) {\n throw new Error(`Invalid minutes: ${minutes}. Must be between 0 and 59`);\n }\n\n if (seconds < 0 || seconds >= 60) {\n throw new Error(`Invalid seconds: ${seconds}. Must be between 0 and 59`);\n }\n\n if (milliseconds < 0 || milliseconds >= 1000) {\n throw new Error(\n `Invalid milliseconds: ${milliseconds}. Must be between 0 and 999`,\n );\n }\n\n // Special case: PostgreSQL allows 24:00:00 to represent end of day\n if (hours === 24 && (minutes !== 0 || seconds !== 0 || milliseconds !== 0)) {\n throw new Error(\n 'Invalid time: when hours is 24, minutes, seconds, and milliseconds must be 0',\n );\n }\n\n // Calculate total milliseconds\n let totalMs =\n hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;\n\n // Timezone Offset\n if (match[5]) {\n const sign = match[5] === '+' ? 1 : -1;\n const tzHours = parseInt(match[6], 10);\n const tzMinutes = match[7] ? parseInt(match[7], 10) : 0;\n const offsetMs = sign * (tzHours * 3600000 + tzMinutes * 60000);\n totalMs -= offsetMs;\n }\n\n // Normalize to 0-24h only if outside valid range\n if (totalMs > MILLISECONDS_PER_DAY || totalMs < 0) {\n return (\n ((totalMs % MILLISECONDS_PER_DAY) + MILLISECONDS_PER_DAY) %\n MILLISECONDS_PER_DAY\n );\n }\n\n return totalMs;\n}\n\nfunction dateToUTCMidnight(date: string): number {\n if (date === 'infinity') return Infinity;\n if (date === '-infinity') return -Infinity;\n const d = new Date(date);\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n}\n\n/**\n * The (javascript) types of objects that can be returned by our configured\n * Postgres clients. For initial-sync, these comes from the postgres.js client:\n *\n * https://github.com/porsager/postgres/blob/master/src/types.js\n *\n * and for the replication stream these come from the the node-postgres client:\n *\n * https://github.com/brianc/node-pg-types/blob/master/lib/textParsers.js\n */\nexport type PostgresValueType = JSONValue | Uint8Array;\n\nexport type TypeOptions = {\n /**\n * Sends strings directly as JSON values (i.e. without JSON stringification).\n * The application is responsible for ensuring that string inputs for JSON\n * columns are already stringified. Other data types (e.g. objects) will\n * still be stringified by the pg client.\n */\n sendStringAsJson?: boolean;\n};\n\n/**\n * Configures types for the Postgres.js client library (`postgres`).\n *\n * @param jsonAsString Keep JSON / JSONB values as strings instead of parsing.\n */\nexport const postgresTypeConfig = ({sendStringAsJson}: TypeOptions = {}) => ({\n // Type the type IDs as `number` so that Typescript doesn't complain about\n // referencing external types during type inference.\n types: {\n bigint: postgres.BigInt,\n json: {\n to: JSON,\n from: [JSON, JSONB],\n serialize: sendStringAsJson\n ? (x: unknown) => (typeof x === 'string' ? x : BigIntJSON.stringify(x))\n : BigIntJSON.stringify,\n parse: BigIntJSON.parse,\n },\n // Timestamps are converted to PreciseDate objects.\n timestamp: {\n to: TIMESTAMP,\n from: [TIMESTAMP, TIMESTAMPTZ],\n serialize: serializeTimestamp,\n parse: timestampToFpMillis,\n },\n // Times are converted as strings\n time: {\n to: TIME,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'time'),\n parse: postgresTimeToMilliseconds,\n },\n\n timetz: {\n to: TIMETZ,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'timetz'),\n parse: postgresTimeToMilliseconds,\n },\n\n // The DATE type is stored directly as the PG normalized date string.\n date: {\n to: DATE,\n from: [DATE],\n serialize: (x: string | Date) =>\n (x instanceof Date ? x : new Date(x)).toISOString(),\n parse: dateToUTCMidnight,\n },\n // Returns a `js` number which can lose precision for large numbers.\n // JS number is 53 bits so this should generally not occur.\n // An API will be provided for users to override this type.\n numeric: {\n to: NUMERIC,\n from: [NUMERIC],\n serialize: (x: number) => String(x), // pg expects a string\n parse: (x: string | number) => Number(x),\n },\n },\n});\n\nexport type PostgresDB = postgres.Sql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport type PostgresTransaction = postgres.TransactionSql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\n// Guards against half-open TCP connections, which otherwise hang the pool\n// indefinitely: a proxy can keep the client-facing socket alive (ACKing TCP\n// keepalives) after its backend is gone, so no 'close' or 'error' event ever\n// fires and statements like BEGIN / COMMIT await forever.\n// See https://github.com/porsager/postgres/issues/1089.\n//\n// If no bytes are read or written for this long, the socket is reset, which\n// rejects all in-flight queries and lets the pool recover. Wire activity\n// resets the timer, so streaming operations (e.g. COPY) are safe, but\n// statements that legitimately compute silently for longer (e.g. index\n// builds in migrations) must raise this. Settable as an emergency measure\n// (0 disables); deliberately not exposed as a server option.\nconst SOCKET_INACTIVITY_TIMEOUT_MS = parseInt(\n process.env.ZERO_PG_SOCKET_INACTIVITY_TIMEOUT ?? '120000',\n);\n\ntype SocketFactoryOptions = {\n host: string[];\n port: number[];\n path?: string | false;\n};\n\n/**\n * Creates the custom socket factory passed to postgres.js via its (untyped)\n * `socket` option. postgres.js does not call `connect()` on custom sockets,\n * and its TLS upgrade reads `socket.host` for the SNI servername, so the\n * factory must handle both itself.\n *\n * Note: unlike postgres.js's internal socket, reconnects do not rotate\n * through multiple hosts; multi-host URIs are not used by the zero-cache.\n *\n * Exported for testing.\n */\nexport function inactivityTimeoutSocket(\n lc: LogContext,\n timeoutMs = SOCKET_INACTIVITY_TIMEOUT_MS,\n // When > 0, enables OS-level TCP keepalive probes with this initial delay.\n // Unlike the inactivity watchdog, keepalive distinguishes an idle-but-alive\n // connection from a dead one (the OS tears down the socket only when probes\n // go unanswered), so it does not false-positive on connections that are\n // legitimately idle for long stretches -- e.g. a backfill COPY stream that\n // this process deliberately back-pressures. It does NOT catch a proxy that\n // ACKs keepalives after its backend is gone (the case the watchdog guards);\n // callers that disable the watchdog (timeoutMs = 0) in favor of keepalive\n // accept that residual risk. Keepalive is a socket option, not a listener,\n // so it survives postgres.js's removeAllListeners() on TLS upgrade.\n keepAliveMs = 0,\n) {\n return (options: SocketFactoryOptions): Socket => {\n const socket = new Socket();\n if (keepAliveMs > 0) {\n socket.setKeepAlive(true, keepAliveMs);\n }\n const target = options.path\n ? options.path\n : `${options.host[0]}:${options.port[0]}`;\n if (timeoutMs > 0) {\n // socket.setTimeout() cannot be used here: it registers its callback\n // as a 'timeout' event listener, and postgres.js removes all listeners\n // from the raw socket when upgrading it to TLS (removeAllListeners()\n // in secure()), which would silently disarm the watchdog on every TLS\n // connection. An interval is not a socket listener, so it survives the\n // upgrade, and encrypted traffic still flows through this raw socket,\n // advancing its byte counters.\n //\n // The counters are sampled once per timeout period, so a half-open\n // socket is reset after one to two timeout periods of inactivity.\n let lastActivity = -1;\n const watchdog = setInterval(() => {\n if (socket.destroyed) {\n clearInterval(watchdog);\n return;\n }\n const activity = socket.bytesRead + socket.bytesWritten;\n if (activity === lastActivity) {\n lc.warn?.(\n `resetting connection to ${target} after ${timeoutMs} ms of inactivity ` +\n `(likely half-open); in-flight queries will be rejected`,\n );\n clearInterval(watchdog);\n socket.resetAndDestroy();\n } else {\n lastActivity = activity;\n }\n }, timeoutMs);\n watchdog.unref();\n // Best-effort cleanup; removed on TLS upgrade, in which case the\n // destroyed check above clears the (unref'ed) interval instead.\n socket.once('close', () => clearInterval(watchdog));\n }\n if (options.path) {\n socket.connect(options.path);\n } else {\n const [host] = options.host;\n const [port] = options.port;\n socket.connect(port, host);\n // Read by postgres.js for the TLS SNI servername.\n Object.assign(socket, {host, port});\n }\n return socket;\n };\n}\n\nexport function pgClient(\n lc: LogContext,\n connectionURI: string,\n // A descriptive name for the code that is making the connection, so that we\n // can debug things like deadlocks.\n applicationName: string,\n options?: postgres.Options<{\n bigint: PostgresType<bigint>;\n json: PostgresType<JSONValue>;\n }> & {\n /**\n * How to detect a dead (\"half-open\") connection:\n * - `'inactivity-timeout'` (default): reset the socket after\n * {@link SOCKET_INACTIVITY_TIMEOUT_MS} of no read/write activity. Catches\n * proxy-level half-opens, but false-positives on connections that are\n * legitimately idle for long stretches.\n * - `'keepalive'`: disable the inactivity watchdog and rely on OS TCP\n * keepalive probes instead. Use for connections that are expected to sit\n * idle under back-pressure (e.g. the backfill COPY stream), where the\n * watchdog would reset a perfectly healthy connection.\n */\n liveness?: 'inactivity-timeout' | 'keepalive';\n },\n opts?: TypeOptions,\n): PostgresDB {\n applicationName = `zero-${applicationName}`;\n\n // Postgres carries row values in the `detail`, `hint`, `where` and\n // `internal_query` fields -- e.g. a unique violation reports\n // `Key (email)=(someone@example.com) already exists` in `detail`. Forward\n // only the severity, SQLSTATE and schema location; never the Notice itself.\n const safeNotice = (n: Notice) => ({\n severity: n.severity,\n code: n.code,\n message: n.message,\n schema: n.schema_name,\n table: n.table_name,\n column: n.column_name,\n constraint: n.constraint_name,\n routine: n.routine,\n });\n\n const onnotice = (n: Notice) => {\n // https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html#PLPGSQL-STATEMENTS-RAISE\n switch (n.severity) {\n case 'NOTICE':\n return; // silenced\n case 'DEBUG':\n lc.debug?.('pg notice', safeNotice(n));\n return;\n case 'WARNING':\n lc.warn?.('pg notice', safeNotice(n));\n return;\n case 'EXCEPTION':\n lc.error?.('pg notice', safeNotice(n));\n return;\n case 'LOG':\n case 'INFO':\n default:\n lc.info?.('pg notice', safeNotice(n));\n }\n };\n const url = new URL(connectionURI);\n const sslFlag =\n url.searchParams.get('ssl') ?? url.searchParams.get('sslmode') ?? 'prefer';\n\n let ssl: boolean | 'prefer' | {rejectUnauthorized: boolean};\n if (sslFlag === 'disable' || sslFlag === 'false') {\n ssl = false;\n } else if (sslFlag === 'no-verify') {\n ssl = {rejectUnauthorized: false};\n } else {\n ssl = sslFlag as 'prefer';\n }\n\n // Set connections to expire between 5 and 10 minutes to free up state on PG.\n const maxLifetimeSeconds = randInt(5 * 60, 10 * 60);\n const {liveness = 'inactivity-timeout', ...pgOptions} = options ?? {};\n const providedConnection = pgOptions.connection ?? {};\n\n // postgres.js supports a custom socket factory via the `socket` option,\n // but it is missing from its type declarations, hence the untyped spread.\n const socketLc = lc.withContext('appName', applicationName);\n const socketFactory = {\n socket:\n liveness === 'keepalive'\n ? // Disable the inactivity watchdog and use TCP keepalive instead.\n inactivityTimeoutSocket(socketLc, 0, 60_000)\n : inactivityTimeoutSocket(socketLc),\n };\n\n return postgres(connectionURI, {\n ...postgresTypeConfig(opts),\n onnotice,\n ['max_lifetime']: maxLifetimeSeconds,\n // Close idle connections cleanly before the socket inactivity timer\n // (which cannot distinguish an idle pool connection from a hung query)\n // resets them.\n ['idle_timeout']: 60,\n ssl,\n ...socketFactory,\n ...pgOptions,\n connection: {\n ...providedConnection,\n ['application_name']: applicationName,\n },\n });\n}\n\nexport async function connectPgClient(\n ...args: Parameters<typeof pgClient>\n): Promise<PostgresDB> {\n const db = pgClient(...args);\n try {\n await db`SELECT 1`.simple().execute();\n return db;\n } catch (e) {\n if (isPostgresConfigError(e)) {\n throw new ConfigurationError(\n 'Unable to connect to Postgres. Check your database configuration.',\n {cause: e},\n );\n }\n throw e;\n }\n}\n\n/**\n * Disables any statement_timeout for the current transaction. By default,\n * Postgres does not impose a statement timeout, but some users and providers\n * set one at the database level (even though it is explicitly discouraged by\n * the Postgres documentation).\n *\n * Zero logic in particular often does not fit into the category of general\n * application logic; for potentially long-running operations like migrations\n * and background cleanup, the statement timeout should be disabled to prevent\n * these operations from timing out.\n */\nexport function disableStatementTimeout(sql: PostgresTransaction) {\n void sql`SET LOCAL statement_timeout = 0;`.execute().catch(() => {});\n}\n\nexport const typeNameByOID: Record<number, string> = Object.freeze(\n Object.fromEntries(\n Object.entries(OID).map(([name, oid]) => [\n oid,\n name.startsWith('_') ? `${name.substring(1)}[]` : name,\n ]),\n ),\n);\n\nexport function isPostgresError(e: unknown, ...codes: [string, ...string[]]) {\n return e instanceof postgres.PostgresError && codes.includes(e.code);\n}\n\nfunction isPostgresStartupConnectionError(e: unknown) {\n if (!(e instanceof Error) || !('code' in e)) {\n return false;\n }\n const {code} = e;\n // postgres.js throws plain Error instances for connection failures that\n // happen before Postgres can return a SQLSTATE. These are still startup\n // configuration problems for Zero: wrong host/port, unreachable DB, DNS,\n // TLS certificate mismatch, etc.\n return (\n typeof code === 'string' &&\n [\n 'CONNECT_TIMEOUT',\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'EHOSTUNREACH',\n 'ENETUNREACH',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'ETIMEDOUT',\n 'ERR_TLS_CERT_ALTNAME_INVALID',\n 'SELF_SIGNED_CERT_IN_CHAIN',\n 'DEPTH_ZERO_SELF_SIGNED_CERT',\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n ].includes(code)\n );\n}\n\nexport function isPostgresConfigError(e: unknown) {\n return (\n isPostgresStartupConnectionError(e) ||\n isPostgresError(\n e,\n PG_CONNECTION_EXCEPTION,\n PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n PG_CONNECTION_DOES_NOT_EXIST,\n PG_CONNECTION_FAILURE,\n PG_INVALID_AUTHORIZATION_SPECIFICATION,\n PG_INVALID_PASSWORD,\n PG_INVALID_CATALOG_NAME,\n PG_INSUFFICIENT_PRIVILEGE,\n PG_TOO_MANY_CONNECTIONS,\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n )\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type PokeChunk } from '../../../zero-protocol/src/poke.ts';
|
|
2
|
+
export declare const POKE_CHUNK_BYTES: number;
|
|
3
|
+
export type EmitPokeChunk = (chunk: PokeChunk) => Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Streams a JSON array into fixed-size UTF-8 chunks.
|
|
6
|
+
*
|
|
7
|
+
* The emitter must finish consuming a chunk before its promise resolves. This
|
|
8
|
+
* lets the encoder reuse one fixed-size buffer for the entire poke. Memory is
|
|
9
|
+
* therefore O(chunk size), independent of poke size.
|
|
10
|
+
*/
|
|
11
|
+
export declare class PokeChunkEncoder {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(chunkBytes?: number);
|
|
14
|
+
addPatch(serializedPatch: string, emit: EmitPokeChunk): Promise<void>;
|
|
15
|
+
finish(emit: EmitPokeChunk): Promise<void>;
|
|
16
|
+
cancel(): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=poke-chunk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poke-chunk.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/poke-chunk.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,SAAS,EACf,MAAM,oCAAoC,CAAC;AAE5C,eAAO,MAAM,gBAAgB,QAAc,CAAC;AAE5C,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhE;;;;;;GAMG;AACH,qBAAa,gBAAgB;;gBAOf,UAAU,SAAmB;IAQnC,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrE,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhD,MAAM,IAAI,IAAI;CA2Bf"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
+
import "../../../zero-protocol/src/poke.js";
|
|
3
|
+
//#region ../zero-cache/src/types/poke-chunk.ts
|
|
4
|
+
var POKE_CHUNK_BYTES = 1024 * 1024;
|
|
5
|
+
/**
|
|
6
|
+
* Streams a JSON array into fixed-size UTF-8 chunks.
|
|
7
|
+
*
|
|
8
|
+
* The emitter must finish consuming a chunk before its promise resolves. This
|
|
9
|
+
* lets the encoder reuse one fixed-size buffer for the entire poke. Memory is
|
|
10
|
+
* therefore O(chunk size), independent of poke size.
|
|
11
|
+
*/
|
|
12
|
+
var PokeChunkEncoder = class {
|
|
13
|
+
#encoder = new TextEncoder();
|
|
14
|
+
#buffer;
|
|
15
|
+
#bufferedBytes = 1;
|
|
16
|
+
#patchCount = 0;
|
|
17
|
+
#finished = false;
|
|
18
|
+
constructor(chunkBytes = POKE_CHUNK_BYTES) {
|
|
19
|
+
assert(chunkBytes >= 5, "poke chunk size must be at least five bytes");
|
|
20
|
+
this.#buffer = new Uint8Array(chunkBytes);
|
|
21
|
+
this.#buffer[0] = 0;
|
|
22
|
+
}
|
|
23
|
+
async addPatch(serializedPatch, emit) {
|
|
24
|
+
assert(!this.#finished, "cannot add a patch to a finished poke");
|
|
25
|
+
await this.#write(this.#patchCount++ === 0 ? "[" : ",", emit);
|
|
26
|
+
await this.#write(serializedPatch, emit);
|
|
27
|
+
}
|
|
28
|
+
async finish(emit) {
|
|
29
|
+
assert(!this.#finished, "cannot finish a poke twice");
|
|
30
|
+
this.#finished = true;
|
|
31
|
+
await this.#write(this.#patchCount === 0 ? "[]" : "]", emit);
|
|
32
|
+
await this.#flush(emit);
|
|
33
|
+
}
|
|
34
|
+
cancel() {
|
|
35
|
+
this.#finished = true;
|
|
36
|
+
this.#bufferedBytes = 1;
|
|
37
|
+
}
|
|
38
|
+
async #write(value, emit) {
|
|
39
|
+
let read = 0;
|
|
40
|
+
while (read < value.length) {
|
|
41
|
+
const target = this.#buffer.subarray(this.#bufferedBytes);
|
|
42
|
+
const result = this.#encoder.encodeInto(value.slice(read), target);
|
|
43
|
+
read += result.read;
|
|
44
|
+
this.#bufferedBytes += result.written;
|
|
45
|
+
if (this.#bufferedBytes === this.#buffer.length || result.read === 0) await this.#flush(emit);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async #flush(emit) {
|
|
49
|
+
if (this.#bufferedBytes === 1) return;
|
|
50
|
+
await emit(this.#buffer.subarray(0, this.#bufferedBytes));
|
|
51
|
+
this.#bufferedBytes = 1;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { PokeChunkEncoder };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=poke-chunk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poke-chunk.js","names":["#encoder","#buffer","#finished","#write","#patchCount","#flush","#bufferedBytes"],"sources":["../../../../../zero-cache/src/types/poke-chunk.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {\n POKE_CHUNK_MESSAGE_TYPE,\n type PokeChunk,\n} from '../../../zero-protocol/src/poke.ts';\n\nexport const POKE_CHUNK_BYTES = 1024 * 1024;\n\nexport type EmitPokeChunk = (chunk: PokeChunk) => Promise<void>;\n\n/**\n * Streams a JSON array into fixed-size UTF-8 chunks.\n *\n * The emitter must finish consuming a chunk before its promise resolves. This\n * lets the encoder reuse one fixed-size buffer for the entire poke. Memory is\n * therefore O(chunk size), independent of poke size.\n */\nexport class PokeChunkEncoder {\n readonly #encoder = new TextEncoder();\n readonly #buffer: Uint8Array;\n #bufferedBytes = 1;\n #patchCount = 0;\n #finished = false;\n\n constructor(chunkBytes = POKE_CHUNK_BYTES) {\n // One byte tags the binary message and four bytes are required to encode\n // the largest UTF-8 code point. A smaller buffer could prevent progress.\n assert(chunkBytes >= 5, 'poke chunk size must be at least five bytes');\n this.#buffer = new Uint8Array(chunkBytes);\n this.#buffer[0] = POKE_CHUNK_MESSAGE_TYPE;\n }\n\n async addPatch(serializedPatch: string, emit: EmitPokeChunk): Promise<void> {\n assert(!this.#finished, 'cannot add a patch to a finished poke');\n await this.#write(this.#patchCount++ === 0 ? '[' : ',', emit);\n await this.#write(serializedPatch, emit);\n }\n\n async finish(emit: EmitPokeChunk): Promise<void> {\n assert(!this.#finished, 'cannot finish a poke twice');\n this.#finished = true;\n await this.#write(this.#patchCount === 0 ? '[]' : ']', emit);\n await this.#flush(emit);\n }\n\n cancel(): void {\n this.#finished = true;\n this.#bufferedBytes = 1;\n }\n\n async #write(value: string, emit: EmitPokeChunk): Promise<void> {\n let read = 0;\n while (read < value.length) {\n const target = this.#buffer.subarray(this.#bufferedBytes);\n const result = this.#encoder.encodeInto(value.slice(read), target);\n read += result.read;\n this.#bufferedBytes += result.written;\n\n // A multi-byte code point may not fit in the remaining space.\n if (this.#bufferedBytes === this.#buffer.length || result.read === 0) {\n await this.#flush(emit);\n }\n }\n }\n\n async #flush(emit: EmitPokeChunk): Promise<void> {\n if (this.#bufferedBytes === 1) {\n return;\n }\n await emit(this.#buffer.subarray(0, this.#bufferedBytes));\n this.#bufferedBytes = 1;\n }\n}\n"],"mappings":";;;AAMA,IAAa,mBAAmB,OAAO;;;;;;;;AAWvC,IAAa,mBAAb,MAA8B;CAC5B,WAAoB,IAAI,YAAY;CACpC;CACA,iBAAiB;CACjB,cAAc;CACd,YAAY;CAEZ,YAAY,aAAa,kBAAkB;EAGzC,OAAO,cAAc,GAAG,6CAA6C;EACrE,KAAKC,UAAU,IAAI,WAAW,UAAU;EACxC,KAAKA,QAAQ,KAAA;CACf;CAEA,MAAM,SAAS,iBAAyB,MAAoC;EAC1E,OAAO,CAAC,KAAKC,WAAW,uCAAuC;EAC/D,MAAM,KAAKC,OAAO,KAAKC,kBAAkB,IAAI,MAAM,KAAK,IAAI;EAC5D,MAAM,KAAKD,OAAO,iBAAiB,IAAI;CACzC;CAEA,MAAM,OAAO,MAAoC;EAC/C,OAAO,CAAC,KAAKD,WAAW,4BAA4B;EACpD,KAAKA,YAAY;EACjB,MAAM,KAAKC,OAAO,KAAKC,gBAAgB,IAAI,OAAO,KAAK,IAAI;EAC3D,MAAM,KAAKC,OAAO,IAAI;CACxB;CAEA,SAAe;EACb,KAAKH,YAAY;EACjB,KAAKI,iBAAiB;CACxB;CAEA,MAAMH,OAAO,OAAe,MAAoC;EAC9D,IAAI,OAAO;EACX,OAAO,OAAO,MAAM,QAAQ;GAC1B,MAAM,SAAS,KAAKF,QAAQ,SAAS,KAAKK,cAAc;GACxD,MAAM,SAAS,KAAKN,SAAS,WAAW,MAAM,MAAM,IAAI,GAAG,MAAM;GACjE,QAAQ,OAAO;GACf,KAAKM,kBAAkB,OAAO;GAG9B,IAAI,KAAKA,mBAAmB,KAAKL,QAAQ,UAAU,OAAO,SAAS,GACjE,MAAM,KAAKI,OAAO,IAAI;EAE1B;CACF;CAEA,MAAMA,OAAO,MAAoC;EAC/C,IAAI,KAAKC,mBAAmB,GAC1B;EAEF,MAAM,KAAK,KAAKL,QAAQ,SAAS,GAAG,KAAKK,cAAc,CAAC;EACxD,KAAKA,iBAAiB;CACxB;AACF"}
|
|
@@ -74,9 +74,6 @@ function childWorker(moduleUrl, env, ...args) {
|
|
|
74
74
|
}
|
|
75
75
|
return forkChildWorkerWithArgs(moduleUrl, env, args);
|
|
76
76
|
}
|
|
77
|
-
function forkChildWorker(moduleUrl, env, ...args) {
|
|
78
|
-
return forkChildWorkerWithArgs(moduleUrl, env, workerArgs(args));
|
|
79
|
-
}
|
|
80
77
|
function workerArgs(args) {
|
|
81
78
|
return [...args, ...process.argv.slice(2)];
|
|
82
79
|
}
|
|
@@ -116,6 +113,6 @@ function inProcChannel() {
|
|
|
116
113
|
}))];
|
|
117
114
|
}
|
|
118
115
|
//#endregion
|
|
119
|
-
export { childWorker,
|
|
116
|
+
export { childWorker, parentWorker, singleProcessMode };
|
|
120
117
|
|
|
121
118
|
//# sourceMappingURL=processes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processes.js","names":[],"sources":["../../../../../zero-cache/src/types/processes.ts"],"sourcesContent":["import {\n type ChildProcess,\n fork,\n type SendHandle,\n type Serializable,\n} from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport {platform} from 'node:os';\nimport {pid} from 'node:process';\n\n/**\n * Central registry of message type names, which are used to identify\n * the payload in {@link Message} objects sent between processes. The\n * payloads themselves are implementation specific and defined in each\n * component; only the type name is reserved here to avoid collisions.\n *\n * Receiving logic can call {@link getMessage()} with the name of\n * the message of interest to filter messages to those of interest.\n */\nexport const MESSAGE_TYPES = {\n handoff: 'handoff',\n status: 'status',\n subscribe: 'subscribe',\n notify: 'notify',\n ready: 'ready',\n backupWatermarkUpdate: 'backupWatermakUpdate',\n} as const;\n\nexport type Message<Payload> = [keyof typeof MESSAGE_TYPES, Payload];\n\nfunction getMessage<M extends Message<unknown>>(\n type: M[0],\n data: unknown,\n): M[1] | null {\n if (Array.isArray(data) && data.length === 2 && data[0] === type) {\n return data[1] as M[1];\n }\n return null;\n}\n\nfunction onMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n return e.on('message', (data, sendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n handler(msg, sendHandle);\n }\n });\n}\n\nfunction onceMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n const listener = (data: unknown, sendHandle: SendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n e.off('message', listener);\n handler(msg, sendHandle);\n }\n };\n return e.on('message', listener);\n}\n\nexport interface Sender {\n send<M extends Message<unknown>>(\n message: M,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ): boolean;\n\n kill(signal?: NodeJS.Signals): void;\n}\n\nexport interface Subprocess extends Sender, EventEmitter {\n pid?: number | undefined;\n}\n\nexport interface Receiver extends EventEmitter {\n /**\n * The receiving side of {@link Sender.send()} that is a wrapper around\n * {@link on}('message', ...) that invokes the `handler` for messages of\n * the specified `type`.\n */\n onMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n\n /**\n * The receiving side of {@link Sender.send()} that behaves like\n * {@link once}('message', ...) that invokes the `handler` for the next\n * message of the specified `type` and then unsubscribes.\n */\n onceMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n}\n\nexport interface Worker extends Subprocess, Receiver {}\n\n/**\n * Adds the {@link Receiver.onMessageType()} and {@link Receiver.onceMessageType()}\n * methods to convert the given `EventEmitter` to a `Receiver`.\n */\nfunction wrap<P extends EventEmitter>(proc: P): P & Receiver {\n return new Proxy(proc, {\n get(target: P, prop: string | symbol, receiver: unknown) {\n switch (prop) {\n case 'onMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onMessageType(target, type, handler);\n return receiver; // this\n };\n case 'onceMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onceMessageType(target, type, handler);\n return receiver; // this\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as P & Receiver;\n}\n\ntype Proc = Pick<ChildProcess, 'send' | 'kill' | 'pid'> & EventEmitter;\n\n/**\n * The parentWorker for forked processes, or `null` if the process was not forked.\n * (Analogous to the `parentPort: MessagePort | null` of the `\"workers\"` library).\n */\nexport const parentWorker: Worker | null = process.send\n ? wrap(process as Proc)\n : null;\n\nconst SINGLE_PROCESS = 'SINGLE_PROCESS';\nlet singleProcessOverride = false;\nexport function singleProcessMode(): boolean {\n return singleProcessOverride || (process.env[SINGLE_PROCESS] ?? '0') !== '0';\n}\n\nexport function setSingleProcessMode(enabled: boolean = true): void {\n singleProcessOverride = enabled;\n}\n\n/**\n * Determines whether a module loaded as a worker process entry point should\n * start its worker.\n *\n * Most workers are launched via {@link childWorker}, which in single-process\n * mode runs them in-process (no real fork) by invoking their exported\n * `runWorker` directly; those entry points must therefore *not* self-start in\n * single-process mode, or they would run twice.\n *\n * A worker launched via {@link forkChildWorker}, however, is *always* a real\n * forked OS process — even in single-process mode — and it inherits the\n * parent's `SINGLE_PROCESS` env. So {@link singleProcessMode} alone would\n * wrongly suppress its startup, leaving the parent to spin re-forking a child\n * that immediately exits. Such a forked child has a non-null\n * {@link parentWorker} (`process.send` is defined), which is the signal to\n * start regardless. A standalone direct launch (no parent, not single-process)\n * also starts.\n *\n * @param parent The entry point's {@link parentWorker} (non-null iff forked).\n * @param isSingleProcessMode The result of {@link singleProcessMode}.\n */\nexport function shouldStartWorker(\n parent: Worker | null,\n isSingleProcessMode: boolean,\n): boolean {\n return parent !== null || !isSingleProcessMode;\n}\n\n/**\n *\n * @param modulePath Path to the module file, relative to zero-cache/src/, or an absolute file:// URL\n */\nexport function childWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n args = workerArgs(args);\n\n if (singleProcessMode()) {\n const [parent, child] = inProcChannel();\n import(moduleUrl.href)\n .then(async ({default: runWorker}) => {\n try {\n await runWorker(parent, env ?? process.env, ...args);\n child.emit('close', 0);\n return;\n } catch (err) {\n child.emit('error', err);\n child.emit('close', -1);\n }\n })\n .catch(err => child.emit('error', err));\n return child;\n }\n return forkChildWorkerWithArgs(moduleUrl, env, args);\n}\n\nexport function forkChildWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n return forkChildWorkerWithArgs(moduleUrl, env, workerArgs(args));\n}\n\nfunction workerArgs(args: string[]): string[] {\n return [...args, ...process.argv.slice(2)];\n}\n\nfunction forkChildWorkerWithArgs(\n moduleUrl: URL,\n env: NodeJS.ProcessEnv | undefined,\n args: string[],\n): Worker {\n const child = fork(moduleUrl, args, {\n // For production / non-windows, set `detached` to `true` so that SIGINT is\n // not automatically propagated and graceful shutdown happens as intended.\n // For Win32, detached: true causes all subprocesses to open in separate\n // terminals and breaks inter-process kill signals, so set it to false.\n detached: platform() !== 'win32',\n serialization: 'advanced', // use structured clone for IPC\n env,\n });\n return wrap(child);\n}\n\n/**\n * Creates two connected `Worker` instances such that messages sent to one\n * via the {@link Worker.send()} method are received by the other's\n * `on('message', ...)` handler.\n *\n * This is analogous to the two `MessagePort`s of a `MessageChannel`, and\n * is useful for executing code written for inter-process communication\n * in a single process.\n */\nexport function inProcChannel(): [Worker, Worker] {\n const worker1 = new EventEmitter();\n const worker2 = new EventEmitter();\n\n const sendTo =\n (dest: EventEmitter) =>\n (\n message: Serializable,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ) => {\n dest.emit('message', message, sendHandle);\n if (callback) {\n callback(null);\n }\n return true;\n };\n\n const kill =\n (dest: EventEmitter) =>\n (signal: NodeJS.Signals = 'SIGTERM') =>\n dest.emit(signal, signal);\n\n return [\n wrap(\n Object.assign(worker1, {send: sendTo(worker2), kill: kill(worker2), pid}),\n ),\n wrap(\n Object.assign(worker2, {send: sendTo(worker1), kill: kill(worker1), pid}),\n ),\n ];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"processes.js","names":[],"sources":["../../../../../zero-cache/src/types/processes.ts"],"sourcesContent":["import {\n type ChildProcess,\n fork,\n type SendHandle,\n type Serializable,\n} from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport {platform} from 'node:os';\nimport {pid} from 'node:process';\n\n/**\n * Central registry of message type names, which are used to identify\n * the payload in {@link Message} objects sent between processes. The\n * payloads themselves are implementation specific and defined in each\n * component; only the type name is reserved here to avoid collisions.\n *\n * Receiving logic can call {@link getMessage()} with the name of\n * the message of interest to filter messages to those of interest.\n */\nexport const MESSAGE_TYPES = {\n handoff: 'handoff',\n status: 'status',\n subscribe: 'subscribe',\n notify: 'notify',\n ready: 'ready',\n backupWatermarkUpdate: 'backupWatermakUpdate',\n} as const;\n\nexport type Message<Payload> = [keyof typeof MESSAGE_TYPES, Payload];\n\nfunction getMessage<M extends Message<unknown>>(\n type: M[0],\n data: unknown,\n): M[1] | null {\n if (Array.isArray(data) && data.length === 2 && data[0] === type) {\n return data[1] as M[1];\n }\n return null;\n}\n\nfunction onMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n return e.on('message', (data, sendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n handler(msg, sendHandle);\n }\n });\n}\n\nfunction onceMessageType<M extends Message<unknown>>(\n e: EventEmitter,\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n) {\n const listener = (data: unknown, sendHandle: SendHandle) => {\n const msg = getMessage(type, data);\n if (msg) {\n e.off('message', listener);\n handler(msg, sendHandle);\n }\n };\n return e.on('message', listener);\n}\n\nexport interface Sender {\n send<M extends Message<unknown>>(\n message: M,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ): boolean;\n\n kill(signal?: NodeJS.Signals): void;\n}\n\nexport interface Subprocess extends Sender, EventEmitter {\n pid?: number | undefined;\n}\n\nexport interface Receiver extends EventEmitter {\n /**\n * The receiving side of {@link Sender.send()} that is a wrapper around\n * {@link on}('message', ...) that invokes the `handler` for messages of\n * the specified `type`.\n */\n onMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n\n /**\n * The receiving side of {@link Sender.send()} that behaves like\n * {@link once}('message', ...) that invokes the `handler` for the next\n * message of the specified `type` and then unsubscribes.\n */\n onceMessageType<M extends Message<unknown>>(\n type: M[0],\n handler: (msg: M[1], sendHandle?: SendHandle) => void,\n ): this;\n}\n\nexport interface Worker extends Subprocess, Receiver {}\n\n/**\n * Adds the {@link Receiver.onMessageType()} and {@link Receiver.onceMessageType()}\n * methods to convert the given `EventEmitter` to a `Receiver`.\n */\nfunction wrap<P extends EventEmitter>(proc: P): P & Receiver {\n return new Proxy(proc, {\n get(target: P, prop: string | symbol, receiver: unknown) {\n switch (prop) {\n case 'onMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onMessageType(target, type, handler);\n return receiver; // this\n };\n case 'onceMessageType':\n return (\n type: keyof typeof MESSAGE_TYPES,\n handler: (msg: unknown, sendHandle?: SendHandle) => void,\n ) => {\n onceMessageType(target, type, handler);\n return receiver; // this\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n }) as P & Receiver;\n}\n\ntype Proc = Pick<ChildProcess, 'send' | 'kill' | 'pid'> & EventEmitter;\n\n/**\n * The parentWorker for forked processes, or `null` if the process was not forked.\n * (Analogous to the `parentPort: MessagePort | null` of the `\"workers\"` library).\n */\nexport const parentWorker: Worker | null = process.send\n ? wrap(process as Proc)\n : null;\n\nconst SINGLE_PROCESS = 'SINGLE_PROCESS';\nlet singleProcessOverride = false;\nexport function singleProcessMode(): boolean {\n return singleProcessOverride || (process.env[SINGLE_PROCESS] ?? '0') !== '0';\n}\n\nexport function setSingleProcessMode(enabled: boolean = true): void {\n singleProcessOverride = enabled;\n}\n\n/**\n * Determines whether a module loaded as a worker process entry point should\n * start its worker.\n *\n * Most workers are launched via {@link childWorker}, which in single-process\n * mode runs them in-process (no real fork) by invoking their exported\n * `runWorker` directly; those entry points must therefore *not* self-start in\n * single-process mode, or they would run twice.\n *\n * A worker launched via {@link forkChildWorker}, however, is *always* a real\n * forked OS process — even in single-process mode — and it inherits the\n * parent's `SINGLE_PROCESS` env. So {@link singleProcessMode} alone would\n * wrongly suppress its startup, leaving the parent to spin re-forking a child\n * that immediately exits. Such a forked child has a non-null\n * {@link parentWorker} (`process.send` is defined), which is the signal to\n * start regardless. A standalone direct launch (no parent, not single-process)\n * also starts.\n *\n * @param parent The entry point's {@link parentWorker} (non-null iff forked).\n * @param isSingleProcessMode The result of {@link singleProcessMode}.\n */\nexport function shouldStartWorker(\n parent: Worker | null,\n isSingleProcessMode: boolean,\n): boolean {\n return parent !== null || !isSingleProcessMode;\n}\n\n/**\n *\n * @param modulePath Path to the module file, relative to zero-cache/src/, or an absolute file:// URL\n */\nexport function childWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n args = workerArgs(args);\n\n if (singleProcessMode()) {\n const [parent, child] = inProcChannel();\n import(moduleUrl.href)\n .then(async ({default: runWorker}) => {\n try {\n await runWorker(parent, env ?? process.env, ...args);\n child.emit('close', 0);\n return;\n } catch (err) {\n child.emit('error', err);\n child.emit('close', -1);\n }\n })\n .catch(err => child.emit('error', err));\n return child;\n }\n return forkChildWorkerWithArgs(moduleUrl, env, args);\n}\n\nexport function forkChildWorker(\n moduleUrl: URL,\n env?: NodeJS.ProcessEnv,\n ...args: string[]\n): Worker {\n return forkChildWorkerWithArgs(moduleUrl, env, workerArgs(args));\n}\n\nfunction workerArgs(args: string[]): string[] {\n return [...args, ...process.argv.slice(2)];\n}\n\nfunction forkChildWorkerWithArgs(\n moduleUrl: URL,\n env: NodeJS.ProcessEnv | undefined,\n args: string[],\n): Worker {\n const child = fork(moduleUrl, args, {\n // For production / non-windows, set `detached` to `true` so that SIGINT is\n // not automatically propagated and graceful shutdown happens as intended.\n // For Win32, detached: true causes all subprocesses to open in separate\n // terminals and breaks inter-process kill signals, so set it to false.\n detached: platform() !== 'win32',\n serialization: 'advanced', // use structured clone for IPC\n env,\n });\n return wrap(child);\n}\n\n/**\n * Creates two connected `Worker` instances such that messages sent to one\n * via the {@link Worker.send()} method are received by the other's\n * `on('message', ...)` handler.\n *\n * This is analogous to the two `MessagePort`s of a `MessageChannel`, and\n * is useful for executing code written for inter-process communication\n * in a single process.\n */\nexport function inProcChannel(): [Worker, Worker] {\n const worker1 = new EventEmitter();\n const worker2 = new EventEmitter();\n\n const sendTo =\n (dest: EventEmitter) =>\n (\n message: Serializable,\n sendHandle?: SendHandle,\n callback?: (error: Error | null) => void,\n ) => {\n dest.emit('message', message, sendHandle);\n if (callback) {\n callback(null);\n }\n return true;\n };\n\n const kill =\n (dest: EventEmitter) =>\n (signal: NodeJS.Signals = 'SIGTERM') =>\n dest.emit(signal, signal);\n\n return [\n wrap(\n Object.assign(worker1, {send: sendTo(worker2), kill: kill(worker2), pid}),\n ),\n wrap(\n Object.assign(worker2, {send: sendTo(worker1), kill: kill(worker1), pid}),\n ),\n ];\n}\n"],"mappings":";;;;;AA8BA,SAAS,WACP,MACA,MACa;CACb,IAAI,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,KAAK,KAAK,OAAO,MAC1D,OAAO,KAAK;CAEd,OAAO;AACT;AAEA,SAAS,cACP,GACA,MACA,SACA;CACA,OAAO,EAAE,GAAG,YAAY,MAAM,eAAe;EAC3C,MAAM,MAAM,WAAW,MAAM,IAAI;EACjC,IAAI,KACF,QAAQ,KAAK,UAAU;CAE3B,CAAC;AACH;AAEA,SAAS,gBACP,GACA,MACA,SACA;CACA,MAAM,YAAY,MAAe,eAA2B;EAC1D,MAAM,MAAM,WAAW,MAAM,IAAI;EACjC,IAAI,KAAK;GACP,EAAE,IAAI,WAAW,QAAQ;GACzB,QAAQ,KAAK,UAAU;EACzB;CACF;CACA,OAAO,EAAE,GAAG,WAAW,QAAQ;AACjC;;;;;AA4CA,SAAS,KAA6B,MAAuB;CAC3D,OAAO,IAAI,MAAM,MAAM,EACrB,IAAI,QAAW,MAAuB,UAAmB;EACvD,QAAQ,MAAR;GACE,KAAK,iBACH,QACE,MACA,YACG;IACH,cAAc,QAAQ,MAAM,OAAO;IACnC,OAAO;GACT;GACF,KAAK,mBACH,QACE,MACA,YACG;IACH,gBAAgB,QAAQ,MAAM,OAAO;IACrC,OAAO;GACT;EACJ;EACA,OAAO,QAAQ,IAAI,QAAQ,MAAM,QAAQ;CAC3C,EACF,CAAC;AACH;;;;;AAQA,IAAa,eAA8B,QAAQ,OAC/C,KAAK,OAAe,IACpB;AAEJ,IAAM,iBAAiB;AACvB,IAAI,wBAAwB;AAC5B,SAAgB,oBAA6B;CAC3C,OAAO,0BAA0B,QAAQ,IAAI,mBAAmB,SAAS;AAC3E;;;;;AAsCA,SAAgB,YACd,WACA,KACA,GAAG,MACK;CACR,OAAO,WAAW,IAAI;CAEtB,IAAI,kBAAkB,GAAG;EACvB,MAAM,CAAC,QAAQ,SAAS,cAAc;EACtC,OAAO,UAAU,MACd,KAAK,OAAO,EAAC,SAAS,gBAAe;GACpC,IAAI;IACF,MAAM,UAAU,QAAQ,OAAO,QAAQ,KAAK,GAAG,IAAI;IACnD,MAAM,KAAK,SAAS,CAAC;IACrB;GACF,SAAS,KAAK;IACZ,MAAM,KAAK,SAAS,GAAG;IACvB,MAAM,KAAK,SAAS,EAAE;GACxB;EACF,CAAC,EACA,OAAM,QAAO,MAAM,KAAK,SAAS,GAAG,CAAC;EACxC,OAAO;CACT;CACA,OAAO,wBAAwB,WAAW,KAAK,IAAI;AACrD;AAUA,SAAS,WAAW,MAA0B;CAC5C,OAAO,CAAC,GAAG,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC3C;AAEA,SAAS,wBACP,WACA,KACA,MACQ;CAUR,OAAO,KATO,KAAK,WAAW,MAAM;EAKlC,UAAU,SAAS,MAAM;EACzB,eAAe;EACf;CACF,CACY,CAAK;AACnB;;;;;;;;;;AAWA,SAAgB,gBAAkC;CAChD,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,UAAU,IAAI,aAAa;CAEjC,MAAM,UACH,UAEC,SACA,YACA,aACG;EACH,KAAK,KAAK,WAAW,SAAS,UAAU;EACxC,IAAI,UACF,SAAS,IAAI;EAEf,OAAO;CACT;CAEF,MAAM,QACH,UACA,SAAyB,cACxB,KAAK,KAAK,QAAQ,MAAM;CAE5B,OAAO,CACL,KACE,OAAO,OAAO,SAAS;EAAC,MAAM,OAAO,OAAO;EAAG,MAAM,KAAK,OAAO;EAAG;CAAG,CAAC,CAC1E,GACA,KACE,OAAO,OAAO,SAAS;EAAC,MAAM,OAAO,OAAO;EAAG,MAAM,KAAK,OAAO;EAAG;CAAG,CAAC,CAC1E,CACF;AACF"}
|
|
@@ -14,16 +14,11 @@ export type Source<T> = AsyncIterable<T> & {
|
|
|
14
14
|
*/
|
|
15
15
|
cancel: (err?: Error) => void;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* This is similar to using `Promise.race([other, sourceDone])`, but
|
|
22
|
-
* in a memory safe way, as repeatedly calling `Promise.race([ ... ])` with
|
|
23
|
-
* a long-lived Promise results in leaking memory via a growing list of
|
|
24
|
-
* `then()` callbacks (https://github.com/nodejs/node/issues/17469).
|
|
17
|
+
* An AbortSignal that can be used to listen for termination or
|
|
18
|
+
* race a short-lived promise against the termination of the iterable
|
|
19
|
+
* (via `promiseOrAbort()`)
|
|
25
20
|
*/
|
|
26
|
-
|
|
21
|
+
readonly signal: AbortSignal;
|
|
27
22
|
/**
|
|
28
23
|
* The presence of a `pipeline` iterable allows the usual "consumed-on-iterate" semantics
|
|
29
24
|
* to be overridden.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/streams.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAGR,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,IAAI,CAAC;AAEZ,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAE9E,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,mBAAmB,CAAC;AAW7D,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG;IACzC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAE9B
|
|
1
|
+
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/streams.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.js","names":["#lc","#ws","#closeStream","#messageHandler","#connected","#handleOpen","#handleClose","#handleError","#conn"],"sources":["../../../../../zero-cache/src/types/streams.ts"],"sourcesContent":["import {\n pipeline,\n Readable,\n Transform,\n Writable,\n type DuplexOptions,\n} from 'node:stream';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {\n createWebSocketStream,\n type CloseEvent,\n type ErrorEvent,\n type MessageEvent,\n type WebSocket,\n} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Subscription, type Options} from './subscription.ts';\nimport {\n closeWithError,\n expectPingsForLiveness,\n sendPingsForLiveness,\n} from './ws.ts';\n\n// Consistent with Postgres keepalives, and shorter than the\n// commonly used default idle timeout of 1 minute.\nconst PING_INTERVAL_MS = 30_000;\n\nexport type Source<T> = AsyncIterable<T> & {\n /**\n * Immediately terminates all current iterations (i.e. {@link AsyncIterator.next next()})\n * will return `{value: undefined, done: true}`), and prevents any subsequent iterations\n * from yielding any values.\n *\n * @param err Terminate the iteration by throwing the `err` instead.\n */\n cancel: (err?: Error) => void;\n\n /**\n * Resolves the result of the specified `other` Promise, or a\n * resolved/rejected Promise when the Source is terminated or\n * rejects.\n *\n * This is similar to using `Promise.race([other, sourceDone])`, but\n * in a memory safe way, as repeatedly calling `Promise.race([ ... ])` with\n * a long-lived Promise results in leaking memory via a growing list of\n * `then()` callbacks (https://github.com/nodejs/node/issues/17469).\n */\n doneOr<R>(other: Promise<R>): Promise<void | R>;\n\n /**\n * The presence of a `pipeline` iterable allows the usual \"consumed-on-iterate\" semantics\n * to be overridden.\n *\n * This is suitable for transport layers that serialize messages across processes, such\n * as the {@link streamOut()} method; pipelining allows the transport to send messages\n * as they arrive without waiting for the previous message to be acked, streaming\n * them to the receiving process where they are presumably queued and processed without\n * a per-message ack delay. The receiving end of the transport then responds with acks\n * asynchronously as the receiving end processes the messages.\n */\n pipeline?: AsyncIterable<{value: T; consumed: () => void}> | undefined;\n};\n\nexport type Sink<T> = {\n push(message: T): void;\n};\n\n/**\n * Back-pressure-aware transformation of a WebSocket into\n * upstream and downstream {@link Subscription} objects.\n */\n// TODO: Change {@link streamIn} and {@link streamOut} to use this\n// under the covers so that internal communication is also\n// responsive to backpressure.\nexport function stream<In extends JSONValue, Out extends JSONValue>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In>,\n outOptions: Options<Out> = {},\n inOptions: Options<In> = {},\n streamOptions: DuplexOptions = {},\n): {outstream: Sink<Out>; instream: Source<In>} {\n const endpoint = ws.url ?? 'client';\n function close(err?: unknown) {\n if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {\n if (err) {\n closeWithError(lc, ws, err);\n } else {\n lc.info?.(`closing connection to ${endpoint}`);\n ws.close();\n }\n }\n }\n\n const instream = Subscription.create<In>({\n ...inOptions,\n cleanup: (unconsumed, err) => {\n inOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n const outstream = Subscription.create<Out>({\n ...outOptions,\n cleanup: (unconsumed, err) => {\n outOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n\n const duplex = createWebSocketStream(ws, {\n ...streamOptions,\n decodeStrings: false,\n });\n\n // Outgoing transform.\n function streamOut() {\n // Mainly used for verifying that back-pressure kicks in tests.\n duplex.on('drain', () => lc.debug?.(`drained messages to ${endpoint}`));\n\n pipeline(\n Readable.from(outstream),\n new Transform({\n objectMode: true,\n transform: (msg, _encoding, callback) =>\n callback(null, BigIntJSON.stringify(msg)),\n }),\n duplex,\n err => (err ? outstream.fail(err) : outstream.cancel()),\n );\n }\n\n if (ws.readyState === ws.CONNECTING) {\n ws.on('open', () => {\n lc.info?.(`connected to ${endpoint}`);\n streamOut();\n });\n } else {\n streamOut();\n }\n\n // Incoming transform.\n pipe({\n source: duplex,\n sink: instream,\n parse: chunk => {\n const json = BigIntJSON.parse(chunk.toString());\n return v.parse(json, inSchema, 'passthrough');\n },\n });\n\n sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);\n\n return {outstream, instream};\n}\n\ntype PipeOptions<T> = {\n source: Readable;\n sink: Subscription<T>;\n parse: (buffer: Buffer) => T | null;\n bufferMessages?: number;\n};\n\nexport function pipe<T>({source, sink, parse, bufferMessages}: PipeOptions<T>) {\n bufferMessages ??= 0;\n assert(bufferMessages >= 0, 'bufferMessages must be non-negative');\n const pending: Promise<unknown>[] = [];\n\n pipeline(\n source,\n new Writable({\n decodeStrings: false,\n write: (chunk, _encoding, callback) => {\n let msg: T | null;\n try {\n if ((msg = parse(chunk)) === null) {\n callback();\n return;\n }\n } catch (err) {\n callback(ensureError(err));\n return;\n }\n // Inbound backpressure is exerted by unconsumed messages in the\n // subscription. A buffer can be used to allow messages to queue up in\n // in the Subscription object, which allows the consumer to \"peek\" at\n // whether there are more messages immediately available\n // (via {@link Subscription.queued}.\n const {result} = sink.push(msg);\n pending.push(result);\n void result.then(() => pending.shift());\n\n if (pending.length <= bufferMessages) {\n // immediately allow more messages\n callback();\n } else {\n // wait for the oldest result in the pending queue\n pending[0].then(\n () => callback(),\n err => callback(ensureError(err)),\n );\n }\n },\n destroy: (err, callback) => {\n if (err) {\n sink.fail(ensureError(err));\n }\n // Otherwise, final will handle the cancel.\n callback();\n },\n final: callback => {\n sink.cancel();\n callback();\n },\n }),\n err => (err ? sink.fail(err) : sink.cancel()),\n );\n}\n\nfunction ensureError(err: unknown) {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nconst ackSchema = v.object({ack: v.number()});\n\ntype Ack = v.Infer<typeof ackSchema>;\n\n/** A parsed JSON value paired with the exact source text it was parsed from. */\nexport type ParsedJSON<T> = {\n data: T;\n json: string;\n};\n\nexport function streamOut<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n): Promise<void> {\n return streamOutInternal(lc, source, sink, BigIntJSON.stringify);\n}\n\n/**\n * Streams out a `Source` for which messages are already stringified JSON.\n */\nexport function streamOutStringified(\n lc: LogContext,\n source: Source<string>,\n sink: WebSocket,\n): Promise<void> {\n return streamOutInternal(lc, source, sink, json => json);\n}\n\nasync function streamOutInternal<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n stringify: (payload: T) => string,\n): Promise<void> {\n sendPingsForLiveness(lc, sink, PING_INTERVAL_MS);\n\n const closer = WebSocketCloser.forSource(lc, sink, source);\n\n const acks = new Queue<Ack>();\n sink.addEventListener('message', ({data}) => {\n try {\n if (typeof data !== 'string') {\n throw new Error('Expected string message');\n }\n acks.enqueue(v.parse(JSON.parse(data), ackSchema));\n } catch (e) {\n lc.error?.(`error parsing ack`, e);\n closer.close(e);\n }\n });\n\n try {\n let nextID = 0;\n const {pipeline} = source;\n if (pipeline) {\n lc.debug?.(`started pipelined outbound stream`);\n for await (const {value: msg, consumed} of pipeline) {\n const id = ++nextID;\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`pipelining`, data);\n sink.send(data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n // lc.debug?.(`received ack`, ack);\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })();\n }\n } else {\n lc.debug?.(`started synchronous outbound stream`);\n for await (const msg of source) {\n const id = ++nextID;\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`sending`, data);\n sink.send(data);\n\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n }\n }\n closer.close();\n } catch (e) {\n closer.close(e);\n }\n}\n\nexport function streamIn<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<T>> {\n return streamInInternal(lc, source, schema, data => data);\n}\n\n/**\n * Streams in messages sent by {@link streamOutStringified}, preserving the\n * exact application-level JSON alongside its parsed and validated value.\n */\nexport function streamInStringified<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<ParsedJSON<T>>> {\n return streamInInternal(lc, source, schema, (data, frame, id) => ({\n data,\n json: extractStringifiedMessage(frame, id),\n }));\n}\n\nasync function streamInInternal<T extends JSONValue, Out>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n transform: (data: T, frame: string, id: number) => Out,\n): Promise<Source<Out>> {\n expectPingsForLiveness(lc, source, PING_INTERVAL_MS);\n\n const streamedSchema = v.object({\n msg: schema,\n id: v.number(),\n });\n\n const sink: Subscription<Out, {id: number; data: Out}> = new Subscription<\n Out,\n {id: number; data: Out}\n >(\n {\n consumed: ({id}) => source.send(JSON.stringify({ack: id} satisfies Ack)),\n cleanup: () => closer.close(),\n },\n ({data}) => data,\n );\n\n const closer = WebSocketCloser.forSink(lc, source, sink, handleMessage);\n\n function handleMessage(event: MessageEvent) {\n const data = event.data.toString();\n if (!sink.active) {\n lc.warn?.('dropping ws message received after close', data);\n return;\n }\n try {\n const value = BigIntJSON.parse(data);\n const msg = v.parse(value, streamedSchema, 'passthrough');\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`received`, data);\n sink.push({id: msg.id, data: transform(msg.msg, data, msg.id)});\n } catch (e) {\n closer.close(e);\n }\n }\n\n await closer.connected;\n return sink;\n}\n\nfunction extractStringifiedMessage(frame: string, id: number): string {\n const prefix = `{\"id\":${id},\"msg\":`;\n assert(\n frame.startsWith(prefix) && frame.endsWith('}'),\n `invalid streamed message frame`,\n );\n return frame.slice(prefix.length, -1);\n}\n\nclass WebSocketCloser {\n readonly #lc: LogContext;\n readonly #ws: WebSocket;\n readonly #closeStream: () => void;\n readonly #messageHandler: ((e: MessageEvent) => void | undefined) | null;\n readonly #connected = resolver();\n\n get connected(): Promise<void> {\n return this.#connected.promise;\n }\n\n static forSource<T>(lc: LogContext, ws: WebSocket, stream: Source<T>) {\n // If the websocket is closed, call cancel() to notify the Source of\n // any unconsumed messages.\n return new WebSocketCloser(lc, ws, () => stream.cancel());\n }\n\n static forSink<T, Input>(\n lc: LogContext,\n ws: WebSocket,\n stream: Subscription<T, Input>,\n messageHandler: (e: MessageEvent) => void | undefined,\n ) {\n // If the websocket is closed, call end() to allow the downstream Sink\n // to process any pending messages before closing the stream.\n return new WebSocketCloser(lc, ws, () => stream.end(), messageHandler);\n }\n\n private constructor(\n lc: LogContext,\n ws: WebSocket,\n closeStream: () => void,\n messageHandler?: (e: MessageEvent) => void | undefined,\n ) {\n this.#lc = lc;\n this.#ws = ws;\n this.#closeStream = closeStream;\n this.#messageHandler = messageHandler ?? null;\n\n ws.addEventListener('open', this.#handleOpen);\n ws.addEventListener('close', this.#handleClose);\n ws.addEventListener('error', this.#handleError);\n if (this.#messageHandler) {\n ws.addEventListener('message', this.#messageHandler);\n }\n\n switch (ws.readyState) {\n case ws.CONNECTING:\n break; // expected for new connections. resolve or reject in handlers.\n case ws.OPEN:\n this.#connected.resolve();\n break;\n default:\n this.#connected.reject(\n new Error(`websocket already in state ${ws.readyState}`),\n );\n break;\n }\n }\n\n get #conn(): string {\n return 'connection' + (this.#ws.url ? ` to ${this.#ws.url}` : '');\n }\n\n #handleOpen = () => {\n this.#lc.info?.(`${this.#conn} established`);\n this.#connected.resolve();\n };\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.#lc.info?.(`${this.#conn} closed`, {\n code,\n reason,\n wasClean,\n });\n this.close();\n this.#connected.reject(`${this.#conn} closed with code ${code}`);\n };\n\n #handleError = ({message, error}: ErrorEvent) => {\n if (this.#ws.readyState === this.#ws.OPEN) {\n this.#lc.error?.(`error in ${this.#conn}`, message, error);\n }\n this.#connected.reject(error);\n };\n\n close(err?: unknown) {\n if (err) {\n this.#lc.error?.(`closing stream with error`, err);\n }\n this.#closeStream();\n if (!this.closed()) {\n this.#ws.close();\n }\n }\n\n closed() {\n return (\n this.#ws.readyState === this.#ws.CLOSED ||\n this.#ws.readyState === this.#ws.CLOSING\n );\n }\n}\n"],"mappings":";;;;;;;;;;AA6BA,IAAM,mBAAmB;;;;;AAiDzB,SAAgB,OACd,IACA,IACA,UACA,aAA2B,CAAC,GAC5B,YAAyB,CAAC,GAC1B,gBAA+B,CAAC,GACc;CAC9C,MAAM,WAAW,GAAG,OAAO;CAC3B,SAAS,MAAM,KAAe;EAC5B,IAAI,GAAG,eAAe,GAAG,UAAU,GAAG,eAAe,GAAG,SACtD,IAAI,KACF,eAAe,IAAI,IAAI,GAAG;OACrB;GACL,GAAG,OAAO,yBAAyB,UAAU;GAC7C,GAAG,MAAM;EACX;CAEJ;CAEA,MAAM,WAAW,aAAa,OAAW;EACvC,GAAG;EACH,UAAU,YAAY,QAAQ;GAC5B,UAAU,UAAU,YAAY,GAAG;GACnC,MAAM,GAAG;EACX;CACF,CAAC;CACD,MAAM,YAAY,aAAa,OAAY;EACzC,GAAG;EACH,UAAU,YAAY,QAAQ;GAC5B,WAAW,UAAU,YAAY,GAAG;GACpC,MAAM,GAAG;EACX;CACF,CAAC;CAED,MAAM,SAAS,sBAAsB,IAAI;EACvC,GAAG;EACH,eAAe;CACjB,CAAC;CAGD,SAAS,YAAY;EAEnB,OAAO,GAAG,eAAe,GAAG,QAAQ,uBAAuB,UAAU,CAAC;EAEtE,SACE,SAAS,KAAK,SAAS,GACvB,IAAI,UAAU;GACZ,YAAY;GACZ,YAAY,KAAK,WAAW,aAC1B,SAAS,MAAM,WAAW,UAAU,GAAG,CAAC;EAC5C,CAAC,GACD,SACA,QAAQ,MAAM,UAAU,KAAK,GAAG,IAAI,UAAU,OAAO,CACvD;CACF;CAEA,IAAI,GAAG,eAAe,GAAG,YACvB,GAAG,GAAG,cAAc;EAClB,GAAG,OAAO,gBAAgB,UAAU;EACpC,UAAU;CACZ,CAAC;MAED,UAAU;CAIZ,KAAK;EACH,QAAQ;EACR,MAAM;EACN,QAAO,UAAS;GAEd,OAAO,MADM,WAAW,MAAM,MAAM,SAAS,CAC9B,GAAM,UAAU,aAAa;EAC9C;CACF,CAAC;CAED,qBAAqB,IAAI,IAAI,gBAAgB;CAE7C,OAAO;EAAC;EAAW;CAAQ;AAC7B;AASA,SAAgB,KAAQ,EAAC,QAAQ,MAAM,OAAO,kBAAiC;CAC7E,mBAAmB;CACnB,OAAO,kBAAkB,GAAG,qCAAqC;CACjE,MAAM,UAA8B,CAAC;CAErC,SACE,QACA,IAAI,SAAS;EACX,eAAe;EACf,QAAQ,OAAO,WAAW,aAAa;GACrC,IAAI;GACJ,IAAI;IACF,KAAK,MAAM,MAAM,KAAK,OAAO,MAAM;KACjC,SAAS;KACT;IACF;GACF,SAAS,KAAK;IACZ,SAAS,YAAY,GAAG,CAAC;IACzB;GACF;GAMA,MAAM,EAAC,WAAU,KAAK,KAAK,GAAG;GAC9B,QAAQ,KAAK,MAAM;GACnB,OAAY,WAAW,QAAQ,MAAM,CAAC;GAEtC,IAAI,QAAQ,UAAU,gBAEpB,SAAS;QAGT,QAAQ,GAAG,WACH,SAAS,IACf,QAAO,SAAS,YAAY,GAAG,CAAC,CAClC;EAEJ;EACA,UAAU,KAAK,aAAa;GAC1B,IAAI,KACF,KAAK,KAAK,YAAY,GAAG,CAAC;GAG5B,SAAS;EACX;EACA,QAAO,aAAY;GACjB,KAAK,OAAO;GACZ,SAAS;EACX;CACF,CAAC,IACD,QAAQ,MAAM,KAAK,KAAK,GAAG,IAAI,KAAK,OAAO,CAC7C;AACF;AAEA,SAAS,YAAY,KAAc;CACjC,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC3D;AAEA,IAAM,YAAY,eAAE,OAAO,EAAC,KAAK,eAAE,OAAO,EAAC,CAAC;AAU5C,SAAgB,UACd,IACA,QACA,MACe;CACf,OAAO,kBAAkB,IAAI,QAAQ,MAAM,WAAW,SAAS;AACjE;;;;AAKA,SAAgB,qBACd,IACA,QACA,MACe;CACf,OAAO,kBAAkB,IAAI,QAAQ,OAAM,SAAQ,IAAI;AACzD;AAEA,eAAe,kBACb,IACA,QACA,MACA,WACe;CACf,qBAAqB,IAAI,MAAM,gBAAgB;CAE/C,MAAM,SAAS,gBAAgB,UAAU,IAAI,MAAM,MAAM;CAEzD,MAAM,OAAO,IAAI,MAAW;CAC5B,KAAK,iBAAiB,YAAY,EAAC,WAAU;EAC3C,IAAI;GACF,IAAI,OAAO,SAAS,UAClB,MAAM,IAAI,MAAM,yBAAyB;GAE3C,KAAK,QAAQ,MAAQ,KAAK,MAAM,IAAI,GAAG,SAAS,CAAC;EACnD,SAAS,GAAG;GACV,GAAG,QAAQ,qBAAqB,CAAC;GACjC,OAAO,MAAM,CAAC;EAChB;CACF,CAAC;CAED,IAAI;EACF,IAAI,SAAS;EACb,MAAM,EAAC,aAAY;EACnB,IAAI,UAAU;GACZ,GAAG,QAAQ,mCAAmC;GAC9C,WAAW,MAAM,EAAC,OAAO,KAAK,cAAa,UAAU;IACnD,MAAM,KAAK,EAAE;IACb,MAAM,OAAO,SAAS,GAAG,SAAS,UAAU,GAAG,EAAE;IAGjD,KAAK,KAAK,IAAI;IAEd,CAAM,YAAY;KAChB,MAAM,EAAC,QAAO,MAAM,KAAK,QAAQ;KAEjC,IAAI,QAAQ,IACV,MAAM,IAAI,MAAM,sBAAsB,GAAG,IAAI,KAAK;KAEpD,SAAS;IACX,GAAG;GACL;EACF,OAAO;GACL,GAAG,QAAQ,qCAAqC;GAChD,WAAW,MAAM,OAAO,QAAQ;IAC9B,MAAM,KAAK,EAAE;IACb,MAAM,OAAO,SAAS,GAAG,SAAS,UAAU,GAAG,EAAE;IAGjD,KAAK,KAAK,IAAI;IAEd,MAAM,EAAC,QAAO,MAAM,KAAK,QAAQ;IACjC,IAAI,QAAQ,IACV,MAAM,IAAI,MAAM,sBAAsB,GAAG,IAAI,KAAK;GAEtD;EACF;EACA,OAAO,MAAM;CACf,SAAS,GAAG;EACV,OAAO,MAAM,CAAC;CAChB;AACF;AAEA,SAAgB,SACd,IACA,QACA,QACoB;CACpB,OAAO,iBAAiB,IAAI,QAAQ,SAAQ,SAAQ,IAAI;AAC1D;;;;;AAMA,SAAgB,oBACd,IACA,QACA,QACgC;CAChC,OAAO,iBAAiB,IAAI,QAAQ,SAAS,MAAM,OAAO,QAAQ;EAChE;EACA,MAAM,0BAA0B,OAAO,EAAE;CAC3C,EAAE;AACJ;AAEA,eAAe,iBACb,IACA,QACA,QACA,WACsB;CACtB,uBAAuB,IAAI,QAAQ,gBAAgB;CAEnD,MAAM,iBAAiB,eAAE,OAAO;EAC9B,KAAK;EACL,IAAI,eAAE,OAAO;CACf,CAAC;CAED,MAAM,OAAmD,IAAI,aAI3D;EACE,WAAW,EAAC,SAAQ,OAAO,KAAK,KAAK,UAAU,EAAC,KAAK,GAAE,CAAe,CAAC;EACvE,eAAe,OAAO,MAAM;CAC9B,IACC,EAAC,WAAU,IACd;CAEA,MAAM,SAAS,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,aAAa;CAEtE,SAAS,cAAc,OAAqB;EAC1C,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,IAAI,CAAC,KAAK,QAAQ;GAChB,GAAG,OAAO,4CAA4C,IAAI;GAC1D;EACF;EACA,IAAI;GAEF,MAAM,MAAM,MADE,WAAW,MAAM,IACX,GAAO,gBAAgB,aAAa;GAGxD,KAAK,KAAK;IAAC,IAAI,IAAI;IAAI,MAAM,UAAU,IAAI,KAAK,MAAM,IAAI,EAAE;GAAC,CAAC;EAChE,SAAS,GAAG;GACV,OAAO,MAAM,CAAC;EAChB;CACF;CAEA,MAAM,OAAO;CACb,OAAO;AACT;AAEA,SAAS,0BAA0B,OAAe,IAAoB;CACpE,MAAM,SAAS,SAAS,GAAG;CAC3B,OACE,MAAM,WAAW,MAAM,KAAK,MAAM,SAAS,GAAG,GAC9C,gCACF;CACA,OAAO,MAAM,MAAM,OAAO,QAAQ,EAAE;AACtC;AAEA,IAAM,kBAAN,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA,aAAsB,SAAS;CAE/B,IAAI,YAA2B;EAC7B,OAAO,KAAKI,WAAW;CACzB;CAEA,OAAO,UAAa,IAAgB,IAAe,QAAmB;EAGpE,OAAO,IAAI,gBAAgB,IAAI,UAAU,OAAO,OAAO,CAAC;CAC1D;CAEA,OAAO,QACL,IACA,IACA,QACA,gBACA;EAGA,OAAO,IAAI,gBAAgB,IAAI,UAAU,OAAO,IAAI,GAAG,cAAc;CACvE;CAEA,YACE,IACA,IACA,aACA,gBACA;EACA,KAAKJ,MAAM;EACX,KAAKC,MAAM;EACX,KAAKC,eAAe;EACpB,KAAKC,kBAAkB,kBAAkB;EAEzC,GAAG,iBAAiB,QAAQ,KAAKE,WAAW;EAC5C,GAAG,iBAAiB,SAAS,KAAKC,YAAY;EAC9C,GAAG,iBAAiB,SAAS,KAAKC,YAAY;EAC9C,IAAI,KAAKJ,iBACP,GAAG,iBAAiB,WAAW,KAAKA,eAAe;EAGrD,QAAQ,GAAG,YAAX;GACE,KAAK,GAAG,YACN;GACF,KAAK,GAAG;IACN,KAAKC,WAAW,QAAQ;IACxB;GACF;IACE,KAAKA,WAAW,uBACd,IAAI,MAAM,8BAA8B,GAAG,YAAY,CACzD;IACA;EACJ;CACF;CAEA,IAAII,QAAgB;EAClB,OAAO,gBAAgB,KAAKP,IAAI,MAAM,OAAO,KAAKA,IAAI,QAAQ;CAChE;CAEA,oBAAoB;EAClB,KAAKD,IAAI,OAAO,GAAG,KAAKQ,MAAM,aAAa;EAC3C,KAAKJ,WAAW,QAAQ;CAC1B;CAEA,gBAAgB,MAAkB;EAChC,MAAM,EAAC,MAAM,QAAQ,aAAY;EACjC,KAAKJ,IAAI,OAAO,GAAG,KAAKQ,MAAM,UAAU;GACtC;GACA;GACA;EACF,CAAC;EACD,KAAK,MAAM;EACX,KAAKJ,WAAW,OAAO,GAAG,KAAKI,MAAM,oBAAoB,MAAM;CACjE;CAEA,gBAAgB,EAAC,SAAS,YAAuB;EAC/C,IAAI,KAAKP,IAAI,eAAe,KAAKA,IAAI,MACnC,KAAKD,IAAI,QAAQ,YAAY,KAAKQ,SAAS,SAAS,KAAK;EAE3D,KAAKJ,WAAW,OAAO,KAAK;CAC9B;CAEA,MAAM,KAAe;EACnB,IAAI,KACF,KAAKJ,IAAI,QAAQ,6BAA6B,GAAG;EAEnD,KAAKE,aAAa;EAClB,IAAI,CAAC,KAAK,OAAO,GACf,KAAKD,IAAI,MAAM;CAEnB;CAEA,SAAS;EACP,OACE,KAAKA,IAAI,eAAe,KAAKA,IAAI,UACjC,KAAKA,IAAI,eAAe,KAAKA,IAAI;CAErC;AACF"}
|
|
1
|
+
{"version":3,"file":"streams.js","names":["#lc","#ws","#closeStream","#messageHandler","#connected","#handleOpen","#handleClose","#handleError","#conn"],"sources":["../../../../../zero-cache/src/types/streams.ts"],"sourcesContent":["import {\n pipeline,\n Readable,\n Transform,\n Writable,\n type DuplexOptions,\n} from 'node:stream';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {\n createWebSocketStream,\n type CloseEvent,\n type ErrorEvent,\n type MessageEvent,\n type WebSocket,\n} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {Queue} from '../../../shared/src/queue.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Subscription, type Options} from './subscription.ts';\nimport {\n closeWithError,\n expectPingsForLiveness,\n sendPingsForLiveness,\n} from './ws.ts';\n\n// Consistent with Postgres keepalives, and shorter than the\n// commonly used default idle timeout of 1 minute.\nconst PING_INTERVAL_MS = 30_000;\n\nexport type Source<T> = AsyncIterable<T> & {\n /**\n * Immediately terminates all current iterations (i.e. {@link AsyncIterator.next next()})\n * will return `{value: undefined, done: true}`), and prevents any subsequent iterations\n * from yielding any values.\n *\n * @param err Terminate the iteration by throwing the `err` instead.\n */\n cancel: (err?: Error) => void;\n\n /**\n * An AbortSignal that can be used to listen for termination or\n * race a short-lived promise against the termination of the iterable\n * (via `promiseOrAbort()`)\n */\n readonly signal: AbortSignal;\n\n /**\n * The presence of a `pipeline` iterable allows the usual \"consumed-on-iterate\" semantics\n * to be overridden.\n *\n * This is suitable for transport layers that serialize messages across processes, such\n * as the {@link streamOut()} method; pipelining allows the transport to send messages\n * as they arrive without waiting for the previous message to be acked, streaming\n * them to the receiving process where they are presumably queued and processed without\n * a per-message ack delay. The receiving end of the transport then responds with acks\n * asynchronously as the receiving end processes the messages.\n */\n pipeline?: AsyncIterable<{value: T; consumed: () => void}> | undefined;\n};\n\nexport type Sink<T> = {\n push(message: T): void;\n};\n\n/**\n * Back-pressure-aware transformation of a WebSocket into\n * upstream and downstream {@link Subscription} objects.\n */\n// TODO: Change {@link streamIn} and {@link streamOut} to use this\n// under the covers so that internal communication is also\n// responsive to backpressure.\nexport function stream<In extends JSONValue, Out extends JSONValue>(\n lc: LogContext,\n ws: WebSocket,\n inSchema: v.Type<In>,\n outOptions: Options<Out> = {},\n inOptions: Options<In> = {},\n streamOptions: DuplexOptions = {},\n): {outstream: Sink<Out>; instream: Source<In>} {\n const endpoint = ws.url ?? 'client';\n function close(err?: unknown) {\n if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {\n if (err) {\n closeWithError(lc, ws, err);\n } else {\n lc.info?.(`closing connection to ${endpoint}`);\n ws.close();\n }\n }\n }\n\n const instream = Subscription.create<In>({\n ...inOptions,\n cleanup: (unconsumed, err) => {\n inOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n const outstream = Subscription.create<Out>({\n ...outOptions,\n cleanup: (unconsumed, err) => {\n outOptions.cleanup?.(unconsumed, err);\n close(err);\n },\n });\n\n const duplex = createWebSocketStream(ws, {\n ...streamOptions,\n decodeStrings: false,\n });\n\n // Outgoing transform.\n function streamOut() {\n // Mainly used for verifying that back-pressure kicks in tests.\n duplex.on('drain', () => lc.debug?.(`drained messages to ${endpoint}`));\n\n pipeline(\n Readable.from(outstream),\n new Transform({\n objectMode: true,\n transform: (msg, _encoding, callback) =>\n callback(null, BigIntJSON.stringify(msg)),\n }),\n duplex,\n err => (err ? outstream.fail(err) : outstream.cancel()),\n );\n }\n\n if (ws.readyState === ws.CONNECTING) {\n ws.on('open', () => {\n lc.info?.(`connected to ${endpoint}`);\n streamOut();\n });\n } else {\n streamOut();\n }\n\n // Incoming transform.\n pipe({\n source: duplex,\n sink: instream,\n parse: chunk => {\n const json = BigIntJSON.parse(chunk.toString());\n return v.parse(json, inSchema, 'passthrough');\n },\n });\n\n sendPingsForLiveness(lc, ws, PING_INTERVAL_MS);\n\n return {outstream, instream};\n}\n\ntype PipeOptions<T> = {\n source: Readable;\n sink: Subscription<T>;\n parse: (buffer: Buffer) => T | null;\n bufferMessages?: number;\n};\n\nexport function pipe<T>({source, sink, parse, bufferMessages}: PipeOptions<T>) {\n bufferMessages ??= 0;\n assert(bufferMessages >= 0, 'bufferMessages must be non-negative');\n const pending: Promise<unknown>[] = [];\n\n pipeline(\n source,\n new Writable({\n decodeStrings: false,\n write: (chunk, _encoding, callback) => {\n let msg: T | null;\n try {\n if ((msg = parse(chunk)) === null) {\n callback();\n return;\n }\n } catch (err) {\n callback(ensureError(err));\n return;\n }\n // Inbound backpressure is exerted by unconsumed messages in the\n // subscription. A buffer can be used to allow messages to queue up in\n // in the Subscription object, which allows the consumer to \"peek\" at\n // whether there are more messages immediately available\n // (via {@link Subscription.queued}.\n const {result} = sink.push(msg);\n pending.push(result);\n void result.then(() => pending.shift());\n\n if (pending.length <= bufferMessages) {\n // immediately allow more messages\n callback();\n } else {\n // wait for the oldest result in the pending queue\n pending[0].then(\n () => callback(),\n err => callback(ensureError(err)),\n );\n }\n },\n destroy: (err, callback) => {\n if (err) {\n sink.fail(ensureError(err));\n }\n // Otherwise, final will handle the cancel.\n callback();\n },\n final: callback => {\n sink.cancel();\n callback();\n },\n }),\n err => (err ? sink.fail(err) : sink.cancel()),\n );\n}\n\nfunction ensureError(err: unknown) {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nconst ackSchema = v.object({ack: v.number()});\n\ntype Ack = v.Infer<typeof ackSchema>;\n\n/** A parsed JSON value paired with the exact source text it was parsed from. */\nexport type ParsedJSON<T> = {\n data: T;\n json: string;\n};\n\nexport function streamOut<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n): Promise<void> {\n return streamOutInternal(lc, source, sink, BigIntJSON.stringify);\n}\n\n/**\n * Streams out a `Source` for which messages are already stringified JSON.\n */\nexport function streamOutStringified(\n lc: LogContext,\n source: Source<string>,\n sink: WebSocket,\n): Promise<void> {\n return streamOutInternal(lc, source, sink, json => json);\n}\n\nasync function streamOutInternal<T extends JSONValue>(\n lc: LogContext,\n source: Source<T>,\n sink: WebSocket,\n stringify: (payload: T) => string,\n): Promise<void> {\n sendPingsForLiveness(lc, sink, PING_INTERVAL_MS);\n\n const closer = WebSocketCloser.forSource(lc, sink, source);\n\n const acks = new Queue<Ack>();\n sink.addEventListener('message', ({data}) => {\n try {\n if (typeof data !== 'string') {\n throw new Error('Expected string message');\n }\n acks.enqueue(v.parse(JSON.parse(data), ackSchema));\n } catch (e) {\n lc.error?.(`error parsing ack`, e);\n closer.close(e);\n }\n });\n\n try {\n let nextID = 0;\n const {pipeline} = source;\n if (pipeline) {\n lc.debug?.(`started pipelined outbound stream`);\n for await (const {value: msg, consumed} of pipeline) {\n const id = ++nextID;\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`pipelining`, data);\n sink.send(data);\n\n void (async () => {\n const {ack} = await acks.dequeue();\n // lc.debug?.(`received ack`, ack);\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n consumed();\n })();\n }\n } else {\n lc.debug?.(`started synchronous outbound stream`);\n for await (const msg of source) {\n const id = ++nextID;\n const data = `{\"id\":${id},\"msg\":${stringify(msg)}}`;\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`sending`, data);\n sink.send(data);\n\n const {ack} = await acks.dequeue();\n if (ack !== id) {\n throw new Error(`Unexpected ack for ${id}: ${ack}`);\n }\n }\n }\n closer.close();\n } catch (e) {\n closer.close(e);\n }\n}\n\nexport function streamIn<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<T>> {\n return streamInInternal(lc, source, schema, data => data);\n}\n\n/**\n * Streams in messages sent by {@link streamOutStringified}, preserving the\n * exact application-level JSON alongside its parsed and validated value.\n */\nexport function streamInStringified<T extends JSONValue>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n): Promise<Source<ParsedJSON<T>>> {\n return streamInInternal(lc, source, schema, (data, frame, id) => ({\n data,\n json: extractStringifiedMessage(frame, id),\n }));\n}\n\nasync function streamInInternal<T extends JSONValue, Out>(\n lc: LogContext,\n source: WebSocket,\n schema: v.Type<T>,\n transform: (data: T, frame: string, id: number) => Out,\n): Promise<Source<Out>> {\n expectPingsForLiveness(lc, source, PING_INTERVAL_MS);\n\n const streamedSchema = v.object({\n msg: schema,\n id: v.number(),\n });\n\n const sink: Subscription<Out, {id: number; data: Out}> = new Subscription<\n Out,\n {id: number; data: Out}\n >(\n {\n consumed: ({id}) => source.send(JSON.stringify({ack: id} satisfies Ack)),\n cleanup: () => closer.close(),\n },\n ({data}) => data,\n );\n\n const closer = WebSocketCloser.forSink(lc, source, sink, handleMessage);\n\n function handleMessage(event: MessageEvent) {\n const data = event.data.toString();\n if (!sink.active) {\n lc.warn?.('dropping ws message received after close', data);\n return;\n }\n try {\n const value = BigIntJSON.parse(data);\n const msg = v.parse(value, streamedSchema, 'passthrough');\n // Enable for debugging. Otherwise too verbose.\n // lc.debug?.(`received`, data);\n sink.push({id: msg.id, data: transform(msg.msg, data, msg.id)});\n } catch (e) {\n closer.close(e);\n }\n }\n\n await closer.connected;\n return sink;\n}\n\nfunction extractStringifiedMessage(frame: string, id: number): string {\n const prefix = `{\"id\":${id},\"msg\":`;\n assert(\n frame.startsWith(prefix) && frame.endsWith('}'),\n `invalid streamed message frame`,\n );\n return frame.slice(prefix.length, -1);\n}\n\nclass WebSocketCloser {\n readonly #lc: LogContext;\n readonly #ws: WebSocket;\n readonly #closeStream: () => void;\n readonly #messageHandler: ((e: MessageEvent) => void | undefined) | null;\n readonly #connected = resolver();\n\n get connected(): Promise<void> {\n return this.#connected.promise;\n }\n\n static forSource<T>(lc: LogContext, ws: WebSocket, stream: Source<T>) {\n // If the websocket is closed, call cancel() to notify the Source of\n // any unconsumed messages.\n return new WebSocketCloser(lc, ws, () => stream.cancel());\n }\n\n static forSink<T, Input>(\n lc: LogContext,\n ws: WebSocket,\n stream: Subscription<T, Input>,\n messageHandler: (e: MessageEvent) => void | undefined,\n ) {\n // If the websocket is closed, call end() to allow the downstream Sink\n // to process any pending messages before closing the stream.\n return new WebSocketCloser(lc, ws, () => stream.end(), messageHandler);\n }\n\n private constructor(\n lc: LogContext,\n ws: WebSocket,\n closeStream: () => void,\n messageHandler?: (e: MessageEvent) => void | undefined,\n ) {\n this.#lc = lc;\n this.#ws = ws;\n this.#closeStream = closeStream;\n this.#messageHandler = messageHandler ?? null;\n\n ws.addEventListener('open', this.#handleOpen);\n ws.addEventListener('close', this.#handleClose);\n ws.addEventListener('error', this.#handleError);\n if (this.#messageHandler) {\n ws.addEventListener('message', this.#messageHandler);\n }\n\n switch (ws.readyState) {\n case ws.CONNECTING:\n break; // expected for new connections. resolve or reject in handlers.\n case ws.OPEN:\n this.#connected.resolve();\n break;\n default:\n this.#connected.reject(\n new Error(`websocket already in state ${ws.readyState}`),\n );\n break;\n }\n }\n\n get #conn(): string {\n return 'connection' + (this.#ws.url ? ` to ${this.#ws.url}` : '');\n }\n\n #handleOpen = () => {\n this.#lc.info?.(`${this.#conn} established`);\n this.#connected.resolve();\n };\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.#lc.info?.(`${this.#conn} closed`, {\n code,\n reason,\n wasClean,\n });\n this.close();\n this.#connected.reject(`${this.#conn} closed with code ${code}`);\n };\n\n #handleError = ({message, error}: ErrorEvent) => {\n if (this.#ws.readyState === this.#ws.OPEN) {\n this.#lc.error?.(`error in ${this.#conn}`, message, error);\n }\n this.#connected.reject(error);\n };\n\n close(err?: unknown) {\n if (err) {\n this.#lc.error?.(`closing stream with error`, err);\n }\n this.#closeStream();\n if (!this.closed()) {\n this.#ws.close();\n }\n }\n\n closed() {\n return (\n this.#ws.readyState === this.#ws.CLOSED ||\n this.#ws.readyState === this.#ws.CLOSING\n );\n }\n}\n"],"mappings":";;;;;;;;;;AA6BA,IAAM,mBAAmB;;;;;AA4CzB,SAAgB,OACd,IACA,IACA,UACA,aAA2B,CAAC,GAC5B,YAAyB,CAAC,GAC1B,gBAA+B,CAAC,GACc;CAC9C,MAAM,WAAW,GAAG,OAAO;CAC3B,SAAS,MAAM,KAAe;EAC5B,IAAI,GAAG,eAAe,GAAG,UAAU,GAAG,eAAe,GAAG,SACtD,IAAI,KACF,eAAe,IAAI,IAAI,GAAG;OACrB;GACL,GAAG,OAAO,yBAAyB,UAAU;GAC7C,GAAG,MAAM;EACX;CAEJ;CAEA,MAAM,WAAW,aAAa,OAAW;EACvC,GAAG;EACH,UAAU,YAAY,QAAQ;GAC5B,UAAU,UAAU,YAAY,GAAG;GACnC,MAAM,GAAG;EACX;CACF,CAAC;CACD,MAAM,YAAY,aAAa,OAAY;EACzC,GAAG;EACH,UAAU,YAAY,QAAQ;GAC5B,WAAW,UAAU,YAAY,GAAG;GACpC,MAAM,GAAG;EACX;CACF,CAAC;CAED,MAAM,SAAS,sBAAsB,IAAI;EACvC,GAAG;EACH,eAAe;CACjB,CAAC;CAGD,SAAS,YAAY;EAEnB,OAAO,GAAG,eAAe,GAAG,QAAQ,uBAAuB,UAAU,CAAC;EAEtE,SACE,SAAS,KAAK,SAAS,GACvB,IAAI,UAAU;GACZ,YAAY;GACZ,YAAY,KAAK,WAAW,aAC1B,SAAS,MAAM,WAAW,UAAU,GAAG,CAAC;EAC5C,CAAC,GACD,SACA,QAAQ,MAAM,UAAU,KAAK,GAAG,IAAI,UAAU,OAAO,CACvD;CACF;CAEA,IAAI,GAAG,eAAe,GAAG,YACvB,GAAG,GAAG,cAAc;EAClB,GAAG,OAAO,gBAAgB,UAAU;EACpC,UAAU;CACZ,CAAC;MAED,UAAU;CAIZ,KAAK;EACH,QAAQ;EACR,MAAM;EACN,QAAO,UAAS;GAEd,OAAO,MADM,WAAW,MAAM,MAAM,SAAS,CAC9B,GAAM,UAAU,aAAa;EAC9C;CACF,CAAC;CAED,qBAAqB,IAAI,IAAI,gBAAgB;CAE7C,OAAO;EAAC;EAAW;CAAQ;AAC7B;AASA,SAAgB,KAAQ,EAAC,QAAQ,MAAM,OAAO,kBAAiC;CAC7E,mBAAmB;CACnB,OAAO,kBAAkB,GAAG,qCAAqC;CACjE,MAAM,UAA8B,CAAC;CAErC,SACE,QACA,IAAI,SAAS;EACX,eAAe;EACf,QAAQ,OAAO,WAAW,aAAa;GACrC,IAAI;GACJ,IAAI;IACF,KAAK,MAAM,MAAM,KAAK,OAAO,MAAM;KACjC,SAAS;KACT;IACF;GACF,SAAS,KAAK;IACZ,SAAS,YAAY,GAAG,CAAC;IACzB;GACF;GAMA,MAAM,EAAC,WAAU,KAAK,KAAK,GAAG;GAC9B,QAAQ,KAAK,MAAM;GACnB,OAAY,WAAW,QAAQ,MAAM,CAAC;GAEtC,IAAI,QAAQ,UAAU,gBAEpB,SAAS;QAGT,QAAQ,GAAG,WACH,SAAS,IACf,QAAO,SAAS,YAAY,GAAG,CAAC,CAClC;EAEJ;EACA,UAAU,KAAK,aAAa;GAC1B,IAAI,KACF,KAAK,KAAK,YAAY,GAAG,CAAC;GAG5B,SAAS;EACX;EACA,QAAO,aAAY;GACjB,KAAK,OAAO;GACZ,SAAS;EACX;CACF,CAAC,IACD,QAAQ,MAAM,KAAK,KAAK,GAAG,IAAI,KAAK,OAAO,CAC7C;AACF;AAEA,SAAS,YAAY,KAAc;CACjC,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC3D;AAEA,IAAM,YAAY,eAAE,OAAO,EAAC,KAAK,eAAE,OAAO,EAAC,CAAC;AAU5C,SAAgB,UACd,IACA,QACA,MACe;CACf,OAAO,kBAAkB,IAAI,QAAQ,MAAM,WAAW,SAAS;AACjE;;;;AAKA,SAAgB,qBACd,IACA,QACA,MACe;CACf,OAAO,kBAAkB,IAAI,QAAQ,OAAM,SAAQ,IAAI;AACzD;AAEA,eAAe,kBACb,IACA,QACA,MACA,WACe;CACf,qBAAqB,IAAI,MAAM,gBAAgB;CAE/C,MAAM,SAAS,gBAAgB,UAAU,IAAI,MAAM,MAAM;CAEzD,MAAM,OAAO,IAAI,MAAW;CAC5B,KAAK,iBAAiB,YAAY,EAAC,WAAU;EAC3C,IAAI;GACF,IAAI,OAAO,SAAS,UAClB,MAAM,IAAI,MAAM,yBAAyB;GAE3C,KAAK,QAAQ,MAAQ,KAAK,MAAM,IAAI,GAAG,SAAS,CAAC;EACnD,SAAS,GAAG;GACV,GAAG,QAAQ,qBAAqB,CAAC;GACjC,OAAO,MAAM,CAAC;EAChB;CACF,CAAC;CAED,IAAI;EACF,IAAI,SAAS;EACb,MAAM,EAAC,aAAY;EACnB,IAAI,UAAU;GACZ,GAAG,QAAQ,mCAAmC;GAC9C,WAAW,MAAM,EAAC,OAAO,KAAK,cAAa,UAAU;IACnD,MAAM,KAAK,EAAE;IACb,MAAM,OAAO,SAAS,GAAG,SAAS,UAAU,GAAG,EAAE;IAGjD,KAAK,KAAK,IAAI;IAEd,CAAM,YAAY;KAChB,MAAM,EAAC,QAAO,MAAM,KAAK,QAAQ;KAEjC,IAAI,QAAQ,IACV,MAAM,IAAI,MAAM,sBAAsB,GAAG,IAAI,KAAK;KAEpD,SAAS;IACX,GAAG;GACL;EACF,OAAO;GACL,GAAG,QAAQ,qCAAqC;GAChD,WAAW,MAAM,OAAO,QAAQ;IAC9B,MAAM,KAAK,EAAE;IACb,MAAM,OAAO,SAAS,GAAG,SAAS,UAAU,GAAG,EAAE;IAGjD,KAAK,KAAK,IAAI;IAEd,MAAM,EAAC,QAAO,MAAM,KAAK,QAAQ;IACjC,IAAI,QAAQ,IACV,MAAM,IAAI,MAAM,sBAAsB,GAAG,IAAI,KAAK;GAEtD;EACF;EACA,OAAO,MAAM;CACf,SAAS,GAAG;EACV,OAAO,MAAM,CAAC;CAChB;AACF;AAEA,SAAgB,SACd,IACA,QACA,QACoB;CACpB,OAAO,iBAAiB,IAAI,QAAQ,SAAQ,SAAQ,IAAI;AAC1D;;;;;AAMA,SAAgB,oBACd,IACA,QACA,QACgC;CAChC,OAAO,iBAAiB,IAAI,QAAQ,SAAS,MAAM,OAAO,QAAQ;EAChE;EACA,MAAM,0BAA0B,OAAO,EAAE;CAC3C,EAAE;AACJ;AAEA,eAAe,iBACb,IACA,QACA,QACA,WACsB;CACtB,uBAAuB,IAAI,QAAQ,gBAAgB;CAEnD,MAAM,iBAAiB,eAAE,OAAO;EAC9B,KAAK;EACL,IAAI,eAAE,OAAO;CACf,CAAC;CAED,MAAM,OAAmD,IAAI,aAI3D;EACE,WAAW,EAAC,SAAQ,OAAO,KAAK,KAAK,UAAU,EAAC,KAAK,GAAE,CAAe,CAAC;EACvE,eAAe,OAAO,MAAM;CAC9B,IACC,EAAC,WAAU,IACd;CAEA,MAAM,SAAS,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,aAAa;CAEtE,SAAS,cAAc,OAAqB;EAC1C,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,IAAI,CAAC,KAAK,QAAQ;GAChB,GAAG,OAAO,4CAA4C,IAAI;GAC1D;EACF;EACA,IAAI;GAEF,MAAM,MAAM,MADE,WAAW,MAAM,IACX,GAAO,gBAAgB,aAAa;GAGxD,KAAK,KAAK;IAAC,IAAI,IAAI;IAAI,MAAM,UAAU,IAAI,KAAK,MAAM,IAAI,EAAE;GAAC,CAAC;EAChE,SAAS,GAAG;GACV,OAAO,MAAM,CAAC;EAChB;CACF;CAEA,MAAM,OAAO;CACb,OAAO;AACT;AAEA,SAAS,0BAA0B,OAAe,IAAoB;CACpE,MAAM,SAAS,SAAS,GAAG;CAC3B,OACE,MAAM,WAAW,MAAM,KAAK,MAAM,SAAS,GAAG,GAC9C,gCACF;CACA,OAAO,MAAM,MAAM,OAAO,QAAQ,EAAE;AACtC;AAEA,IAAM,kBAAN,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA,aAAsB,SAAS;CAE/B,IAAI,YAA2B;EAC7B,OAAO,KAAKI,WAAW;CACzB;CAEA,OAAO,UAAa,IAAgB,IAAe,QAAmB;EAGpE,OAAO,IAAI,gBAAgB,IAAI,UAAU,OAAO,OAAO,CAAC;CAC1D;CAEA,OAAO,QACL,IACA,IACA,QACA,gBACA;EAGA,OAAO,IAAI,gBAAgB,IAAI,UAAU,OAAO,IAAI,GAAG,cAAc;CACvE;CAEA,YACE,IACA,IACA,aACA,gBACA;EACA,KAAKJ,MAAM;EACX,KAAKC,MAAM;EACX,KAAKC,eAAe;EACpB,KAAKC,kBAAkB,kBAAkB;EAEzC,GAAG,iBAAiB,QAAQ,KAAKE,WAAW;EAC5C,GAAG,iBAAiB,SAAS,KAAKC,YAAY;EAC9C,GAAG,iBAAiB,SAAS,KAAKC,YAAY;EAC9C,IAAI,KAAKJ,iBACP,GAAG,iBAAiB,WAAW,KAAKA,eAAe;EAGrD,QAAQ,GAAG,YAAX;GACE,KAAK,GAAG,YACN;GACF,KAAK,GAAG;IACN,KAAKC,WAAW,QAAQ;IACxB;GACF;IACE,KAAKA,WAAW,uBACd,IAAI,MAAM,8BAA8B,GAAG,YAAY,CACzD;IACA;EACJ;CACF;CAEA,IAAII,QAAgB;EAClB,OAAO,gBAAgB,KAAKP,IAAI,MAAM,OAAO,KAAKA,IAAI,QAAQ;CAChE;CAEA,oBAAoB;EAClB,KAAKD,IAAI,OAAO,GAAG,KAAKQ,MAAM,aAAa;EAC3C,KAAKJ,WAAW,QAAQ;CAC1B;CAEA,gBAAgB,MAAkB;EAChC,MAAM,EAAC,MAAM,QAAQ,aAAY;EACjC,KAAKJ,IAAI,OAAO,GAAG,KAAKQ,MAAM,UAAU;GACtC;GACA;GACA;EACF,CAAC;EACD,KAAK,MAAM;EACX,KAAKJ,WAAW,OAAO,GAAG,KAAKI,MAAM,oBAAoB,MAAM;CACjE;CAEA,gBAAgB,EAAC,SAAS,YAAuB;EAC/C,IAAI,KAAKP,IAAI,eAAe,KAAKA,IAAI,MACnC,KAAKD,IAAI,QAAQ,YAAY,KAAKQ,SAAS,SAAS,KAAK;EAE3D,KAAKJ,WAAW,OAAO,KAAK;CAC9B;CAEA,MAAM,KAAe;EACnB,IAAI,KACF,KAAKJ,IAAI,QAAQ,6BAA6B,GAAG;EAEnD,KAAKE,aAAa;EAClB,IAAI,CAAC,KAAK,OAAO,GACf,KAAKD,IAAI,MAAM;CAEnB;CAEA,SAAS;EACP,OACE,KAAKA,IAAI,eAAe,KAAKA,IAAI,UACjC,KAAKA,IAAI,eAAe,KAAKA,IAAI;CAErC;AACF"}
|
|
@@ -90,6 +90,8 @@ export declare class Subscription<T, M = T> implements Source<T>, Sink<M> {
|
|
|
90
90
|
get queued(): number;
|
|
91
91
|
/** The number of messages dequeued but not yet "consumed" */
|
|
92
92
|
get consuming(): number;
|
|
93
|
+
/** Satisfies the Source interface. */
|
|
94
|
+
get signal(): AbortSignal;
|
|
93
95
|
/**
|
|
94
96
|
* Cancels the subscription after any queued messages are consumed. This is
|
|
95
97
|
* meant for the producer-side code.
|
|
@@ -115,17 +117,6 @@ export declare class Subscription<T, M = T> implements Source<T>, Sink<M> {
|
|
|
115
117
|
cancel(err?: Error): void;
|
|
116
118
|
/** Fails the subscription, cleans up, and throws from any iteration. */
|
|
117
119
|
fail(err: Error): void;
|
|
118
|
-
/**
|
|
119
|
-
* Resolves the result of the specified `other` Promise, or a
|
|
120
|
-
* resolved/rejected Promise when the Subscription {@link end}s,
|
|
121
|
-
* is {@link cancel}ed, or {@link fail}s.
|
|
122
|
-
*
|
|
123
|
-
* This is similar to using `Promise.race([other, subscriptionDone])`, but
|
|
124
|
-
* in a memory safe way, as repeatedly calling `Promise.race([ ... ])` with
|
|
125
|
-
* a long-lived Promise results in leaking memory via a growing list of
|
|
126
|
-
* `then()` callbacks (https://github.com/nodejs/node/issues/17469).
|
|
127
|
-
*/
|
|
128
|
-
doneOr<R>(other: Promise<R>): Promise<void | R>;
|
|
129
120
|
get pipeline(): AsyncIterable<{
|
|
130
121
|
value: T;
|
|
131
122
|
consumed: () => void;
|