@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":"transform-query.js","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {\n transformResponseMessageSchema,\n type ErroredQuery,\n type TransformRequestBody,\n type TransformRequestMessage,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type TransformFailedBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {hashOfAST} from '../../../zero-protocol/src/query-hash.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {\n compileUrlPattern,\n fetchFromAPIServer,\n type HeaderOptions,\n} from '../custom/fetch.ts';\nimport
|
|
1
|
+
{"version":3,"file":"transform-query.js","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {\n transformResponseMessageSchema,\n type ErroredQuery,\n type TransformRequestBody,\n type TransformRequestMessage,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type TransformFailedBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {hashOfAST} from '../../../zero-protocol/src/query-hash.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {\n compileUrlPattern,\n fetchFromAPIServer,\n type HeaderOptions,\n} from '../custom/fetch.ts';\nimport type {CustomQueryRecord} from '../services/view-syncer/schema/types.ts';\nimport type {ShardID} from '../types/shards.ts';\n\n/**\n * Transforms a custom query by calling the user's API server.\n * Caches the transformed queries for 5 seconds to avoid unnecessary API calls.\n *\n * Error responses are not cached as the user may want to retry the query\n * and the error may be transient.\n *\n * The TTL was chosen to be 5 seconds since custom query requests come with\n * a token which itself may have a short TTL (e.g., 10 seconds).\n *\n * Token expiration isn't expected to be exact so this 5 second\n * caching shouldn't cause unexpected behavior. E.g., many JWT libraries\n * implement leeway for expiration checks: https://github.com/panva/jose/blob/main/docs/jwt/verify/interfaces/JWTVerifyOptions.md#clocktolerance\n *\n * The ViewSyncer will call the API server 3-4 times with the exact same queries\n * if we do not cache requests.\n *\n * Caching is safe here because the cache key encodes both\n * the user's cookies and auth token. A user cannot see another user's\n * transformed queries unless they share the same token and cookies.\n */\nexport class CustomQueryTransformer {\n readonly #shard: ShardID;\n readonly #cache: TimedCache<TransformedAndHashed>;\n readonly #config: {\n url: string[];\n forwardCookies: boolean;\n };\n readonly #urlPatterns: URLPattern[];\n readonly #lc: LogContext;\n\n constructor(\n lc: LogContext,\n config: {\n url: string[];\n forwardCookies: boolean;\n },\n shard: ShardID,\n ) {\n this.#config = config;\n this.#shard = shard;\n this.#lc = lc;\n this.#urlPatterns = config.url.map(compileUrlPattern);\n this.#cache = new TimedCache(5000); // 5 seconds cache TTL\n }\n\n async transform(\n headerOptions: HeaderOptions,\n queries: Iterable<CustomQueryRecord>,\n userQueryURL: string | undefined,\n ): Promise<(TransformedAndHashed | ErroredQuery)[] | TransformFailedBody> {\n const request: TransformRequestBody = [];\n const cachedResponses: TransformedAndHashed[] = [];\n\n if (!this.#config.forwardCookies && headerOptions.cookie) {\n headerOptions = {\n ...headerOptions,\n cookie: undefined, // remove cookies if not forwarded\n };\n }\n\n // split queries into cached and uncached\n for (const query of queries) {\n const cacheKey = getCacheKey(headerOptions, query.id);\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n cachedResponses.push(cached);\n } else {\n request.push({\n id: query.id,\n name: query.name,\n args: query.args,\n });\n }\n }\n\n if (request.length === 0) {\n return cachedResponses;\n }\n\n const queryIDs = request.map(r => r.id);\n\n try {\n const transformResponse = await fetchFromAPIServer(\n transformResponseMessageSchema,\n 'transform',\n this.#lc,\n userQueryURL ??\n must(\n this.#config.url[0],\n 'A ZERO_QUERY_URL must be configured for custom queries',\n ),\n userQueryURL !== undefined,\n this.#urlPatterns,\n this.#shard,\n headerOptions,\n ['transform', request] satisfies TransformRequestMessage,\n );\n\n if (transformResponse[0] === 'transformFailed') {\n return transformResponse[1];\n }\n\n const newResponses = transformResponse[1].map(transformed => {\n if ('error' in transformed) {\n return transformed;\n }\n return {\n id: transformed.id,\n transformedAst: transformed.ast,\n transformationHash: hashOfAST(transformed.ast),\n } satisfies TransformedAndHashed;\n });\n\n for (const transformed of newResponses) {\n if ('error' in transformed) {\n // do not cache error responses\n continue;\n }\n const cacheKey = getCacheKey(headerOptions, transformed.id);\n this.#cache.set(cacheKey, transformed);\n }\n\n return newResponses.concat(cachedResponses);\n } catch (e) {\n if (\n isProtocolError(e) &&\n e.errorBody.kind === ErrorKind.TransformFailed\n ) {\n return {\n ...e.errorBody,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Failed to transform queries: ${getErrorMessage(e)}`,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n }\n}\n\nfunction getCacheKey(headerOptions: HeaderOptions, queryID: string) {\n // For custom queries, queryID is a hash of the name + args.\n // the APIKey from headerOptions is static. Not needed for the cache key.\n // The token is used to identify the user and should be included in the cache key.\n return `${headerOptions.token}:${headerOptions.cookie}:${queryID}`;\n}\n"],"names":["ErrorKind.TransformFailed","ErrorOrigin.ZeroCache","ErrorReason.Internal"],"mappings":";;;;;;;;;;AAgDO,MAAM,uBAAuB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EAIA;AAAA,EACA;AAAA,EAET,YACE,IACA,QAIA,OACA;AACA,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,eAAe,OAAO,IAAI,IAAI,iBAAiB;AACpD,SAAK,SAAS,IAAI,WAAW,GAAI;AAAA,EACnC;AAAA,EAEA,MAAM,UACJ,eACA,SACA,cACwE;AACxE,UAAM,UAAgC,CAAA;AACtC,UAAM,kBAA0C,CAAA;AAEhD,QAAI,CAAC,KAAK,QAAQ,kBAAkB,cAAc,QAAQ;AACxD,sBAAgB;AAAA,QACd,GAAG;AAAA,QACH,QAAQ;AAAA;AAAA,MAAA;AAAA,IAEZ;AAGA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,YAAY,eAAe,MAAM,EAAE;AACpD,YAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,UAAI,QAAQ;AACV,wBAAgB,KAAK,MAAM;AAAA,MAC7B,OAAO;AACL,gBAAQ,KAAK;AAAA,UACX,IAAI,MAAM;AAAA,UACV,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM;AAAA,QAAA,CACb;AAAA,MACH;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,QAAQ,IAAI,CAAA,MAAK,EAAE,EAAE;AAEtC,QAAI;AACF,YAAM,oBAAoB,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL,gBACE;AAAA,UACE,KAAK,QAAQ,IAAI,CAAC;AAAA,UAClB;AAAA,QAAA;AAAA,QAEJ,iBAAiB;AAAA,QACjB,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,CAAC,aAAa,OAAO;AAAA,MAAA;AAGvB,UAAI,kBAAkB,CAAC,MAAM,mBAAmB;AAC9C,eAAO,kBAAkB,CAAC;AAAA,MAC5B;AAEA,YAAM,eAAe,kBAAkB,CAAC,EAAE,IAAI,CAAA,gBAAe;AAC3D,YAAI,WAAW,aAAa;AAC1B,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,UACL,IAAI,YAAY;AAAA,UAChB,gBAAgB,YAAY;AAAA,UAC5B,oBAAoB,UAAU,YAAY,GAAG;AAAA,QAAA;AAAA,MAEjD,CAAC;AAED,iBAAW,eAAe,cAAc;AACtC,YAAI,WAAW,aAAa;AAE1B;AAAA,QACF;AACA,cAAM,WAAW,YAAY,eAAe,YAAY,EAAE;AAC1D,aAAK,OAAO,IAAI,UAAU,WAAW;AAAA,MACvC;AAEA,aAAO,aAAa,OAAO,eAAe;AAAA,IAC5C,SAAS,GAAG;AACV,UACE,gBAAgB,CAAC,KACjB,EAAE,UAAU,SAASA,iBACrB;AACA,eAAO;AAAA,UACL,GAAG,EAAE;AAAA,UACL;AAAA,QAAA;AAAA,MAEJ;AAEA,aAAO;AAAA,QACL,MAAMA;AAAAA,QACN,QAAQC;AAAAA,QACR,QAAQC;AAAAA,QACR,SAAS,gCAAgC,gBAAgB,CAAC,CAAC;AAAA,QAC3D;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAEA,SAAS,YAAY,eAA8B,SAAiB;AAIlE,SAAO,GAAG,cAAc,KAAK,IAAI,cAAc,MAAM,IAAI,OAAO;AAClE;"}
|
|
@@ -56,7 +56,7 @@ export declare const publishedTableSpec: v.ObjectType<Omit<Omit<{
|
|
|
56
56
|
primaryKey: v.Optional<string[]>;
|
|
57
57
|
}, "schema"> & {
|
|
58
58
|
schema: v.Type<string>;
|
|
59
|
-
}, "
|
|
59
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
60
60
|
oid: v.Type<number>;
|
|
61
61
|
columns: v.Type<Record<string, {
|
|
62
62
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AAiBA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAK/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AAiBA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAK/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAwHf"}
|
|
@@ -24,7 +24,13 @@ async function runWorker(parent, env, ...args) {
|
|
|
24
24
|
const config = getNormalizedZeroConfig({ env, argv: args.slice(1) });
|
|
25
25
|
const {
|
|
26
26
|
taskID,
|
|
27
|
-
changeStreamer: {
|
|
27
|
+
changeStreamer: {
|
|
28
|
+
port,
|
|
29
|
+
address,
|
|
30
|
+
protocol,
|
|
31
|
+
startupDelayMs,
|
|
32
|
+
startupDelayKeepalives
|
|
33
|
+
},
|
|
28
34
|
upstream,
|
|
29
35
|
change,
|
|
30
36
|
replica,
|
|
@@ -102,19 +108,13 @@ async function runWorker(parent, env, ...args) {
|
|
|
102
108
|
const changeStreamerWebServer = new ChangeStreamerHttpServer(
|
|
103
109
|
lc,
|
|
104
110
|
config,
|
|
105
|
-
{ port },
|
|
111
|
+
{ port, startupDelayMs, startupDelayKeepalives },
|
|
106
112
|
parent,
|
|
107
113
|
changeStreamer,
|
|
108
114
|
monitor instanceof BackupMonitor ? monitor : null
|
|
109
115
|
);
|
|
110
116
|
parent.send(["ready", { ready: true }]);
|
|
111
|
-
return runUntilKilled(
|
|
112
|
-
lc,
|
|
113
|
-
parent,
|
|
114
|
-
changeStreamer,
|
|
115
|
-
changeStreamerWebServer,
|
|
116
|
-
monitor
|
|
117
|
-
);
|
|
117
|
+
return runUntilKilled(lc, parent, changeStreamerWebServer, monitor);
|
|
118
118
|
}
|
|
119
119
|
if (!singleProcessMode()) {
|
|
120
120
|
void exitAfter(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer.js","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {DatabaseInitError} from '../../../zqlite/src/db.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {BackupMonitor} from '../services/change-streamer/backup-monitor.ts';\nimport {ChangeStreamerHttpServer} from '../services/change-streamer/change-streamer-http.ts';\nimport {initializeStreamer} from '../services/change-streamer/change-streamer-service.ts';\nimport type {ChangeStreamerService} from '../services/change-streamer/change-streamer.ts';\nimport {ReplicaMonitor} from '../services/change-streamer/replica-monitor.ts';\nimport {AutoResetSignal} from '../services/change-streamer/schema/tables.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardConfig} from '../types/shards.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length > 0, `parent startMs not specified`);\n const parentStartMs = parseInt(args[0]);\n\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n const {\n taskID,\n changeStreamer: {port
|
|
1
|
+
{"version":3,"file":"change-streamer.js","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {DatabaseInitError} from '../../../zqlite/src/db.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {BackupMonitor} from '../services/change-streamer/backup-monitor.ts';\nimport {ChangeStreamerHttpServer} from '../services/change-streamer/change-streamer-http.ts';\nimport {initializeStreamer} from '../services/change-streamer/change-streamer-service.ts';\nimport type {ChangeStreamerService} from '../services/change-streamer/change-streamer.ts';\nimport {ReplicaMonitor} from '../services/change-streamer/replica-monitor.ts';\nimport {AutoResetSignal} from '../services/change-streamer/schema/tables.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardConfig} from '../types/shards.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length > 0, `parent startMs not specified`);\n const parentStartMs = parseInt(args[0]);\n\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n const {\n taskID,\n changeStreamer: {\n port,\n address,\n protocol,\n startupDelayMs,\n startupDelayKeepalives,\n },\n upstream,\n change,\n replica,\n initialSync,\n litestream,\n } = config;\n\n startOtelAuto(createLogContext(config, {worker: 'change-streamer'}, false));\n const lc = createLogContext(config, {worker: 'change-streamer'}, true);\n initEventSink(lc, config);\n\n // Kick off DB connection warmup in the background.\n const changeDB = pgClient(lc, change.db, {\n max: change.maxConns,\n connection: {['application_name']: 'zero-change-streamer'},\n });\n void warmupConnections(lc, changeDB, 'change');\n\n const {autoReset} = config;\n const shard = getShardConfig(config);\n\n let changeStreamer: ChangeStreamerService | undefined;\n\n for (const first of [true, false]) {\n try {\n // Note: This performs initial sync of the replica if necessary.\n const {changeSource, subscriptionState} =\n upstream.type === 'pg'\n ? await initializePostgresChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n initialSync,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n );\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n subscriptionState,\n autoReset ?? false,\n );\n break;\n } catch (e) {\n if (first && e instanceof AutoResetSignal) {\n lc.warn?.(`resetting replica ${replica.file}`, e);\n // TODO: Make deleteLiteDB work with litestream. It will probably have to be\n // a semantic wipe instead of a file delete.\n deleteLiteDB(replica.file);\n continue; // execute again with a fresh initial-sync\n }\n if (e instanceof DatabaseInitError) {\n throw new Error(\n `Cannot open ZERO_REPLICA_FILE at \"${replica.file}\". Please check that the path is valid.`,\n {cause: e},\n );\n }\n throw e;\n }\n }\n // impossible: upstream must have advanced in order for replication to be stuck.\n assert(changeStreamer, `resetting replica did not advance replicaVersion`);\n\n const {backupURL, port: metricsPort} = litestream;\n const monitor = backupURL\n ? new BackupMonitor(\n lc,\n backupURL,\n `http://localhost:${metricsPort}/metrics`,\n changeStreamer,\n // The time between when the zero-cache was started to when the\n // change-streamer is ready to start serves as the initial delay for\n // watermark cleanup (as it either includes a similar replica\n // restoration/preparation step, or an initial-sync, which\n // generally takes longer).\n //\n // Consider: Also account for permanent volumes?\n Date.now() - parentStartMs,\n )\n : new ReplicaMonitor(lc, replica.file, changeStreamer);\n\n const changeStreamerWebServer = new ChangeStreamerHttpServer(\n lc,\n config,\n {port, startupDelayMs, startupDelayKeepalives},\n parent,\n changeStreamer,\n monitor instanceof BackupMonitor ? monitor : null,\n );\n\n parent.send(['ready', {ready: true}]);\n\n // Note: The changeStreamer itself is not started here; it is started by the\n // changeStreamerWebServer.\n return runUntilKilled(lc, parent, changeStreamerWebServer, monitor);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(() =>\n runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,eAA8B,UAC5B,QACA,QACG,MACY;AACf,SAAO,KAAK,SAAS,GAAG,8BAA8B;AACtD,QAAM,gBAAgB,SAAS,KAAK,CAAC,CAAC;AAEtC,QAAM,SAAS,wBAAwB,EAAC,KAAK,MAAM,KAAK,MAAM,CAAC,GAAE;AACjE,QAAM;AAAA,IACJ;AAAA,IACA,gBAAgB;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAEJ,gBAAc,iBAAiB,QAAQ,EAAC,QAAQ,kBAAA,GAAoB,KAAK,CAAC;AAC1E,QAAM,KAAK,iBAAiB,QAAQ,EAAC,QAAQ,kBAAA,GAAoB,IAAI;AACrE,gBAAc,IAAI,MAAM;AAGxB,QAAM,WAAW,SAAS,IAAI,OAAO,IAAI;AAAA,IACvC,KAAK,OAAO;AAAA,IACZ,YAAY,EAAC,CAAC,kBAAkB,GAAG,uBAAA;AAAA,EAAsB,CAC1D;AACD,OAAK,kBAAkB,IAAI,UAAU,QAAQ;AAE7C,QAAM,EAAC,cAAa;AACpB,QAAM,QAAQ,eAAe,MAAM;AAEnC,MAAI;AAEJ,aAAW,SAAS,CAAC,MAAM,KAAK,GAAG;AACjC,QAAI;AAEF,YAAM,EAAC,cAAc,kBAAA,IACnB,SAAS,SAAS,OACd,MAAM;AAAA,QACJ;AAAA,QACA,SAAS;AAAA,QACT;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,MAAA,IAEF,MAAM;AAAA,QACJ;AAAA,QACA,SAAS;AAAA,QACT;AAAA,QACA,QAAQ;AAAA,MAAA;AAGhB,uBAAiB,MAAM;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa;AAAA,MAAA;AAEf;AAAA,IACF,SAAS,GAAG;AACV,UAAI,SAAS,aAAa,iBAAiB;AACzC,WAAG,OAAO,qBAAqB,QAAQ,IAAI,IAAI,CAAC;AAGhD,qBAAa,QAAQ,IAAI;AACzB;AAAA,MACF;AACA,UAAI,aAAa,mBAAmB;AAClC,cAAM,IAAI;AAAA,UACR,qCAAqC,QAAQ,IAAI;AAAA,UACjD,EAAC,OAAO,EAAA;AAAA,QAAC;AAAA,MAEb;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAEA,SAAO,gBAAgB,kDAAkD;AAEzE,QAAM,EAAC,WAAW,MAAM,YAAA,IAAe;AACvC,QAAM,UAAU,YACZ,IAAI;AAAA,IACF;AAAA,IACA;AAAA,IACA,oBAAoB,WAAW;AAAA,IAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,KAAK,QAAQ;AAAA,EAAA,IAEf,IAAI,eAAe,IAAI,QAAQ,MAAM,cAAc;AAEvD,QAAM,0BAA0B,IAAI;AAAA,IAClC;AAAA,IACA;AAAA,IACA,EAAC,MAAM,gBAAgB,uBAAA;AAAA,IACvB;AAAA,IACA;AAAA,IACA,mBAAmB,gBAAgB,UAAU;AAAA,EAAA;AAG/C,SAAO,KAAK,CAAC,SAAS,EAAC,OAAO,KAAA,CAAK,CAAC;AAIpC,SAAO,eAAe,IAAI,QAAQ,yBAAyB,OAAO;AACpE;AAGA,IAAI,CAAC,qBAAqB;AACxB,OAAK;AAAA,IAAU,MACb,UAAU,KAAK,YAAY,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC;AAAA,EAAA;AAEvE;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"AAuBA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AA6B/B,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAsIf"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { tmpdir } from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { pid } from "node:process";
|
|
@@ -28,6 +29,16 @@ import { startOtelAuto } from "./otel-start.js";
|
|
|
28
29
|
function randomID() {
|
|
29
30
|
return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);
|
|
30
31
|
}
|
|
32
|
+
function getCustomQueryConfig(config) {
|
|
33
|
+
const queryConfig = config.query?.url ? config.query : config.getQueries;
|
|
34
|
+
if (!queryConfig?.url) {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
url: queryConfig.url,
|
|
39
|
+
forwardCookies: queryConfig.forwardCookies ?? false
|
|
40
|
+
};
|
|
41
|
+
}
|
|
31
42
|
function runWorker(parent, env, ...args) {
|
|
32
43
|
const config = getNormalizedZeroConfig({ env, argv: args.slice(1) });
|
|
33
44
|
startOtelAuto(createLogContext(config, { worker: "syncer" }, false));
|
|
@@ -55,7 +66,11 @@ function runWorker(parent, env, ...args) {
|
|
|
55
66
|
const tmpDir = config.storageDBTmpDir ?? tmpdir();
|
|
56
67
|
const operatorStorage = DatabaseStorage.create(
|
|
57
68
|
lc,
|
|
58
|
-
path.join(tmpDir, `sync-worker-${
|
|
69
|
+
path.join(tmpDir, `sync-worker-${randomUUID()}`)
|
|
70
|
+
);
|
|
71
|
+
const writeAuthzStorage = DatabaseStorage.create(
|
|
72
|
+
lc,
|
|
73
|
+
path.join(tmpDir, `mutagen-${randomUUID()}`)
|
|
59
74
|
);
|
|
60
75
|
const shard = getShardID(config);
|
|
61
76
|
const viewSyncerFactory = (id, sub, drainCoordinator) => {
|
|
@@ -63,12 +78,8 @@ function runWorker(parent, env, ...args) {
|
|
|
63
78
|
lc.debug?.(
|
|
64
79
|
`creating view syncer. Query Planner Enabled: ${config.enableQueryPlanner}`
|
|
65
80
|
);
|
|
66
|
-
const
|
|
67
|
-
const customQueryTransformer =
|
|
68
|
-
logger,
|
|
69
|
-
{ url: getQueries.url, forwardCookies: getQueries.forwardCookies },
|
|
70
|
-
shard
|
|
71
|
-
);
|
|
81
|
+
const customQueryConfig = getCustomQueryConfig(config);
|
|
82
|
+
const customQueryTransformer = customQueryConfig && new CustomQueryTransformer(logger, customQueryConfig, shard);
|
|
72
83
|
const inspectorDelegate = new InspectorDelegate(customQueryTransformer);
|
|
73
84
|
return new ViewSyncerService(
|
|
74
85
|
config,
|
|
@@ -100,7 +111,8 @@ function runWorker(parent, env, ...args) {
|
|
|
100
111
|
shard,
|
|
101
112
|
id,
|
|
102
113
|
upstreamDB,
|
|
103
|
-
config
|
|
114
|
+
config,
|
|
115
|
+
writeAuthzStorage
|
|
104
116
|
);
|
|
105
117
|
const pusherFactory = config.push.url === void 0 && config.mutate.url === void 0 ? void 0 : (id) => new PusherService(
|
|
106
118
|
upstreamDB,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.js","sources":["../../../../../zero-cache/src/server/syncer.ts"],"sourcesContent":["import {tmpdir} from 'node:os';\nimport path from 'node:path';\nimport {pid} from 'node:process';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {DatabaseStorage} from '../../../zqlite/src/database-storage.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {CustomQueryTransformer} from '../custom-queries/transform-query.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {MutagenService} from '../services/mutagen/mutagen.ts';\nimport {PusherService} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport type {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport {PipelineDriver} from '../services/view-syncer/pipeline-driver.ts';\nimport {Snapshotter} from '../services/view-syncer/snapshotter.ts';\nimport {ViewSyncerService} from '../services/view-syncer/view-syncer.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardID} from '../types/shards.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {replicaFileModeSchema, replicaFileName} from '../workers/replicator.ts';\nimport {Syncer} from '../workers/syncer.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {InspectorDelegate} from './inspector-delegate.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nexport default function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n\n startOtelAuto(createLogContext(config, {worker: 'syncer'}, false));\n const lc = createLogContext(config, {worker: 'syncer'}, true);\n initEventSink(lc, config);\n\n assert(args.length > 0, `replicator mode not specified`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n\n const {cvr, upstream} = config;\n assert(cvr.maxConnsPerWorker, 'cvr.maxConnsPerWorker must be set');\n assert(upstream.maxConnsPerWorker, 'upstream.maxConnsPerWorker must be set');\n\n const replicaFile = replicaFileName(config.replica.file, fileMode);\n lc.debug?.(`running view-syncer on ${replicaFile}`);\n\n const cvrDB = pgClient(lc, cvr.db, {\n max: cvr.maxConnsPerWorker,\n connection: {['application_name']: `zero-sync-worker-${pid}-cvr`},\n });\n\n const upstreamDB = pgClient(lc, upstream.db, {\n max: upstream.maxConnsPerWorker,\n connection: {['application_name']: `zero-sync-worker-${pid}-upstream`},\n });\n\n const dbWarmup = Promise.allSettled([\n warmupConnections(lc, cvrDB, 'cvr'),\n warmupConnections(lc, upstreamDB, 'upstream'),\n ]);\n\n const tmpDir = config.storageDBTmpDir ?? tmpdir();\n const operatorStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `sync-worker-${
|
|
1
|
+
{"version":3,"file":"syncer.js","sources":["../../../../../zero-cache/src/server/syncer.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {tmpdir} from 'node:os';\nimport path from 'node:path';\nimport {pid} from 'node:process';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {DatabaseStorage} from '../../../zqlite/src/database-storage.ts';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {CustomQueryTransformer} from '../custom-queries/transform-query.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {MutagenService} from '../services/mutagen/mutagen.ts';\nimport {PusherService} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport type {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport {PipelineDriver} from '../services/view-syncer/pipeline-driver.ts';\nimport {Snapshotter} from '../services/view-syncer/snapshotter.ts';\nimport {ViewSyncerService} from '../services/view-syncer/view-syncer.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardID} from '../types/shards.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {replicaFileModeSchema, replicaFileName} from '../workers/replicator.ts';\nimport {Syncer} from '../workers/syncer.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {InspectorDelegate} from './inspector-delegate.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nfunction getCustomQueryConfig(\n config: Pick<NormalizedZeroConfig, 'query' | 'getQueries'>,\n) {\n const queryConfig = config.query?.url ? config.query : config.getQueries;\n\n if (!queryConfig?.url) {\n return undefined;\n }\n\n return {\n url: queryConfig.url,\n forwardCookies: queryConfig.forwardCookies ?? false,\n };\n}\n\nexport default function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n\n startOtelAuto(createLogContext(config, {worker: 'syncer'}, false));\n const lc = createLogContext(config, {worker: 'syncer'}, true);\n initEventSink(lc, config);\n\n assert(args.length > 0, `replicator mode not specified`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n\n const {cvr, upstream} = config;\n assert(cvr.maxConnsPerWorker, 'cvr.maxConnsPerWorker must be set');\n assert(upstream.maxConnsPerWorker, 'upstream.maxConnsPerWorker must be set');\n\n const replicaFile = replicaFileName(config.replica.file, fileMode);\n lc.debug?.(`running view-syncer on ${replicaFile}`);\n\n const cvrDB = pgClient(lc, cvr.db, {\n max: cvr.maxConnsPerWorker,\n connection: {['application_name']: `zero-sync-worker-${pid}-cvr`},\n });\n\n const upstreamDB = pgClient(lc, upstream.db, {\n max: upstream.maxConnsPerWorker,\n connection: {['application_name']: `zero-sync-worker-${pid}-upstream`},\n });\n\n const dbWarmup = Promise.allSettled([\n warmupConnections(lc, cvrDB, 'cvr'),\n warmupConnections(lc, upstreamDB, 'upstream'),\n ]);\n\n const tmpDir = config.storageDBTmpDir ?? tmpdir();\n const operatorStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `sync-worker-${randomUUID()}`),\n );\n const writeAuthzStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `mutagen-${randomUUID()}`),\n );\n\n const shard = getShardID(config);\n\n const viewSyncerFactory = (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => {\n const logger = lc\n .withContext('component', 'view-syncer')\n .withContext('clientGroupID', id)\n .withContext('instance', randomID());\n lc.debug?.(\n `creating view syncer. Query Planner Enabled: ${config.enableQueryPlanner}`,\n );\n\n // Create the custom query transformer if configured\n const customQueryConfig = getCustomQueryConfig(config);\n const customQueryTransformer =\n customQueryConfig &&\n new CustomQueryTransformer(logger, customQueryConfig, shard);\n\n const inspectorDelegate = new InspectorDelegate(customQueryTransformer);\n\n return new ViewSyncerService(\n config,\n logger,\n shard,\n config.taskID,\n id,\n cvrDB,\n config.upstream.type === 'pg' ? upstreamDB : undefined,\n new PipelineDriver(\n logger,\n config.log,\n new Snapshotter(logger, replicaFile, shard),\n shard,\n operatorStorage.createClientGroupStorage(id),\n id,\n inspectorDelegate,\n config.enableQueryPlanner,\n ),\n sub,\n drainCoordinator,\n config.log.slowHydrateThreshold,\n inspectorDelegate,\n customQueryTransformer,\n );\n };\n\n const mutagenFactory = (id: string) =>\n new MutagenService(\n lc.withContext('component', 'mutagen').withContext('clientGroupID', id),\n shard,\n id,\n upstreamDB,\n config,\n writeAuthzStorage,\n );\n\n const pusherFactory =\n config.push.url === undefined && config.mutate.url === undefined\n ? undefined\n : (id: string) =>\n new PusherService(\n upstreamDB,\n config,\n {\n ...config.push,\n ...config.mutate,\n url: must(\n config.push.url ?? config.mutate.url,\n 'No push or mutate URL configured',\n ),\n },\n lc.withContext('clientGroupID', id),\n id,\n );\n\n const syncer = new Syncer(\n lc,\n config,\n viewSyncerFactory,\n mutagenFactory,\n pusherFactory,\n parent,\n );\n\n startAnonymousTelemetry(lc, config);\n\n void dbWarmup.then(() => parent.send(['ready', {ready: true}]));\n\n return runUntilKilled(lc, parent, syncer);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(() =>\n runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"names":["v.parse"],"mappings":"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"}
|
|
@@ -5,5 +5,5 @@ import type { PermissionsConfig } from '../../../zero-schema/src/compiled-permis
|
|
|
5
5
|
import type { NormalizedZeroConfig } from '../config/normalize.ts';
|
|
6
6
|
import type { TokenData } from './view-syncer/view-syncer.ts';
|
|
7
7
|
import type { ClientSchema } from '../../../zero-protocol/src/client-schema.ts';
|
|
8
|
-
export declare function analyzeQuery(lc: LogContext, config: NormalizedZeroConfig, clientSchema: ClientSchema, ast: AST, syncedRows?: boolean, vendedRows?: boolean, permissions?: PermissionsConfig, authData?: TokenData): Promise<AnalyzeQueryResult>;
|
|
8
|
+
export declare function analyzeQuery(lc: LogContext, config: NormalizedZeroConfig, clientSchema: ClientSchema, ast: AST, syncedRows?: boolean, vendedRows?: boolean, permissions?: PermissionsConfig, authData?: TokenData, plannerDebug?: boolean): Promise<AnalyzeQueryResult>;
|
|
9
9
|
//# sourceMappingURL=analyze.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/services/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,oDAAoD,CAAC;AAC3F,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,mCAAmC,CAAC;AAC3D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,kDAAkD,CAAC;
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/services/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,oDAAoD,CAAC;AAC3F,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,mCAAmC,CAAC;AAC3D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,kDAAkD,CAAC;AAWxF,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAIjE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6CAA6C,CAAC;AAE9E,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,oBAAoB,EAC5B,YAAY,EAAE,YAAY,EAC1B,GAAG,EAAE,GAAG,EACR,UAAU,UAAO,EACjB,UAAU,UAAQ,EAClB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,QAAQ,CAAC,EAAE,SAAS,EACpB,YAAY,UAAQ,GACnB,OAAO,CAAC,kBAAkB,CAAC,CA6D7B"}
|
|
@@ -45,12 +45,14 @@ var __callDispose = (stack, error, hasError) => {
|
|
|
45
45
|
};
|
|
46
46
|
import { Debug } from "../../../zql/src/builder/debug-delegate.js";
|
|
47
47
|
import { MemoryStorage } from "../../../zql/src/ivm/memory-storage.js";
|
|
48
|
+
import { serializePlanDebugEvents, AccumulatorDebugger } from "../../../zql/src/planner/planner-debug.js";
|
|
48
49
|
import { Database } from "../../../zqlite/src/db.js";
|
|
49
50
|
import { explainQueries } from "../../../zqlite/src/explain-queries.js";
|
|
51
|
+
import { createSQLiteCostModel } from "../../../zqlite/src/sqlite-cost-model.js";
|
|
50
52
|
import { TableSource } from "../../../zqlite/src/table-source.js";
|
|
51
53
|
import { computeZqlSpecs, mustGetTableSpec } from "../db/lite-tables.js";
|
|
52
54
|
import { runAst } from "./run-ast.js";
|
|
53
|
-
async function analyzeQuery(lc, config, clientSchema, ast, syncedRows = true, vendedRows = false, permissions, authData) {
|
|
55
|
+
async function analyzeQuery(lc, config, clientSchema, ast, syncedRows = true, vendedRows = false, permissions, authData, plannerDebug = false) {
|
|
54
56
|
var _stack = [];
|
|
55
57
|
try {
|
|
56
58
|
const db = __using(_stack, new Database(lc, config.replica.file));
|
|
@@ -58,12 +60,16 @@ async function analyzeQuery(lc, config, clientSchema, ast, syncedRows = true, ve
|
|
|
58
60
|
const tableSpecs = /* @__PURE__ */ new Map();
|
|
59
61
|
const tables = /* @__PURE__ */ new Map();
|
|
60
62
|
computeZqlSpecs(lc, db, tableSpecs, fullTables);
|
|
63
|
+
const planDebugger = plannerDebug ? new AccumulatorDebugger() : void 0;
|
|
64
|
+
const costModel = plannerDebug ? createSQLiteCostModel(db, tableSpecs) : void 0;
|
|
61
65
|
const result = await runAst(lc, clientSchema, ast, true, {
|
|
62
66
|
applyPermissions: permissions !== void 0,
|
|
63
67
|
syncedRows,
|
|
64
68
|
vendedRows,
|
|
65
69
|
authData,
|
|
66
70
|
permissions,
|
|
71
|
+
costModel,
|
|
72
|
+
planDebugger,
|
|
67
73
|
host: {
|
|
68
74
|
debug: new Debug(),
|
|
69
75
|
getSource(tableName) {
|
|
@@ -95,6 +101,9 @@ async function analyzeQuery(lc, config, clientSchema, ast, syncedRows = true, ve
|
|
|
95
101
|
}
|
|
96
102
|
});
|
|
97
103
|
result.plans = explainQueries(result.readRowCountsByQuery ?? {}, db);
|
|
104
|
+
if (planDebugger) {
|
|
105
|
+
result.plannerEvents = serializePlanDebugEvents(planDebugger.events);
|
|
106
|
+
}
|
|
98
107
|
return result;
|
|
99
108
|
} catch (_) {
|
|
100
109
|
var _error = _, _hasError = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.js","sources":["../../../../../zero-cache/src/services/analyze.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {AnalyzeQueryResult} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../zero-protocol/src/ast.ts';\nimport type {PermissionsConfig} from '../../../zero-schema/src/compiled-permissions.ts';\nimport {Debug} from '../../../zql/src/builder/debug-delegate.ts';\nimport {MemoryStorage} from '../../../zql/src/ivm/memory-storage.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport {explainQueries} from '../../../zqlite/src/explain-queries.ts';\nimport {TableSource} from '../../../zqlite/src/table-source.ts';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {computeZqlSpecs, mustGetTableSpec} from '../db/lite-tables.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../db/specs.ts';\nimport {runAst} from './run-ast.ts';\nimport type {TokenData} from './view-syncer/view-syncer.ts';\nimport type {ClientSchema} from '../../../zero-protocol/src/client-schema.ts';\n\nexport async function analyzeQuery(\n lc: LogContext,\n config: NormalizedZeroConfig,\n clientSchema: ClientSchema,\n ast: AST,\n syncedRows = true,\n vendedRows = false,\n permissions?: PermissionsConfig,\n authData?: TokenData,\n): Promise<AnalyzeQueryResult> {\n using db = new Database(lc, config.replica.file);\n const fullTables = new Map<string, LiteTableSpec>();\n const tableSpecs = new Map<string, LiteAndZqlSpec>();\n const tables = new Map<string, TableSource>();\n\n computeZqlSpecs(lc, db, tableSpecs, fullTables);\n\n const result = await runAst(lc, clientSchema, ast, true, {\n applyPermissions: permissions !== undefined,\n syncedRows,\n vendedRows,\n authData,\n db,\n tableSpecs,\n permissions,\n host: {\n debug: new Debug(),\n getSource(tableName: string) {\n let source = tables.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSpec = mustGetTableSpec(tableSpecs, tableName);\n const {primaryKey} = tableSpec.tableSpec;\n\n source = new TableSource(\n lc,\n config.log,\n db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n );\n tables.set(tableName, source);\n return source;\n },\n createStorage() {\n return new MemoryStorage();\n },\n decorateSourceInput: input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n });\n\n result.plans = explainQueries(result.readRowCountsByQuery ?? {}, db);\n return result;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyze.js","sources":["../../../../../zero-cache/src/services/analyze.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {AnalyzeQueryResult} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../zero-protocol/src/ast.ts';\nimport type {PermissionsConfig} from '../../../zero-schema/src/compiled-permissions.ts';\nimport {Debug} from '../../../zql/src/builder/debug-delegate.ts';\nimport {MemoryStorage} from '../../../zql/src/ivm/memory-storage.ts';\nimport {\n AccumulatorDebugger,\n serializePlanDebugEvents,\n} from '../../../zql/src/planner/planner-debug.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport {explainQueries} from '../../../zqlite/src/explain-queries.ts';\nimport {createSQLiteCostModel} from '../../../zqlite/src/sqlite-cost-model.ts';\nimport {TableSource} from '../../../zqlite/src/table-source.ts';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {computeZqlSpecs, mustGetTableSpec} from '../db/lite-tables.ts';\nimport type {LiteAndZqlSpec, LiteTableSpec} from '../db/specs.ts';\nimport {runAst} from './run-ast.ts';\nimport type {TokenData} from './view-syncer/view-syncer.ts';\nimport type {ClientSchema} from '../../../zero-protocol/src/client-schema.ts';\n\nexport async function analyzeQuery(\n lc: LogContext,\n config: NormalizedZeroConfig,\n clientSchema: ClientSchema,\n ast: AST,\n syncedRows = true,\n vendedRows = false,\n permissions?: PermissionsConfig,\n authData?: TokenData,\n plannerDebug = false,\n): Promise<AnalyzeQueryResult> {\n using db = new Database(lc, config.replica.file);\n const fullTables = new Map<string, LiteTableSpec>();\n const tableSpecs = new Map<string, LiteAndZqlSpec>();\n const tables = new Map<string, TableSource>();\n\n computeZqlSpecs(lc, db, tableSpecs, fullTables);\n\n const planDebugger = plannerDebug ? new AccumulatorDebugger() : undefined;\n const costModel = plannerDebug\n ? createSQLiteCostModel(db, tableSpecs)\n : undefined;\n const result = await runAst(lc, clientSchema, ast, true, {\n applyPermissions: permissions !== undefined,\n syncedRows,\n vendedRows,\n authData,\n db,\n tableSpecs,\n permissions,\n costModel,\n planDebugger,\n host: {\n debug: new Debug(),\n getSource(tableName: string) {\n let source = tables.get(tableName);\n if (source) {\n return source;\n }\n\n const tableSpec = mustGetTableSpec(tableSpecs, tableName);\n const {primaryKey} = tableSpec.tableSpec;\n\n source = new TableSource(\n lc,\n config.log,\n db,\n tableName,\n tableSpec.zqlSpec,\n primaryKey,\n );\n tables.set(tableName, source);\n return source;\n },\n createStorage() {\n return new MemoryStorage();\n },\n decorateSourceInput: input => input,\n decorateInput: input => input,\n addEdge() {},\n decorateFilterInput: input => input,\n },\n });\n\n result.plans = explainQueries(result.readRowCountsByQuery ?? {}, db);\n\n if (planDebugger) {\n result.plannerEvents = serializePlanDebugEvents(planDebugger.events);\n }\n\n return result;\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBA,eAAsB,aACpB,IACA,QACA,cACA,KACA,aAAa,MACb,aAAa,OACb,aACA,UACA,eAAe,OACc;AAC7B;AAAA;AAAA,UAAM,KAAK,oBAAI,SAAS,IAAI,OAAO,QAAQ,IAAI;AAC/C,UAAM,iCAAiB,IAAA;AACvB,UAAM,iCAAiB,IAAA;AACvB,UAAM,6BAAa,IAAA;AAEnB,oBAAgB,IAAI,IAAI,YAAY,UAAU;AAE9C,UAAM,eAAe,eAAe,IAAI,oBAAA,IAAwB;AAChE,UAAM,YAAY,eACd,sBAAsB,IAAI,UAAU,IACpC;AACJ,UAAM,SAAS,MAAM,OAAO,IAAI,cAAc,KAAK,MAAM;AAAA,MACvD,kBAAkB,gBAAgB;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MAGA;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,OAAO,IAAI,MAAA;AAAA,QACX,UAAU,WAAmB;AAC3B,cAAI,SAAS,OAAO,IAAI,SAAS;AACjC,cAAI,QAAQ;AACV,mBAAO;AAAA,UACT;AAEA,gBAAM,YAAY,iBAAiB,YAAY,SAAS;AACxD,gBAAM,EAAC,eAAc,UAAU;AAE/B,mBAAS,IAAI;AAAA,YACX;AAAA,YACA,OAAO;AAAA,YACP;AAAA,YACA;AAAA,YACA,UAAU;AAAA,YACV;AAAA,UAAA;AAEF,iBAAO,IAAI,WAAW,MAAM;AAC5B,iBAAO;AAAA,QACT;AAAA,QACA,gBAAgB;AACd,iBAAO,IAAI,cAAA;AAAA,QACb;AAAA,QACA,qBAAqB,CAAA,UAAS;AAAA,QAC9B,eAAe,CAAA,UAAS;AAAA,QACxB,UAAU;AAAA,QAAC;AAAA,QACX,qBAAqB,CAAA,UAAS;AAAA,MAAA;AAAA,IAChC,CACD;AAED,WAAO,QAAQ,eAAe,OAAO,wBAAwB,CAAA,GAAI,EAAE;AAEnE,QAAI,cAAc;AAChB,aAAO,gBAAgB,yBAAyB,aAAa,MAAM;AAAA,IACrE;AAEA,WAAO;AAAA,WA3DP;AAAA;AAAA;AAAA;AAAA;AA4DF;"}
|
|
@@ -22,7 +22,7 @@ export declare const ddlEventSchema: v.ObjectType<Omit<{
|
|
|
22
22
|
primaryKey: v.Optional<string[]>;
|
|
23
23
|
}, "schema"> & {
|
|
24
24
|
schema: v.Type<string>;
|
|
25
|
-
}, "
|
|
25
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
26
26
|
oid: v.Type<number>;
|
|
27
27
|
columns: v.Type<Record<string, {
|
|
28
28
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -74,7 +74,7 @@ export declare const ddlStartEventSchema: v.ObjectType<Omit<Omit<{
|
|
|
74
74
|
primaryKey: v.Optional<string[]>;
|
|
75
75
|
}, "schema"> & {
|
|
76
76
|
schema: v.Type<string>;
|
|
77
|
-
}, "
|
|
77
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
78
78
|
oid: v.Type<number>;
|
|
79
79
|
columns: v.Type<Record<string, {
|
|
80
80
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -141,7 +141,7 @@ export declare const ddlUpdateEventSchema: v.ObjectType<Omit<Omit<{
|
|
|
141
141
|
primaryKey: v.Optional<string[]>;
|
|
142
142
|
}, "schema"> & {
|
|
143
143
|
schema: v.Type<string>;
|
|
144
|
-
}, "
|
|
144
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
145
145
|
oid: v.Type<number>;
|
|
146
146
|
columns: v.Type<Record<string, {
|
|
147
147
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -199,7 +199,7 @@ export declare const replicationEventSchema: v.UnionType<[v.ObjectType<Omit<Omit
|
|
|
199
199
|
primaryKey: v.Optional<string[]>;
|
|
200
200
|
}, "schema"> & {
|
|
201
201
|
schema: v.Type<string>;
|
|
202
|
-
}, "
|
|
202
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
203
203
|
oid: v.Type<number>;
|
|
204
204
|
columns: v.Type<Record<string, {
|
|
205
205
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -252,7 +252,7 @@ export declare const replicationEventSchema: v.UnionType<[v.ObjectType<Omit<Omit
|
|
|
252
252
|
primaryKey: v.Optional<string[]>;
|
|
253
253
|
}, "schema"> & {
|
|
254
254
|
schema: v.Type<string>;
|
|
255
|
-
}, "
|
|
255
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
256
256
|
oid: v.Type<number>;
|
|
257
257
|
columns: v.Type<Record<string, {
|
|
258
258
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -17,7 +17,7 @@ export declare const publishedSchema: v.ObjectType<Omit<{
|
|
|
17
17
|
primaryKey: v.Optional<string[]>;
|
|
18
18
|
}, "schema"> & {
|
|
19
19
|
schema: v.Type<string>;
|
|
20
|
-
}, "
|
|
20
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
21
21
|
oid: v.Type<number>;
|
|
22
22
|
columns: v.Type<Record<string, {
|
|
23
23
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -63,7 +63,7 @@ declare const publicationInfoSchema: v.ObjectType<Omit<Omit<{
|
|
|
63
63
|
primaryKey: v.Optional<string[]>;
|
|
64
64
|
}, "schema"> & {
|
|
65
65
|
schema: v.Type<string>;
|
|
66
|
-
}, "
|
|
66
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
67
67
|
oid: v.Type<number>;
|
|
68
68
|
columns: v.Type<Record<string, {
|
|
69
69
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -53,7 +53,7 @@ declare const replicaSchema: v.ObjectType<Omit<{
|
|
|
53
53
|
primaryKey: v.Optional<string[]>;
|
|
54
54
|
}, "schema"> & {
|
|
55
55
|
schema: v.Type<string>;
|
|
56
|
-
}, "
|
|
56
|
+
}, "columns" | "publications" | "oid" | "replicaIdentity"> & {
|
|
57
57
|
oid: v.Type<number>;
|
|
58
58
|
columns: v.Type<Record<string, {
|
|
59
59
|
pgTypeClass?: "e" | "d" | "b" | "c" | "p" | "r" | "m" | undefined;
|
|
@@ -4,14 +4,23 @@ import type { ZeroConfig } from '../../config/zero-config.ts';
|
|
|
4
4
|
import { type Worker } from '../../types/processes.ts';
|
|
5
5
|
import { type ShardID } from '../../types/shards.ts';
|
|
6
6
|
import { type Source } from '../../types/streams.ts';
|
|
7
|
-
import { HttpService
|
|
7
|
+
import { HttpService } from '../http-service.ts';
|
|
8
|
+
import type { Service } from '../service.ts';
|
|
8
9
|
import type { BackupMonitor } from './backup-monitor.ts';
|
|
9
10
|
import { type ChangeStreamer, type Downstream, type SubscriberContext } from './change-streamer.ts';
|
|
10
11
|
import { type SnapshotMessage } from './snapshot.ts';
|
|
12
|
+
type Options = {
|
|
13
|
+
port: number;
|
|
14
|
+
startupDelayMs: number;
|
|
15
|
+
startupDelayKeepalives: number;
|
|
16
|
+
};
|
|
11
17
|
export declare class ChangeStreamerHttpServer extends HttpService {
|
|
12
18
|
#private;
|
|
13
19
|
readonly id = "change-streamer-http-server";
|
|
14
|
-
constructor(lc: LogContext, config: ZeroConfig, opts: Options, parent: Worker, changeStreamer: ChangeStreamer, backupMonitor: BackupMonitor | null);
|
|
20
|
+
constructor(lc: LogContext, config: ZeroConfig, opts: Options, parent: Worker, changeStreamer: ChangeStreamer & Service, backupMonitor: BackupMonitor | null);
|
|
21
|
+
protected _onStart(): void;
|
|
22
|
+
protected _onHeartbeat(count: number): void;
|
|
23
|
+
protected _onStop(): Promise<void>;
|
|
15
24
|
}
|
|
16
25
|
export declare class ChangeStreamerHttpClient implements ChangeStreamer {
|
|
17
26
|
#private;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer-http.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,WAAW,CAAC;AAI/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,6BAA6B,CAAC;AAG5D,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAC,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAsB,KAAK,MAAM,EAAC,MAAM,wBAAwB,CAAC;AAIxE,OAAO,EAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"change-streamer-http.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,WAAW,CAAC;AAI/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,6BAA6B,CAAC;AAG5D,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAC,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAsB,KAAK,MAAM,EAAC,MAAM,wBAAwB,CAAC;AAIxE,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAC/C,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAwB,KAAK,eAAe,EAAC,MAAM,eAAe,CAAC;AAW1E,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,qBAAa,wBAAyB,SAAQ,WAAW;;IACvD,QAAQ,CAAC,EAAE,iCAAiC;gBAO1C,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,cAAc,GAAG,OAAO,EACxC,aAAa,EAAE,aAAa,GAAG,IAAI;cA+HlB,QAAQ,IAAI,IAAI;cAWhB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;cAS3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAKlD;AAED,qBAAa,wBAAyB,YAAW,cAAc;;gBAO3D,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,MAAM,GAAG,SAAS;IA+BjC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IASjE,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;CAQrE;AAED,KAAK,cAAc,GAAG,IAAI,CAAC,eAAe,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;AAE/D,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,iBAAiB,CAc3E"}
|
|
@@ -20,6 +20,8 @@ const SNAPSHOT_PATH = `/replication/v${PROTOCOL_VERSION}/snapshot`;
|
|
|
20
20
|
const CHANGES_PATH = `/replication/v${PROTOCOL_VERSION}/changes`;
|
|
21
21
|
class ChangeStreamerHttpServer extends HttpService {
|
|
22
22
|
id = "change-streamer-http-server";
|
|
23
|
+
#lc;
|
|
24
|
+
#opts;
|
|
23
25
|
#changeStreamer;
|
|
24
26
|
#backupMonitor;
|
|
25
27
|
constructor(lc, config, opts, parent, changeStreamer, backupMonitor) {
|
|
@@ -56,6 +58,8 @@ class ChangeStreamerHttpServer extends HttpService {
|
|
|
56
58
|
parent
|
|
57
59
|
);
|
|
58
60
|
});
|
|
61
|
+
this.#lc = lc;
|
|
62
|
+
this.#opts = opts;
|
|
59
63
|
this.#changeStreamer = changeStreamer;
|
|
60
64
|
this.#backupMonitor = backupMonitor;
|
|
61
65
|
}
|
|
@@ -101,6 +105,9 @@ class ChangeStreamerHttpServer extends HttpService {
|
|
|
101
105
|
#subscribe = async (ws, req) => {
|
|
102
106
|
try {
|
|
103
107
|
const ctx = getSubscriberContext(req);
|
|
108
|
+
if (ctx.mode === "serving") {
|
|
109
|
+
this.#ensureChangeStreamerStarted("incoming subscription");
|
|
110
|
+
}
|
|
104
111
|
const downstream = await this.#changeStreamer.subscribe(ctx);
|
|
105
112
|
if (ctx.initial && ctx.taskID && this.#backupMonitor) {
|
|
106
113
|
this.#backupMonitor.endReservation(ctx.taskID);
|
|
@@ -110,6 +117,35 @@ class ChangeStreamerHttpServer extends HttpService {
|
|
|
110
117
|
closeWithError(this._lc, ws, err, PROTOCOL_ERROR);
|
|
111
118
|
}
|
|
112
119
|
};
|
|
120
|
+
#changeStreamerStarted = false;
|
|
121
|
+
#ensureChangeStreamerStarted(reason) {
|
|
122
|
+
if (!this.#changeStreamerStarted && this._state.shouldRun()) {
|
|
123
|
+
this.#lc.info?.(`starting ChangeStreamerService: ${reason}`);
|
|
124
|
+
void this.#changeStreamer.run().catch(
|
|
125
|
+
(e) => this.#lc.warn?.(`ChangeStreamerService ended with error`, e)
|
|
126
|
+
).finally(() => this.stop());
|
|
127
|
+
this.#changeStreamerStarted = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
_onStart() {
|
|
131
|
+
const { startupDelayMs } = this.#opts;
|
|
132
|
+
this._state.setTimeout(
|
|
133
|
+
() => this.#ensureChangeStreamerStarted(
|
|
134
|
+
`startup delay elapsed (${startupDelayMs} ms)`
|
|
135
|
+
),
|
|
136
|
+
startupDelayMs
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
_onHeartbeat(count) {
|
|
140
|
+
if (count === this.#opts.startupDelayKeepalives && this._state.shouldRun()) {
|
|
141
|
+
this.#ensureChangeStreamerStarted(`${count} startup keepalives received`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async _onStop() {
|
|
145
|
+
if (this.#changeStreamerStarted) {
|
|
146
|
+
await this.#changeStreamer.stop();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
113
149
|
}
|
|
114
150
|
class ChangeStreamerHttpClient {
|
|
115
151
|
#lc;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer-http.js","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"sourcesContent":["import websocket from '@fastify/websocket';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {IncomingMessage} from 'node:http';\nimport WebSocket from 'ws';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport type {IncomingMessageSubset} from '../../types/http.ts';\nimport {pgClient, type PostgresDB} from '../../types/pg.ts';\nimport {type Worker} from '../../types/processes.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {streamIn, streamOut, type Source} from '../../types/streams.ts';\nimport {URLParams} from '../../types/url-params.ts';\nimport {installWebSocketReceiver} from '../../types/websocket-handoff.ts';\nimport {closeWithError, PROTOCOL_ERROR} from '../../types/ws.ts';\nimport {HttpService, type Options} from '../http-service.ts';\nimport type {BackupMonitor} from './backup-monitor.ts';\nimport {\n downstreamSchema,\n PROTOCOL_VERSION,\n type ChangeStreamer,\n type Downstream,\n type SubscriberContext,\n} from './change-streamer.ts';\nimport {discoverChangeStreamerAddress} from './schema/tables.ts';\nimport {snapshotMessageSchema, type SnapshotMessage} from './snapshot.ts';\n\nconst MIN_SUPPORTED_PROTOCOL_VERSION = 1;\n\nconst SNAPSHOT_PATH_PATTERN = '/replication/:version/snapshot';\nconst CHANGES_PATH_PATTERN = '/replication/:version/changes';\nconst PATH_REGEX = /\\/replication\\/v(?<version>\\d+)\\/(changes|snapshot)$/;\n\nconst SNAPSHOT_PATH = `/replication/v${PROTOCOL_VERSION}/snapshot`;\nconst CHANGES_PATH = `/replication/v${PROTOCOL_VERSION}/changes`;\n\nexport class ChangeStreamerHttpServer extends HttpService {\n readonly id = 'change-streamer-http-server';\n readonly #changeStreamer: ChangeStreamer;\n readonly #backupMonitor: BackupMonitor | null;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n opts: Options,\n parent: Worker,\n changeStreamer: ChangeStreamer,\n backupMonitor: BackupMonitor | null,\n ) {\n super('change-streamer-http-server', lc, opts, async fastify => {\n const websocketOptions: {perMessageDeflate?: boolean | object} = {};\n if (config.websocketCompression) {\n if (config.websocketCompressionOptions) {\n try {\n websocketOptions.perMessageDeflate = JSON.parse(\n config.websocketCompressionOptions,\n );\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n } else {\n websocketOptions.perMessageDeflate = true;\n }\n }\n\n await fastify.register(websocket, {\n options: websocketOptions,\n });\n\n fastify.get(CHANGES_PATH_PATTERN, {websocket: true}, this.#subscribe);\n fastify.get(\n SNAPSHOT_PATH_PATTERN,\n {websocket: true},\n this.#reserveSnapshot,\n );\n\n installWebSocketReceiver<'snapshot' | 'changes'>(\n lc,\n fastify.websocketServer,\n this.#receiveWebsocket,\n parent,\n );\n });\n\n this.#changeStreamer = changeStreamer;\n this.#backupMonitor = backupMonitor;\n }\n\n #getBackupMonitor() {\n return must(\n this.#backupMonitor,\n 'replication-manager is not configured with a ZERO_LITESTREAM_BACKUP_URL',\n );\n }\n\n // Called when receiving a web socket via the main dispatcher handoff.\n readonly #receiveWebsocket = (\n ws: WebSocket,\n action: 'changes' | 'snapshot',\n msg: IncomingMessageSubset,\n ) => {\n switch (action) {\n case 'snapshot':\n return this.#reserveSnapshot(ws, msg);\n case 'changes':\n return this.#subscribe(ws, msg);\n default:\n closeWithError(\n this._lc,\n ws,\n `invalid action \"${action}\" received in handoff`,\n );\n return;\n }\n };\n\n readonly #reserveSnapshot = async (ws: WebSocket, req: RequestHeaders) => {\n try {\n const url = new URL(\n req.url ?? '',\n req.headers.origin ?? 'http://localhost',\n );\n checkProtocolVersion(url.pathname);\n const taskID = url.searchParams.get('taskID');\n if (!taskID) {\n throw new Error('Missing taskID in snapshot request');\n }\n const downstream =\n await this.#getBackupMonitor().startSnapshotReservation(taskID);\n void streamOut(this._lc, downstream, ws);\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n\n readonly #subscribe = async (ws: WebSocket, req: RequestHeaders) => {\n try {\n const ctx = getSubscriberContext(req);\n\n const downstream = await this.#changeStreamer.subscribe(ctx);\n if (ctx.initial && ctx.taskID && this.#backupMonitor) {\n // Now that the change-streamer knows about the subscriber and watermark,\n // end the reservation to safely resume scheduling cleanup.\n this.#backupMonitor.endReservation(ctx.taskID);\n }\n void streamOut(this._lc, downstream, ws);\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n}\n\nexport class ChangeStreamerHttpClient implements ChangeStreamer {\n readonly #lc: LogContext;\n readonly #shardID: ShardID;\n readonly #changeDB: PostgresDB;\n readonly #changeStreamerURI: string | undefined;\n\n constructor(\n lc: LogContext,\n shardID: ShardID,\n changeDB: string,\n changeStreamerURI: string | undefined,\n ) {\n this.#lc = lc;\n this.#shardID = shardID;\n // Create a pg client with a single short-lived connection for the purpose\n // of change-streamer discovery (i.e. ChangeDB as DNS).\n this.#changeDB = pgClient(lc, changeDB, {\n max: 1,\n ['idle_timeout']: 15,\n connection: {['application_name']: 'change-streamer-discovery'},\n });\n this.#changeStreamerURI = changeStreamerURI;\n }\n\n async #resolveChangeStreamer(path: string) {\n let baseURL = this.#changeStreamerURI;\n if (!baseURL) {\n const address = await discoverChangeStreamerAddress(\n this.#shardID,\n this.#changeDB,\n );\n if (!address) {\n throw new Error(`no change-streamer is running`);\n }\n baseURL = address.includes('://') ? `${address}/` : `ws://${address}/`;\n }\n const uri = new URL(path, baseURL);\n this.#lc.info?.(`connecting to change-streamer@${uri}`);\n return uri;\n }\n\n async reserveSnapshot(taskID: string): Promise<Source<SnapshotMessage>> {\n const uri = await this.#resolveChangeStreamer(SNAPSHOT_PATH);\n\n const params = new URLSearchParams({taskID});\n const ws = new WebSocket(uri + `?${params.toString()}`);\n\n return streamIn(this.#lc, ws, snapshotMessageSchema);\n }\n\n async subscribe(ctx: SubscriberContext): Promise<Source<Downstream>> {\n const uri = await this.#resolveChangeStreamer(CHANGES_PATH);\n\n const params = getParams(ctx);\n const ws = new WebSocket(uri + `?${params.toString()}`);\n\n return streamIn(this.#lc, ws, downstreamSchema);\n }\n}\n\ntype RequestHeaders = Pick<IncomingMessage, 'url' | 'headers'>;\n\nexport function getSubscriberContext(req: RequestHeaders): SubscriberContext {\n const url = new URL(req.url ?? '', req.headers.origin ?? 'http://localhost');\n const protocolVersion = checkProtocolVersion(url.pathname);\n const params = new URLParams(url);\n\n return {\n protocolVersion,\n id: params.get('id', true),\n taskID: params.get('taskID', false),\n mode: params.get('mode', false) === 'backup' ? 'backup' : 'serving',\n replicaVersion: params.get('replicaVersion', true),\n watermark: params.get('watermark', true),\n initial: params.getBoolean('initial'),\n };\n}\n\nfunction checkProtocolVersion(pathname: string): number {\n const match = PATH_REGEX.exec(pathname);\n if (!match) {\n throw new Error(`invalid path: ${pathname}`);\n }\n const v = Number(match.groups?.version);\n if (\n Number.isNaN(v) ||\n v > PROTOCOL_VERSION ||\n v < MIN_SUPPORTED_PROTOCOL_VERSION\n ) {\n throw new Error(\n `Cannot service client at protocol v${v}. ` +\n `Supported protocols: [v${MIN_SUPPORTED_PROTOCOL_VERSION} ... v${PROTOCOL_VERSION}]`,\n );\n }\n return v;\n}\n\n// This is called from the client-side (i.e. the replicator).\nfunction getParams(ctx: SubscriberContext): URLSearchParams {\n // The protocolVersion is hard-coded into the CHANGES_PATH.\n const {protocolVersion, ...stringParams} = ctx;\n assert(\n protocolVersion === PROTOCOL_VERSION,\n `replicator should be setting protocolVersion to ${PROTOCOL_VERSION}`,\n );\n return new URLSearchParams({\n ...stringParams,\n taskID: ctx.taskID ? ctx.taskID : '',\n initial: ctx.initial ? 'true' : 'false',\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"change-streamer-http.js","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-http.ts"],"sourcesContent":["import websocket from '@fastify/websocket';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {IncomingMessage} from 'node:http';\nimport WebSocket from 'ws';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {ZeroConfig} from '../../config/zero-config.ts';\nimport type {IncomingMessageSubset} from '../../types/http.ts';\nimport {pgClient, type PostgresDB} from '../../types/pg.ts';\nimport {type Worker} from '../../types/processes.ts';\nimport {type ShardID} from '../../types/shards.ts';\nimport {streamIn, streamOut, type Source} from '../../types/streams.ts';\nimport {URLParams} from '../../types/url-params.ts';\nimport {installWebSocketReceiver} from '../../types/websocket-handoff.ts';\nimport {closeWithError, PROTOCOL_ERROR} from '../../types/ws.ts';\nimport {HttpService} from '../http-service.ts';\nimport type {Service} from '../service.ts';\nimport type {BackupMonitor} from './backup-monitor.ts';\nimport {\n downstreamSchema,\n PROTOCOL_VERSION,\n type ChangeStreamer,\n type Downstream,\n type SubscriberContext,\n} from './change-streamer.ts';\nimport {discoverChangeStreamerAddress} from './schema/tables.ts';\nimport {snapshotMessageSchema, type SnapshotMessage} from './snapshot.ts';\n\nconst MIN_SUPPORTED_PROTOCOL_VERSION = 1;\n\nconst SNAPSHOT_PATH_PATTERN = '/replication/:version/snapshot';\nconst CHANGES_PATH_PATTERN = '/replication/:version/changes';\nconst PATH_REGEX = /\\/replication\\/v(?<version>\\d+)\\/(changes|snapshot)$/;\n\nconst SNAPSHOT_PATH = `/replication/v${PROTOCOL_VERSION}/snapshot`;\nconst CHANGES_PATH = `/replication/v${PROTOCOL_VERSION}/changes`;\n\ntype Options = {\n port: number;\n startupDelayMs: number;\n startupDelayKeepalives: number;\n};\n\nexport class ChangeStreamerHttpServer extends HttpService {\n readonly id = 'change-streamer-http-server';\n readonly #lc: LogContext;\n readonly #opts: Options;\n readonly #changeStreamer: ChangeStreamer & Service;\n readonly #backupMonitor: BackupMonitor | null;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n opts: Options,\n parent: Worker,\n changeStreamer: ChangeStreamer & Service,\n backupMonitor: BackupMonitor | null,\n ) {\n super('change-streamer-http-server', lc, opts, async fastify => {\n const websocketOptions: {perMessageDeflate?: boolean | object} = {};\n if (config.websocketCompression) {\n if (config.websocketCompressionOptions) {\n try {\n websocketOptions.perMessageDeflate = JSON.parse(\n config.websocketCompressionOptions,\n );\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n } else {\n websocketOptions.perMessageDeflate = true;\n }\n }\n\n await fastify.register(websocket, {\n options: websocketOptions,\n });\n\n fastify.get(CHANGES_PATH_PATTERN, {websocket: true}, this.#subscribe);\n fastify.get(\n SNAPSHOT_PATH_PATTERN,\n {websocket: true},\n this.#reserveSnapshot,\n );\n\n installWebSocketReceiver<'snapshot' | 'changes'>(\n lc,\n fastify.websocketServer,\n this.#receiveWebsocket,\n parent,\n );\n });\n\n this.#lc = lc;\n this.#opts = opts;\n this.#changeStreamer = changeStreamer;\n this.#backupMonitor = backupMonitor;\n }\n\n #getBackupMonitor() {\n return must(\n this.#backupMonitor,\n 'replication-manager is not configured with a ZERO_LITESTREAM_BACKUP_URL',\n );\n }\n\n // Called when receiving a web socket via the main dispatcher handoff.\n readonly #receiveWebsocket = (\n ws: WebSocket,\n action: 'changes' | 'snapshot',\n msg: IncomingMessageSubset,\n ) => {\n switch (action) {\n case 'snapshot':\n return this.#reserveSnapshot(ws, msg);\n case 'changes':\n return this.#subscribe(ws, msg);\n default:\n closeWithError(\n this._lc,\n ws,\n `invalid action \"${action}\" received in handoff`,\n );\n return;\n }\n };\n\n readonly #reserveSnapshot = async (ws: WebSocket, req: RequestHeaders) => {\n try {\n const url = new URL(\n req.url ?? '',\n req.headers.origin ?? 'http://localhost',\n );\n checkProtocolVersion(url.pathname);\n const taskID = url.searchParams.get('taskID');\n if (!taskID) {\n throw new Error('Missing taskID in snapshot request');\n }\n const downstream =\n await this.#getBackupMonitor().startSnapshotReservation(taskID);\n void streamOut(this._lc, downstream, ws);\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n\n readonly #subscribe = async (ws: WebSocket, req: RequestHeaders) => {\n try {\n const ctx = getSubscriberContext(req);\n if (ctx.mode === 'serving') {\n this.#ensureChangeStreamerStarted('incoming subscription');\n }\n\n const downstream = await this.#changeStreamer.subscribe(ctx);\n if (ctx.initial && ctx.taskID && this.#backupMonitor) {\n // Now that the change-streamer knows about the subscriber and watermark,\n // end the reservation to safely resume scheduling cleanup.\n this.#backupMonitor.endReservation(ctx.taskID);\n }\n void streamOut(this._lc, downstream, ws);\n } catch (err) {\n closeWithError(this._lc, ws, err, PROTOCOL_ERROR);\n }\n };\n\n #changeStreamerStarted = false;\n\n #ensureChangeStreamerStarted(reason: string) {\n if (!this.#changeStreamerStarted && this._state.shouldRun()) {\n this.#lc.info?.(`starting ChangeStreamerService: ${reason}`);\n void this.#changeStreamer\n .run()\n .catch(e =>\n this.#lc.warn?.(`ChangeStreamerService ended with error`, e),\n )\n .finally(() => this.stop());\n\n this.#changeStreamerStarted = true;\n }\n }\n\n protected override _onStart(): void {\n const {startupDelayMs} = this.#opts;\n this._state.setTimeout(\n () =>\n this.#ensureChangeStreamerStarted(\n `startup delay elapsed (${startupDelayMs} ms)`,\n ),\n startupDelayMs,\n );\n }\n\n protected override _onHeartbeat(count: number): void {\n if (\n count === this.#opts.startupDelayKeepalives &&\n this._state.shouldRun()\n ) {\n this.#ensureChangeStreamerStarted(`${count} startup keepalives received`);\n }\n }\n\n protected override async _onStop(): Promise<void> {\n if (this.#changeStreamerStarted) {\n await this.#changeStreamer.stop();\n }\n }\n}\n\nexport class ChangeStreamerHttpClient implements ChangeStreamer {\n readonly #lc: LogContext;\n readonly #shardID: ShardID;\n readonly #changeDB: PostgresDB;\n readonly #changeStreamerURI: string | undefined;\n\n constructor(\n lc: LogContext,\n shardID: ShardID,\n changeDB: string,\n changeStreamerURI: string | undefined,\n ) {\n this.#lc = lc;\n this.#shardID = shardID;\n // Create a pg client with a single short-lived connection for the purpose\n // of change-streamer discovery (i.e. ChangeDB as DNS).\n this.#changeDB = pgClient(lc, changeDB, {\n max: 1,\n ['idle_timeout']: 15,\n connection: {['application_name']: 'change-streamer-discovery'},\n });\n this.#changeStreamerURI = changeStreamerURI;\n }\n\n async #resolveChangeStreamer(path: string) {\n let baseURL = this.#changeStreamerURI;\n if (!baseURL) {\n const address = await discoverChangeStreamerAddress(\n this.#shardID,\n this.#changeDB,\n );\n if (!address) {\n throw new Error(`no change-streamer is running`);\n }\n baseURL = address.includes('://') ? `${address}/` : `ws://${address}/`;\n }\n const uri = new URL(path, baseURL);\n this.#lc.info?.(`connecting to change-streamer@${uri}`);\n return uri;\n }\n\n async reserveSnapshot(taskID: string): Promise<Source<SnapshotMessage>> {\n const uri = await this.#resolveChangeStreamer(SNAPSHOT_PATH);\n\n const params = new URLSearchParams({taskID});\n const ws = new WebSocket(uri + `?${params.toString()}`);\n\n return streamIn(this.#lc, ws, snapshotMessageSchema);\n }\n\n async subscribe(ctx: SubscriberContext): Promise<Source<Downstream>> {\n const uri = await this.#resolveChangeStreamer(CHANGES_PATH);\n\n const params = getParams(ctx);\n const ws = new WebSocket(uri + `?${params.toString()}`);\n\n return streamIn(this.#lc, ws, downstreamSchema);\n }\n}\n\ntype RequestHeaders = Pick<IncomingMessage, 'url' | 'headers'>;\n\nexport function getSubscriberContext(req: RequestHeaders): SubscriberContext {\n const url = new URL(req.url ?? '', req.headers.origin ?? 'http://localhost');\n const protocolVersion = checkProtocolVersion(url.pathname);\n const params = new URLParams(url);\n\n return {\n protocolVersion,\n id: params.get('id', true),\n taskID: params.get('taskID', false),\n mode: params.get('mode', false) === 'backup' ? 'backup' : 'serving',\n replicaVersion: params.get('replicaVersion', true),\n watermark: params.get('watermark', true),\n initial: params.getBoolean('initial'),\n };\n}\n\nfunction checkProtocolVersion(pathname: string): number {\n const match = PATH_REGEX.exec(pathname);\n if (!match) {\n throw new Error(`invalid path: ${pathname}`);\n }\n const v = Number(match.groups?.version);\n if (\n Number.isNaN(v) ||\n v > PROTOCOL_VERSION ||\n v < MIN_SUPPORTED_PROTOCOL_VERSION\n ) {\n throw new Error(\n `Cannot service client at protocol v${v}. ` +\n `Supported protocols: [v${MIN_SUPPORTED_PROTOCOL_VERSION} ... v${PROTOCOL_VERSION}]`,\n );\n }\n return v;\n}\n\n// This is called from the client-side (i.e. the replicator).\nfunction getParams(ctx: SubscriberContext): URLSearchParams {\n // The protocolVersion is hard-coded into the CHANGES_PATH.\n const {protocolVersion, ...stringParams} = ctx;\n assert(\n protocolVersion === PROTOCOL_VERSION,\n `replicator should be setting protocolVersion to ${PROTOCOL_VERSION}`,\n );\n return new URLSearchParams({\n ...stringParams,\n taskID: ctx.taskID ? ctx.taskID : '',\n initial: ctx.initial ? 'true' : 'false',\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LogContext } from '@rocicorp/logger';
|
|
2
2
|
import { type FastifyInstance } from 'fastify';
|
|
3
|
+
import { RunningState } from './running-state.ts';
|
|
3
4
|
import type { Service } from './service.ts';
|
|
4
5
|
export type Options = {
|
|
5
6
|
port: number;
|
|
@@ -13,11 +14,11 @@ export declare class HttpService implements Service {
|
|
|
13
14
|
#private;
|
|
14
15
|
readonly id: string;
|
|
15
16
|
protected readonly _lc: LogContext;
|
|
17
|
+
protected readonly _state: RunningState;
|
|
16
18
|
constructor(id: string, lc: LogContext, opts: Options, init: (fastify: FastifyInstance) => void | Promise<void>);
|
|
17
|
-
|
|
18
|
-
protected
|
|
19
|
-
|
|
20
|
-
protected _respondToKeepalive(): boolean;
|
|
19
|
+
protected _onStart(): void;
|
|
20
|
+
protected _onStop(): Promise<void>;
|
|
21
|
+
protected _onHeartbeat(_count: number): void;
|
|
21
22
|
start(): Promise<string>;
|
|
22
23
|
run(): Promise<void>;
|
|
23
24
|
stop(): Promise<void>;
|