@rocicorp/zero 0.25.0-canary.8 → 0.25.0-canary.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/shared/src/deep-merge.d.ts +20 -3
- package/out/shared/src/deep-merge.d.ts.map +1 -1
- package/out/shared/src/deep-merge.js +27 -0
- package/out/shared/src/deep-merge.js.map +1 -0
- package/out/shared/src/logging.d.ts.map +1 -1
- package/out/shared/src/logging.js +25 -9
- package/out/shared/src/logging.js.map +1 -1
- package/out/shared/src/object-traversal.d.ts +19 -0
- package/out/shared/src/object-traversal.d.ts.map +1 -0
- package/out/shared/src/object-traversal.js +27 -0
- package/out/shared/src/object-traversal.js.map +1 -0
- package/out/zero/package.json.js +1 -1
- package/out/zero/src/pg.js +0 -2
- package/out/zero/src/pg.js.map +1 -1
- package/out/zero/src/server.js +0 -2
- package/out/zero/src/server.js.map +1 -1
- package/out/zero/src/zero.js +19 -3
- package/out/zero/src/zero.js.map +1 -1
- package/out/zero-cache/src/auth/jwt.d.ts +3 -0
- package/out/zero-cache/src/auth/jwt.d.ts.map +1 -1
- package/out/zero-cache/src/auth/jwt.js.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.d.ts +2 -1
- package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
- package/out/zero-cache/src/auth/write-authorizer.js +1 -11
- package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +27 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +35 -7
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.d.ts +5 -5
- package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +14 -11
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js +2 -4
- package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
- package/out/zero-cache/src/db/specs.d.ts +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +9 -9
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +20 -8
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/analyze.d.ts +1 -1
- package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
- package/out/zero-cache/src/services/analyze.js +10 -1
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +5 -5
- package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +2 -2
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +11 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +36 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/http-service.d.ts +5 -4
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js +15 -10
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts +2 -1
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +3 -2
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.d.ts +198 -0
- package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +5 -5
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.d.ts +4 -0
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +8 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +15 -8
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.d.ts +4 -4
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +48 -25
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +20 -15
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +3 -3
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/bindings.d.ts +4 -4
- package/out/zero-client/src/client/bindings.d.ts.map +1 -1
- package/out/zero-client/src/client/bindings.js.map +1 -1
- package/out/zero-client/src/client/connection.d.ts +1 -1
- package/out/zero-client/src/client/connection.d.ts.map +1 -1
- package/out/zero-client/src/client/connection.js +1 -1
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/crud.d.ts +7 -5
- package/out/zero-client/src/client/crud.d.ts.map +1 -1
- package/out/zero-client/src/client/crud.js +7 -7
- package/out/zero-client/src/client/crud.js.map +1 -1
- package/out/zero-client/src/client/custom.d.ts +7 -5
- package/out/zero-client/src/client/custom.d.ts.map +1 -1
- package/out/zero-client/src/client/custom.js +12 -7
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.d.ts +5 -1
- package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.js +7 -0
- package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +13 -13
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/make-mutate-property.d.ts +43 -0
- package/out/zero-client/src/client/make-mutate-property.d.ts.map +1 -0
- package/out/zero-client/src/client/make-mutate-property.js +38 -0
- package/out/zero-client/src/client/make-mutate-property.js.map +1 -0
- package/out/zero-client/src/client/make-replicache-mutators.d.ts +34 -0
- package/out/zero-client/src/client/make-replicache-mutators.d.ts.map +1 -0
- package/out/zero-client/src/client/make-replicache-mutators.js +103 -0
- package/out/zero-client/src/client/make-replicache-mutators.js.map +1 -0
- package/out/zero-client/src/client/options.d.ts +39 -27
- 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/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts +23 -33
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +52 -118
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.d.ts +12 -7
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.d.ts +236 -0
- package/out/zero-protocol/src/analyze-query-result.d.ts.map +1 -1
- package/out/zero-protocol/src/analyze-query-result.js +128 -2
- package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
- package/out/zero-protocol/src/ast.d.ts +1 -1
- package/out/zero-protocol/src/connect.d.ts.map +1 -1
- package/out/zero-protocol/src/connect.js +4 -0
- package/out/zero-protocol/src/connect.js.map +1 -1
- package/out/zero-protocol/src/custom-queries.d.ts +1 -1
- package/out/zero-protocol/src/down.d.ts +99 -0
- package/out/zero-protocol/src/down.d.ts.map +1 -1
- package/out/zero-protocol/src/error.d.ts +4 -4
- package/out/zero-protocol/src/inspect-down.d.ts +297 -0
- package/out/zero-protocol/src/inspect-down.d.ts.map +1 -1
- package/out/zero-protocol/src/inspect-up.d.ts +4 -0
- package/out/zero-protocol/src/inspect-up.d.ts.map +1 -1
- package/out/zero-protocol/src/inspect-up.js +2 -1
- package/out/zero-protocol/src/inspect-up.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 +1 -1
- package/out/zero-protocol/src/up.d.ts +1 -0
- package/out/zero-protocol/src/up.d.ts.map +1 -1
- package/out/zero-react/src/components/inspector.d.ts +3 -2
- package/out/zero-react/src/components/inspector.d.ts.map +1 -1
- package/out/zero-react/src/components/inspector.js.map +1 -1
- package/out/zero-react/src/components/zero-inspector.d.ts +3 -2
- package/out/zero-react/src/components/zero-inspector.d.ts.map +1 -1
- package/out/zero-react/src/components/zero-inspector.js.map +1 -1
- package/out/zero-react/src/use-query.d.ts +5 -4
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +4 -3
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-react/src/zero-provider.d.ts +7 -7
- package/out/zero-react/src/zero-provider.d.ts.map +1 -1
- package/out/zero-react/src/zero-provider.js.map +1 -1
- package/out/zero-schema/src/builder/schema-builder.js +1 -1
- package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
- package/out/zero-server/src/custom.d.ts +4 -5
- package/out/zero-server/src/custom.d.ts.map +1 -1
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/mod.d.ts +0 -1
- package/out/zero-server/src/mod.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.d.ts +9 -14
- package/out/zero-server/src/process-mutations.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.js +151 -105
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zero-server/src/push-processor.d.ts +5 -3
- package/out/zero-server/src/push-processor.d.ts.map +1 -1
- package/out/zero-server/src/push-processor.js +17 -25
- package/out/zero-server/src/push-processor.js.map +1 -1
- package/out/zero-server/src/queries/process-queries.js +1 -1
- package/out/zero-server/src/queries/process-queries.js.map +1 -1
- package/out/zero-server/src/zql-database.d.ts.map +1 -1
- package/out/zero-server/src/zql-database.js +1 -1
- package/out/zero-server/src/zql-database.js.map +1 -1
- package/out/zero-solid/src/use-query.d.ts +3 -3
- package/out/zero-solid/src/use-query.d.ts.map +1 -1
- package/out/zero-solid/src/use-query.js +27 -38
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-zero-connection-state.d.ts.map +1 -1
- package/out/zero-solid/src/use-zero-connection-state.js +7 -5
- package/out/zero-solid/src/use-zero-connection-state.js.map +1 -1
- package/out/zero-solid/src/use-zero-online.d.ts.map +1 -1
- package/out/zero-solid/src/use-zero-online.js +7 -5
- package/out/zero-solid/src/use-zero-online.js.map +1 -1
- package/out/zero-solid/src/use-zero.d.ts +6 -5
- package/out/zero-solid/src/use-zero.d.ts.map +1 -1
- package/out/zero-solid/src/use-zero.js +2 -6
- package/out/zero-solid/src/use-zero.js.map +1 -1
- package/out/zql/src/builder/builder.d.ts +2 -1
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +4 -3
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/mutate/custom.d.ts +15 -6
- package/out/zql/src/mutate/custom.d.ts.map +1 -1
- package/out/zql/src/mutate/custom.js +6 -6
- package/out/zql/src/mutate/custom.js.map +1 -1
- package/out/zql/src/mutate/mutator-registry.d.ts +142 -0
- package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -0
- package/out/zql/src/mutate/mutator-registry.js +97 -0
- package/out/zql/src/mutate/mutator-registry.js.map +1 -0
- package/out/zql/src/mutate/mutator.d.ts +98 -0
- package/out/zql/src/mutate/mutator.d.ts.map +1 -0
- package/out/zql/src/mutate/mutator.js +35 -0
- package/out/zql/src/mutate/mutator.js.map +1 -0
- package/out/zql/src/planner/planner-connection.d.ts +7 -15
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +30 -24
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts +37 -43
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js +242 -0
- package/out/zql/src/planner/planner-debug.js.map +1 -0
- package/out/zql/src/planner/planner-fan-in.d.ts.map +1 -1
- package/out/zql/src/planner/planner-fan-in.js +11 -8
- package/out/zql/src/planner/planner-fan-in.js.map +1 -1
- package/out/zql/src/planner/planner-fan-out.d.ts.map +1 -1
- package/out/zql/src/planner/planner-fan-out.js +11 -8
- package/out/zql/src/planner/planner-fan-out.js.map +1 -1
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +13 -5
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-join.d.ts.map +1 -1
- package/out/zql/src/planner/planner-join.js +12 -9
- package/out/zql/src/planner/planner-join.js.map +1 -1
- package/out/zql/src/planner/planner-node.d.ts +4 -0
- package/out/zql/src/planner/planner-node.d.ts.map +1 -1
- package/out/zql/src/planner/planner-node.js +8 -0
- package/out/zql/src/planner/planner-node.js.map +1 -0
- package/out/zql/src/query/create-builder.d.ts +7 -0
- package/out/zql/src/query/create-builder.d.ts.map +1 -0
- package/out/zql/src/query/create-builder.js +44 -0
- package/out/zql/src/query/create-builder.js.map +1 -0
- package/out/zql/src/query/named.d.ts +1 -7
- package/out/zql/src/query/named.d.ts.map +1 -1
- package/out/zql/src/query/named.js +0 -21
- package/out/zql/src/query/named.js.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +4 -3
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +3 -0
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-internals.js +0 -4
- package/out/zql/src/query/query-internals.js.map +1 -1
- package/out/zql/src/query/query-registry.d.ts +253 -0
- package/out/zql/src/query/query-registry.d.ts.map +1 -0
- package/out/zql/src/query/query-registry.js +131 -0
- package/out/zql/src/query/query-registry.js.map +1 -0
- package/out/zql/src/query/query.d.ts +16 -1
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/schema-query.d.ts +6 -0
- package/out/zql/src/query/schema-query.d.ts.map +1 -0
- package/out/zql/src/query/validate-input.js +12 -13
- package/out/zql/src/query/validate-input.js.map +1 -1
- package/package.json +2 -1
- package/out/zero-server/src/query-registry.d.ts +0 -10
- package/out/zero-server/src/query-registry.d.ts.map +0 -1
- package/out/zero-server/src/query-registry.js +0 -35
- package/out/zero-server/src/query-registry.js.map +0 -1
- package/out/zql/src/query/define-query.d.ts +0 -75
- package/out/zql/src/query/define-query.d.ts.map +0 -1
- package/out/zql/src/query/define-query.js +0 -47
- package/out/zql/src/query/define-query.js.map +0 -1
- package/out/zql/src/query/query-definitions.d.ts +0 -32
- package/out/zql/src/query/query-definitions.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {pipeline, Readable, Writable} from 'node:stream';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.ts';\nimport type {Downstream} from '../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult = {\n type: 'stream';\n source: 'viewSyncer' | 'pusher';\n stream: Source<Downstream>;\n};\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n\n #viewSyncerOutboundStream: Source<Downstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n this.#lc.warn?.(`failed to parse message \"${data}\": ${String(e)}`);\n this.#closeWithError(\n {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n },\n e,\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n this.#proxyOutbound(result.stream);\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n void this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#lc.error?.('WebSocket error event', e.message, e.error);\n };\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound(outboundStream: Source<Downstream>) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: Downstream, _encoding, callback) =>\n this.send(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n return send(this.#lc, this.#ws, data, callback);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(\n JSON.stringify(data),\n callback === 'ignore-backpressure' ? undefined : callback,\n );\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno are low-level, transient I/O issues (e.g., EPIPE, ECONNRESET)\n // and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n containsTransientSocketCode(errorBody.message) ||\n hasTransientSocketCode(thrown)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\nconst TRANSIENT_SOCKET_ERROR_CODES = ['EPIPE', 'ECONNRESET', 'ECANCELED'];\n\nfunction containsTransientSocketCode(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const upper = message.toUpperCase();\n return TRANSIENT_SOCKET_ERROR_CODES.some(code => upper.includes(code));\n}\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n if (\n maybeCode &&\n TRANSIENT_SOCKET_ERROR_CODES.includes(maybeCode.toUpperCase())\n ) {\n return true;\n }\n if (\n 'message' in error &&\n typeof (error as {message?: unknown}).message === 'string'\n ) {\n return containsTransientSocketCode((error as {message?: string}).message);\n }\n return false;\n}\n"],"names":["ErrorKind.VersionNotSupported","ErrorOrigin.ZeroCache","valita.parse","ErrorKind.InvalidMessage","ErrorKind.Internal","ErrorKind.ClientNotFound","ErrorKind.TransformFailed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connection.js","sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {pipeline, Readable, Writable} from 'node:stream';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.ts';\nimport type {Downstream} from '../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult = {\n type: 'stream';\n source: 'viewSyncer' | 'pusher';\n stream: Source<Downstream>;\n};\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n\n #viewSyncerOutboundStream: Source<Downstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n this.#lc.warn?.(`failed to parse message \"${data}\": ${String(e)}`);\n this.#closeWithError(\n {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n },\n e,\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n this.#proxyOutbound(result.stream);\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n void this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#lc.error?.('WebSocket error event', e.message, e.error);\n };\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound(outboundStream: Source<Downstream>) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: Downstream, _encoding, callback) =>\n this.send(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n return send(this.#lc, this.#ws, data, callback);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(\n JSON.stringify(data),\n callback === 'ignore-backpressure' ? undefined : callback,\n );\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno or transient socket codes are low-level, transient I/O issues\n // (e.g., EPIPE, ECONNRESET) and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n hasTransientSocketCode(thrown) ||\n isTransientSocketMessage(errorBody.message)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\n// System error codes that indicate transient socket conditions.\n// These are checked via the `code` property on errors.\nconst TRANSIENT_SOCKET_ERROR_CODES = new Set([\n 'EPIPE',\n 'ECONNRESET',\n 'ECANCELED',\n]);\n\n// Error messages that indicate transient socket conditions but don't have\n// standard error codes (e.g., WebSocket library errors).\nconst TRANSIENT_SOCKET_MESSAGE_PATTERNS = [\n 'socket was closed while data was being compressed',\n];\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n return Boolean(\n maybeCode && TRANSIENT_SOCKET_ERROR_CODES.has(maybeCode.toUpperCase()),\n );\n}\n\nfunction isTransientSocketMessage(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const lower = message.toLowerCase();\n return TRANSIENT_SOCKET_MESSAGE_PATTERNS.some(pattern =>\n lower.includes(pattern),\n );\n}\n"],"names":["ErrorKind.VersionNotSupported","ErrorOrigin.ZeroCache","valita.parse","ErrorKind.InvalidMessage","ErrorKind.Internal","ErrorKind.ClientNotFound","ErrorKind.TransformFailed"],"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;AA2BF;;;;;;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;
|
|
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;AA2BF;;;;;;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;IA6JhB,GAAG;IAIH;;;;;OAKG;IACG,KAAK;IAqBX,IAAI;CAKL"}
|
|
@@ -90,12 +90,12 @@ class Syncer {
|
|
|
90
90
|
if (auth) {
|
|
91
91
|
const tokenOptions = tokenConfigOptions(this.#config.auth);
|
|
92
92
|
const hasPushOrMutate = this.#config?.push?.url !== void 0 || this.#config?.mutate?.url !== void 0;
|
|
93
|
-
const
|
|
93
|
+
const hasQueries = this.#config?.query?.url !== void 0 || this.#config?.getQueries?.url !== void 0;
|
|
94
94
|
const hasExactlyOneTokenOption = tokenOptions.length === 1;
|
|
95
|
-
const hasCustomEndpoints = hasPushOrMutate &&
|
|
95
|
+
const hasCustomEndpoints = hasPushOrMutate && hasQueries;
|
|
96
96
|
if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {
|
|
97
97
|
throw new Error(
|
|
98
|
-
"Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: " + JSON.stringify(tokenOptions) + ". You may also set both ZERO_MUTATE_URL and
|
|
98
|
+
"Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: " + JSON.stringify(tokenOptions) + ". You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options."
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
if (tokenOptions.length > 0) {
|
|
@@ -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 hasGetQueries = 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 && hasGetQueries;\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_GET_QUERIES_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 };\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,12 +1,12 @@
|
|
|
1
|
-
import type { CustomMutatorDefs } from '../../../zero-client/src/client/custom.ts';
|
|
2
1
|
import type { Schema } from '../../../zero-types/src/schema.ts';
|
|
3
2
|
import type { Format, ViewFactory } from '../../../zql/src/ivm/view.ts';
|
|
4
|
-
import type {
|
|
3
|
+
import type { AnyMutatorRegistry } from '../../../zql/src/mutate/mutator-registry.ts';
|
|
5
4
|
import type { QueryDelegate } from '../../../zql/src/query/query-delegate.ts';
|
|
6
5
|
import type { HumanReadable, MaterializeOptions, Query } from '../../../zql/src/query/query.ts';
|
|
7
6
|
import type { TypedView } from '../../../zql/src/query/typed-view.ts';
|
|
7
|
+
import type { CustomMutatorDefs } from './custom.ts';
|
|
8
8
|
import type { Zero } from './zero.ts';
|
|
9
|
-
export declare function registerZeroDelegate<TSchema extends Schema, MD extends CustomMutatorDefs | undefined, TContext
|
|
9
|
+
export declare function registerZeroDelegate<TSchema extends Schema, MD extends AnyMutatorRegistry | CustomMutatorDefs | undefined, TContext>(zero: Zero<TSchema, MD, TContext>, delegate: QueryDelegate): void;
|
|
10
10
|
/**
|
|
11
11
|
* Bindings interface for Zero instances, providing methods to materialize queries
|
|
12
12
|
* and extract query metadata.
|
|
@@ -39,5 +39,5 @@ export interface BindingsForZero<TSchema extends Schema> {
|
|
|
39
39
|
*
|
|
40
40
|
* @internal This API is for bindings only, not end users.
|
|
41
41
|
*/
|
|
42
|
-
export declare function bindingsForZero<TSchema extends Schema, MD extends CustomMutatorDefs | undefined, TContext
|
|
42
|
+
export declare function bindingsForZero<TSchema extends Schema, MD extends AnyMutatorRegistry | CustomMutatorDefs | undefined, TContext>(zero: Zero<TSchema, MD, TContext>): BindingsForZero<TSchema>;
|
|
43
43
|
//# sourceMappingURL=bindings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/bindings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/bindings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,EAAC,MAAM,EAAE,WAAW,EAAC,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,6CAA6C,CAAC;AACpF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,0CAA0C,CAAC;AAE5E,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,KAAK,EACN,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,sCAAsC,CAAC;AACpE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAapC,wBAAgB,oBAAoB,CAClC,OAAO,SAAS,MAAM,EACtB,EAAE,SAAS,kBAAkB,GAAG,iBAAiB,GAAG,SAAS,EAC7D,QAAQ,EACR,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAElE;AAcD;;;;;GAKG;AACH,MAAM,WAAW,eAAe,CAAC,OAAO,SAAS,MAAM;IACrD;;;OAGG;IACH,WAAW,CAAC,MAAM,SAAS,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,EAClE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EACtC,OAAO,CAAC,EAAE,SAAS,EACnB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAErC;;;OAGG;IACH,WAAW,CAAC,MAAM,SAAS,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,EACrE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EACtC,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,EACjD,OAAO,CAAC,EAAE,kBAAkB,GAC3B,CAAC,CAAC;IAEL;;OAEG;IACH,IAAI,CAAC,MAAM,SAAS,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,EAC3D,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,GACrC,MAAM,CAAC;IAEV;;OAEG;IACH,MAAM,CAAC,MAAM,SAAS,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,EAC7D,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,GACrC,MAAM,CAAC;CACX;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,OAAO,SAAS,MAAM,EACtB,EAAE,SAAS,kBAAkB,GAAG,iBAAiB,GAAG,SAAS,EAC7D,QAAQ,EACR,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,GAAG,eAAe,CAAC,OAAO,CAAC,CA0B7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.js","sources":["../../../../../zero-client/src/client/bindings.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"bindings.js","sources":["../../../../../zero-client/src/client/bindings.ts"],"sourcesContent":["import type {Schema} from '../../../zero-types/src/schema.ts';\nimport type {Format, ViewFactory} from '../../../zql/src/ivm/view.ts';\nimport type {AnyMutatorRegistry} from '../../../zql/src/mutate/mutator-registry.ts';\nimport type {QueryDelegate} from '../../../zql/src/query/query-delegate.ts';\nimport {asQueryInternals} from '../../../zql/src/query/query-internals.ts';\nimport type {\n HumanReadable,\n MaterializeOptions,\n Query,\n} from '../../../zql/src/query/query.ts';\nimport type {TypedView} from '../../../zql/src/query/typed-view.ts';\nimport type {CustomMutatorDefs} from './custom.ts';\nimport type {Zero} from './zero.ts';\n\n/**\n * Internal WeakMap to store QueryDelegate for each Zero instance.\n * This is populated by Zero's constructor and allows bindings to access\n * the delegate without exposing it as a public API.\n */\nconst zeroDelegates = new WeakMap<\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n Zero<any, any, any>,\n QueryDelegate\n>();\n\nexport function registerZeroDelegate<\n TSchema extends Schema,\n MD extends AnyMutatorRegistry | CustomMutatorDefs | undefined,\n TContext,\n>(zero: Zero<TSchema, MD, TContext>, delegate: QueryDelegate): void {\n zeroDelegates.set(zero, delegate);\n}\n\nfunction mustGetDelegate<\n TSchema extends Schema,\n MD extends AnyMutatorRegistry | CustomMutatorDefs | undefined,\n TContext,\n>(zero: Zero<TSchema, MD, TContext>): QueryDelegate {\n const delegate = zeroDelegates.get(zero);\n if (!delegate) {\n throw new Error('Zero instance not registered with bindings');\n }\n return delegate;\n}\n\n/**\n * Bindings interface for Zero instances, providing methods to materialize queries\n * and extract query metadata.\n *\n * @internal This API is for bindings only, not end users.\n */\nexport interface BindingsForZero<TSchema extends Schema> {\n /**\n * Materialize a query into a reactive view without a custom factory.\n * Returns a TypedView that automatically updates when underlying data changes.\n */\n materialize<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TSchema, TTable, TReturn>,\n factory?: undefined,\n options?: MaterializeOptions,\n ): TypedView<HumanReadable<TReturn>>;\n\n /**\n * Materialize a query into a reactive view using a custom factory.\n * The factory can transform the view into a framework-specific reactive object.\n */\n materialize<TTable extends keyof TSchema['tables'] & string, TReturn, T>(\n query: Query<TSchema, TTable, TReturn>,\n factory: ViewFactory<TSchema, TTable, TReturn, T>,\n options?: MaterializeOptions,\n ): T;\n\n /**\n * Compute the hash of a query for caching and deduplication purposes.\n */\n hash<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TSchema, TTable, TReturn>,\n ): string;\n\n /**\n * Get the format/schema of a query's result set.\n */\n format<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TSchema, TTable, TReturn>,\n ): Format;\n}\n\n/**\n * Create a bindings object for a Zero instance.\n * This provides low-level access to query materialization and metadata extraction.\n *\n * @internal This API is for bindings only, not end users.\n */\nexport function bindingsForZero<\n TSchema extends Schema,\n MD extends AnyMutatorRegistry | CustomMutatorDefs | undefined,\n TContext,\n>(zero: Zero<TSchema, MD, TContext>): BindingsForZero<TSchema> {\n const delegate = mustGetDelegate(zero);\n\n return {\n materialize<TTable extends keyof TSchema['tables'] & string, TReturn, T>(\n query: Query<TSchema, TTable, TReturn>,\n factory?: ViewFactory<TSchema, TTable, TReturn, T>,\n options?: MaterializeOptions,\n ) {\n return delegate.materialize(query, factory, options);\n },\n\n hash<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TSchema, TTable, TReturn>,\n ): string {\n const queryInternals = asQueryInternals(query);\n return queryInternals.hash();\n },\n\n format<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TSchema, TTable, TReturn>,\n ): Format {\n const queryInternals = asQueryInternals(query);\n return queryInternals.format;\n },\n };\n}\n"],"names":[],"mappings":";AAmBA,MAAM,oCAAoB,QAAA;AAMnB,SAAS,qBAId,MAAmC,UAA+B;AAClE,gBAAc,IAAI,MAAM,QAAQ;AAClC;AAEA,SAAS,gBAIP,MAAkD;AAClD,QAAM,WAAW,cAAc,IAAI,IAAI;AACvC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,SAAO;AACT;AAkDO,SAAS,gBAId,MAA6D;AAC7D,QAAM,WAAW,gBAAgB,IAAI;AAErC,SAAO;AAAA,IACL,YACE,OACA,SACA,SACA;AACA,aAAO,SAAS,YAAY,OAAO,SAAS,OAAO;AAAA,IACrD;AAAA,IAEA,KACE,OACQ;AACR,YAAM,iBAAiB,iBAAiB,KAAK;AAC7C,aAAO,eAAe,KAAA;AAAA,IACxB;AAAA,IAEA,OACE,OACQ;AACR,YAAM,iBAAiB,iBAAiB,KAAK;AAC7C,aAAO,eAAe;AAAA,IACxB;AAAA,EAAA;AAEJ;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EACF;QACE,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACD;QACE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EACF;QACE,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACD;QACE,IAAI,EAAE,OAAO,CAAC;QACd,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACD;QACE,IAAI,EAAE,YAAY,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACP,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpB;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;IAExC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;KAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,qBAAa,cAAe,YAAW,UAAU;;gBAO7C,iBAAiB,EAAE,iBAAiB,EACpC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,IAAI;IAQpD,IAAI,KAAK,IAAI,MAAM,CAAC,eAAe,CAAC,CAEnC;IAEK,OAAO,CAAC,IAAI,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;KAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BvE;AAED,qBAAa,gBAAiB,YAAW,MAAM,CAAC,eAAe,CAAC;;gBAIlD,iBAAiB,EAAE,iBAAiB;IAKhD,IAAI,OAAO,IAAI,eAAe,CAE7B;IAED,SAAS,GAAI,UAAU,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAI7D;CA0DN"}
|
|
@@ -83,7 +83,7 @@ class ConnectionSource {
|
|
|
83
83
|
status: state.reason.errorBody.status,
|
|
84
84
|
...state.reason.errorBody.bodyPreview ? { body: state.reason.errorBody.bodyPreview } : {}
|
|
85
85
|
} : state.reason.errorBody.kind === TransformFailed ? {
|
|
86
|
-
type: "
|
|
86
|
+
type: "query",
|
|
87
87
|
status: state.reason.errorBody.status,
|
|
88
88
|
...state.reason.errorBody.bodyPreview ? { body: state.reason.errorBody.bodyPreview } : {}
|
|
89
89
|
} : {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sources":["../../../../../zero-client/src/client/connection.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {\n ConnectionManager,\n ConnectionManagerState,\n} from './connection-manager.ts';\nimport {ConnectionStatus} from './connection-status.ts';\n\n/**\n * The current connection state of the Zero instance. One of the following states:\n *\n * - `connecting`: The client is actively trying to connect every 5 seconds.\n * - `disconnected`: The client is now in an \"offline\" state. It will continue\n * to try to connect every 5 seconds.\n * - `connected`: The client has opened a successful connection to the server.\n * - `needs-auth`: Authentication is invalid or expired. No connection retries will be made\n * until the host application calls `connect()`.\n * - `error`: A fatal error occurred. No connection retries will be made until the host\n * application calls `connect()` again.\n * - `closed`: The client was shut down (for example via `zero.close()`). This is\n * a terminal state, and a new Zero instance must be created to reconnect.\n */\nexport type ConnectionState =\n | {\n name: 'disconnected';\n reason: string;\n }\n | {\n name: 'connecting';\n reason?: string;\n }\n | {\n name: 'connected';\n }\n | {\n name: 'needs-auth';\n reason:\n | {\n type: 'mutate';\n status: 401 | 403;\n body?: string;\n }\n | {\n type: '
|
|
1
|
+
{"version":3,"file":"connection.js","sources":["../../../../../zero-client/src/client/connection.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {\n ConnectionManager,\n ConnectionManagerState,\n} from './connection-manager.ts';\nimport {ConnectionStatus} from './connection-status.ts';\n\n/**\n * The current connection state of the Zero instance. One of the following states:\n *\n * - `connecting`: The client is actively trying to connect every 5 seconds.\n * - `disconnected`: The client is now in an \"offline\" state. It will continue\n * to try to connect every 5 seconds.\n * - `connected`: The client has opened a successful connection to the server.\n * - `needs-auth`: Authentication is invalid or expired. No connection retries will be made\n * until the host application calls `connect()`.\n * - `error`: A fatal error occurred. No connection retries will be made until the host\n * application calls `connect()` again.\n * - `closed`: The client was shut down (for example via `zero.close()`). This is\n * a terminal state, and a new Zero instance must be created to reconnect.\n */\nexport type ConnectionState =\n | {\n name: 'disconnected';\n reason: string;\n }\n | {\n name: 'connecting';\n reason?: string;\n }\n | {\n name: 'connected';\n }\n | {\n name: 'needs-auth';\n reason:\n | {\n type: 'mutate';\n status: 401 | 403;\n body?: string;\n }\n | {\n type: 'query';\n status: 401 | 403;\n body?: string;\n }\n | {\n type: 'zero-cache';\n reason: string;\n };\n }\n | {\n name: 'error';\n reason: string;\n }\n | {\n name: 'closed';\n reason: string;\n };\n\nexport interface Source<T> {\n /**\n * The current state value.\n */\n readonly current: T;\n\n /**\n * Subscribe to state changes.\n *\n * @param listener - Called when the state changes with the new state value.\n * @returns A function to unsubscribe from state changes.\n */\n subscribe(listener: (state: T) => void): () => void;\n}\n\n/**\n * Connection API for managing Zero's connection lifecycle.\n */\nexport interface Connection {\n /**\n * The current connection state as a subscribable value.\n */\n readonly state: Source<ConnectionState>;\n\n /**\n * Resumes the connection from a terminal state.\n *\n * If called when not in a terminal state, this method does nothing.\n *\n * @param opts - Optional connection options\n * @param opts.auth - Token to use for authentication. If provided, this overrides\n * the stored auth credential for this connection attempt.\n * If `null` or `undefined`, the stored auth credential is cleared.\n * @returns A promise that resolves once the connection state has transitioned to connecting.\n */\n connect(opts?: {auth: string | null | undefined}): Promise<void>;\n}\n\nexport class ConnectionImpl implements Connection {\n readonly #connectionManager: ConnectionManager;\n readonly #lc: LogContext;\n readonly #source: ConnectionSource;\n readonly #setAuth: (auth: string | null | undefined) => void;\n\n constructor(\n connectionManager: ConnectionManager,\n lc: LogContext,\n setAuth: (auth: string | null | undefined) => void,\n ) {\n this.#connectionManager = connectionManager;\n this.#lc = lc;\n this.#source = new ConnectionSource(connectionManager);\n this.#setAuth = setAuth;\n }\n\n get state(): Source<ConnectionState> {\n return this.#source;\n }\n\n async connect(opts?: {auth: string | null | undefined}): Promise<void> {\n const lc = this.#lc.withContext('connect');\n\n if (opts && 'auth' in opts) {\n lc.debug?.('Updating auth credential from connect()');\n this.#setAuth(opts.auth);\n }\n\n // only allow connect() to be called from a terminal state\n if (!this.#connectionManager.isInTerminalState()) {\n lc.debug?.(\n 'connect() called but not in a terminal state. Current state:',\n this.#connectionManager.state.name,\n );\n return;\n }\n\n lc.info?.(\n `Resuming connection from state: ${this.#connectionManager.state.name}`,\n );\n\n // Transition to connecting, which will trigger the state change resolver\n // and unblock the run loop. Wait for the next state change (connected, disconnected, etc.)\n const {nextStatePromise} = this.#connectionManager.connecting();\n await nextStatePromise;\n }\n}\n\nexport class ConnectionSource implements Source<ConnectionState> {\n #state: ConnectionState;\n readonly #connectionManager: ConnectionManager;\n\n constructor(connectionManager: ConnectionManager) {\n this.#connectionManager = connectionManager;\n this.#state = this.#mapConnectionManagerState(connectionManager.state);\n }\n\n get current(): ConnectionState {\n return this.#state;\n }\n\n subscribe = (listener: (s: ConnectionState) => void): (() => void) =>\n this.#connectionManager.subscribe(state => {\n this.#state = this.#mapConnectionManagerState(state);\n listener(this.#state);\n });\n\n #mapConnectionManagerState(state: ConnectionManagerState): ConnectionState {\n switch (state.name) {\n case ConnectionStatus.Closed:\n return {\n name: 'closed',\n reason: state.reason.message,\n };\n case ConnectionStatus.Connected:\n return {\n name: 'connected',\n };\n case ConnectionStatus.Connecting:\n return {\n name: 'connecting',\n ...(state.reason?.message ? {reason: state.reason.message} : {}),\n };\n case ConnectionStatus.Disconnected:\n return {\n name: 'disconnected',\n reason: state.reason.message,\n };\n case ConnectionStatus.Error:\n return {\n name: 'error',\n reason: state.reason.message,\n };\n case ConnectionStatus.NeedsAuth:\n return {\n name: 'needs-auth',\n reason:\n state.reason.errorBody.kind === ErrorKind.PushFailed\n ? {\n type: 'mutate',\n status: state.reason.errorBody.status,\n ...(state.reason.errorBody.bodyPreview\n ? {body: state.reason.errorBody.bodyPreview}\n : {}),\n }\n : state.reason.errorBody.kind === ErrorKind.TransformFailed\n ? {\n type: 'query',\n status: state.reason.errorBody.status,\n ...(state.reason.errorBody.bodyPreview\n ? {body: state.reason.errorBody.bodyPreview}\n : {}),\n }\n : {\n type: 'zero-cache',\n reason: state.reason.message,\n },\n };\n\n default:\n unreachable(state);\n }\n }\n}\n"],"names":["ConnectionStatus.Closed","ConnectionStatus.Connected","ConnectionStatus.Connecting","ConnectionStatus.Disconnected","ConnectionStatus.Error","ConnectionStatus.NeedsAuth","ErrorKind.PushFailed","ErrorKind.TransformFailed"],"mappings":";;;AAoGO,MAAM,eAAqC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YACE,mBACA,IACA,SACA;AACA,SAAK,qBAAqB;AAC1B,SAAK,MAAM;AACX,SAAK,UAAU,IAAI,iBAAiB,iBAAiB;AACrD,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,IAAI,QAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,QAAQ,MAAyD;AACrE,UAAM,KAAK,KAAK,IAAI,YAAY,SAAS;AAEzC,QAAI,QAAQ,UAAU,MAAM;AAC1B,SAAG,QAAQ,yCAAyC;AACpD,WAAK,SAAS,KAAK,IAAI;AAAA,IACzB;AAGA,QAAI,CAAC,KAAK,mBAAmB,qBAAqB;AAChD,SAAG;AAAA,QACD;AAAA,QACA,KAAK,mBAAmB,MAAM;AAAA,MAAA;AAEhC;AAAA,IACF;AAEA,OAAG;AAAA,MACD,mCAAmC,KAAK,mBAAmB,MAAM,IAAI;AAAA,IAAA;AAKvE,UAAM,EAAC,iBAAA,IAAoB,KAAK,mBAAmB,WAAA;AACnD,UAAM;AAAA,EACR;AACF;AAEO,MAAM,iBAAoD;AAAA,EAC/D;AAAA,EACS;AAAA,EAET,YAAY,mBAAsC;AAChD,SAAK,qBAAqB;AAC1B,SAAK,SAAS,KAAK,2BAA2B,kBAAkB,KAAK;AAAA,EACvE;AAAA,EAEA,IAAI,UAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAY,CAAC,aACX,KAAK,mBAAmB,UAAU,CAAA,UAAS;AACzC,SAAK,SAAS,KAAK,2BAA2B,KAAK;AACnD,aAAS,KAAK,MAAM;AAAA,EACtB,CAAC;AAAA,EAEH,2BAA2B,OAAgD;AACzE,YAAQ,MAAM,MAAA;AAAA,MACZ,KAAKA;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ,MAAM,OAAO;AAAA,QAAA;AAAA,MAEzB,KAAKC;AACH,eAAO;AAAA,UACL,MAAM;AAAA,QAAA;AAAA,MAEV,KAAKC;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,GAAI,MAAM,QAAQ,UAAU,EAAC,QAAQ,MAAM,OAAO,YAAW,CAAA;AAAA,QAAC;AAAA,MAElE,KAAKC;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ,MAAM,OAAO;AAAA,QAAA;AAAA,MAEzB,KAAKC;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ,MAAM,OAAO;AAAA,QAAA;AAAA,MAEzB,KAAKC;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QACE,MAAM,OAAO,UAAU,SAASC,aAC5B;AAAA,YACE,MAAM;AAAA,YACN,QAAQ,MAAM,OAAO,UAAU;AAAA,YAC/B,GAAI,MAAM,OAAO,UAAU,cACvB,EAAC,MAAM,MAAM,OAAO,UAAU,gBAC9B,CAAA;AAAA,UAAC,IAEP,MAAM,OAAO,UAAU,SAASC,kBAC9B;AAAA,YACE,MAAM;AAAA,YACN,QAAQ,MAAM,OAAO,UAAU;AAAA,YAC/B,GAAI,MAAM,OAAO,UAAU,cACvB,EAAC,MAAM,MAAM,OAAO,UAAU,gBAC9B,CAAA;AAAA,UAAC,IAEP;AAAA,YACE,MAAM;AAAA,YACN,QAAQ,MAAM,OAAO;AAAA,UAAA;AAAA,QACvB;AAAA,MAGZ;AACE,oBAAiB;AAAA,IAAA;AAAA,EAEvB;AACF;"}
|
|
@@ -44,13 +44,15 @@ type ZeroCRUDMutate = {
|
|
|
44
44
|
};
|
|
45
45
|
/**
|
|
46
46
|
* This is the zero.mutate object part representing the CRUD operations. If the
|
|
47
|
-
*
|
|
47
|
+
* tables are `issue` and `label`, then this object will have `issue` and
|
|
48
48
|
* `label` properties.
|
|
49
|
+
*
|
|
50
|
+
* @param schema - The schema defining the tables
|
|
51
|
+
* @param repMutate - The replicache mutate object with the CRUD mutation
|
|
52
|
+
* @param mutate - The object to use as the mutate object. Properties for each
|
|
53
|
+
* table will be assigned to this object.
|
|
49
54
|
*/
|
|
50
|
-
export declare function makeCRUDMutate<const S extends Schema>(schema: S, repMutate: ZeroCRUDMutate):
|
|
51
|
-
mutate: DBMutator<S>;
|
|
52
|
-
mutateBatch: BatchMutator<S>;
|
|
53
|
-
};
|
|
55
|
+
export declare function makeCRUDMutate<const S extends Schema>(schema: S, repMutate: ZeroCRUDMutate, mutate: object): BatchMutator<S>;
|
|
54
56
|
/**
|
|
55
57
|
* Creates the `{inesrt, upsert, update, delete}` object for use inside a
|
|
56
58
|
* batch.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/crud.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0CAA0C,CAAC;AAC1E,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,WAAW,EACZ,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAC,WAAW,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,WAAW,IAAI;IAChD;;;;;OAKG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjD;;;;;OAKG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjD;;;;OAIG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjD;;;OAGG;IACH,MAAM,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,IACpC,CAAC,CAAC,sBAAsB,CAAC,SAAS,KAAK,GACnC,EAAE,GACF;KACG,CAAC,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;CACvD,CAAC;AAER,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,CAAC,EAC7C,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,KACvC,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhB,KAAK,cAAc,GAAG;IACpB,CAAC,kBAAkB,CAAC,EAAE,UAAU,CAAC;CAClC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/crud.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { must } from "../../../shared/src/must.js";
|
|
|
2
2
|
import { promiseVoid } from "../../../shared/src/resolved-promises.js";
|
|
3
3
|
import { CRUD_MUTATION_NAME } from "../../../zero-protocol/src/push.js";
|
|
4
4
|
import { toPrimaryKeyString } from "./keys.js";
|
|
5
|
-
function makeCRUDMutate(schema, repMutate) {
|
|
5
|
+
function makeCRUDMutate(schema, repMutate, mutate) {
|
|
6
6
|
const { [CRUD_MUTATION_NAME]: zeroCRUD } = repMutate;
|
|
7
7
|
const mutateBatch = async (body) => {
|
|
8
8
|
const ops = [];
|
|
@@ -14,14 +14,14 @@ function makeCRUDMutate(schema, repMutate) {
|
|
|
14
14
|
await zeroCRUD({ ops });
|
|
15
15
|
return rv;
|
|
16
16
|
};
|
|
17
|
-
const mutate = {};
|
|
18
17
|
for (const [name, tableSchema] of Object.entries(schema.tables)) {
|
|
19
|
-
mutate[name] = makeEntityCRUDMutate(
|
|
18
|
+
mutate[name] = makeEntityCRUDMutate(
|
|
19
|
+
name,
|
|
20
|
+
tableSchema.primaryKey,
|
|
21
|
+
zeroCRUD
|
|
22
|
+
);
|
|
20
23
|
}
|
|
21
|
-
return
|
|
22
|
-
mutate,
|
|
23
|
-
mutateBatch
|
|
24
|
-
};
|
|
24
|
+
return mutateBatch;
|
|
25
25
|
}
|
|
26
26
|
function makeEntityCRUDMutate(tableName, primaryKey, zeroCRUD) {
|
|
27
27
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud.js","sources":["../../../../../zero-client/src/client/crud.ts"],"sourcesContent":["import type {ReadonlyJSONObject} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport {\n CRUD_MUTATION_NAME,\n type CRUDMutationArg,\n type CRUDOp,\n type DeleteOp,\n type InsertOp,\n type UpdateOp,\n type UpsertOp,\n} from '../../../zero-protocol/src/push.ts';\nimport type {TableSchema} from '../../../zero-schema/src/table-schema.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport type {\n DeleteID,\n InsertValue,\n UpdateValue,\n UpsertValue,\n} from '../../../zql/src/mutate/custom.ts';\nimport type {IVMSourceBranch} from './ivm-branch.ts';\nimport {toPrimaryKeyString} from './keys.ts';\nimport type {MutatorDefs, WriteTransaction} from './replicache-types.ts';\n\n/**\n * This is the type of the generated mutate.<name>.<verb> function.\n */\nexport type TableMutator<S extends TableSchema> = {\n /**\n * Writes a row if a row with the same primary key doesn't already exists.\n * Non-primary-key fields that are 'optional' can be omitted or set to\n * `undefined`. Such fields will be assigned the value `null` optimistically\n * and then the default value as defined by the server.\n */\n insert: (value: InsertValue<S>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<S>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<S>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<S>) => Promise<void>;\n};\n\nexport type DBMutator<S extends Schema> =\n S['enableLegacyMutators'] extends false\n ? {} // {} is needed here for intersection type identity\n : {\n [K in keyof S['tables']]: TableMutator<S['tables'][K]>;\n };\n\nexport type BatchMutator<S extends Schema> = <R>(\n body: (m: DBMutator<S>) => MaybePromise<R>,\n) => Promise<R>;\n\ntype ZeroCRUDMutate = {\n [CRUD_MUTATION_NAME]: CRUDMutate;\n};\n\n/**\n * This is the zero.mutate object part representing the CRUD operations. If the\n * queries are `issue` and `label`, then this object will have `issue` and\n * `label` properties.\n */\nexport function makeCRUDMutate<const S extends Schema>(\n schema: S,\n repMutate: ZeroCRUDMutate,\n): {mutate: DBMutator<S>; mutateBatch: BatchMutator<S>} {\n const {[CRUD_MUTATION_NAME]: zeroCRUD} = repMutate;\n\n const mutateBatch = async <R>(body: (m: DBMutator<S>) => R): Promise<R> => {\n const ops: CRUDOp[] = [];\n const m = {} as Record<string, unknown>;\n for (const name of Object.keys(schema.tables)) {\n m[name] = makeBatchCRUDMutate(name, schema, ops);\n }\n\n const rv = await body(m as DBMutator<S>);\n await zeroCRUD({ops});\n return rv;\n };\n\n const mutate: Record<string, TableMutator<TableSchema>> = {};\n for (const [name, tableSchema] of Object.entries(schema.tables)) {\n mutate[name] = makeEntityCRUDMutate(name, tableSchema.primaryKey, zeroCRUD);\n }\n return {\n mutate: mutate as DBMutator<S>,\n mutateBatch: mutateBatch as BatchMutator<S>,\n };\n}\n\n/**\n * Creates the `{insert, upsert, update, delete}` object for use outside a\n * batch.\n */\nfunction makeEntityCRUDMutate<S extends TableSchema>(\n tableName: string,\n primaryKey: S['primaryKey'],\n zeroCRUD: CRUDMutate,\n): TableMutator<S> {\n return {\n insert: (value: InsertValue<S>) => {\n const op: InsertOp = {\n op: 'insert',\n tableName,\n primaryKey,\n value,\n };\n return zeroCRUD({ops: [op]});\n },\n upsert: (value: UpsertValue<S>) => {\n const op: UpsertOp = {\n op: 'upsert',\n tableName,\n primaryKey,\n value,\n };\n return zeroCRUD({ops: [op]});\n },\n update: (value: UpdateValue<S>) => {\n const op: UpdateOp = {\n op: 'update',\n tableName,\n primaryKey,\n value,\n };\n return zeroCRUD({ops: [op]});\n },\n delete: (id: DeleteID<S>) => {\n const op: DeleteOp = {\n op: 'delete',\n tableName,\n primaryKey,\n value: id,\n };\n return zeroCRUD({ops: [op]});\n },\n };\n}\n\n/**\n * Creates the `{inesrt, upsert, update, delete}` object for use inside a\n * batch.\n */\nexport function makeBatchCRUDMutate<S extends TableSchema>(\n tableName: string,\n schema: Schema,\n ops: CRUDOp[],\n): TableMutator<S> {\n const {primaryKey} = schema.tables[tableName];\n return {\n insert: (value: InsertValue<S>) => {\n const op: InsertOp = {\n op: 'insert',\n tableName,\n primaryKey,\n value,\n };\n ops.push(op);\n return promiseVoid;\n },\n upsert: (value: UpsertValue<S>) => {\n const op: UpsertOp = {\n op: 'upsert',\n tableName,\n primaryKey,\n value,\n };\n ops.push(op);\n return promiseVoid;\n },\n update: (value: UpdateValue<S>) => {\n const op: UpdateOp = {\n op: 'update',\n tableName,\n primaryKey,\n value,\n };\n ops.push(op);\n return promiseVoid;\n },\n delete: (id: DeleteID<S>) => {\n const op: DeleteOp = {\n op: 'delete',\n tableName,\n primaryKey,\n value: id,\n };\n ops.push(op);\n return promiseVoid;\n },\n };\n}\n\nexport type WithCRUD<MD extends MutatorDefs> = MD & {\n [CRUD_MUTATION_NAME]: CRUDMutator;\n};\n\nexport type CRUDMutate = (crudArg: CRUDMutationArg) => Promise<void>;\n\nexport type CRUDMutator = (\n tx: WriteTransaction,\n crudArg: CRUDMutationArg,\n) => Promise<void>;\n\n// Zero crud mutators cannot function at the same\n// time as custom mutators as the rebase of crud mutators will not\n// update the IVM branch. That's ok, we're removing crud mutators\n// in favor of custom mutators.\nexport function makeCRUDMutator(schema: Schema): CRUDMutator {\n return async function zeroCRUDMutator(\n tx: WriteTransaction,\n crudArg: CRUDMutationArg,\n ): Promise<void> {\n for (const op of crudArg.ops) {\n switch (op.op) {\n case 'insert':\n await insertImpl(tx, op, schema, undefined);\n break;\n case 'upsert':\n await upsertImpl(tx, op, schema, undefined);\n break;\n case 'update':\n await updateImpl(tx, op, schema, undefined);\n break;\n case 'delete':\n await deleteImpl(tx, op, schema, undefined);\n break;\n }\n }\n };\n}\n\nfunction defaultOptionalFieldsToNull(\n schema: TableSchema,\n value: ReadonlyJSONObject,\n): ReadonlyJSONObject {\n let rv = value;\n for (const name in schema.columns) {\n if (rv[name] === undefined) {\n rv = {...rv, [name]: null};\n }\n }\n return rv;\n}\n\nexport async function insertImpl(\n tx: WriteTransaction,\n arg: InsertOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n if (!(await tx.has(key))) {\n const val = defaultOptionalFieldsToNull(\n schema.tables[arg.tableName],\n arg.value,\n );\n await tx.set(key, val);\n if (ivmBranch) {\n must(ivmBranch.getSource(arg.tableName)).push({\n type: 'add',\n row: arg.value,\n });\n }\n }\n}\n\nexport async function upsertImpl(\n tx: WriteTransaction,\n arg: InsertOp | UpsertOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n if (await tx.has(key)) {\n await updateImpl(tx, {...arg, op: 'update'}, schema, ivmBranch);\n } else {\n await insertImpl(tx, {...arg, op: 'insert'}, schema, ivmBranch);\n }\n}\n\nexport async function updateImpl(\n tx: WriteTransaction,\n arg: UpdateOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n const prev = await tx.get(key);\n if (prev === undefined) {\n return;\n }\n const update = arg.value;\n const next = {...(prev as ReadonlyJSONObject)};\n for (const k in update) {\n if (update[k] !== undefined) {\n next[k] = update[k];\n }\n }\n await tx.set(key, next);\n if (ivmBranch) {\n must(ivmBranch.getSource(arg.tableName)).push({\n type: 'edit',\n oldRow: prev as Row,\n row: next,\n });\n }\n}\n\nexport async function deleteImpl(\n tx: WriteTransaction,\n arg: DeleteOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n const prev = await tx.get(key);\n if (prev === undefined) {\n return;\n }\n await tx.del(key);\n if (ivmBranch) {\n must(ivmBranch.getSource(arg.tableName)).push({\n type: 'remove',\n row: prev as Row,\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"crud.js","sources":["../../../../../zero-client/src/client/crud.ts"],"sourcesContent":["import type {ReadonlyJSONObject} from '../../../shared/src/json.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport {\n CRUD_MUTATION_NAME,\n type CRUDMutationArg,\n type CRUDOp,\n type DeleteOp,\n type InsertOp,\n type UpdateOp,\n type UpsertOp,\n} from '../../../zero-protocol/src/push.ts';\nimport type {TableSchema} from '../../../zero-schema/src/table-schema.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport type {\n DeleteID,\n InsertValue,\n UpdateValue,\n UpsertValue,\n} from '../../../zql/src/mutate/custom.ts';\nimport type {IVMSourceBranch} from './ivm-branch.ts';\nimport {toPrimaryKeyString} from './keys.ts';\nimport type {MutatorDefs, WriteTransaction} from './replicache-types.ts';\n\n/**\n * This is the type of the generated mutate.<name>.<verb> function.\n */\nexport type TableMutator<S extends TableSchema> = {\n /**\n * Writes a row if a row with the same primary key doesn't already exists.\n * Non-primary-key fields that are 'optional' can be omitted or set to\n * `undefined`. Such fields will be assigned the value `null` optimistically\n * and then the default value as defined by the server.\n */\n insert: (value: InsertValue<S>) => Promise<void>;\n\n /**\n * Writes a row unconditionally, overwriting any existing row with the same\n * primary key. Non-primary-key fields that are 'optional' can be omitted or\n * set to `undefined`. Such fields will be assigned the value `null`\n * optimistically and then the default value as defined by the server.\n */\n upsert: (value: UpsertValue<S>) => Promise<void>;\n\n /**\n * Updates a row with the same primary key. If no such row exists, this\n * function does nothing. All non-primary-key fields can be omitted or set to\n * `undefined`. Such fields will be left unchanged from previous value.\n */\n update: (value: UpdateValue<S>) => Promise<void>;\n\n /**\n * Deletes the row with the specified primary key. If no such row exists, this\n * function does nothing.\n */\n delete: (id: DeleteID<S>) => Promise<void>;\n};\n\nexport type DBMutator<S extends Schema> =\n S['enableLegacyMutators'] extends false\n ? {} // {} is needed here for intersection type identity\n : {\n [K in keyof S['tables']]: TableMutator<S['tables'][K]>;\n };\n\nexport type BatchMutator<S extends Schema> = <R>(\n body: (m: DBMutator<S>) => MaybePromise<R>,\n) => Promise<R>;\n\ntype ZeroCRUDMutate = {\n [CRUD_MUTATION_NAME]: CRUDMutate;\n};\n\n/**\n * This is the zero.mutate object part representing the CRUD operations. If the\n * tables are `issue` and `label`, then this object will have `issue` and\n * `label` properties.\n *\n * @param schema - The schema defining the tables\n * @param repMutate - The replicache mutate object with the CRUD mutation\n * @param mutate - The object to use as the mutate object. Properties for each\n * table will be assigned to this object.\n */\nexport function makeCRUDMutate<const S extends Schema>(\n schema: S,\n repMutate: ZeroCRUDMutate,\n mutate: object,\n): BatchMutator<S> {\n const {[CRUD_MUTATION_NAME]: zeroCRUD} = repMutate;\n\n const mutateBatch = async <R>(body: (m: DBMutator<S>) => R): Promise<R> => {\n const ops: CRUDOp[] = [];\n const m = {} as Record<string, unknown>;\n for (const name of Object.keys(schema.tables)) {\n m[name] = makeBatchCRUDMutate(name, schema, ops);\n }\n\n const rv = await body(m as DBMutator<S>);\n await zeroCRUD({ops});\n return rv;\n };\n\n for (const [name, tableSchema] of Object.entries(schema.tables)) {\n (mutate as Record<string, unknown>)[name] = makeEntityCRUDMutate(\n name,\n tableSchema.primaryKey,\n zeroCRUD,\n );\n }\n return mutateBatch as BatchMutator<S>;\n}\n\n/**\n * Creates the `{insert, upsert, update, delete}` object for use outside a\n * batch.\n */\nfunction makeEntityCRUDMutate<S extends TableSchema>(\n tableName: string,\n primaryKey: S['primaryKey'],\n zeroCRUD: CRUDMutate,\n): TableMutator<S> {\n return {\n insert: (value: InsertValue<S>) => {\n const op: InsertOp = {\n op: 'insert',\n tableName,\n primaryKey,\n value,\n };\n return zeroCRUD({ops: [op]});\n },\n upsert: (value: UpsertValue<S>) => {\n const op: UpsertOp = {\n op: 'upsert',\n tableName,\n primaryKey,\n value,\n };\n return zeroCRUD({ops: [op]});\n },\n update: (value: UpdateValue<S>) => {\n const op: UpdateOp = {\n op: 'update',\n tableName,\n primaryKey,\n value,\n };\n return zeroCRUD({ops: [op]});\n },\n delete: (id: DeleteID<S>) => {\n const op: DeleteOp = {\n op: 'delete',\n tableName,\n primaryKey,\n value: id,\n };\n return zeroCRUD({ops: [op]});\n },\n };\n}\n\n/**\n * Creates the `{inesrt, upsert, update, delete}` object for use inside a\n * batch.\n */\nexport function makeBatchCRUDMutate<S extends TableSchema>(\n tableName: string,\n schema: Schema,\n ops: CRUDOp[],\n): TableMutator<S> {\n const {primaryKey} = schema.tables[tableName];\n return {\n insert: (value: InsertValue<S>) => {\n const op: InsertOp = {\n op: 'insert',\n tableName,\n primaryKey,\n value,\n };\n ops.push(op);\n return promiseVoid;\n },\n upsert: (value: UpsertValue<S>) => {\n const op: UpsertOp = {\n op: 'upsert',\n tableName,\n primaryKey,\n value,\n };\n ops.push(op);\n return promiseVoid;\n },\n update: (value: UpdateValue<S>) => {\n const op: UpdateOp = {\n op: 'update',\n tableName,\n primaryKey,\n value,\n };\n ops.push(op);\n return promiseVoid;\n },\n delete: (id: DeleteID<S>) => {\n const op: DeleteOp = {\n op: 'delete',\n tableName,\n primaryKey,\n value: id,\n };\n ops.push(op);\n return promiseVoid;\n },\n };\n}\n\nexport type WithCRUD<MD extends MutatorDefs> = MD & {\n [CRUD_MUTATION_NAME]: CRUDMutator;\n};\n\nexport type CRUDMutate = (crudArg: CRUDMutationArg) => Promise<void>;\n\nexport type CRUDMutator = (\n tx: WriteTransaction,\n crudArg: CRUDMutationArg,\n) => Promise<void>;\n\n// Zero crud mutators cannot function at the same\n// time as custom mutators as the rebase of crud mutators will not\n// update the IVM branch. That's ok, we're removing crud mutators\n// in favor of custom mutators.\nexport function makeCRUDMutator(schema: Schema): CRUDMutator {\n return async function zeroCRUDMutator(\n tx: WriteTransaction,\n crudArg: CRUDMutationArg,\n ): Promise<void> {\n for (const op of crudArg.ops) {\n switch (op.op) {\n case 'insert':\n await insertImpl(tx, op, schema, undefined);\n break;\n case 'upsert':\n await upsertImpl(tx, op, schema, undefined);\n break;\n case 'update':\n await updateImpl(tx, op, schema, undefined);\n break;\n case 'delete':\n await deleteImpl(tx, op, schema, undefined);\n break;\n }\n }\n };\n}\n\nfunction defaultOptionalFieldsToNull(\n schema: TableSchema,\n value: ReadonlyJSONObject,\n): ReadonlyJSONObject {\n let rv = value;\n for (const name in schema.columns) {\n if (rv[name] === undefined) {\n rv = {...rv, [name]: null};\n }\n }\n return rv;\n}\n\nexport async function insertImpl(\n tx: WriteTransaction,\n arg: InsertOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n if (!(await tx.has(key))) {\n const val = defaultOptionalFieldsToNull(\n schema.tables[arg.tableName],\n arg.value,\n );\n await tx.set(key, val);\n if (ivmBranch) {\n must(ivmBranch.getSource(arg.tableName)).push({\n type: 'add',\n row: arg.value,\n });\n }\n }\n}\n\nexport async function upsertImpl(\n tx: WriteTransaction,\n arg: InsertOp | UpsertOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n if (await tx.has(key)) {\n await updateImpl(tx, {...arg, op: 'update'}, schema, ivmBranch);\n } else {\n await insertImpl(tx, {...arg, op: 'insert'}, schema, ivmBranch);\n }\n}\n\nexport async function updateImpl(\n tx: WriteTransaction,\n arg: UpdateOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n const prev = await tx.get(key);\n if (prev === undefined) {\n return;\n }\n const update = arg.value;\n const next = {...(prev as ReadonlyJSONObject)};\n for (const k in update) {\n if (update[k] !== undefined) {\n next[k] = update[k];\n }\n }\n await tx.set(key, next);\n if (ivmBranch) {\n must(ivmBranch.getSource(arg.tableName)).push({\n type: 'edit',\n oldRow: prev as Row,\n row: next,\n });\n }\n}\n\nexport async function deleteImpl(\n tx: WriteTransaction,\n arg: DeleteOp,\n schema: Schema,\n ivmBranch: IVMSourceBranch | undefined,\n): Promise<void> {\n const key = toPrimaryKeyString(\n arg.tableName,\n schema.tables[arg.tableName].primaryKey,\n arg.value,\n );\n const prev = await tx.get(key);\n if (prev === undefined) {\n return;\n }\n await tx.del(key);\n if (ivmBranch) {\n must(ivmBranch.getSource(arg.tableName)).push({\n type: 'remove',\n row: prev as Row,\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { ZeroTxData } from '../../../replicache/src/replicache-options.ts';
|
|
2
3
|
import type { ReadonlyJSONValue } from '../../../shared/src/json.ts';
|
|
3
4
|
import type { Schema } from '../../../zero-types/src/schema.ts';
|
|
4
|
-
import type { ClientTransaction, SchemaCRUD,
|
|
5
|
+
import type { ClientTransaction, SchemaCRUD, Transaction } from '../../../zql/src/mutate/custom.ts';
|
|
5
6
|
import { type HumanReadable, type Query, type RunOptions } from '../../../zql/src/query/query.ts';
|
|
7
|
+
import type { SchemaQuery } from '../../../zql/src/query/schema-query.ts';
|
|
6
8
|
import type { ClientID } from '../types/client-state.ts';
|
|
7
9
|
import type { WriteTransaction } from './replicache-types.ts';
|
|
8
10
|
/**
|
|
@@ -10,8 +12,7 @@ import type { WriteTransaction } from './replicache-types.ts';
|
|
|
10
12
|
* Supports arbitrary depth nesting of namespaces.
|
|
11
13
|
*/
|
|
12
14
|
export type CustomMutatorDefs = {
|
|
13
|
-
[namespaceOrKey: string]:
|
|
14
|
-
CustomMutatorImpl<any> | CustomMutatorDefs;
|
|
15
|
+
[namespaceOrKey: string]: CustomMutatorImpl<any> | CustomMutatorDefs;
|
|
15
16
|
};
|
|
16
17
|
export type MutatorResultDetails = {
|
|
17
18
|
readonly type: 'success';
|
|
@@ -36,7 +37,7 @@ export type MutatorResult = {
|
|
|
36
37
|
client: Promise<MutatorResultDetails & {}>;
|
|
37
38
|
server: Promise<MutatorResultDetails & {}>;
|
|
38
39
|
} & {};
|
|
39
|
-
export type CustomMutatorImpl<S extends Schema, TWrappedTransaction = unknown, TArgs = any> = (tx: Transaction<S, TWrappedTransaction>, args: TArgs) => Promise<void>;
|
|
40
|
+
export type CustomMutatorImpl<S extends Schema, TWrappedTransaction = unknown, TArgs = any, Context = unknown> = (tx: Transaction<S, TWrappedTransaction>, args: TArgs, ctx: Context) => Promise<void>;
|
|
40
41
|
/**
|
|
41
42
|
* The shape exposed on the `Zero.mutate` instance.
|
|
42
43
|
* The signature of a custom mutator takes a `transaction` as its first arg
|
|
@@ -61,5 +62,6 @@ export declare class TransactionImpl<TSchema extends Schema> implements ClientTr
|
|
|
61
62
|
get token(): string | undefined;
|
|
62
63
|
run<TTable extends keyof TSchema['tables'] & string, TReturn>(query: Query<TSchema, TTable, TReturn>, options?: RunOptions): Promise<HumanReadable<TReturn>>;
|
|
63
64
|
}
|
|
64
|
-
export declare function
|
|
65
|
+
export declare function getZeroTxData(repTx: WriteTransaction): ZeroTxData;
|
|
66
|
+
export declare function makeReplicacheMutator<S extends Schema, TWrappedTransaction, Context>(lc: LogContext, mutator: CustomMutatorImpl<S, TWrappedTransaction>, schema: S, context: Context): (repTx: WriteTransaction, args: ReadonlyJSONValue) => Promise<void>;
|
|
65
67
|
//# sourceMappingURL=custom.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/custom.ts"],"names":[],"mappings":"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"}
|