@rocicorp/zero 0.26.0-canary.2 → 0.26.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/README.md +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +4 -4
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +22 -19
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/refresh.d.ts.map +1 -1
- package/out/replicache/src/persist/refresh.js +0 -8
- package/out/replicache/src/persist/refresh.js.map +1 -1
- package/out/replicache/src/process-scheduler.d.ts +23 -0
- package/out/replicache/src/process-scheduler.d.ts.map +1 -1
- package/out/replicache/src/process-scheduler.js +50 -1
- package/out/replicache/src/process-scheduler.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +8 -0
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +11 -2
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/shared/src/falsy.d.ts +3 -0
- package/out/shared/src/falsy.d.ts.map +1 -0
- package/out/zero/package.json.js +1 -1
- package/out/zero/src/adapters/drizzle.js +1 -2
- package/out/zero/src/adapters/prisma.d.ts +2 -0
- package/out/zero/src/adapters/prisma.d.ts.map +1 -0
- package/out/zero/src/adapters/prisma.js +6 -0
- package/out/zero/src/adapters/prisma.js.map +1 -0
- package/out/zero/src/pg.js +4 -7
- package/out/zero/src/react.js +3 -1
- package/out/zero/src/react.js.map +1 -1
- package/out/zero/src/server.js +5 -8
- package/out/zero-cache/src/auth/load-permissions.d.ts +3 -2
- package/out/zero-cache/src/auth/load-permissions.d.ts.map +1 -1
- package/out/zero-cache/src/auth/load-permissions.js +14 -8
- package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.d.ts +6 -0
- package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +16 -3
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +44 -8
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +53 -13
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.d.ts +3 -0
- package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +26 -0
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/db/lite-tables.js +1 -1
- package/out/zero-cache/src/db/lite-tables.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +9 -3
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/migration.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration.js +9 -3
- package/out/zero-cache/src/db/migration.js.map +1 -1
- package/out/zero-cache/src/db/specs.d.ts +4 -3
- package/out/zero-cache/src/db/specs.d.ts.map +1 -1
- package/out/zero-cache/src/db/specs.js +4 -1
- package/out/zero-cache/src/db/specs.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +9 -3
- package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.d.ts +1 -1
- package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js +11 -30
- package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
- package/out/zero-cache/src/server/main.js +1 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/priority-op.d.ts +8 -0
- package/out/zero-cache/src/server/priority-op.d.ts.map +1 -0
- package/out/zero-cache/src/server/priority-op.js +29 -0
- package/out/zero-cache/src/server/priority-op.js.map +1 -0
- package/out/zero-cache/src/server/syncer.d.ts +0 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +3 -21
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/analyze.js +1 -1
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +4 -3
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +68 -13
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +7 -2
- 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.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +7 -4
- 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/schema/ddl.d.ts +125 -180
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +18 -10
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +36 -90
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/published.js +51 -14
- package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +31 -36
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +24 -3
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/validation.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/validation.js +2 -4
- package/out/zero-cache/src/services/change-source/pg/schema/validation.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +158 -53
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +55 -10
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +210 -72
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +4 -2
- package/out/zero-cache/src/services/change-source/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/replica-schema.js +19 -10
- package/out/zero-cache/src/services/change-source/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +1 -1
- 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 +71 -25
- 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 +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +1 -0
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +6 -5
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +2 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +14 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/heapz.d.ts.map +1 -1
- package/out/zero-cache/src/services/heapz.js +1 -0
- package/out/zero-cache/src/services/heapz.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/error.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/error.js +4 -1
- package/out/zero-cache/src/services/mutagen/error.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 -0
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.d.ts +7 -4
- package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +80 -8
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +21 -29
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +1 -2
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/change-log.js +2 -5
- package/out/zero-cache/src/services/replicator/schema/change-log.js.map +1 -1
- package/out/zero-cache/src/services/{change-source → replicator/schema}/column-metadata.d.ts +3 -3
- package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts.map +1 -0
- package/out/zero-cache/src/services/{change-source → replicator/schema}/column-metadata.js +3 -3
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js +3 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +1 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/statz.d.ts.map +1 -1
- package/out/zero-cache/src/services/statz.js +1 -0
- package/out/zero-cache/src/services/statz.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +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 +59 -40
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +0 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +23 -6
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +13 -14
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +44 -56
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js +22 -11
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +6 -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 +192 -217
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/lexi-version.d.ts.map +1 -1
- package/out/zero-cache/src/types/lexi-version.js +4 -1
- package/out/zero-cache/src/types/lexi-version.js.map +1 -1
- package/out/zero-cache/src/types/lite.d.ts.map +1 -1
- package/out/zero-cache/src/types/lite.js +8 -2
- package/out/zero-cache/src/types/lite.js.map +1 -1
- package/out/zero-cache/src/types/shards.js +1 -1
- package/out/zero-cache/src/types/shards.js.map +1 -1
- package/out/zero-cache/src/types/sql.d.ts +5 -0
- package/out/zero-cache/src/types/sql.d.ts.map +1 -1
- package/out/zero-cache/src/types/sql.js +5 -1
- package/out/zero-cache/src/types/sql.js.map +1 -1
- package/out/zero-cache/src/types/subscription.js +1 -1
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/workers/connect-params.d.ts +1 -0
- package/out/zero-cache/src/workers/connect-params.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connect-params.js +2 -1
- package/out/zero-cache/src/workers/connect-params.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +14 -6
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +17 -10
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/connection-manager.d.ts +8 -0
- package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-manager.js +33 -0
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/connection.d.ts.map +1 -1
- package/out/zero-client/src/client/connection.js +6 -3
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/error.js +1 -1
- package/out/zero-client/src/client/error.js.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.js +15 -1
- package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +10 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/query-manager.d.ts +4 -0
- package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/query-manager.js +7 -0
- package/out/zero-client/src/client/query-manager.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts +3 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +52 -7
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.d.ts +1 -0
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-protocol/src/connect.d.ts +4 -0
- package/out/zero-protocol/src/connect.d.ts.map +1 -1
- package/out/zero-protocol/src/connect.js +3 -1
- package/out/zero-protocol/src/connect.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/push.d.ts +11 -2
- package/out/zero-protocol/src/push.d.ts.map +1 -1
- package/out/zero-protocol/src/push.js +22 -6
- package/out/zero-protocol/src/push.js.map +1 -1
- package/out/zero-protocol/src/up.d.ts +2 -0
- package/out/zero-protocol/src/up.d.ts.map +1 -1
- package/out/zero-react/src/mod.d.ts +3 -1
- package/out/zero-react/src/mod.d.ts.map +1 -1
- package/out/zero-react/src/paging-reducer.d.ts +61 -0
- package/out/zero-react/src/paging-reducer.d.ts.map +1 -0
- package/out/zero-react/src/paging-reducer.js +77 -0
- package/out/zero-react/src/paging-reducer.js.map +1 -0
- package/out/zero-react/src/use-query.d.ts +11 -1
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +13 -11
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-react/src/use-rows.d.ts +39 -0
- package/out/zero-react/src/use-rows.d.ts.map +1 -0
- package/out/zero-react/src/use-rows.js +130 -0
- package/out/zero-react/src/use-rows.js.map +1 -0
- package/out/zero-react/src/use-zero-virtualizer.d.ts +122 -0
- package/out/zero-react/src/use-zero-virtualizer.d.ts.map +1 -0
- package/out/zero-react/src/use-zero-virtualizer.js +342 -0
- package/out/zero-react/src/use-zero-virtualizer.js.map +1 -0
- package/out/zero-react/src/zero-provider.js +1 -1
- package/out/zero-react/src/zero-provider.js.map +1 -1
- package/out/zero-server/src/adapters/drizzle.d.ts +18 -18
- package/out/zero-server/src/adapters/drizzle.d.ts.map +1 -1
- package/out/zero-server/src/adapters/drizzle.js +8 -22
- package/out/zero-server/src/adapters/drizzle.js.map +1 -1
- package/out/zero-server/src/adapters/pg.d.ts +19 -13
- package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
- package/out/zero-server/src/adapters/pg.js.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.d.ts +19 -13
- package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
- package/out/zero-server/src/adapters/prisma.d.ts +66 -0
- package/out/zero-server/src/adapters/prisma.d.ts.map +1 -0
- package/out/zero-server/src/adapters/prisma.js +63 -0
- package/out/zero-server/src/adapters/prisma.js.map +1 -0
- package/out/zero-server/src/custom.js +1 -15
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/mod.d.ts +9 -8
- package/out/zero-server/src/mod.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.d.ts +2 -2
- package/out/zero-server/src/process-mutations.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.js +4 -8
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zero-server/src/push-processor.js +1 -1
- package/out/zero-server/src/push-processor.js.map +1 -1
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +4 -1
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-server/src/zql-database.d.ts.map +1 -1
- package/out/zero-server/src/zql-database.js +17 -8
- package/out/zero-server/src/zql-database.js.map +1 -1
- package/out/zero-solid/src/mod.d.ts +1 -1
- package/out/zero-solid/src/mod.d.ts.map +1 -1
- package/out/zero-solid/src/use-query.d.ts +10 -1
- package/out/zero-solid/src/use-query.d.ts.map +1 -1
- package/out/zero-solid/src/use-query.js +21 -5
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-zero.js +1 -1
- package/out/zero-solid/src/use-zero.js.map +1 -1
- package/out/zql/src/ivm/constraint.d.ts.map +1 -1
- package/out/zql/src/ivm/constraint.js +4 -1
- package/out/zql/src/ivm/constraint.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +4 -1
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
- package/out/zql/src/ivm/join-utils.js +8 -2
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +12 -3
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +25 -2
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +24 -6
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +12 -3
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +1 -2
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +6 -2
- package/out/zero-cache/src/services/change-source/column-metadata.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-source/column-metadata.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lite.js","sources":["../../../../../zero-cache/src/types/lite.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {stringify, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../../zero-schema/src/table-schema.ts';\nimport type {ColumnSpec, LiteTableSpec} from '../db/specs.ts';\nimport {dataTypeToZqlValueType as upstreamDataTypeToZqlValueType} from './pg-data-type.ts';\nimport type {PostgresValueType} from './pg.ts';\nimport type {RowValue} from './row-key.ts';\n\n/** Javascript value types supported by better-sqlite3. */\nexport type LiteValueType = number | bigint | string | null | Uint8Array;\n\nexport type LiteRow = Readonly<Record<string, LiteValueType>>;\nexport type LiteRowKey = LiteRow; // just for API readability\n\nfunction columnType(col: string, table: LiteTableSpec) {\n const spec = table.columns[col];\n assert(spec, `Unknown column ${col} in table ${table.name}`);\n return spec.dataType;\n}\n\nexport const JSON_STRINGIFIED = 's';\nexport const JSON_PARSED = 'p';\n\nexport type JSONFormat = typeof JSON_STRINGIFIED | typeof JSON_PARSED;\n\n/**\n * Creates a LiteRow from the supplied RowValue. A copy of the `row`\n * is made only if a value conversion is performed.\n */\nexport function liteRow(\n row: RowValue,\n table: LiteTableSpec,\n jsonFormat: JSONFormat,\n): {row: LiteRow; numCols: number} {\n let copyNeeded = false;\n let numCols = 0;\n\n for (const key in row) {\n numCols++;\n const val = row[key];\n const liteVal = liteValue(val, columnType(key, table), jsonFormat);\n if (val !== liteVal) {\n copyNeeded = true;\n break;\n }\n }\n if (!copyNeeded) {\n return {row: row as unknown as LiteRow, numCols};\n }\n // Slow path for when a conversion is needed.\n numCols = 0;\n const converted: Record<string, LiteValueType> = {};\n for (const key in row) {\n numCols++;\n converted[key] = liteValue(row[key], columnType(key, table), jsonFormat);\n }\n return {row: converted, numCols};\n}\n\n/**\n * Postgres values types that are supported by SQLite are stored as-is.\n * This includes Uint8Arrays for the `bytea` / `BLOB` type.\n * * `boolean` values are converted to `0` or `1` integers.\n * * `PreciseDate` values are converted to epoch microseconds.\n * * JSON and Array values are stored as `JSON.stringify()` strings.\n *\n * Note that this currently does not handle the `bytea[]` type, but that's\n * already a pretty questionable type.\n */\nexport function liteValue(\n val: PostgresValueType,\n pgType: string,\n jsonFormat: JSONFormat,\n): LiteValueType {\n if (val instanceof Uint8Array || val === null) {\n return val;\n }\n const valueType = liteTypeToZqlValueType(pgType);\n if (valueType === 'json') {\n if (jsonFormat === JSON_STRINGIFIED && typeof val === 'string') {\n // JSON and JSONB values are already strings if the JSON was not parsed.\n return val;\n }\n // Non-JSON/JSONB values will always appear as objects / arrays.\n return stringify(val);\n }\n const obj = toLiteValue(val);\n return obj && typeof obj === 'object' ? stringify(obj) : obj;\n}\n\nfunction toLiteValue(val: JSONValue): Exclude<JSONValue, boolean> {\n switch (typeof val) {\n case 'string':\n case 'number':\n case 'bigint':\n return val;\n case 'boolean':\n return val ? 1 : 0;\n }\n if (val === null) {\n return val;\n }\n if (Array.isArray(val)) {\n return val.map(v => toLiteValue(v));\n }\n assert(\n val.constructor?.name === 'Object',\n `Unhandled object type ${val.constructor?.name}`,\n );\n return val; // JSON\n}\n\nexport function mapLiteDataTypeToZqlSchemaValue(\n liteDataType: LiteTypeString,\n): SchemaValue {\n return {type: mapLiteDataTypeToZqlValueType(liteDataType)};\n}\n\nfunction mapLiteDataTypeToZqlValueType(dataType: LiteTypeString): ValueType {\n const type = liteTypeToZqlValueType(dataType);\n if (type === undefined) {\n throw new Error(`Unsupported data type ${dataType}`);\n }\n return type;\n}\n\n// Note: Includes the \"TEXT\" substring for SQLite type affinity\nconst TEXT_ENUM_ATTRIBUTE = '|TEXT_ENUM';\nconst NOT_NULL_ATTRIBUTE = '|NOT_NULL';\nexport const TEXT_ARRAY_ATTRIBUTE = '|TEXT_ARRAY';\n\n/**\n * The `LiteTypeString` utilizes SQLite's loose type system to encode\n * auxiliary information about the upstream column (e.g. type and\n * constraints) that does not necessarily affect how SQLite handles the data,\n * but nonetheless determines how higher level logic handles the data.\n *\n * The format of the type string is the original upstream type, followed\n * by any number of attributes, each of which begins with the `|` character.\n * The current list of attributes are:\n * * `|NOT_NULL` to indicate that the upstream column does not allow nulls\n * * `|TEXT_ENUM` to indicate an enum that should be treated as a string\n * * `|TEXT_ARRAY` to indicate an array\n *\n * Examples:\n * * `int8`\n * * `int8|NOT_NULL`\n * * `timestamp with time zone`\n * * `timestamp with time zone|NOT_NULL`\n * * `nomz|TEXT_ENUM`\n * * `nomz|NOT_NULL|TEXT_ENUM`\n * * `int8[]` - Legacy (read support)\n * * `int8[]|TEXT_ARRAY`\n * * `int8|TEXT_ARRAY[]` - Legacy (read support)\n * * `int8[]|TEXT_ARRAY[]` - Legacy (read support)\n * * `int8|NOT_NULL[]` - Legacy (read support)\n * * `nomz[]|TEXT_ENUM|TEXT_ARRAY`\n * * `nomz|TEXT_ENUM[]` - Legacy (read support)\n * * `nomz|TEXT_ENUM|TEXT_ARRAY[]` - Legacy (read support)\n */\nexport type LiteTypeString = string;\n\n/**\n * Formats a {@link LiteTypeString}.\n */\nexport function liteTypeString(\n upstreamDataType: string,\n notNull: boolean | null | undefined,\n textEnum: boolean,\n textArray: boolean,\n): LiteTypeString {\n let typeString = upstreamDataType;\n assert(typeString.indexOf('|') === -1, 'Upstream type should not contain |');\n if (notNull) {\n typeString += NOT_NULL_ATTRIBUTE;\n }\n if (textEnum) {\n typeString += TEXT_ENUM_ATTRIBUTE;\n }\n if (textArray) {\n typeString += TEXT_ARRAY_ATTRIBUTE;\n }\n return typeString;\n}\n\nexport function upstreamDataType(liteTypeString: LiteTypeString) {\n const delim = liteTypeString.indexOf('|');\n return delim > 0 ? liteTypeString.substring(0, delim) : liteTypeString;\n}\n\nexport function nullableUpstream(liteTypeString: LiteTypeString) {\n return !liteTypeString.includes(NOT_NULL_ATTRIBUTE);\n}\n\n/**\n * Returns the value type for the `pgDataType` if it is supported by ZQL.\n * (Note that `pgDataType` values are stored as-is in the SQLite column defs).\n *\n * For types not supported by ZQL, returns `undefined`.\n */\nexport function liteTypeToZqlValueType(\n liteTypeString: LiteTypeString,\n): ValueType | undefined {\n return upstreamDataTypeToZqlValueType(\n upstreamDataType(liteTypeString).toLowerCase(),\n liteTypeString.includes(TEXT_ENUM_ATTRIBUTE),\n isArray(liteTypeString),\n );\n}\n\nexport function isEnum(liteTypeString: LiteTypeString) {\n return liteTypeString.includes(TEXT_ENUM_ATTRIBUTE);\n}\n\nexport function isArray(liteTypeString: LiteTypeString) {\n return (\n liteTypeString.includes(TEXT_ARRAY_ATTRIBUTE) ||\n // Before we added `|TEXT_ARRAY`, we just used `[]` suffix to indicate arrays.\n liteTypeString.includes('[]')\n );\n}\n\nexport function assertValidLiteColumnSpec(spec: ColumnSpec) {\n const {dataType} = spec;\n assert(dataType.includes(TEXT_ARRAY_ATTRIBUTE) === dataType.includes('[]'));\n assert(dataType.includes('[]') === (spec.elemPgTypeClass !== null));\n\n // and no [] after |\n assert(!/^.+\\|.*\\[\\]/.test(dataType), `Invalid dataType ${dataType}`);\n}\n"],"names":["upstreamDataType","liteTypeString","upstreamDataTypeToZqlValueType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lite.js","sources":["../../../../../zero-cache/src/types/lite.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {stringify, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport type {\n SchemaValue,\n ValueType,\n} from '../../../zero-schema/src/table-schema.ts';\nimport type {ColumnSpec, LiteTableSpec} from '../db/specs.ts';\nimport {dataTypeToZqlValueType as upstreamDataTypeToZqlValueType} from './pg-data-type.ts';\nimport type {PostgresValueType} from './pg.ts';\nimport type {RowValue} from './row-key.ts';\n\n/** Javascript value types supported by better-sqlite3. */\nexport type LiteValueType = number | bigint | string | null | Uint8Array;\n\nexport type LiteRow = Readonly<Record<string, LiteValueType>>;\nexport type LiteRowKey = LiteRow; // just for API readability\n\nfunction columnType(col: string, table: LiteTableSpec) {\n const spec = table.columns[col];\n assert(spec, `Unknown column ${col} in table ${table.name}`);\n return spec.dataType;\n}\n\nexport const JSON_STRINGIFIED = 's';\nexport const JSON_PARSED = 'p';\n\nexport type JSONFormat = typeof JSON_STRINGIFIED | typeof JSON_PARSED;\n\n/**\n * Creates a LiteRow from the supplied RowValue. A copy of the `row`\n * is made only if a value conversion is performed.\n */\nexport function liteRow(\n row: RowValue,\n table: LiteTableSpec,\n jsonFormat: JSONFormat,\n): {row: LiteRow; numCols: number} {\n let copyNeeded = false;\n let numCols = 0;\n\n for (const key in row) {\n numCols++;\n const val = row[key];\n const liteVal = liteValue(val, columnType(key, table), jsonFormat);\n if (val !== liteVal) {\n copyNeeded = true;\n break;\n }\n }\n if (!copyNeeded) {\n return {row: row as unknown as LiteRow, numCols};\n }\n // Slow path for when a conversion is needed.\n numCols = 0;\n const converted: Record<string, LiteValueType> = {};\n for (const key in row) {\n numCols++;\n converted[key] = liteValue(row[key], columnType(key, table), jsonFormat);\n }\n return {row: converted, numCols};\n}\n\n/**\n * Postgres values types that are supported by SQLite are stored as-is.\n * This includes Uint8Arrays for the `bytea` / `BLOB` type.\n * * `boolean` values are converted to `0` or `1` integers.\n * * `PreciseDate` values are converted to epoch microseconds.\n * * JSON and Array values are stored as `JSON.stringify()` strings.\n *\n * Note that this currently does not handle the `bytea[]` type, but that's\n * already a pretty questionable type.\n */\nexport function liteValue(\n val: PostgresValueType,\n pgType: string,\n jsonFormat: JSONFormat,\n): LiteValueType {\n if (val instanceof Uint8Array || val === null) {\n return val;\n }\n const valueType = liteTypeToZqlValueType(pgType);\n if (valueType === 'json') {\n if (jsonFormat === JSON_STRINGIFIED && typeof val === 'string') {\n // JSON and JSONB values are already strings if the JSON was not parsed.\n return val;\n }\n // Non-JSON/JSONB values will always appear as objects / arrays.\n return stringify(val);\n }\n const obj = toLiteValue(val);\n return obj && typeof obj === 'object' ? stringify(obj) : obj;\n}\n\nfunction toLiteValue(val: JSONValue): Exclude<JSONValue, boolean> {\n switch (typeof val) {\n case 'string':\n case 'number':\n case 'bigint':\n return val;\n case 'boolean':\n return val ? 1 : 0;\n }\n if (val === null) {\n return val;\n }\n if (Array.isArray(val)) {\n return val.map(v => toLiteValue(v));\n }\n assert(\n val.constructor?.name === 'Object',\n `Unhandled object type ${val.constructor?.name}`,\n );\n return val; // JSON\n}\n\nexport function mapLiteDataTypeToZqlSchemaValue(\n liteDataType: LiteTypeString,\n): SchemaValue {\n return {type: mapLiteDataTypeToZqlValueType(liteDataType)};\n}\n\nfunction mapLiteDataTypeToZqlValueType(dataType: LiteTypeString): ValueType {\n const type = liteTypeToZqlValueType(dataType);\n if (type === undefined) {\n throw new Error(`Unsupported data type ${dataType}`);\n }\n return type;\n}\n\n// Note: Includes the \"TEXT\" substring for SQLite type affinity\nconst TEXT_ENUM_ATTRIBUTE = '|TEXT_ENUM';\nconst NOT_NULL_ATTRIBUTE = '|NOT_NULL';\nexport const TEXT_ARRAY_ATTRIBUTE = '|TEXT_ARRAY';\n\n/**\n * The `LiteTypeString` utilizes SQLite's loose type system to encode\n * auxiliary information about the upstream column (e.g. type and\n * constraints) that does not necessarily affect how SQLite handles the data,\n * but nonetheless determines how higher level logic handles the data.\n *\n * The format of the type string is the original upstream type, followed\n * by any number of attributes, each of which begins with the `|` character.\n * The current list of attributes are:\n * * `|NOT_NULL` to indicate that the upstream column does not allow nulls\n * * `|TEXT_ENUM` to indicate an enum that should be treated as a string\n * * `|TEXT_ARRAY` to indicate an array\n *\n * Examples:\n * * `int8`\n * * `int8|NOT_NULL`\n * * `timestamp with time zone`\n * * `timestamp with time zone|NOT_NULL`\n * * `nomz|TEXT_ENUM`\n * * `nomz|NOT_NULL|TEXT_ENUM`\n * * `int8[]` - Legacy (read support)\n * * `int8[]|TEXT_ARRAY`\n * * `int8|TEXT_ARRAY[]` - Legacy (read support)\n * * `int8[]|TEXT_ARRAY[]` - Legacy (read support)\n * * `int8|NOT_NULL[]` - Legacy (read support)\n * * `nomz[]|TEXT_ENUM|TEXT_ARRAY`\n * * `nomz|TEXT_ENUM[]` - Legacy (read support)\n * * `nomz|TEXT_ENUM|TEXT_ARRAY[]` - Legacy (read support)\n */\nexport type LiteTypeString = string;\n\n/**\n * Formats a {@link LiteTypeString}.\n */\nexport function liteTypeString(\n upstreamDataType: string,\n notNull: boolean | null | undefined,\n textEnum: boolean,\n textArray: boolean,\n): LiteTypeString {\n let typeString = upstreamDataType;\n assert(typeString.indexOf('|') === -1, 'Upstream type should not contain |');\n if (notNull) {\n typeString += NOT_NULL_ATTRIBUTE;\n }\n if (textEnum) {\n typeString += TEXT_ENUM_ATTRIBUTE;\n }\n if (textArray) {\n typeString += TEXT_ARRAY_ATTRIBUTE;\n }\n return typeString;\n}\n\nexport function upstreamDataType(liteTypeString: LiteTypeString) {\n const delim = liteTypeString.indexOf('|');\n return delim > 0 ? liteTypeString.substring(0, delim) : liteTypeString;\n}\n\nexport function nullableUpstream(liteTypeString: LiteTypeString) {\n return !liteTypeString.includes(NOT_NULL_ATTRIBUTE);\n}\n\n/**\n * Returns the value type for the `pgDataType` if it is supported by ZQL.\n * (Note that `pgDataType` values are stored as-is in the SQLite column defs).\n *\n * For types not supported by ZQL, returns `undefined`.\n */\nexport function liteTypeToZqlValueType(\n liteTypeString: LiteTypeString,\n): ValueType | undefined {\n return upstreamDataTypeToZqlValueType(\n upstreamDataType(liteTypeString).toLowerCase(),\n liteTypeString.includes(TEXT_ENUM_ATTRIBUTE),\n isArray(liteTypeString),\n );\n}\n\nexport function isEnum(liteTypeString: LiteTypeString) {\n return liteTypeString.includes(TEXT_ENUM_ATTRIBUTE);\n}\n\nexport function isArray(liteTypeString: LiteTypeString) {\n return (\n liteTypeString.includes(TEXT_ARRAY_ATTRIBUTE) ||\n // Before we added `|TEXT_ARRAY`, we just used `[]` suffix to indicate arrays.\n liteTypeString.includes('[]')\n );\n}\n\nexport function assertValidLiteColumnSpec(spec: ColumnSpec) {\n const {dataType} = spec;\n assert(\n dataType.includes(TEXT_ARRAY_ATTRIBUTE) === dataType.includes('[]'),\n () =>\n `TEXT_ARRAY_ATTRIBUTE and [] must be consistent in dataType: ${dataType}`,\n );\n assert(\n dataType.includes('[]') === (spec.elemPgTypeClass !== null),\n () =>\n `[] in dataType (${dataType}) must match elemPgTypeClass presence (${spec.elemPgTypeClass})`,\n );\n\n // and no [] after |\n assert(!/^.+\\|.*\\[\\]/.test(dataType), `Invalid dataType ${dataType}`);\n}\n"],"names":["upstreamDataType","liteTypeString","upstreamDataTypeToZqlValueType"],"mappings":"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"}
|
|
@@ -25,7 +25,7 @@ function check(shard) {
|
|
|
25
25
|
if (!ALLOWED_APP_ID_CHARACTERS.test(appID)) {
|
|
26
26
|
throw new Error(INVALID_APP_ID_MESSAGE);
|
|
27
27
|
}
|
|
28
|
-
assert(typeof shardNum === "number");
|
|
28
|
+
assert(typeof shardNum === "number", "shardNum must be a number");
|
|
29
29
|
return { appID, shardNum };
|
|
30
30
|
}
|
|
31
31
|
function appSchema({ appID }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shards.js","sources":["../../../../../zero-cache/src/types/shards.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\n\nexport type AppID = {\n readonly appID: string;\n};\n\nexport type ShardID = AppID & {\n readonly shardNum: number;\n};\n\nexport type ShardConfig = ShardID & {\n readonly publications: readonly string[];\n};\n\n// Gets a ShardID from a ZeroConfig.\nexport function getShardID({\n app,\n shard,\n}: {\n app: {id: string};\n shard: {num: number};\n}): ShardID {\n return {\n appID: app.id,\n shardNum: shard.num,\n };\n}\n\n// Gets a ShardConfig from a ZeroConfig.\nexport function getShardConfig({\n app,\n shard,\n}: {\n app: {id: string; publications: string[]};\n shard: {num: number};\n}): ShardConfig {\n return {\n appID: app.id,\n shardNum: shard.num,\n publications: app.publications,\n };\n}\n\n// Constrained by https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS-MANIPULATION\nexport const ALLOWED_APP_ID_CHARACTERS = /^[a-z0-9_]+$/;\n\nexport const INVALID_APP_ID_MESSAGE =\n 'The App ID may only consist of lower-case letters, numbers, and the underscore character';\n\nexport function check(shard: ShardID): {appID: string; shardNum: number} {\n const {appID, shardNum} = shard;\n if (!ALLOWED_APP_ID_CHARACTERS.test(appID)) {\n throw new Error(INVALID_APP_ID_MESSAGE);\n }\n assert(typeof shardNum === 'number');\n return {appID, shardNum};\n}\n\nexport function appSchema({appID}: AppID) {\n check({appID, shardNum: 0});\n return appID;\n}\n\nexport function upstreamSchema(shard: ShardID) {\n const {appID, shardNum} = check(shard);\n return `${appID}_${shardNum}`;\n}\n\nexport function cdcSchema(shard: ShardID) {\n const {appID, shardNum} = check(shard);\n return `${appID}_${shardNum}/cdc`;\n}\n\nexport function cvrSchema(shard: ShardID) {\n const {appID, shardNum} = check(shard);\n return `${appID}_${shardNum}/cvr`;\n}\n"],"names":[],"mappings":";AAeO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGY;AACV,SAAO;AAAA,IACL,OAAO,IAAI;AAAA,IACX,UAAU,MAAM;AAAA,EAAA;AAEpB;AAGO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGgB;AACd,SAAO;AAAA,IACL,OAAO,IAAI;AAAA,IACX,UAAU,MAAM;AAAA,IAChB,cAAc,IAAI;AAAA,EAAA;AAEtB;AAGO,MAAM,4BAA4B;AAElC,MAAM,yBACX;AAEK,SAAS,MAAM,OAAmD;AACvE,QAAM,EAAC,OAAO,SAAA,IAAY;AAC1B,MAAI,CAAC,0BAA0B,KAAK,KAAK,GAAG;AAC1C,UAAM,IAAI,MAAM,sBAAsB;AAAA,EACxC;AACA,SAAO,OAAO,aAAa,
|
|
1
|
+
{"version":3,"file":"shards.js","sources":["../../../../../zero-cache/src/types/shards.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\n\nexport type AppID = {\n readonly appID: string;\n};\n\nexport type ShardID = AppID & {\n readonly shardNum: number;\n};\n\nexport type ShardConfig = ShardID & {\n readonly publications: readonly string[];\n};\n\n// Gets a ShardID from a ZeroConfig.\nexport function getShardID({\n app,\n shard,\n}: {\n app: {id: string};\n shard: {num: number};\n}): ShardID {\n return {\n appID: app.id,\n shardNum: shard.num,\n };\n}\n\n// Gets a ShardConfig from a ZeroConfig.\nexport function getShardConfig({\n app,\n shard,\n}: {\n app: {id: string; publications: string[]};\n shard: {num: number};\n}): ShardConfig {\n return {\n appID: app.id,\n shardNum: shard.num,\n publications: app.publications,\n };\n}\n\n// Constrained by https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS-MANIPULATION\nexport const ALLOWED_APP_ID_CHARACTERS = /^[a-z0-9_]+$/;\n\nexport const INVALID_APP_ID_MESSAGE =\n 'The App ID may only consist of lower-case letters, numbers, and the underscore character';\n\nexport function check(shard: ShardID): {appID: string; shardNum: number} {\n const {appID, shardNum} = shard;\n if (!ALLOWED_APP_ID_CHARACTERS.test(appID)) {\n throw new Error(INVALID_APP_ID_MESSAGE);\n }\n assert(typeof shardNum === 'number', 'shardNum must be a number');\n return {appID, shardNum};\n}\n\nexport function appSchema({appID}: AppID) {\n check({appID, shardNum: 0});\n return appID;\n}\n\nexport function upstreamSchema(shard: ShardID) {\n const {appID, shardNum} = check(shard);\n return `${appID}_${shardNum}`;\n}\n\nexport function cdcSchema(shard: ShardID) {\n const {appID, shardNum} = check(shard);\n return `${appID}_${shardNum}/cdc`;\n}\n\nexport function cvrSchema(shard: ShardID) {\n const {appID, shardNum} = check(shard);\n return `${appID}_${shardNum}/cvr`;\n}\n"],"names":[],"mappings":";AAeO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGY;AACV,SAAO;AAAA,IACL,OAAO,IAAI;AAAA,IACX,UAAU,MAAM;AAAA,EAAA;AAEpB;AAGO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGgB;AACd,SAAO;AAAA,IACL,OAAO,IAAI;AAAA,IACX,UAAU,MAAM;AAAA,IAChB,cAAc,IAAI;AAAA,EAAA;AAEtB;AAGO,MAAM,4BAA4B;AAElC,MAAM,yBACX;AAEK,SAAS,MAAM,OAAmD;AACvE,QAAM,EAAC,OAAO,SAAA,IAAY;AAC1B,MAAI,CAAC,0BAA0B,KAAK,KAAK,GAAG;AAC1C,UAAM,IAAI,MAAM,sBAAsB;AAAA,EACxC;AACA,SAAO,OAAO,aAAa,UAAU,2BAA2B;AAChE,SAAO,EAAC,OAAO,SAAA;AACjB;AAEO,SAAS,UAAU,EAAC,SAAe;AACxC,QAAM,EAAC,OAAO,UAAU,EAAA,CAAE;AAC1B,SAAO;AACT;AAEO,SAAS,eAAe,OAAgB;AAC7C,QAAM,EAAC,OAAO,aAAY,MAAM,KAAK;AACrC,SAAO,GAAG,KAAK,IAAI,QAAQ;AAC7B;AAEO,SAAS,UAAU,OAAgB;AACxC,QAAM,EAAC,OAAO,aAAY,MAAM,KAAK;AACrC,SAAO,GAAG,KAAK,IAAI,QAAQ;AAC7B;AAEO,SAAS,UAAU,OAAgB;AACxC,QAAM,EAAC,OAAO,aAAY,MAAM,KAAK;AACrC,SAAO,GAAG,KAAK,IAAI,QAAQ;AAC7B;"}
|
|
@@ -8,4 +8,9 @@ export declare function id(name: string): string;
|
|
|
8
8
|
* Escapes and comma-separates a list of identifiers.
|
|
9
9
|
*/
|
|
10
10
|
export declare function idList(names: Iterable<string>): string;
|
|
11
|
+
/**
|
|
12
|
+
* Escapes a string literal with single quotes, as per:
|
|
13
|
+
* https://www.postgresql.org/docs/16/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
|
|
14
|
+
*/
|
|
15
|
+
export declare function lit(value: string): string;
|
|
11
16
|
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/sql.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAEtD"}
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/sql.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAEtD;AAED;;;GAGG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzC"}
|
|
@@ -4,8 +4,12 @@ function id(name) {
|
|
|
4
4
|
function idList(names) {
|
|
5
5
|
return Array.from(names, (name) => id(name)).join(",");
|
|
6
6
|
}
|
|
7
|
+
function lit(value) {
|
|
8
|
+
return "'" + value.replace(/'/g, "''") + "'";
|
|
9
|
+
}
|
|
7
10
|
export {
|
|
8
11
|
id,
|
|
9
|
-
idList
|
|
12
|
+
idList,
|
|
13
|
+
lit
|
|
10
14
|
};
|
|
11
15
|
//# sourceMappingURL=sql.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql.js","sources":["../../../../../zero-cache/src/types/sql.ts"],"sourcesContent":["/**\n * Escapes the identifier with double quotes, as per:\n *\n * https://www.postgresql.org/docs/16/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS\n */\nexport function id(name: string): string {\n return '\"' + name.replace(/\"/g, '\"\"') + '\"'; //.replace(/\\./g, '\".\"') + '\"';\n}\n\n/**\n * Escapes and comma-separates a list of identifiers.\n */\nexport function idList(names: Iterable<string>): string {\n return Array.from(names, name => id(name)).join(',');\n}\n"],"names":[],"mappings":"AAKO,SAAS,GAAG,MAAsB;AACvC,SAAO,MAAM,KAAK,QAAQ,MAAM,IAAI,IAAI;AAC1C;AAKO,SAAS,OAAO,OAAiC;AACtD,SAAO,MAAM,KAAK,OAAO,CAAA,SAAQ,GAAG,IAAI,CAAC,EAAE,KAAK,GAAG;AACrD;"}
|
|
1
|
+
{"version":3,"file":"sql.js","sources":["../../../../../zero-cache/src/types/sql.ts"],"sourcesContent":["/**\n * Escapes the identifier with double quotes, as per:\n *\n * https://www.postgresql.org/docs/16/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS\n */\nexport function id(name: string): string {\n return '\"' + name.replace(/\"/g, '\"\"') + '\"'; //.replace(/\\./g, '\".\"') + '\"';\n}\n\n/**\n * Escapes and comma-separates a list of identifiers.\n */\nexport function idList(names: Iterable<string>): string {\n return Array.from(names, name => id(name)).join(',');\n}\n\n/**\n * Escapes a string literal with single quotes, as per:\n * https://www.postgresql.org/docs/16/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS\n */\nexport function lit(value: string): string {\n return \"'\" + value.replace(/'/g, \"''\") + \"'\";\n}\n"],"names":[],"mappings":"AAKO,SAAS,GAAG,MAAsB;AACvC,SAAO,MAAM,KAAK,QAAQ,MAAM,IAAI,IAAI;AAC1C;AAKO,SAAS,OAAO,OAAiC;AACtD,SAAO,MAAM,KAAK,OAAO,CAAA,SAAQ,GAAG,IAAI,CAAC,EAAE,KAAK,GAAG;AACrD;AAMO,SAAS,IAAI,OAAuB;AACzC,SAAO,MAAM,MAAM,QAAQ,MAAM,IAAI,IAAI;AAC3C;"}
|
|
@@ -79,7 +79,7 @@ class Subscription {
|
|
|
79
79
|
consumer.resolve(entry);
|
|
80
80
|
} else if (this.#coalesce && this.#messages.length && this.#messages[this.#messages.length - 1] !== "terminus") {
|
|
81
81
|
const prev = this.#messages[this.#messages.length - 1];
|
|
82
|
-
assert(prev !== "terminus");
|
|
82
|
+
assert(prev !== "terminus", "prev should not be terminus after check");
|
|
83
83
|
this.#messages[this.#messages.length - 1] = {
|
|
84
84
|
value: this.#coalesce(entry, prev),
|
|
85
85
|
resolve
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.js","sources":["../../../../../zero-cache/src/types/subscription.ts"],"sourcesContent":["import {resolver, type Resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Sink, Source} from './streams.ts';\n\n/**\n * A Subscription abstracts a continuous, logically infinite stream of messages intended\n * for serial processing. Unlike the more general Node `Stream` API, a Subscription has\n * a limited API with specific semantics:\n *\n * * **Serial processing**: Messages must be consumed via the {@link AsyncIterable}\n * interface, e.g.\n * ```ts\n * const subscription = server.subscribe(parameters);\n *\n * for await (const message of subscription) {\n * await process(message); // fully process the message before consuming the next\n * }\n * ```\n *\n * Moreover, the consumer is expected to completely process each message before\n * requesting the next. This is important for cleanup semantics (explained later).\n *\n * * **cancel()**, not close(): The underlying data in a subscription is logically infinite\n * and only terminated when the consumer is no longer interested in receiving the messages\n * (or requires a Subscription with a different configuration). As such, there is no API\n * for gracefully closing the subscription after pending messages are consumed; rather,\n * cancellation is immediate, and upon cancellation, pending messages are dropped. A\n * Subscription can also be terminated with exceptional (i.e. `Error`) circumstances,\n * for which the behavior is equivalent.\n *\n * * **Coalescing** (optional): A producer can configure pending messages in the Subscription\n * to be merged together with a {@link Options.coalesce coalesce} function. This is useful\n * for semantics in which the consumer is not necessarily interested in every incremental\n * change, but rather the cumulative change since the last processed message. A\n * Subscription with coalescing is guaranteed to have at most one outstanding message,\n * regardless of how quickly messages are produced and consumed. This effectively constrains\n * the amount of outstanding work in the system.\n *\n * ### Resource Tracking and Cleanup\n *\n * Because message consumption is constrained to the async iteration API, standard\n * control flow mechanisms allow the producer to perform bookkeeping without any\n * explicit cleanup actions on the part of the consumer. This includes:\n *\n * * **Per-message cleanup**: Each request for the {@link AsyncIterator.next next}\n * message, or the termination of the iteration, signals that the consumer has\n * finished processing the previous message. The producer of a Subscription can\n * supply a {@link Options.consumed consumed} callback to receive these processed\n * messages, allowing it to clean up attached resources (e.g. TransactionPools, etc.).\n *\n * * **Per-subscription cleanup**: The producer of a Subscription can supply a\n * {@link Options.cleanup cleanup} callback that is invoked when the Subscription\n * is terminated, either explicitly via {@link Subscription.cancel cancel()} /\n * {@link Subscription.fail fail()}, or implicitly when an iteration is exited via a\n * `break`, `return`, or `throw` statement. All unconsumed messages are passed to the\n * call back to facilitate bookkeeping.\n *\n * @param T The external message type, published to the AsyncIterable\n * @param M The internal message type used in the producer-side interfaces\n * (e.g. {@link push}, {@link Options.consumed}, {@link Options.coalesce},\n * and {@link Options.cleanup}). This is often the same as the external type\n * T, but may be diverged to facilitate internal bookkeeping.\n */\nexport class Subscription<T, M = T> implements Source<T>, Sink<M> {\n /**\n * Convenience factory method for creating a {@link Subscription} with internal message type\n * `M` as a subtype of `T`, defaulting to the same type. The default `publish` method publishes\n * the message of type `M` directly to the AsyncIterable.\n */\n static create<T, M extends T = T>(\n options: Options<M> = {},\n publish: (m: M) => T = m => m,\n ) {\n return new Subscription(options, publish);\n }\n\n // Consumers waiting to consume messages (i.e. an async iteration awaiting the next message).\n readonly #consumers: Resolver<Entry<M> | null>[] = [];\n // Messages waiting to be consumed.\n readonly #messages: (Entry<M> | 'terminus')[] = [];\n readonly #pipelineEnabled: boolean;\n // Sentinel value signaling that the subscription is \"done\" and no more\n // messages can be added.\n #sentinel: 'canceled' | Error | undefined = undefined;\n\n #coalesce: ((curr: Entry<M>, prev: Entry<M>) => M) | undefined;\n #consumed: (prev: Entry<M>) => void;\n #cleanup: (unconsumed: Entry<M>[], err?: Error) => void;\n #publish: (internal: M) => T;\n\n /**\n * @param publish function for converting the internally pushed / coalesced message\n * of type `M` to the external type `T` exposed via async iteration.\n */\n constructor(options: Options<M> = {}, publish: (m: M) => T) {\n const {\n coalesce,\n consumed = () => {},\n cleanup = () => {},\n pipeline = coalesce === undefined,\n } = options;\n\n this.#coalesce = !coalesce\n ? undefined\n : (curr, prev) => {\n try {\n return coalesce(curr.value, prev.value);\n } finally {\n prev.resolve('coalesced');\n }\n };\n\n this.#consumed = entry => {\n consumed(entry.value);\n entry.resolve('consumed');\n };\n\n this.#cleanup = (entries, err) => {\n cleanup(\n entries.map(e => e.value),\n err,\n );\n entries.forEach(e => e.resolve('unconsumed'));\n };\n\n this.#publish = publish;\n\n this.#pipelineEnabled = pipeline;\n }\n\n /**\n * Pushes the next message to be consumed, and returns a `result` that resolves to the\n * eventual {@link Result} of the `value`.\n *\n * If there is an existing unconsumed message and the Subscription has a\n * {@link Options#coalesce coalesce} function, the specified `value` will be coalesced\n * with the pending message. In this case, the result of the pending message\n * is resolved to `coalesced`, regardless of the `coalesce` function implementation.\n *\n * If the subscription is in a terminal state, the message is dropped and the\n * result resolves to `unconsumed`.\n */\n push(value: M): PendingResult {\n const {promise: result, resolve} = resolver<Result>();\n const entry = {value, resolve};\n\n if (this.#sentinel) {\n entry.resolve('unconsumed');\n return {result};\n }\n const consumer = this.#consumers.shift();\n if (consumer) {\n consumer.resolve(entry);\n } else if (\n this.#coalesce &&\n this.#messages.length &&\n this.#messages[this.#messages.length - 1] !== 'terminus'\n ) {\n const prev = this.#messages[this.#messages.length - 1];\n assert(prev !== 'terminus');\n this.#messages[this.#messages.length - 1] = {\n value: this.#coalesce(entry, prev),\n resolve,\n };\n } else {\n this.#messages.push(entry);\n }\n return {result};\n }\n\n /** False if the subscription has been canceled or has failed. */\n get active(): boolean {\n return this.#sentinel === undefined;\n }\n\n /** The number messages waiting to be consumed. This is largely for testing. */\n get queued(): number {\n return this.#messages.length;\n }\n\n /**\n * Cancels the subscription after any queued messages are consumed. This is\n * meant for the producer-side code.\n *\n * Any messages pushed after calling `end()` will be unconsumed as if\n * `cancel()` were called (once the first set of pending messages is\n * consumed). In particular, if a coalesce function is defined, the new\n * messages will not be coalesced with the messages enqueued before `end()`\n * was called. However, to effect the intent of memory efficiency, multiple\n * messages pushed after calling `end()` will be coalesced together.\n *\n */\n end() {\n if (this.#sentinel) {\n // already terminated\n } else if (this.#messages.length === 0) {\n this.cancel();\n } else {\n this.#messages.push('terminus');\n }\n }\n\n /**\n * Cancels the subscription immediately, cleans up, and terminates any iteration.\n */\n cancel() {\n this.#terminate('canceled');\n }\n\n /** Fails the subscription, cleans up, and throws from any iteration. */\n fail(err: Error) {\n this.#terminate(err);\n }\n\n #terminate(sentinel: 'canceled' | Error) {\n if (!this.#sentinel) {\n this.#sentinel = sentinel;\n this.#cleanup(\n this.#messages.filter(m => m !== 'terminus'),\n sentinel instanceof Error ? sentinel : undefined,\n );\n this.#messages.splice(0);\n\n for (\n let consumer = this.#consumers.shift();\n consumer;\n consumer = this.#consumers.shift()\n ) {\n sentinel === 'canceled'\n ? consumer.resolve(null)\n : consumer.reject(sentinel);\n }\n }\n }\n\n get pipeline(): AsyncIterable<{value: T; consumed: () => void}> | undefined {\n return this.#pipelineEnabled\n ? {[Symbol.asyncIterator]: () => this.#pipeline()}\n : undefined;\n }\n\n #pipeline(): AsyncIterator<{value: T; consumed: () => void}> {\n return {\n next: async () => {\n const entry = this.#messages.shift();\n if (entry === 'terminus') {\n this.cancel();\n return {value: undefined, done: true};\n }\n if (entry !== undefined) {\n return {\n value: {\n value: this.#publish(entry.value),\n consumed: () => this.#consumed(entry),\n },\n };\n }\n if (this.#sentinel === 'canceled') {\n return {value: undefined, done: true};\n }\n if (this.#sentinel) {\n return Promise.reject(this.#sentinel);\n }\n const consumer = resolver<Entry<M> | null>();\n this.#consumers.push(consumer);\n\n // Wait for push() (or termination) to resolve the consumer.\n const result = await consumer.promise;\n return result\n ? {\n value: {\n value: this.#publish(result.value),\n consumed: () => this.#consumed(result),\n },\n }\n : {value: undefined, done: true};\n },\n\n return: value => {\n this.cancel();\n return Promise.resolve({value, done: true});\n },\n };\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n const delegate = this.#pipeline();\n\n let prevConsumed = () => {};\n return {\n next: async () => {\n prevConsumed();\n\n const entry = await delegate.next();\n if (entry.done) {\n return entry;\n }\n\n const {value, consumed} = entry.value;\n prevConsumed = consumed;\n return {value};\n },\n\n return: value => {\n prevConsumed();\n\n this.cancel();\n return Promise.resolve({value, done: true});\n },\n };\n }\n}\n\nexport type Options<M> = {\n /**\n * Coalesces messages waiting to be consumed. This is useful for \"watermark\" type\n * subscriptions in which the consumer is only interested in the cumulative state\n * change since the last processed message. When a `coalesce` function is specified,\n * there is guaranteed to be at most one message waiting to be consumed.\n *\n * Note that the `curr` argument comes before `prev`. This facilitates a common\n * scenario in which coalescing just means using the newest value; in such a case,\n * `coalesce` can simply be the identity function (e.g. `msg => msg`).\n */\n coalesce?: (curr: M, prev: M) => M;\n\n /**\n * Called on the previous message in an iteration (1) when the next message is requested,\n * or (2) when the iteration is terminated. This allows the producer to perform\n * per-message cleanup.\n *\n * Note that when a {@link Options.coalesce coalesce} function is defined,\n * `consumed` is _not_ called on the `prev` message; it is the responsibility of\n * producers requiring both coalescing and consumption notification to perform any\n * necessary cleanup of `prev` messages when coalescing.\n */\n consumed?: (prev: M) => void;\n\n /**\n * `cleanup` is called exactly once when the subscription is terminated via a failure or\n * cancelation (whichever happens first), which includes implicit cancelation when\n * the consumer exits an iteration via a `break`, `return`, or `throw` statement.\n *\n * Note that the `err` argument will only reflect an explicit cancelation via a call\n * to {@link Subscription.fail()}. On the other hand, if the iteration is canceled via\n * a `throw` statement, the thrown reason is not reflected in the `err` parameter, as that\n * information is not made available to the AsyncIterator implementation.\n */\n cleanup?: (unconsumed: M[], err?: Error) => void;\n\n /**\n * Enable or disable pipelining when streaming messages over a websocket.\n *\n * If unspecified, pipelining is enabled if there is no {@link Options.coalesce coalesce}\n * method, as pipelining is counter to the semantics of coalescing. However, the\n * application can explicitly enable pipelining even if there is a coalesce method\n * by specifying `true` for this option. This assumes that coalescing is either\n * not important for websocket semantics, or that the receiving end of the websocket\n * transport performs the desired coalescing.\n */\n pipeline?: boolean;\n};\n\n/** Post-queueing results of messages. */\nexport type Result = 'consumed' | 'coalesced' | 'unconsumed';\n\n/**\n * {@link Subscription.subscribe()} wraps the `Promise<Result>` in a `PendingResult`\n * object to avoid forcing all callers to handle the Promise, as most logic does not\n * need to.\n */\nexport type PendingResult = {result: Promise<Result>};\n\ntype Entry<M> = {\n readonly value: M;\n readonly resolve: (r: Result) => void;\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subscription.js","sources":["../../../../../zero-cache/src/types/subscription.ts"],"sourcesContent":["import {resolver, type Resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Sink, Source} from './streams.ts';\n\n/**\n * A Subscription abstracts a continuous, logically infinite stream of messages intended\n * for serial processing. Unlike the more general Node `Stream` API, a Subscription has\n * a limited API with specific semantics:\n *\n * * **Serial processing**: Messages must be consumed via the {@link AsyncIterable}\n * interface, e.g.\n * ```ts\n * const subscription = server.subscribe(parameters);\n *\n * for await (const message of subscription) {\n * await process(message); // fully process the message before consuming the next\n * }\n * ```\n *\n * Moreover, the consumer is expected to completely process each message before\n * requesting the next. This is important for cleanup semantics (explained later).\n *\n * * **cancel()**, not close(): The underlying data in a subscription is logically infinite\n * and only terminated when the consumer is no longer interested in receiving the messages\n * (or requires a Subscription with a different configuration). As such, there is no API\n * for gracefully closing the subscription after pending messages are consumed; rather,\n * cancellation is immediate, and upon cancellation, pending messages are dropped. A\n * Subscription can also be terminated with exceptional (i.e. `Error`) circumstances,\n * for which the behavior is equivalent.\n *\n * * **Coalescing** (optional): A producer can configure pending messages in the Subscription\n * to be merged together with a {@link Options.coalesce coalesce} function. This is useful\n * for semantics in which the consumer is not necessarily interested in every incremental\n * change, but rather the cumulative change since the last processed message. A\n * Subscription with coalescing is guaranteed to have at most one outstanding message,\n * regardless of how quickly messages are produced and consumed. This effectively constrains\n * the amount of outstanding work in the system.\n *\n * ### Resource Tracking and Cleanup\n *\n * Because message consumption is constrained to the async iteration API, standard\n * control flow mechanisms allow the producer to perform bookkeeping without any\n * explicit cleanup actions on the part of the consumer. This includes:\n *\n * * **Per-message cleanup**: Each request for the {@link AsyncIterator.next next}\n * message, or the termination of the iteration, signals that the consumer has\n * finished processing the previous message. The producer of a Subscription can\n * supply a {@link Options.consumed consumed} callback to receive these processed\n * messages, allowing it to clean up attached resources (e.g. TransactionPools, etc.).\n *\n * * **Per-subscription cleanup**: The producer of a Subscription can supply a\n * {@link Options.cleanup cleanup} callback that is invoked when the Subscription\n * is terminated, either explicitly via {@link Subscription.cancel cancel()} /\n * {@link Subscription.fail fail()}, or implicitly when an iteration is exited via a\n * `break`, `return`, or `throw` statement. All unconsumed messages are passed to the\n * call back to facilitate bookkeeping.\n *\n * @param T The external message type, published to the AsyncIterable\n * @param M The internal message type used in the producer-side interfaces\n * (e.g. {@link push}, {@link Options.consumed}, {@link Options.coalesce},\n * and {@link Options.cleanup}). This is often the same as the external type\n * T, but may be diverged to facilitate internal bookkeeping.\n */\nexport class Subscription<T, M = T> implements Source<T>, Sink<M> {\n /**\n * Convenience factory method for creating a {@link Subscription} with internal message type\n * `M` as a subtype of `T`, defaulting to the same type. The default `publish` method publishes\n * the message of type `M` directly to the AsyncIterable.\n */\n static create<T, M extends T = T>(\n options: Options<M> = {},\n publish: (m: M) => T = m => m,\n ) {\n return new Subscription(options, publish);\n }\n\n // Consumers waiting to consume messages (i.e. an async iteration awaiting the next message).\n readonly #consumers: Resolver<Entry<M> | null>[] = [];\n // Messages waiting to be consumed.\n readonly #messages: (Entry<M> | 'terminus')[] = [];\n readonly #pipelineEnabled: boolean;\n // Sentinel value signaling that the subscription is \"done\" and no more\n // messages can be added.\n #sentinel: 'canceled' | Error | undefined = undefined;\n\n #coalesce: ((curr: Entry<M>, prev: Entry<M>) => M) | undefined;\n #consumed: (prev: Entry<M>) => void;\n #cleanup: (unconsumed: Entry<M>[], err?: Error) => void;\n #publish: (internal: M) => T;\n\n /**\n * @param publish function for converting the internally pushed / coalesced message\n * of type `M` to the external type `T` exposed via async iteration.\n */\n constructor(options: Options<M> = {}, publish: (m: M) => T) {\n const {\n coalesce,\n consumed = () => {},\n cleanup = () => {},\n pipeline = coalesce === undefined,\n } = options;\n\n this.#coalesce = !coalesce\n ? undefined\n : (curr, prev) => {\n try {\n return coalesce(curr.value, prev.value);\n } finally {\n prev.resolve('coalesced');\n }\n };\n\n this.#consumed = entry => {\n consumed(entry.value);\n entry.resolve('consumed');\n };\n\n this.#cleanup = (entries, err) => {\n cleanup(\n entries.map(e => e.value),\n err,\n );\n entries.forEach(e => e.resolve('unconsumed'));\n };\n\n this.#publish = publish;\n\n this.#pipelineEnabled = pipeline;\n }\n\n /**\n * Pushes the next message to be consumed, and returns a `result` that resolves to the\n * eventual {@link Result} of the `value`.\n *\n * If there is an existing unconsumed message and the Subscription has a\n * {@link Options#coalesce coalesce} function, the specified `value` will be coalesced\n * with the pending message. In this case, the result of the pending message\n * is resolved to `coalesced`, regardless of the `coalesce` function implementation.\n *\n * If the subscription is in a terminal state, the message is dropped and the\n * result resolves to `unconsumed`.\n */\n push(value: M): PendingResult {\n const {promise: result, resolve} = resolver<Result>();\n const entry = {value, resolve};\n\n if (this.#sentinel) {\n entry.resolve('unconsumed');\n return {result};\n }\n const consumer = this.#consumers.shift();\n if (consumer) {\n consumer.resolve(entry);\n } else if (\n this.#coalesce &&\n this.#messages.length &&\n this.#messages[this.#messages.length - 1] !== 'terminus'\n ) {\n const prev = this.#messages[this.#messages.length - 1];\n assert(prev !== 'terminus', 'prev should not be terminus after check');\n this.#messages[this.#messages.length - 1] = {\n value: this.#coalesce(entry, prev),\n resolve,\n };\n } else {\n this.#messages.push(entry);\n }\n return {result};\n }\n\n /** False if the subscription has been canceled or has failed. */\n get active(): boolean {\n return this.#sentinel === undefined;\n }\n\n /** The number messages waiting to be consumed. This is largely for testing. */\n get queued(): number {\n return this.#messages.length;\n }\n\n /**\n * Cancels the subscription after any queued messages are consumed. This is\n * meant for the producer-side code.\n *\n * Any messages pushed after calling `end()` will be unconsumed as if\n * `cancel()` were called (once the first set of pending messages is\n * consumed). In particular, if a coalesce function is defined, the new\n * messages will not be coalesced with the messages enqueued before `end()`\n * was called. However, to effect the intent of memory efficiency, multiple\n * messages pushed after calling `end()` will be coalesced together.\n *\n */\n end() {\n if (this.#sentinel) {\n // already terminated\n } else if (this.#messages.length === 0) {\n this.cancel();\n } else {\n this.#messages.push('terminus');\n }\n }\n\n /**\n * Cancels the subscription immediately, cleans up, and terminates any iteration.\n */\n cancel() {\n this.#terminate('canceled');\n }\n\n /** Fails the subscription, cleans up, and throws from any iteration. */\n fail(err: Error) {\n this.#terminate(err);\n }\n\n #terminate(sentinel: 'canceled' | Error) {\n if (!this.#sentinel) {\n this.#sentinel = sentinel;\n this.#cleanup(\n this.#messages.filter(m => m !== 'terminus'),\n sentinel instanceof Error ? sentinel : undefined,\n );\n this.#messages.splice(0);\n\n for (\n let consumer = this.#consumers.shift();\n consumer;\n consumer = this.#consumers.shift()\n ) {\n sentinel === 'canceled'\n ? consumer.resolve(null)\n : consumer.reject(sentinel);\n }\n }\n }\n\n get pipeline(): AsyncIterable<{value: T; consumed: () => void}> | undefined {\n return this.#pipelineEnabled\n ? {[Symbol.asyncIterator]: () => this.#pipeline()}\n : undefined;\n }\n\n #pipeline(): AsyncIterator<{value: T; consumed: () => void}> {\n return {\n next: async () => {\n const entry = this.#messages.shift();\n if (entry === 'terminus') {\n this.cancel();\n return {value: undefined, done: true};\n }\n if (entry !== undefined) {\n return {\n value: {\n value: this.#publish(entry.value),\n consumed: () => this.#consumed(entry),\n },\n };\n }\n if (this.#sentinel === 'canceled') {\n return {value: undefined, done: true};\n }\n if (this.#sentinel) {\n return Promise.reject(this.#sentinel);\n }\n const consumer = resolver<Entry<M> | null>();\n this.#consumers.push(consumer);\n\n // Wait for push() (or termination) to resolve the consumer.\n const result = await consumer.promise;\n return result\n ? {\n value: {\n value: this.#publish(result.value),\n consumed: () => this.#consumed(result),\n },\n }\n : {value: undefined, done: true};\n },\n\n return: value => {\n this.cancel();\n return Promise.resolve({value, done: true});\n },\n };\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n const delegate = this.#pipeline();\n\n let prevConsumed = () => {};\n return {\n next: async () => {\n prevConsumed();\n\n const entry = await delegate.next();\n if (entry.done) {\n return entry;\n }\n\n const {value, consumed} = entry.value;\n prevConsumed = consumed;\n return {value};\n },\n\n return: value => {\n prevConsumed();\n\n this.cancel();\n return Promise.resolve({value, done: true});\n },\n };\n }\n}\n\nexport type Options<M> = {\n /**\n * Coalesces messages waiting to be consumed. This is useful for \"watermark\" type\n * subscriptions in which the consumer is only interested in the cumulative state\n * change since the last processed message. When a `coalesce` function is specified,\n * there is guaranteed to be at most one message waiting to be consumed.\n *\n * Note that the `curr` argument comes before `prev`. This facilitates a common\n * scenario in which coalescing just means using the newest value; in such a case,\n * `coalesce` can simply be the identity function (e.g. `msg => msg`).\n */\n coalesce?: (curr: M, prev: M) => M;\n\n /**\n * Called on the previous message in an iteration (1) when the next message is requested,\n * or (2) when the iteration is terminated. This allows the producer to perform\n * per-message cleanup.\n *\n * Note that when a {@link Options.coalesce coalesce} function is defined,\n * `consumed` is _not_ called on the `prev` message; it is the responsibility of\n * producers requiring both coalescing and consumption notification to perform any\n * necessary cleanup of `prev` messages when coalescing.\n */\n consumed?: (prev: M) => void;\n\n /**\n * `cleanup` is called exactly once when the subscription is terminated via a failure or\n * cancelation (whichever happens first), which includes implicit cancelation when\n * the consumer exits an iteration via a `break`, `return`, or `throw` statement.\n *\n * Note that the `err` argument will only reflect an explicit cancelation via a call\n * to {@link Subscription.fail()}. On the other hand, if the iteration is canceled via\n * a `throw` statement, the thrown reason is not reflected in the `err` parameter, as that\n * information is not made available to the AsyncIterator implementation.\n */\n cleanup?: (unconsumed: M[], err?: Error) => void;\n\n /**\n * Enable or disable pipelining when streaming messages over a websocket.\n *\n * If unspecified, pipelining is enabled if there is no {@link Options.coalesce coalesce}\n * method, as pipelining is counter to the semantics of coalescing. However, the\n * application can explicitly enable pipelining even if there is a coalesce method\n * by specifying `true` for this option. This assumes that coalescing is either\n * not important for websocket semantics, or that the receiving end of the websocket\n * transport performs the desired coalescing.\n */\n pipeline?: boolean;\n};\n\n/** Post-queueing results of messages. */\nexport type Result = 'consumed' | 'coalesced' | 'unconsumed';\n\n/**\n * {@link Subscription.subscribe()} wraps the `Promise<Result>` in a `PendingResult`\n * object to avoid forcing all callers to handle the Promise, as most logic does not\n * need to.\n */\nexport type PendingResult = {result: Promise<Result>};\n\ntype Entry<M> = {\n readonly value: M;\n readonly resolve: (r: Result) => void;\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ export type ConnectParams = {
|
|
|
14
14
|
readonly userID: string;
|
|
15
15
|
readonly initConnectionMsg: InitConnectionMessage | undefined;
|
|
16
16
|
readonly httpCookie: string | undefined;
|
|
17
|
+
readonly origin: string | undefined;
|
|
17
18
|
};
|
|
18
19
|
export declare function getConnectParams(protocolVersion: number, url: URL, headers: IncomingHttpHeaders): {
|
|
19
20
|
params: ConnectParams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-params.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/connect-params.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,YAAY,CAAC;AAEpD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uCAAuC,CAAC;AAG/C,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"connect-params.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/connect-params.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,YAAY,CAAC;AAEpD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uCAAuC,CAAC;AAG/C,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,eAAe,EAAE,MAAM,EACvB,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,mBAAmB,GAE1B;IACE,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CA0CJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-params.js","sources":["../../../../../zero-cache/src/workers/connect-params.ts"],"sourcesContent":["import type {IncomingHttpHeaders} from 'node:http2';\nimport {must} from '../../../shared/src/must.ts';\nimport {\n decodeSecProtocols,\n type InitConnectionMessage,\n} from '../../../zero-protocol/src/connect.ts';\nimport {URLParams} from '../types/url-params.ts';\n\nexport type ConnectParams = {\n readonly protocolVersion: number;\n readonly clientID: string;\n readonly clientGroupID: string;\n readonly profileID: string | null;\n readonly baseCookie: string | null;\n readonly timestamp: number;\n readonly lmID: number;\n readonly wsID: string;\n readonly debugPerf: boolean;\n readonly auth: string | undefined;\n readonly userID: string;\n readonly initConnectionMsg: InitConnectionMessage | undefined;\n readonly httpCookie: string | undefined;\n};\n\nexport function getConnectParams(\n protocolVersion: number,\n url: URL,\n headers: IncomingHttpHeaders,\n):\n | {\n params: ConnectParams;\n error: null;\n }\n | {\n params: null;\n error: string;\n } {\n const params = new URLParams(url);\n\n try {\n const clientID = params.get('clientID', true);\n const clientGroupID = params.get('clientGroupID', true);\n const profileID = params.get('profileID', false);\n const baseCookie = params.get('baseCookie', false);\n const timestamp = params.getInteger('ts', true);\n const lmID = params.getInteger('lmid', true);\n const wsID = params.get('wsid', false) ?? '';\n const userID = params.get('userID', false) ?? '';\n const debugPerf = params.getBoolean('debugPerf');\n const {initConnectionMessage, authToken} = decodeSecProtocols(\n must(headers['sec-websocket-protocol']),\n );\n\n return {\n params: {\n protocolVersion,\n clientID,\n clientGroupID,\n profileID,\n baseCookie,\n timestamp,\n lmID,\n wsID,\n debugPerf,\n initConnectionMsg: initConnectionMessage,\n auth: authToken,\n userID,\n httpCookie: headers.cookie,\n },\n error: null,\n };\n } catch (e) {\n return {\n params: null,\n error: e instanceof Error ? e.message : String(e),\n };\n }\n}\n"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"connect-params.js","sources":["../../../../../zero-cache/src/workers/connect-params.ts"],"sourcesContent":["import type {IncomingHttpHeaders} from 'node:http2';\nimport {must} from '../../../shared/src/must.ts';\nimport {\n decodeSecProtocols,\n type InitConnectionMessage,\n} from '../../../zero-protocol/src/connect.ts';\nimport {URLParams} from '../types/url-params.ts';\n\nexport type ConnectParams = {\n readonly protocolVersion: number;\n readonly clientID: string;\n readonly clientGroupID: string;\n readonly profileID: string | null;\n readonly baseCookie: string | null;\n readonly timestamp: number;\n readonly lmID: number;\n readonly wsID: string;\n readonly debugPerf: boolean;\n readonly auth: string | undefined;\n readonly userID: string;\n readonly initConnectionMsg: InitConnectionMessage | undefined;\n readonly httpCookie: string | undefined;\n readonly origin: string | undefined;\n};\n\nexport function getConnectParams(\n protocolVersion: number,\n url: URL,\n headers: IncomingHttpHeaders,\n):\n | {\n params: ConnectParams;\n error: null;\n }\n | {\n params: null;\n error: string;\n } {\n const params = new URLParams(url);\n\n try {\n const clientID = params.get('clientID', true);\n const clientGroupID = params.get('clientGroupID', true);\n const profileID = params.get('profileID', false);\n const baseCookie = params.get('baseCookie', false);\n const timestamp = params.getInteger('ts', true);\n const lmID = params.getInteger('lmid', true);\n const wsID = params.get('wsid', false) ?? '';\n const userID = params.get('userID', false) ?? '';\n const debugPerf = params.getBoolean('debugPerf');\n const {initConnectionMessage, authToken} = decodeSecProtocols(\n must(headers['sec-websocket-protocol']),\n );\n\n return {\n params: {\n protocolVersion,\n clientID,\n clientGroupID,\n profileID,\n baseCookie,\n timestamp,\n lmID,\n wsID,\n debugPerf,\n initConnectionMsg: initConnectionMessage,\n auth: authToken,\n userID,\n httpCookie: headers.cookie,\n origin: headers.origin,\n },\n error: null,\n };\n } catch (e) {\n return {\n params: null,\n error: e instanceof Error ? e.message : String(e),\n };\n }\n}\n"],"names":[],"mappings":";;;AAyBO,SAAS,iBACd,iBACA,KACA,SASI;AACJ,QAAM,SAAS,IAAI,UAAU,GAAG;AAEhC,MAAI;AACF,UAAM,WAAW,OAAO,IAAI,YAAY,IAAI;AAC5C,UAAM,gBAAgB,OAAO,IAAI,iBAAiB,IAAI;AACtD,UAAM,YAAY,OAAO,IAAI,aAAa,KAAK;AAC/C,UAAM,aAAa,OAAO,IAAI,cAAc,KAAK;AACjD,UAAM,YAAY,OAAO,WAAW,MAAM,IAAI;AAC9C,UAAM,OAAO,OAAO,WAAW,QAAQ,IAAI;AAC3C,UAAM,OAAO,OAAO,IAAI,QAAQ,KAAK,KAAK;AAC1C,UAAM,SAAS,OAAO,IAAI,UAAU,KAAK,KAAK;AAC9C,UAAM,YAAY,OAAO,WAAW,WAAW;AAC/C,UAAM,EAAC,uBAAuB,UAAA,IAAa;AAAA,MACzC,KAAK,QAAQ,wBAAwB,CAAC;AAAA,IAAA;AAGxC,WAAO;AAAA,MACL,QAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,mBAAmB;AAAA,QACnB,MAAM;AAAA,QACN;AAAA,QACA,YAAY,QAAQ;AAAA,QACpB,QAAQ,QAAQ;AAAA,MAAA;AAAA,MAElB,OAAO;AAAA,IAAA;AAAA,EAEX,SAAS,GAAG;AACV,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,IAAA;AAAA,EAEpD;AACF;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer-ws-message-handler.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer-ws-message-handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAQjD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,kCAAkC,CAAC;AAC/D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAC1D,OAAO,KAAK,EAEV,SAAS,EACT,UAAU,EACX,MAAM,wCAAwC,CAAC;AAChD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAC,aAAa,EAAE,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAInE,qBAAa,sBAAuB,YAAW,cAAc;;gBAezD,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"syncer-ws-message-handler.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer-ws-message-handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAQjD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,kCAAkC,CAAC;AAC/D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAC1D,OAAO,KAAK,EAEV,SAAS,EACT,UAAU,EACX,MAAM,wCAAwC,CAAC;AAChD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAC,aAAa,EAAE,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAInE,qBAAa,sBAAuB,YAAW,cAAc;;gBAezD,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS;IAoCtB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CA2J7D"}
|
|
@@ -27,7 +27,8 @@ class SyncerWsMessageHandler {
|
|
|
27
27
|
wsID,
|
|
28
28
|
baseCookie,
|
|
29
29
|
protocolVersion,
|
|
30
|
-
httpCookie
|
|
30
|
+
httpCookie,
|
|
31
|
+
origin
|
|
31
32
|
} = connectParams;
|
|
32
33
|
this.#viewSyncer = viewSyncer;
|
|
33
34
|
this.#mutagen = mutagen;
|
|
@@ -44,7 +45,8 @@ class SyncerWsMessageHandler {
|
|
|
44
45
|
baseCookie,
|
|
45
46
|
protocolVersion,
|
|
46
47
|
tokenData,
|
|
47
|
-
httpCookie
|
|
48
|
+
httpCookie,
|
|
49
|
+
origin
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
52
|
async handleMessage(msg) {
|
|
@@ -93,7 +95,8 @@ class SyncerWsMessageHandler {
|
|
|
93
95
|
this.#syncContext.clientID,
|
|
94
96
|
msg[1],
|
|
95
97
|
this.#token,
|
|
96
|
-
this.#syncContext.httpCookie
|
|
98
|
+
this.#syncContext.httpCookie,
|
|
99
|
+
this.#syncContext.origin
|
|
97
100
|
)
|
|
98
101
|
];
|
|
99
102
|
}
|
|
@@ -129,13 +132,17 @@ class SyncerWsMessageHandler {
|
|
|
129
132
|
() => viewSyncer.changeDesiredQueries(this.#syncContext, msg)
|
|
130
133
|
);
|
|
131
134
|
break;
|
|
132
|
-
case "deleteClients":
|
|
133
|
-
await startAsyncSpan(
|
|
135
|
+
case "deleteClients": {
|
|
136
|
+
const deletedClientIDs = await startAsyncSpan(
|
|
134
137
|
tracer,
|
|
135
138
|
"connection.deleteClients",
|
|
136
139
|
() => viewSyncer.deleteClients(this.#syncContext, msg)
|
|
137
140
|
);
|
|
141
|
+
if (this.#pusher && deletedClientIDs.length > 0) {
|
|
142
|
+
await this.#pusher.deleteClientMutations(deletedClientIDs);
|
|
143
|
+
}
|
|
138
144
|
break;
|
|
145
|
+
}
|
|
139
146
|
case "initConnection": {
|
|
140
147
|
const ret = [
|
|
141
148
|
{
|
|
@@ -155,7 +162,8 @@ class SyncerWsMessageHandler {
|
|
|
155
162
|
stream: this.#pusher.initConnection(
|
|
156
163
|
this.#syncContext.clientID,
|
|
157
164
|
this.#syncContext.wsID,
|
|
158
|
-
msg[1].userPushURL
|
|
165
|
+
msg[1].userPushURL,
|
|
166
|
+
msg[1].userPushHeaders
|
|
159
167
|
)
|
|
160
168
|
});
|
|
161
169
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer-ws-message-handler.js","sources":["../../../../../zero-cache/src/workers/syncer-ws-message-handler.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\nimport {startAsyncSpan, startSpan} from '../../../otel/src/span.ts';\nimport {version} from '../../../otel/src/version.ts';\nimport {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport type {Upstream} from '../../../zero-protocol/src/up.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {\n SyncContext,\n TokenData,\n ViewSyncer,\n} from '../services/view-syncer/view-syncer.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport type {HandlerResult, MessageHandler} from './connection.ts';\n\nconst tracer = trace.getTracer('syncer-ws-server', version);\n\nexport class SyncerWsMessageHandler implements MessageHandler {\n readonly #viewSyncer: ViewSyncer;\n readonly #mutagen: Mutagen;\n readonly #mutationLock: Lock;\n readonly #lc: LogContext;\n readonly #authData: JWTPayload | undefined;\n readonly #clientGroupID: string;\n readonly #syncContext: SyncContext;\n readonly #pusher: Pusher | undefined;\n // DEPRECATED: remove #token\n // and forward auth and cookie headers that were\n // sent with the push.\n readonly #token: string | undefined;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n tokenData: TokenData | undefined,\n viewSyncer: ViewSyncer,\n mutagen: Mutagen,\n pusher: Pusher | undefined,\n ) {\n const {\n clientGroupID,\n clientID,\n profileID,\n wsID,\n baseCookie,\n protocolVersion,\n httpCookie,\n } = connectParams;\n this.#viewSyncer = viewSyncer;\n this.#mutagen = mutagen;\n this.#mutationLock = new Lock();\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#authData = tokenData?.decoded;\n this.#token = tokenData?.raw;\n this.#clientGroupID = clientGroupID;\n this.#pusher = pusher;\n this.#syncContext = {\n clientID,\n profileID,\n wsID,\n baseCookie,\n protocolVersion,\n tokenData,\n httpCookie,\n };\n }\n\n async handleMessage(msg: Upstream): Promise<HandlerResult[]> {\n const lc = this.#lc;\n const msgType = msg[0];\n const viewSyncer = this.#viewSyncer;\n switch (msgType) {\n case 'ping':\n lc.error?.('Ping is not supported at this layer by Zero');\n break;\n case 'pull':\n lc.error?.('Pull is not supported by Zero');\n break;\n case 'push': {\n return startAsyncSpan<HandlerResult[]>(\n tracer,\n 'connection.push',\n async () => {\n const {clientGroupID, mutations} = msg[1];\n if (clientGroupID !== this.#clientGroupID) {\n return [\n {\n type: 'fatal',\n error: {\n kind: ErrorKind.InvalidPush,\n message:\n `clientGroupID in mutation \"${clientGroupID}\" does not match ` +\n `clientGroupID of connection \"${this.#clientGroupID}`,\n origin: ErrorOrigin.ZeroCache,\n },\n } satisfies HandlerResult,\n ];\n }\n\n if (mutations.length === 0) {\n return [\n {\n type: 'ok',\n },\n ];\n }\n\n // The client only ever sends 1 mutation per push.\n // #pusher will throw if it sees a CRUD mutation.\n // #mutagen will throw if it see a custom mutation.\n if (mutations[0].type === 'custom') {\n assert(\n this.#pusher,\n 'A ZERO_MUTATE_URL must be set in order to process custom mutations.',\n );\n return [\n this.#pusher.enqueuePush(\n this.#syncContext.clientID,\n msg[1],\n this.#token,\n this.#syncContext.httpCookie,\n ),\n ];\n }\n\n // Hold a connection-level lock while processing mutations so that:\n // 1. Mutations are processed in the order in which they are received and\n // 2. A single view syncer connection cannot hog multiple upstream connections.\n const ret = await this.#mutationLock.withLock(async () => {\n const errors: ErrorBody[] = [];\n for (const mutation of mutations) {\n const maybeError = await this.#mutagen.processMutation(\n mutation,\n this.#authData,\n this.#pusher !== undefined,\n );\n if (maybeError !== undefined) {\n errors.push({\n kind: maybeError[0],\n message: maybeError[1],\n origin: ErrorOrigin.ZeroCache,\n });\n }\n }\n if (errors.length > 0) {\n return {type: 'transient', errors} satisfies HandlerResult;\n }\n return {type: 'ok'} satisfies HandlerResult;\n });\n return [ret];\n },\n );\n }\n case 'changeDesiredQueries':\n await startAsyncSpan(tracer, 'connection.changeDesiredQueries', () =>\n viewSyncer.changeDesiredQueries(this.#syncContext, msg),\n );\n break;\n case 'deleteClients':\n await startAsyncSpan(tracer, 'connection.deleteClients', () =>\n viewSyncer.deleteClients(this.#syncContext, msg),\n );\n break;\n case 'initConnection': {\n const ret: HandlerResult[] = [\n {\n type: 'stream',\n source: 'viewSyncer',\n stream: startSpan(tracer, 'connection.initConnection', () =>\n viewSyncer.initConnection(this.#syncContext, msg),\n ),\n },\n ];\n\n // Given we support both CRUD and Custom mutators,\n // we do not initialize the `pusher` unless the user has opted\n // into custom mutations. We detect that by checking\n // if the pushURL has been set.\n if (this.#pusher) {\n ret.push({\n type: 'stream',\n source: 'pusher',\n stream: this.#pusher.initConnection(\n this.#syncContext.clientID,\n this.#syncContext.wsID,\n msg[1].userPushURL,\n ),\n });\n }\n\n return ret;\n }\n case 'closeConnection':\n // This message is deprecated and no longer used.\n break;\n\n case 'inspect':\n await startAsyncSpan(tracer, 'connection.inspect', () =>\n viewSyncer.inspect(this.#syncContext, msg),\n );\n break;\n\n case 'ackMutationResponses':\n if (this.#pusher) {\n await this.#pusher.ackMutationResponses(msg[1]);\n }\n break;\n\n default:\n unreachable(msgType);\n }\n\n return [{type: 'ok'}];\n }\n}\n"],"names":["ErrorKind.InvalidPush","ErrorOrigin.ZeroCache"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"syncer-ws-message-handler.js","sources":["../../../../../zero-cache/src/workers/syncer-ws-message-handler.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {JWTPayload} from 'jose';\nimport {startAsyncSpan, startSpan} from '../../../otel/src/span.ts';\nimport {version} from '../../../otel/src/version.ts';\nimport {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport type {Upstream} from '../../../zero-protocol/src/up.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {\n SyncContext,\n TokenData,\n ViewSyncer,\n} from '../services/view-syncer/view-syncer.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport type {HandlerResult, MessageHandler} from './connection.ts';\n\nconst tracer = trace.getTracer('syncer-ws-server', version);\n\nexport class SyncerWsMessageHandler implements MessageHandler {\n readonly #viewSyncer: ViewSyncer;\n readonly #mutagen: Mutagen;\n readonly #mutationLock: Lock;\n readonly #lc: LogContext;\n readonly #authData: JWTPayload | undefined;\n readonly #clientGroupID: string;\n readonly #syncContext: SyncContext;\n readonly #pusher: Pusher | undefined;\n // DEPRECATED: remove #token\n // and forward auth and cookie headers that were\n // sent with the push.\n readonly #token: string | undefined;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n tokenData: TokenData | undefined,\n viewSyncer: ViewSyncer,\n mutagen: Mutagen,\n pusher: Pusher | undefined,\n ) {\n const {\n clientGroupID,\n clientID,\n profileID,\n wsID,\n baseCookie,\n protocolVersion,\n httpCookie,\n origin,\n } = connectParams;\n this.#viewSyncer = viewSyncer;\n this.#mutagen = mutagen;\n this.#mutationLock = new Lock();\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#authData = tokenData?.decoded;\n this.#token = tokenData?.raw;\n this.#clientGroupID = clientGroupID;\n this.#pusher = pusher;\n this.#syncContext = {\n clientID,\n profileID,\n wsID,\n baseCookie,\n protocolVersion,\n tokenData,\n httpCookie,\n origin,\n };\n }\n\n async handleMessage(msg: Upstream): Promise<HandlerResult[]> {\n const lc = this.#lc;\n const msgType = msg[0];\n const viewSyncer = this.#viewSyncer;\n switch (msgType) {\n case 'ping':\n lc.error?.('Ping is not supported at this layer by Zero');\n break;\n case 'pull':\n lc.error?.('Pull is not supported by Zero');\n break;\n case 'push': {\n return startAsyncSpan<HandlerResult[]>(\n tracer,\n 'connection.push',\n async () => {\n const {clientGroupID, mutations} = msg[1];\n if (clientGroupID !== this.#clientGroupID) {\n return [\n {\n type: 'fatal',\n error: {\n kind: ErrorKind.InvalidPush,\n message:\n `clientGroupID in mutation \"${clientGroupID}\" does not match ` +\n `clientGroupID of connection \"${this.#clientGroupID}`,\n origin: ErrorOrigin.ZeroCache,\n },\n } satisfies HandlerResult,\n ];\n }\n\n if (mutations.length === 0) {\n return [\n {\n type: 'ok',\n },\n ];\n }\n\n // The client only ever sends 1 mutation per push.\n // #pusher will throw if it sees a CRUD mutation.\n // #mutagen will throw if it see a custom mutation.\n if (mutations[0].type === 'custom') {\n assert(\n this.#pusher,\n 'A ZERO_MUTATE_URL must be set in order to process custom mutations.',\n );\n return [\n this.#pusher.enqueuePush(\n this.#syncContext.clientID,\n msg[1],\n this.#token,\n this.#syncContext.httpCookie,\n this.#syncContext.origin,\n ),\n ];\n }\n\n // Hold a connection-level lock while processing mutations so that:\n // 1. Mutations are processed in the order in which they are received and\n // 2. A single view syncer connection cannot hog multiple upstream connections.\n const ret = await this.#mutationLock.withLock(async () => {\n const errors: ErrorBody[] = [];\n for (const mutation of mutations) {\n const maybeError = await this.#mutagen.processMutation(\n mutation,\n this.#authData,\n this.#pusher !== undefined,\n );\n if (maybeError !== undefined) {\n errors.push({\n kind: maybeError[0],\n message: maybeError[1],\n origin: ErrorOrigin.ZeroCache,\n });\n }\n }\n if (errors.length > 0) {\n return {type: 'transient', errors} satisfies HandlerResult;\n }\n return {type: 'ok'} satisfies HandlerResult;\n });\n return [ret];\n },\n );\n }\n case 'changeDesiredQueries':\n await startAsyncSpan(tracer, 'connection.changeDesiredQueries', () =>\n viewSyncer.changeDesiredQueries(this.#syncContext, msg),\n );\n break;\n case 'deleteClients': {\n const deletedClientIDs = await startAsyncSpan(\n tracer,\n 'connection.deleteClients',\n () => viewSyncer.deleteClients(this.#syncContext, msg),\n );\n if (this.#pusher && deletedClientIDs.length > 0) {\n await this.#pusher.deleteClientMutations(deletedClientIDs);\n }\n break;\n }\n case 'initConnection': {\n const ret: HandlerResult[] = [\n {\n type: 'stream',\n source: 'viewSyncer',\n stream: startSpan(tracer, 'connection.initConnection', () =>\n viewSyncer.initConnection(this.#syncContext, msg),\n ),\n },\n ];\n\n // Given we support both CRUD and Custom mutators,\n // we do not initialize the `pusher` unless the user has opted\n // into custom mutations. We detect that by checking\n // if the pushURL has been set.\n if (this.#pusher) {\n ret.push({\n type: 'stream',\n source: 'pusher',\n stream: this.#pusher.initConnection(\n this.#syncContext.clientID,\n this.#syncContext.wsID,\n msg[1].userPushURL,\n msg[1].userPushHeaders,\n ),\n });\n }\n\n return ret;\n }\n case 'closeConnection':\n // This message is deprecated and no longer used.\n break;\n\n case 'inspect':\n await startAsyncSpan(tracer, 'connection.inspect', () =>\n viewSyncer.inspect(this.#syncContext, msg),\n );\n break;\n\n case 'ackMutationResponses':\n if (this.#pusher) {\n await this.#pusher.ackMutationResponses(msg[1]);\n }\n break;\n\n default:\n unreachable(msgType);\n }\n\n return [{type: 'ok'}];\n }\n}\n"],"names":["ErrorKind.InvalidPush","ErrorOrigin.ZeroCache"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAMrD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAMzD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAC1D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sCAAsC,CAAC;AAEvE,OAAO,KAAK,EACV,oBAAoB,EACpB,OAAO,EACP,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAC,gBAAgB,EAAC,MAAM,8CAA8C,CAAC;AAC9E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAO3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,WAAW,CAAC;CAC7B,CAAC;
|
|
1
|
+
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAMrD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAMzD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAC1D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sCAAsC,CAAC;AAEvE,OAAO,KAAK,EACV,oBAAoB,EACpB,OAAO,EACP,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAC,gBAAgB,EAAC,MAAM,8CAA8C,CAAC;AAC9E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AACvE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAO3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,WAAW,CAAC;CAC7B,CAAC;AA4BF;;;;;;GAMG;AACH,qBAAa,MAAO,YAAW,gBAAgB;;IAC7C,QAAQ,CAAC,EAAE,SAAmB;gBAa5B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,iBAAiB,EAAE,CACjB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,YAAY,CAAC,YAAY,CAAC,EAC/B,gBAAgB,EAAE,gBAAgB,KAC/B,UAAU,GAAG,oBAAoB,EACtC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,EACjD,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,GAAG,SAAS,EAC7D,MAAM,EAAE,MAAM;IAuKhB,GAAG;IAIH;;;;;OAKG;IACG,KAAK;IAqBX,IAAI;CAKL"}
|
|
@@ -16,7 +16,8 @@ import { createNotifierFrom, subscribeTo } from "./replicator.js";
|
|
|
16
16
|
import { SyncerWsMessageHandler } from "./syncer-ws-message-handler.js";
|
|
17
17
|
function getWebSocketServerOptions(config) {
|
|
18
18
|
const options = {
|
|
19
|
-
noServer: true
|
|
19
|
+
noServer: true,
|
|
20
|
+
maxPayload: config.websocketMaxPayloadBytes
|
|
20
21
|
};
|
|
21
22
|
if (config.websocketCompression) {
|
|
22
23
|
options.perMessageDeflate = true;
|
|
@@ -79,13 +80,6 @@ class Syncer {
|
|
|
79
80
|
);
|
|
80
81
|
recordConnectionAttempted();
|
|
81
82
|
const { clientID, clientGroupID, auth, userID } = params;
|
|
82
|
-
const existing = this.#connections.get(clientID);
|
|
83
|
-
if (existing) {
|
|
84
|
-
this.#lc.debug?.(
|
|
85
|
-
`client ${clientID} already connected, closing existing connection`
|
|
86
|
-
);
|
|
87
|
-
existing.close(`replaced by ${params.wsID}`);
|
|
88
|
-
}
|
|
89
83
|
let decodedToken;
|
|
90
84
|
if (auth) {
|
|
91
85
|
const tokenOptions = tokenConfigOptions(this.#config.auth);
|
|
@@ -101,10 +95,16 @@ class Syncer {
|
|
|
101
95
|
if (tokenOptions.length > 0) {
|
|
102
96
|
try {
|
|
103
97
|
decodedToken = await verifyToken(this.#config.auth, auth, {
|
|
104
|
-
subject: userID
|
|
98
|
+
subject: userID,
|
|
99
|
+
...this.#config.auth.issuer && {
|
|
100
|
+
issuer: this.#config.auth.issuer
|
|
101
|
+
},
|
|
102
|
+
...this.#config.auth.audience && {
|
|
103
|
+
audience: this.#config.auth.audience
|
|
104
|
+
}
|
|
105
105
|
});
|
|
106
106
|
this.#lc.debug?.(
|
|
107
|
-
`Received auth token
|
|
107
|
+
`Received auth token [redacted...${auth.slice(-8)}] for clientID ${clientID}`
|
|
108
108
|
);
|
|
109
109
|
} catch (e) {
|
|
110
110
|
sendError(
|
|
@@ -128,6 +128,13 @@ class Syncer {
|
|
|
128
128
|
} else {
|
|
129
129
|
this.#lc.debug?.(`No auth token received for clientID ${clientID}`);
|
|
130
130
|
}
|
|
131
|
+
const existing = this.#connections.get(clientID);
|
|
132
|
+
if (existing) {
|
|
133
|
+
this.#lc.debug?.(
|
|
134
|
+
`client ${clientID} already connected, closing existing connection`
|
|
135
|
+
);
|
|
136
|
+
existing.close(`replaced by ${params.wsID}`);
|
|
137
|
+
}
|
|
131
138
|
const mutagen = this.#mutagens.getService(clientGroupID);
|
|
132
139
|
const pusher = this.#pushers?.getService(clientGroupID);
|
|
133
140
|
mutagen.ref();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.js","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {type JWTPayload} from 'jose';\nimport {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\n};\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service>;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: (id: string) => Mutagen & Service,\n pusherFactory: ((id: string) => Pusher & Service) | undefined,\n parent: Worker,\n ) {\n this.#config = config;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent);\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator),\n v => v.keepalive(),\n );\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(lc, pusherFactory, p => p.hasRefs());\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n this.#lc.debug?.(\n 'creating connection',\n params.clientGroupID,\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n let decodedToken: JWTPayload | undefined;\n if (auth) {\n const tokenOptions = tokenConfigOptions(this.#config.auth);\n\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n\n // must either have one of the token options set or have custom mutations & queries enabled\n const hasExactlyOneTokenOption = tokenOptions.length === 1;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n\n if (tokenOptions.length > 0) {\n try {\n decodedToken = await verifyToken(this.#config.auth, auth, {\n subject: userID,\n });\n this.#lc.debug?.(\n `Received auth token ${auth} for clientID ${clientID}, decoded: ${JSON.stringify(decodedToken)}`,\n );\n } catch (e) {\n sendError(\n this.#lc,\n ws,\n {\n kind: ErrorKind.AuthInvalidated,\n message: `Failed to decode auth token: ${String(e)}`,\n origin: ErrorOrigin.ZeroCache,\n },\n e,\n );\n ws.close(3000, 'Failed to decode JWT');\n return;\n }\n } else {\n this.#lc.warn?.(\n `One of jwk, secret, or jwksUrl is not configured - the \\`authorization\\` header must be manually verified by the user`,\n );\n }\n } else {\n this.#lc.debug?.(`No auth token received for clientID ${clientID}`);\n }\n\n const mutagen = this.#mutagens.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n auth\n ? {\n raw: auth,\n decoded: decodedToken ?? {},\n }\n : undefined,\n this.#viewSyncers.getService(clientGroupID),\n mutagen,\n pusher,\n ),\n () => {\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n mutagen.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n connection.init() && recordConnectionSuccess();\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n params.clientGroupID,\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(`draining view-syncer ${vs.id} (forced)`);\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n}\n"],"names":["ErrorKind.AuthInvalidated","ErrorOrigin.ZeroCache"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"syncer.js","sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {type JWTPayload} from 'jose';\nimport {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\n};\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n maxPayload: config.websocketMaxPayloadBytes,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service>;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: (id: string) => Mutagen & Service,\n pusherFactory: ((id: string) => Pusher & Service) | undefined,\n parent: Worker,\n ) {\n this.#config = config;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent);\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator),\n v => v.keepalive(),\n );\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(lc, pusherFactory, p => p.hasRefs());\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n this.#lc.debug?.(\n 'creating connection',\n params.clientGroupID,\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS\n let decodedToken: JWTPayload | undefined;\n if (auth) {\n const tokenOptions = tokenConfigOptions(this.#config.auth);\n\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n\n // must either have one of the token options set or have custom mutations & queries enabled\n const hasExactlyOneTokenOption = tokenOptions.length === 1;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n\n if (tokenOptions.length > 0) {\n try {\n decodedToken = await verifyToken(this.#config.auth, auth, {\n subject: userID,\n ...(this.#config.auth.issuer && {\n issuer: this.#config.auth.issuer,\n }),\n ...(this.#config.auth.audience && {\n audience: this.#config.auth.audience,\n }),\n });\n this.#lc.debug?.(\n `Received auth token [redacted...${auth.slice(-8)}] for clientID ${clientID}`,\n );\n } catch (e) {\n sendError(\n this.#lc,\n ws,\n {\n kind: ErrorKind.AuthInvalidated,\n message: `Failed to decode auth token: ${String(e)}`,\n origin: ErrorOrigin.ZeroCache,\n },\n e,\n );\n ws.close(3000, 'Failed to decode JWT');\n return;\n }\n } else {\n this.#lc.warn?.(\n `One of jwk, secret, or jwksUrl is not configured - the \\`authorization\\` header must be manually verified by the user`,\n );\n }\n } else {\n this.#lc.debug?.(`No auth token received for clientID ${clientID}`);\n }\n\n // Only check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n const mutagen = this.#mutagens.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n auth\n ? {\n raw: auth,\n decoded: decodedToken ?? {},\n }\n : undefined,\n this.#viewSyncers.getService(clientGroupID),\n mutagen,\n pusher,\n ),\n () => {\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n mutagen.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n connection.init() && recordConnectionSuccess();\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n params.clientGroupID,\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(`draining view-syncer ${vs.id} (forced)`);\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n}\n"],"names":["ErrorKind.AuthInvalidated","ErrorOrigin.ZeroCache"],"mappings":"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"}
|
|
@@ -67,6 +67,14 @@ export declare class ConnectionManager extends Subscribable<ConnectionManagerSta
|
|
|
67
67
|
* @returns A promise that resolves when the next state change occurs.
|
|
68
68
|
*/
|
|
69
69
|
waitForStateChange(): Promise<ConnectionManagerState>;
|
|
70
|
+
requestConnect(): void;
|
|
71
|
+
waitForConnectRequest(): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Consume a pending connect request and resume connecting.
|
|
74
|
+
*
|
|
75
|
+
* @returns true if a pending request was handled.
|
|
76
|
+
*/
|
|
77
|
+
resumeFromConnectRequest(): boolean;
|
|
70
78
|
/**
|
|
71
79
|
* Transition to connecting state.
|
|
72
80
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/connection-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,YAAY,EAAC,MAAM,qCAAqC,CAAC;AAEjE,OAAO,EAAC,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAIpB,MAAM,MAAM,sBAAsB,GAC9B;IACE,IAAI,EAAE,gBAAgB,CAAC,YAAY,CAAC;IACpC,MAAM,EAAE,kBAAkB,CAAC;CAC5B,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC,UAAU,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAChC,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC,SAAS,CAAC;CAClC,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC,SAAS,CAAC;IACjC,MAAM,EAAE,SAAS,CAAC;CACnB,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC;IAC7B,MAAM,EAAE,SAAS,CAAC;CACnB,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC;IAC9B,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEN,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C,CAAC;AAEF,QAAA,MAAM,eAAe,yBAGkB,CAAC;AAExC,KAAK,wBAAwB,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AACjE,KAAK,8BAA8B,GAAG,OAAO,CAC3C,sBAAsB,EACtB;IAAC,IAAI,EAAE,wBAAwB,CAAA;CAAC,CACjC,CAAC;AAEF,qBAAa,iBAAkB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;
|
|
1
|
+
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/connection-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ const TERMINAL_STATES = [
|
|
|
10
10
|
];
|
|
11
11
|
class ConnectionManager extends Subscribable {
|
|
12
12
|
#state;
|
|
13
|
+
#connectRequestResolver = resolver();
|
|
13
14
|
/**
|
|
14
15
|
* The timestamp when we first started trying to connect.
|
|
15
16
|
* This is used to track the retry window.
|
|
@@ -87,6 +88,38 @@ class ConnectionManager extends Subscribable {
|
|
|
87
88
|
waitForStateChange() {
|
|
88
89
|
return this.#nextStatePromise();
|
|
89
90
|
}
|
|
91
|
+
requestConnect() {
|
|
92
|
+
if (this.#connectRequestResolver === void 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.#connectRequestResolver.resolve();
|
|
96
|
+
this.#connectRequestResolver = void 0;
|
|
97
|
+
}
|
|
98
|
+
waitForConnectRequest() {
|
|
99
|
+
return this.#connectRequestResolver === void 0 ? Promise.resolve() : this.#connectRequestResolver.promise;
|
|
100
|
+
}
|
|
101
|
+
#consumeConnectRequest() {
|
|
102
|
+
if (this.#connectRequestResolver !== void 0) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
this.#connectRequestResolver = resolver();
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Consume a pending connect request and resume connecting.
|
|
110
|
+
*
|
|
111
|
+
* @returns true if a pending request was handled.
|
|
112
|
+
*/
|
|
113
|
+
resumeFromConnectRequest() {
|
|
114
|
+
if (!this.isInTerminalState()) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (!this.#consumeConnectRequest()) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
this.connecting();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
90
123
|
/**
|
|
91
124
|
* Transition to connecting state.
|
|
92
125
|
*
|