@rocicorp/zero 1.10.0-canary.1 → 1.10.0-canary.3
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/zero/package.js +1 -1
- 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.map +1 -1
- package/out/zero-cache/src/config/normalize.js +7 -2
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +14 -4
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +26 -8
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.d.ts +5 -2
- package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +5 -3
- 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 +24 -13
- package/out/zero-cache/src/server/change-streamer.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/change-source/pg/backfill-stream.js +1 -1
- 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.js +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.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +51 -74
- 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-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/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 +149 -27
- 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 +3 -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/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 +23 -0
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +40 -0
- package/out/zero-cache/src/services/change-streamer/storer.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/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/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/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.map +1 -1
- package/out/zero-cache/src/types/pg.js +14 -4
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/poke-chunk.d.ts +18 -0
- package/out/zero-cache/src/types/poke-chunk.d.ts.map +1 -0
- package/out/zero-cache/src/types/poke-chunk.js +57 -0
- package/out/zero-cache/src/types/poke-chunk.js.map +1 -0
- package/out/zero-cache/src/types/processes.js +1 -4
- package/out/zero-cache/src/types/processes.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-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.d.ts +7 -2
- package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js +52 -2
- package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +38 -6
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +1 -1
- package/out/zero-protocol/src/poke.d.ts +89 -7
- package/out/zero-protocol/src/poke.d.ts.map +1 -1
- package/out/zero-protocol/src/poke.js +16 -8
- package/out/zero-protocol/src/poke.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts +12 -7
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +10 -2
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +14 -6
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/stream.d.ts +0 -1
- package/out/zql/src/ivm/stream.d.ts.map +1 -1
- package/out/zql/src/ivm/stream.js +1 -7
- package/out/zql/src/ivm/stream.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +11 -2
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zqlite/src/db.js +2 -2
- package/out/zqlite/src/sqlite-cost-model.js +5 -5
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +4 -4
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
- 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"}
|
|
@@ -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;AAmBH,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,IAEhC,SAAS,oBAAoB,KAAG,MAAM,CAkD/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,EACF,IAAI,CAAC,EAAE,WAAW,GACjB,UAAU,
|
|
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;AAmBH,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,IAEhC,SAAS,oBAAoB,KAAG,MAAM,CAkD/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,EACF,IAAI,CAAC,EAAE,WAAW,GACjB,UAAU,CA6EZ;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"}
|
|
@@ -193,19 +193,29 @@ function inactivityTimeoutSocket(lc, timeoutMs = SOCKET_INACTIVITY_TIMEOUT_MS) {
|
|
|
193
193
|
}
|
|
194
194
|
function pgClient(lc, connectionURI, applicationName, options, opts) {
|
|
195
195
|
applicationName = `zero-${applicationName}`;
|
|
196
|
+
const safeNotice = (n) => ({
|
|
197
|
+
severity: n.severity,
|
|
198
|
+
code: n.code,
|
|
199
|
+
message: n.message,
|
|
200
|
+
schema: n.schema_name,
|
|
201
|
+
table: n.table_name,
|
|
202
|
+
column: n.column_name,
|
|
203
|
+
constraint: n.constraint_name,
|
|
204
|
+
routine: n.routine
|
|
205
|
+
});
|
|
196
206
|
const onnotice = (n) => {
|
|
197
207
|
switch (n.severity) {
|
|
198
208
|
case "NOTICE": return;
|
|
199
209
|
case "DEBUG":
|
|
200
|
-
lc.debug?.(n);
|
|
210
|
+
lc.debug?.("pg notice", safeNotice(n));
|
|
201
211
|
return;
|
|
202
212
|
case "WARNING":
|
|
203
|
-
lc.warn?.(n);
|
|
213
|
+
lc.warn?.("pg notice", safeNotice(n));
|
|
204
214
|
return;
|
|
205
215
|
case "EXCEPTION":
|
|
206
|
-
lc.error?.(n);
|
|
216
|
+
lc.error?.("pg notice", safeNotice(n));
|
|
207
217
|
return;
|
|
208
|
-
default: lc.info?.(n);
|
|
218
|
+
default: lc.info?.("pg notice", safeNotice(n));
|
|
209
219
|
}
|
|
210
220
|
};
|
|
211
221
|
const url = new URL(connectionURI);
|
|
@@ -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":"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"}
|
|
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 // 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 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":"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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"}
|
|
@@ -3,7 +3,9 @@ import WebSocket from 'ws';
|
|
|
3
3
|
import type { Downstream } from '../../../zero-protocol/src/down.ts';
|
|
4
4
|
import type { ErrorBody } from '../../../zero-protocol/src/error.ts';
|
|
5
5
|
import { type ProtocolError } from '../../../zero-protocol/src/error.ts';
|
|
6
|
+
import { type PokeChunk } from '../../../zero-protocol/src/poke.ts';
|
|
6
7
|
import { type Upstream } from '../../../zero-protocol/src/up.ts';
|
|
8
|
+
import type { ViewSyncerDownstream } from '../types/downstream.ts';
|
|
7
9
|
import type { Source } from '../types/streams.ts';
|
|
8
10
|
import type { ConnectParams } from './connect-params.ts';
|
|
9
11
|
export type HandlerResult = {
|
|
@@ -17,7 +19,11 @@ export type HandlerResult = {
|
|
|
17
19
|
} | StreamResult;
|
|
18
20
|
export type StreamResult = {
|
|
19
21
|
type: 'stream';
|
|
20
|
-
source: 'viewSyncer'
|
|
22
|
+
source: 'viewSyncer';
|
|
23
|
+
stream: Source<ViewSyncerDownstream>;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'stream';
|
|
26
|
+
source: 'pusher';
|
|
21
27
|
stream: Source<Downstream>;
|
|
22
28
|
};
|
|
23
29
|
export interface MessageHandler {
|
|
@@ -45,13 +51,25 @@ export declare class Connection {
|
|
|
45
51
|
init(): boolean;
|
|
46
52
|
close(reason: string, ...args: unknown[]): void;
|
|
47
53
|
handleInitConnection(initConnectionMsg: string): Promise<void>;
|
|
48
|
-
send(data: Downstream, callback: ((err?: Error | null) => void) | 'ignore-backpressure'): void;
|
|
54
|
+
send(data: Downstream, callback: ((err?: Error | null) => void) | 'ignore-backpressure', serialized?: string | undefined): void;
|
|
49
55
|
sendError(errorBody: ErrorBody, thrown?: unknown): void;
|
|
50
56
|
}
|
|
51
57
|
export type WebSocketLike = Pick<WebSocket, 'readyState'> & {
|
|
52
|
-
send(data: string, cb?: (err?: Error) => void): void;
|
|
58
|
+
send(data: string | PokeChunk, cb?: (err?: Error) => void): void;
|
|
53
59
|
};
|
|
54
|
-
export declare function send(lc: LogContext, ws: WebSocketLike, data: Downstream, callback: ((err?: Error | null) => void) | 'ignore-backpressure'): void;
|
|
60
|
+
export declare function send(lc: LogContext, ws: WebSocketLike, data: Downstream, callback: ((err?: Error | null) => void) | 'ignore-backpressure', serialized?: string | undefined): void;
|
|
61
|
+
/**
|
|
62
|
+
* Sends downstream messages in the format supported by a connection's sync
|
|
63
|
+
* protocol version. Keep version forks contained here so the view-syncer can
|
|
64
|
+
* continue producing one canonical poke representation.
|
|
65
|
+
*
|
|
66
|
+
* Exported for compatibility testing.
|
|
67
|
+
*/
|
|
68
|
+
export declare class DownstreamSender {
|
|
69
|
+
#private;
|
|
70
|
+
constructor(lc: LogContext, ws: WebSocketLike, protocolVersion: number);
|
|
71
|
+
send(data: Downstream, callback: ((err?: Error | null) => void) | 'ignore-backpressure', serialized?: string | undefined): void;
|
|
72
|
+
}
|
|
55
73
|
export declare function sendError(lc: LogContext, ws: WebSocket, errorBody: ErrorBody, thrown?: unknown): void;
|
|
56
74
|
export declare function findProtocolError(error: unknown): ProtocolError | undefined;
|
|
57
75
|
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/connection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAW,MAAM,kBAAkB,CAAC;AAE3D,OAAO,SAAkC,MAAM,IAAI,CAAC;AAIpD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oCAAoC,CAAC;AAGnE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qCAAqC,CAAC;AACnE,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,10 +3,12 @@ import { parse } from "../../../shared/src/valita.js";
|
|
|
3
3
|
import { Internal, InvalidMessage, VersionNotSupported } from "../../../zero-protocol/src/error-kind-enum.js";
|
|
4
4
|
import { ZeroCache } from "../../../zero-protocol/src/error-origin-enum.js";
|
|
5
5
|
import { isProtocolError } from "../../../zero-protocol/src/error.js";
|
|
6
|
+
import "../../../zero-protocol/src/poke.js";
|
|
6
7
|
import "../../../zero-protocol/src/protocol-version.js";
|
|
7
8
|
import { getOrCreateCounter } from "../observability/metrics.js";
|
|
8
9
|
import { ProtocolErrorWithLevel, getLogLevel, wrapWithProtocolError } from "../types/error-with-level.js";
|
|
9
10
|
import { upstreamSchema } from "../../../zero-protocol/src/up.js";
|
|
11
|
+
import { PokeChunkEncoder } from "../types/poke-chunk.js";
|
|
10
12
|
import WebSocket$1, { createWebSocketStream } from "ws";
|
|
11
13
|
import { Readable, Writable, pipeline } from "node:stream";
|
|
12
14
|
//#region ../zero-cache/src/workers/connection.ts
|
|
@@ -29,6 +31,7 @@ var Connection = class {
|
|
|
29
31
|
#lc;
|
|
30
32
|
#onClose;
|
|
31
33
|
#messageHandler;
|
|
34
|
+
#downstreamSender;
|
|
32
35
|
#downstreamMsgTimer;
|
|
33
36
|
#webSocketErrors = getOrCreateCounter("sync", "websocket.errors", "Client WebSocket error events.");
|
|
34
37
|
#viewSyncerOutboundStream;
|
|
@@ -41,6 +44,7 @@ var Connection = class {
|
|
|
41
44
|
this.#wsID = wsID;
|
|
42
45
|
this.#protocolVersion = protocolVersion;
|
|
43
46
|
this.#lc = lc.withContext("connection").withContext("clientID", clientID).withContext("clientGroupID", clientGroupID).withContext("wsID", wsID);
|
|
47
|
+
this.#downstreamSender = new DownstreamSender(this.#lc, ws, protocolVersion);
|
|
44
48
|
this.#lc.debug?.("new connection");
|
|
45
49
|
this.#onClose = onClose;
|
|
46
50
|
this.#ws.addEventListener("close", this.#handleClose);
|
|
@@ -56,9 +60,9 @@ var Connection = class {
|
|
|
56
60
|
* will only parse messages with schema(s) of supported protocol versions.
|
|
57
61
|
*/
|
|
58
62
|
init() {
|
|
59
|
-
if (this.#protocolVersion >
|
|
63
|
+
if (this.#protocolVersion > 52 || this.#protocolVersion < 30) this.#closeWithError({
|
|
60
64
|
kind: VersionNotSupported,
|
|
61
|
-
message: `server is at sync protocol
|
|
65
|
+
message: `server is at sync protocol v52 and does not support v${this.#protocolVersion}. The ${this.#protocolVersion > 52 ? "server" : "client"} must be updated to a newer release.`,
|
|
62
66
|
origin: ZeroCache
|
|
63
67
|
});
|
|
64
68
|
else {
|
|
@@ -134,7 +138,8 @@ var Connection = class {
|
|
|
134
138
|
this.#pusherOutboundStream = result.stream;
|
|
135
139
|
break;
|
|
136
140
|
}
|
|
137
|
-
this.#proxyOutbound(result.stream);
|
|
141
|
+
if (result.source === "viewSyncer") this.#proxyOutbound(result.stream, (downstream, callback) => this.send(downstream.message, callback, downstream.serialized));
|
|
142
|
+
else this.#proxyOutbound(result.stream, (downstream, callback) => this.send(downstream, callback));
|
|
138
143
|
break;
|
|
139
144
|
case "transient": for (const error of result.errors) this.sendError(error);
|
|
140
145
|
}
|
|
@@ -163,10 +168,10 @@ var Connection = class {
|
|
|
163
168
|
this.#handleMessage({ data }).then(() => callback(), callback);
|
|
164
169
|
} }), () => {});
|
|
165
170
|
}
|
|
166
|
-
#proxyOutbound(outboundStream) {
|
|
171
|
+
#proxyOutbound(outboundStream, sendMessage) {
|
|
167
172
|
pipeline(Readable.from(outboundStream), new Writable({
|
|
168
173
|
objectMode: true,
|
|
169
|
-
write: (downstream, _encoding, callback) =>
|
|
174
|
+
write: (downstream, _encoding, callback) => sendMessage(downstream, callback)
|
|
170
175
|
}), (e) => e ? this.#closeWithThrown(e) : this.close(`downstream closed by ViewSyncer`));
|
|
171
176
|
}
|
|
172
177
|
#closeWithThrown(e) {
|
|
@@ -184,28 +189,24 @@ var Connection = class {
|
|
|
184
189
|
this.send(["pong", {}], "ignore-backpressure");
|
|
185
190
|
}
|
|
186
191
|
};
|
|
187
|
-
send(data, callback) {
|
|
192
|
+
send(data, callback, serialized) {
|
|
188
193
|
this.#lastDownstreamMsgTime = Date.now();
|
|
189
|
-
|
|
194
|
+
this.#downstreamSender.send(data, callback, serialized);
|
|
190
195
|
}
|
|
191
196
|
sendError(errorBody, thrown) {
|
|
192
197
|
sendError(this.#lc, this.#ws, errorBody, thrown);
|
|
193
198
|
}
|
|
194
199
|
};
|
|
195
|
-
function send(lc, ws, data, callback) {
|
|
200
|
+
function send(lc, ws, data, callback, serialized) {
|
|
196
201
|
if (ws.readyState === WebSocket$1.OPEN) {
|
|
197
|
-
|
|
202
|
+
serialized ??= JSON.stringify(data);
|
|
198
203
|
if (callback === "ignore-backpressure") {
|
|
199
204
|
ws.send(serialized);
|
|
200
205
|
return;
|
|
201
206
|
}
|
|
202
207
|
let completed = false;
|
|
203
208
|
const timer = setTimeout(() => {
|
|
204
|
-
complete(
|
|
205
|
-
kind: Internal,
|
|
206
|
-
message: `WebSocket send timed out after ${WEBSOCKET_SEND_TIMEOUT_MS} ms`,
|
|
207
|
-
origin: ZeroCache
|
|
208
|
-
}, "info"));
|
|
209
|
+
complete(webSocketSendTimeoutError());
|
|
209
210
|
}, WEBSOCKET_SEND_TIMEOUT_MS);
|
|
210
211
|
timer.unref();
|
|
211
212
|
const complete = (error) => {
|
|
@@ -224,6 +225,103 @@ function send(lc, ws, data, callback) {
|
|
|
224
225
|
}, "info"));
|
|
225
226
|
}
|
|
226
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Sends downstream messages in the format supported by a connection's sync
|
|
230
|
+
* protocol version. Keep version forks contained here so the view-syncer can
|
|
231
|
+
* continue producing one canonical poke representation.
|
|
232
|
+
*
|
|
233
|
+
* Exported for compatibility testing.
|
|
234
|
+
*/
|
|
235
|
+
var DownstreamSender = class {
|
|
236
|
+
#lc;
|
|
237
|
+
#ws;
|
|
238
|
+
#protocolVersion;
|
|
239
|
+
#pokeChunkEncoder;
|
|
240
|
+
constructor(lc, ws, protocolVersion) {
|
|
241
|
+
this.#lc = lc;
|
|
242
|
+
this.#ws = ws;
|
|
243
|
+
this.#protocolVersion = protocolVersion;
|
|
244
|
+
}
|
|
245
|
+
send(data, callback, serialized) {
|
|
246
|
+
if (this.#protocolVersion >= 52) switch (data[0]) {
|
|
247
|
+
case "pokeStart":
|
|
248
|
+
assert(this.#pokeChunkEncoder === void 0, "cannot start a poke while another poke is in progress");
|
|
249
|
+
this.#pokeChunkEncoder = new PokeChunkEncoder();
|
|
250
|
+
break;
|
|
251
|
+
case "pokePart":
|
|
252
|
+
this.#sendPokePart(data, callback, serialized);
|
|
253
|
+
return;
|
|
254
|
+
case "pokeEnd":
|
|
255
|
+
this.#sendPokeEnd(data, callback);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
send(this.#lc, this.#ws, data, callback, serialized);
|
|
259
|
+
}
|
|
260
|
+
#sendPokePart(pokePart, callback, serialized) {
|
|
261
|
+
const encoder = this.#pokeChunkEncoder;
|
|
262
|
+
assert(encoder, "pokePart received without a pokeStart");
|
|
263
|
+
serialized ??= JSON.stringify(pokePart);
|
|
264
|
+
assert(serialized.startsWith(POKE_PART_PREFIX) && serialized.endsWith("]"), "invalid serialized pokePart");
|
|
265
|
+
encoder.addPatch(serialized.slice(POKE_PART_PREFIX.length, -1), (chunk) => sendBinary(this.#lc, this.#ws, chunk)).then(() => invokeCallback(callback), (error) => invokeCallback(callback, toError(error)));
|
|
266
|
+
}
|
|
267
|
+
#sendPokeEnd(pokeEnd, callback) {
|
|
268
|
+
const encoder = this.#pokeChunkEncoder;
|
|
269
|
+
assert(encoder, "pokeEnd received without a pokeStart");
|
|
270
|
+
this.#pokeChunkEncoder = void 0;
|
|
271
|
+
if (pokeEnd[1].cancel) {
|
|
272
|
+
encoder.cancel();
|
|
273
|
+
send(this.#lc, this.#ws, pokeEnd, callback);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
encoder.finish((chunk) => sendBinary(this.#lc, this.#ws, chunk)).then(() => sendAsync(this.#lc, this.#ws, pokeEnd)).then(() => invokeCallback(callback), (error) => invokeCallback(callback, toError(error)));
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
var POKE_PART_PREFIX = "[\"pokePart\",";
|
|
280
|
+
function sendAsync(lc, ws, data) {
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
send(lc, ws, data, (error) => error ? reject(error) : resolve());
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function sendBinary(lc, ws, chunk) {
|
|
286
|
+
return new Promise((resolve, reject) => {
|
|
287
|
+
if (ws.readyState !== WebSocket$1.OPEN) {
|
|
288
|
+
const error = webSocketClosedError();
|
|
289
|
+
lc.debug?.(`Dropping outbound binary message on ws (state: ${ws.readyState})`);
|
|
290
|
+
reject(error);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
let completed = false;
|
|
294
|
+
const timer = setTimeout(() => complete(webSocketSendTimeoutError()), WEBSOCKET_SEND_TIMEOUT_MS);
|
|
295
|
+
timer.unref();
|
|
296
|
+
const complete = (error) => {
|
|
297
|
+
if (completed) return;
|
|
298
|
+
completed = true;
|
|
299
|
+
clearTimeout(timer);
|
|
300
|
+
error ? reject(error) : resolve();
|
|
301
|
+
};
|
|
302
|
+
ws.send(chunk, complete);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function invokeCallback(callback, error) {
|
|
306
|
+
if (callback !== "ignore-backpressure") callback(error);
|
|
307
|
+
}
|
|
308
|
+
function toError(error) {
|
|
309
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
310
|
+
}
|
|
311
|
+
function webSocketClosedError() {
|
|
312
|
+
return new ProtocolErrorWithLevel({
|
|
313
|
+
kind: Internal,
|
|
314
|
+
message: "WebSocket closed",
|
|
315
|
+
origin: ZeroCache
|
|
316
|
+
}, "info");
|
|
317
|
+
}
|
|
318
|
+
function webSocketSendTimeoutError() {
|
|
319
|
+
return new ProtocolErrorWithLevel({
|
|
320
|
+
kind: Internal,
|
|
321
|
+
message: `WebSocket send timed out after ${WEBSOCKET_SEND_TIMEOUT_MS} ms`,
|
|
322
|
+
origin: ZeroCache
|
|
323
|
+
}, "info");
|
|
324
|
+
}
|
|
227
325
|
function sendError(lc, ws, errorBody, thrown) {
|
|
228
326
|
lc = lc.withContext("errorKind", errorBody.kind);
|
|
229
327
|
let logLevel;
|