@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,5 +1,6 @@
|
|
|
1
1
|
import { assert } from "../../shared/src/asserts.js";
|
|
2
2
|
import { getErrorMessage, getErrorDetails } from "../../shared/src/error.js";
|
|
3
|
+
import { promiseVoid } from "../../shared/src/resolved-promises.js";
|
|
3
4
|
import { parse } from "../../shared/src/valita.js";
|
|
4
5
|
import { MutationAlreadyProcessedError } from "../../zero-cache/src/services/mutagen/error.js";
|
|
5
6
|
import { isApplicationError, wrapWithApplicationError } from "../../zero-protocol/src/application-error.js";
|
|
@@ -7,6 +8,7 @@ import { PushFailed } from "../../zero-protocol/src/error-kind-enum.js";
|
|
|
7
8
|
import { Server } from "../../zero-protocol/src/error-origin-enum.js";
|
|
8
9
|
import { Parse, UnsupportedPushVersion, OutOfOrderMutation as OutOfOrderMutation$1, Database, Internal } from "../../zero-protocol/src/error-reason-enum.js";
|
|
9
10
|
import { pushBodySchema, pushParamsSchema } from "../../zero-protocol/src/push.js";
|
|
11
|
+
import { isMutator } from "../../zql/src/mutate/mutator.js";
|
|
10
12
|
import { createLogContext } from "./logging.js";
|
|
11
13
|
const applicationErrorWrapper = async (fn) => {
|
|
12
14
|
try {
|
|
@@ -18,26 +20,47 @@ const applicationErrorWrapper = async (fn) => {
|
|
|
18
20
|
throw wrapWithApplicationError(error);
|
|
19
21
|
}
|
|
20
22
|
};
|
|
21
|
-
async function handleMutationRequest(cb,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
async function handleMutationRequest(dbProvider, cb, queryStringOrRequest, bodyOrContext, contextOrLogLevel, logLevel) {
|
|
24
|
+
const isRequestOverload = queryStringOrRequest instanceof Request;
|
|
25
|
+
let request;
|
|
26
|
+
let queryString;
|
|
27
|
+
let jsonBody;
|
|
28
|
+
let context;
|
|
29
|
+
let lc;
|
|
30
|
+
if (isRequestOverload) {
|
|
31
|
+
request = queryStringOrRequest;
|
|
32
|
+
context = bodyOrContext;
|
|
33
|
+
const level = contextOrLogLevel ?? "info";
|
|
34
|
+
lc = createLogContext(level).withContext("PushProcessor");
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
queryString = url.searchParams;
|
|
37
|
+
try {
|
|
38
|
+
jsonBody = await request.json();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
lc.error?.("Failed to parse push body", error);
|
|
41
|
+
const message = `Failed to parse push body: ${getErrorMessage(error)}`;
|
|
42
|
+
const details = getErrorDetails(error);
|
|
43
|
+
return {
|
|
44
|
+
kind: PushFailed,
|
|
45
|
+
origin: Server,
|
|
46
|
+
reason: Parse,
|
|
47
|
+
message,
|
|
48
|
+
mutationIDs: [],
|
|
49
|
+
...details ? { details } : {}
|
|
50
|
+
};
|
|
27
51
|
}
|
|
52
|
+
} else {
|
|
53
|
+
queryString = queryStringOrRequest;
|
|
54
|
+
jsonBody = bodyOrContext;
|
|
55
|
+
context = contextOrLogLevel;
|
|
56
|
+
const level = logLevel ?? "info";
|
|
57
|
+
lc = createLogContext(level).withContext("PushProcessor");
|
|
28
58
|
}
|
|
29
|
-
const lc = createLogContext(logLevel).withContext("PushProcessor");
|
|
30
59
|
let mutationIDs = [];
|
|
31
|
-
let
|
|
60
|
+
let pushBody;
|
|
32
61
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
rawBody = await queryOrQueryString.json();
|
|
36
|
-
} else {
|
|
37
|
-
rawBody = body;
|
|
38
|
-
}
|
|
39
|
-
req = parse(rawBody, pushBodySchema);
|
|
40
|
-
mutationIDs = req.mutations.map((m) => ({
|
|
62
|
+
pushBody = parse(jsonBody, pushBodySchema);
|
|
63
|
+
mutationIDs = pushBody.mutations.map((m) => ({
|
|
41
64
|
id: m.id,
|
|
42
65
|
clientID: m.clientID
|
|
43
66
|
}));
|
|
@@ -56,17 +79,8 @@ async function handleMutationRequest(cb, queryOrQueryString, body, logLevel) {
|
|
|
56
79
|
}
|
|
57
80
|
let queryParams;
|
|
58
81
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const url = new URL(queryOrQueryString.url);
|
|
62
|
-
queryString = url.searchParams;
|
|
63
|
-
} else {
|
|
64
|
-
queryString = queryOrQueryString;
|
|
65
|
-
}
|
|
66
|
-
if (queryString instanceof URLSearchParams) {
|
|
67
|
-
queryString = Object.fromEntries(queryString);
|
|
68
|
-
}
|
|
69
|
-
queryParams = parse(queryString, pushParamsSchema, "passthrough");
|
|
82
|
+
const queryStringObj = queryString instanceof URLSearchParams ? Object.fromEntries(queryString) : queryString;
|
|
83
|
+
queryParams = parse(queryStringObj, pushParamsSchema, "passthrough");
|
|
70
84
|
} catch (error) {
|
|
71
85
|
lc.error?.("Failed to parse push query parameters", error);
|
|
72
86
|
const message = `Failed to parse push query parameters: ${getErrorMessage(error)}`;
|
|
@@ -80,35 +94,59 @@ async function handleMutationRequest(cb, queryOrQueryString, body, logLevel) {
|
|
|
80
94
|
...details ? { details } : {}
|
|
81
95
|
};
|
|
82
96
|
}
|
|
83
|
-
if (
|
|
97
|
+
if (pushBody.pushVersion !== 1) {
|
|
84
98
|
const response = {
|
|
85
99
|
kind: PushFailed,
|
|
86
100
|
origin: Server,
|
|
87
101
|
reason: UnsupportedPushVersion,
|
|
88
102
|
mutationIDs,
|
|
89
|
-
message: `Unsupported push version: ${
|
|
103
|
+
message: `Unsupported push version: ${pushBody.pushVersion}`
|
|
90
104
|
};
|
|
91
105
|
return response;
|
|
92
106
|
}
|
|
93
107
|
const responses = [];
|
|
94
108
|
let processedCount = 0;
|
|
95
109
|
try {
|
|
96
|
-
const transactor = new Transactor(
|
|
97
|
-
|
|
110
|
+
const transactor = new Transactor(
|
|
111
|
+
dbProvider,
|
|
112
|
+
pushBody,
|
|
113
|
+
queryParams,
|
|
114
|
+
lc,
|
|
115
|
+
context
|
|
116
|
+
);
|
|
117
|
+
for (const m of pushBody.mutations) {
|
|
98
118
|
assert(m.type === "custom", "Expected custom mutation");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
m
|
|
102
|
-
(tx, name, args) => applicationErrorWrapper(() => innerCb(tx, name, args))
|
|
119
|
+
lc.debug?.(
|
|
120
|
+
`Processing mutation '${m.name}' (id=${m.id}, clientID=${m.clientID})`,
|
|
121
|
+
m.args
|
|
103
122
|
);
|
|
123
|
+
let mutationPhase = "preTransaction";
|
|
124
|
+
const transactProxy = async (innerCb) => {
|
|
125
|
+
mutationPhase = "transactionPending";
|
|
126
|
+
const result = await transactor.transact(
|
|
127
|
+
m,
|
|
128
|
+
(tx, name, args) => applicationErrorWrapper(() => innerCb(tx, name, args, context))
|
|
129
|
+
);
|
|
130
|
+
mutationPhase = "postCommit";
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
104
133
|
try {
|
|
105
134
|
const res = await applicationErrorWrapper(() => cb(transactProxy, m));
|
|
106
135
|
responses.push(res);
|
|
136
|
+
lc.debug?.(`Mutation '${m.name}' (id=${m.id}) completed successfully`);
|
|
107
137
|
processedCount++;
|
|
108
138
|
} catch (error) {
|
|
109
139
|
if (!isApplicationError(error)) {
|
|
110
140
|
throw error;
|
|
111
141
|
}
|
|
142
|
+
if (mutationPhase === "preTransaction") {
|
|
143
|
+
await transactor.persistPreTransactionFailure(m, error);
|
|
144
|
+
} else if (mutationPhase === "postCommit") {
|
|
145
|
+
lc.error?.(
|
|
146
|
+
`Post-commit mutation handler failed for mutation ${m.id} for client ${m.clientID}`,
|
|
147
|
+
error
|
|
148
|
+
);
|
|
149
|
+
}
|
|
112
150
|
lc.warn?.(
|
|
113
151
|
`Application error processing mutation ${m.id} for client ${m.clientID}`,
|
|
114
152
|
error
|
|
@@ -136,24 +174,23 @@ async function handleMutationRequest(cb, queryOrQueryString, body, logLevel) {
|
|
|
136
174
|
}
|
|
137
175
|
}
|
|
138
176
|
class Transactor {
|
|
177
|
+
#dbProvider;
|
|
139
178
|
#req;
|
|
140
179
|
#params;
|
|
141
180
|
#lc;
|
|
142
|
-
|
|
181
|
+
#context;
|
|
182
|
+
constructor(dbProvider, req, params, lc, context) {
|
|
183
|
+
this.#dbProvider = dbProvider;
|
|
143
184
|
this.#req = req;
|
|
144
185
|
this.#params = params;
|
|
145
186
|
this.#lc = lc;
|
|
187
|
+
this.#context = context;
|
|
146
188
|
}
|
|
147
|
-
transact = async (
|
|
189
|
+
transact = async (mutation, cb) => {
|
|
148
190
|
let appError = void 0;
|
|
149
191
|
for (; ; ) {
|
|
150
192
|
try {
|
|
151
|
-
const ret = await this.#transactImpl(
|
|
152
|
-
dbProvider,
|
|
153
|
-
mutation,
|
|
154
|
-
cb,
|
|
155
|
-
appError
|
|
156
|
-
);
|
|
193
|
+
const ret = await this.#transactImpl(mutation, cb, appError);
|
|
157
194
|
if (appError !== void 0) {
|
|
158
195
|
this.#lc.warn?.(
|
|
159
196
|
`Mutation ${mutation.id} for client ${mutation.clientID} was retried after an error`,
|
|
@@ -188,61 +225,72 @@ class Transactor {
|
|
|
188
225
|
);
|
|
189
226
|
continue;
|
|
190
227
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
);
|
|
196
|
-
} else {
|
|
197
|
-
this.#lc.error?.(
|
|
198
|
-
`Unexpected error processing mutation ${mutation.id} for client ${mutation.clientID}`,
|
|
199
|
-
error
|
|
200
|
-
);
|
|
201
|
-
}
|
|
228
|
+
this.#lc.error?.(
|
|
229
|
+
`Unexpected error processing mutation ${mutation.id} for client ${mutation.clientID}`,
|
|
230
|
+
error
|
|
231
|
+
);
|
|
202
232
|
throw error;
|
|
203
233
|
}
|
|
204
234
|
}
|
|
205
235
|
};
|
|
206
|
-
|
|
236
|
+
async persistPreTransactionFailure(mutation, appError) {
|
|
237
|
+
const ret = await this.#transactImpl(
|
|
238
|
+
mutation,
|
|
239
|
+
// noop callback since there's no transaction to execute
|
|
240
|
+
() => promiseVoid,
|
|
241
|
+
appError
|
|
242
|
+
);
|
|
243
|
+
return ret;
|
|
244
|
+
}
|
|
245
|
+
async #transactImpl(mutation, cb, appError) {
|
|
207
246
|
let transactionPhase = "open";
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
247
|
+
try {
|
|
248
|
+
const ret = await this.#dbProvider.transaction(
|
|
249
|
+
async (dbTx, transactionHooks) => {
|
|
250
|
+
transactionPhase = "execute";
|
|
251
|
+
await this.#checkAndIncrementLastMutationID(
|
|
252
|
+
transactionHooks,
|
|
253
|
+
mutation.clientID,
|
|
254
|
+
mutation.id
|
|
255
|
+
);
|
|
256
|
+
if (appError === void 0) {
|
|
257
|
+
this.#lc.debug?.(
|
|
258
|
+
`Executing mutator '${mutation.name}' (id=${mutation.id})`
|
|
259
|
+
);
|
|
260
|
+
try {
|
|
261
|
+
await cb(dbTx, mutation.name, mutation.args[0], this.#context);
|
|
262
|
+
} catch (appError2) {
|
|
263
|
+
throw wrapWithApplicationError(appError2);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
const mutationResult = makeAppErrorResponse(mutation, appError);
|
|
267
|
+
await transactionHooks.writeMutationResult(mutationResult);
|
|
221
268
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
{
|
|
235
|
-
upstreamSchema: this.#params.schema,
|
|
236
|
-
clientGroupID: this.#req.clientGroupID,
|
|
237
|
-
clientID: mutation.clientID,
|
|
238
|
-
mutationID: mutation.id
|
|
239
|
-
}
|
|
240
|
-
).catch((error) => {
|
|
269
|
+
return {
|
|
270
|
+
id: {
|
|
271
|
+
clientID: mutation.clientID,
|
|
272
|
+
id: mutation.id
|
|
273
|
+
},
|
|
274
|
+
result: {}
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
this.#getTransactionInput(mutation)
|
|
278
|
+
);
|
|
279
|
+
return ret;
|
|
280
|
+
} catch (error) {
|
|
241
281
|
if (isApplicationError(error) || error instanceof OutOfOrderMutation || error instanceof MutationAlreadyProcessedError) {
|
|
242
282
|
throw error;
|
|
243
283
|
}
|
|
244
284
|
throw new DatabaseTransactionError(transactionPhase, { cause: error });
|
|
245
|
-
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
#getTransactionInput(mutation) {
|
|
288
|
+
return {
|
|
289
|
+
upstreamSchema: this.#params.schema,
|
|
290
|
+
clientGroupID: this.#req.clientGroupID,
|
|
291
|
+
clientID: mutation.clientID,
|
|
292
|
+
mutationID: mutation.id
|
|
293
|
+
};
|
|
246
294
|
}
|
|
247
295
|
async #checkAndIncrementLastMutationID(transactionHooks, clientID, receivedMutationID) {
|
|
248
296
|
const { lastMutationID } = await transactionHooks.updateClientMutationID();
|
|
@@ -282,26 +330,24 @@ function makeAppErrorResponse(m, error) {
|
|
|
282
330
|
};
|
|
283
331
|
}
|
|
284
332
|
function getMutation(mutators, name) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
let mutator;
|
|
292
|
-
if (path.length === 1) {
|
|
293
|
-
mutator = mutators[path[0]];
|
|
294
|
-
} else {
|
|
295
|
-
const nextMap = mutators[path[0]];
|
|
296
|
-
assert(
|
|
297
|
-
typeof nextMap === "object" && nextMap !== void 0,
|
|
298
|
-
`could not find mutator map for ${name}`
|
|
299
|
-
);
|
|
300
|
-
mutator = nextMap[path[1]];
|
|
333
|
+
const path = name.split(/\.|\|/);
|
|
334
|
+
const mutator = getObjectAtPath(mutators, path);
|
|
335
|
+
assert(typeof mutator === "function", `could not find mutator ${name}`);
|
|
336
|
+
if (isMutator(mutator)) {
|
|
337
|
+
return (tx, args, ctx) => mutator.fn({ args, ctx, tx });
|
|
301
338
|
}
|
|
302
|
-
assert(typeof mutator === "function", () => `could not find mutator ${name}`);
|
|
303
339
|
return mutator;
|
|
304
340
|
}
|
|
341
|
+
function getObjectAtPath(obj, path) {
|
|
342
|
+
let current = obj;
|
|
343
|
+
for (const part of path) {
|
|
344
|
+
if (typeof current !== "object" || current === null || !(part in current)) {
|
|
345
|
+
return void 0;
|
|
346
|
+
}
|
|
347
|
+
current = current[part];
|
|
348
|
+
}
|
|
349
|
+
return current;
|
|
350
|
+
}
|
|
305
351
|
class DatabaseTransactionError extends Error {
|
|
306
352
|
constructor(phase, options) {
|
|
307
353
|
super(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-mutations.js","sources":["../../../../zero-server/src/process-mutations.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {getErrorDetails, getErrorMessage} from '../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport type {MaybePromise} from '../../shared/src/types.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {MutationAlreadyProcessedError} from '../../zero-cache/src/services/mutagen/error.ts';\nimport type {ApplicationError} from '../../zero-protocol/src/application-error.ts';\nimport {\n isApplicationError,\n wrapWithApplicationError,\n} from '../../zero-protocol/src/application-error.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 type {PushFailedBody} from '../../zero-protocol/src/error.ts';\nimport {\n pushBodySchema,\n pushParamsSchema,\n type CustomMutation,\n type Mutation,\n type MutationID,\n type MutationResponse,\n type PushBody,\n type PushResponse,\n} from '../../zero-protocol/src/push.ts';\nimport type {CustomMutatorDefs, CustomMutatorImpl} from './custom.ts';\nimport {createLogContext} from './logging.ts';\n\nexport interface TransactionProviderHooks {\n updateClientMutationID: () => Promise<{lastMutationID: number | bigint}>;\n writeMutationResult: (result: MutationResponse) => Promise<void>;\n}\n\nexport interface TransactionProviderInput {\n upstreamSchema: string;\n clientGroupID: string;\n clientID: string;\n mutationID: number;\n}\n\n/**\n * Defines the abstract interface for a database that PushProcessor can execute\n * transactions against.\n */\nexport interface Database<T> {\n transaction: <R>(\n callback: (\n tx: T,\n transactionHooks: TransactionProviderHooks,\n ) => MaybePromise<R>,\n transactionInput?: TransactionProviderInput,\n ) => Promise<R>;\n}\n\nexport type ExtractTransactionType<D> = D extends Database<infer T> ? T : never;\nexport type Params = v.Infer<typeof pushParamsSchema>;\n\nexport type TransactFn = <D extends Database<ExtractTransactionType<D>>>(\n dbProvider: D,\n cb: TransactFnCallback<D>,\n) => Promise<MutationResponse>;\n\nexport type TransactFnCallback<D extends Database<ExtractTransactionType<D>>> =\n (\n tx: ExtractTransactionType<D>,\n mutatorName: string,\n mutatorArgs: ReadonlyJSONValue,\n ) => Promise<void>;\n\nexport type Parsed = {\n transact: TransactFn;\n mutations: CustomMutation[];\n};\n\nconst applicationErrorWrapper = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn();\n } catch (error) {\n if (\n error instanceof DatabaseTransactionError ||\n error instanceof OutOfOrderMutation ||\n error instanceof MutationAlreadyProcessedError ||\n isApplicationError(error)\n ) {\n throw error;\n }\n\n throw wrapWithApplicationError(error);\n }\n};\n\n/**\n * Call `cb` for each mutation in the request.\n * The callback is called sequentially for each mutation.\n * If a mutation is out of order, the processing will stop and an error will be returned.\n * If a mutation has already been processed, it will be skipped and the processing will continue.\n * If a mutation receives an application error, it will be skipped, the error will be returned to the client, and processing will continue.\n */\nexport function handleMutationRequest(\n cb: (\n transact: TransactFn,\n mutation: CustomMutation,\n ) => Promise<MutationResponse>,\n queryString: URLSearchParams | Record<string, string>,\n body: ReadonlyJSONValue,\n logLevel?: LogLevel,\n): Promise<PushResponse>;\nexport function handleMutationRequest(\n cb: (\n transact: TransactFn,\n mutation: CustomMutation,\n ) => Promise<MutationResponse>,\n request: Request,\n logLevel?: LogLevel,\n): Promise<PushResponse>;\nexport async function handleMutationRequest(\n cb: (\n transact: TransactFn,\n mutation: CustomMutation,\n ) => Promise<MutationResponse>,\n queryOrQueryString: Request | URLSearchParams | Record<string, string>,\n body?: ReadonlyJSONValue | LogLevel,\n logLevel?: LogLevel,\n): Promise<PushResponse> {\n if (logLevel === undefined) {\n if (queryOrQueryString instanceof Request && typeof body === 'string') {\n logLevel = body as LogLevel;\n } else {\n logLevel = 'info';\n }\n }\n\n const lc = createLogContext(logLevel).withContext('PushProcessor');\n\n let mutationIDs: MutationID[] = [];\n\n let req: PushBody;\n try {\n let rawBody: unknown;\n if (queryOrQueryString instanceof Request) {\n rawBody = await queryOrQueryString.json();\n } else {\n rawBody = body;\n }\n req = v.parse(rawBody, pushBodySchema);\n\n mutationIDs = req.mutations.map(m => ({\n id: m.id,\n clientID: m.clientID,\n }));\n } catch (error) {\n lc.error?.('Failed to parse push body', error);\n\n const message = `Failed to parse push body: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n mutationIDs,\n ...(details ? {details} : {}),\n } as const satisfies PushFailedBody;\n }\n\n let queryParams: Params;\n try {\n let queryString: URLSearchParams | Record<string, string>;\n\n if (queryOrQueryString instanceof Request) {\n const url = new URL(queryOrQueryString.url);\n queryString = url.searchParams;\n } else {\n queryString = queryOrQueryString;\n }\n\n if (queryString instanceof URLSearchParams) {\n queryString = Object.fromEntries(queryString);\n }\n\n queryParams = v.parse(queryString, pushParamsSchema, 'passthrough');\n } catch (error) {\n lc.error?.('Failed to parse push query parameters', error);\n\n const message = `Failed to parse push query parameters: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n mutationIDs,\n ...(details ? {details} : {}),\n } as const satisfies PushFailedBody;\n }\n\n if (req.pushVersion !== 1) {\n const response = {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.UnsupportedPushVersion,\n mutationIDs,\n message: `Unsupported push version: ${req.pushVersion}`,\n } as const satisfies PushFailedBody;\n return response;\n }\n\n const responses: MutationResponse[] = [];\n let processedCount = 0;\n\n try {\n const transactor = new Transactor(req, queryParams, lc);\n\n for (const m of req.mutations) {\n assert(m.type === 'custom', 'Expected custom mutation');\n\n const transactProxy: TransactFn = (dbProvider, innerCb) =>\n transactor.transact(dbProvider, m, (tx, name, args) =>\n applicationErrorWrapper(() => innerCb(tx, name, args)),\n );\n\n try {\n const res = await applicationErrorWrapper(() => cb(transactProxy, m));\n responses.push(res);\n\n processedCount++;\n } catch (error) {\n if (!isApplicationError(error)) {\n throw error;\n }\n\n lc.warn?.(\n `Application error processing mutation ${m.id} for client ${m.clientID}`,\n error,\n );\n responses.push(makeAppErrorResponse(m, error));\n processedCount++;\n }\n }\n\n return {\n mutations: responses,\n };\n } catch (error) {\n lc.error?.('Failed to process push request', error);\n // only include mutationIDs for mutations that were not processed\n const unprocessedMutationIDs = mutationIDs.slice(processedCount);\n\n const message = getErrorMessage(error);\n const details = getErrorDetails(error);\n\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason:\n error instanceof OutOfOrderMutation\n ? ErrorReason.OutOfOrderMutation\n : error instanceof DatabaseTransactionError\n ? ErrorReason.Database\n : ErrorReason.Internal,\n message,\n mutationIDs: unprocessedMutationIDs,\n ...(details ? {details} : {}),\n };\n }\n}\n\nclass Transactor {\n readonly #req: PushBody;\n readonly #params: Params;\n readonly #lc: LogContext;\n\n constructor(req: PushBody, params: Params, lc: LogContext) {\n this.#req = req;\n this.#params = params;\n this.#lc = lc;\n }\n\n transact = async <D extends Database<ExtractTransactionType<D>>>(\n dbProvider: D,\n mutation: CustomMutation,\n cb: TransactFnCallback<D>,\n ): Promise<MutationResponse> => {\n let appError: ApplicationError | undefined = undefined;\n for (;;) {\n try {\n const ret = await this.#transactImpl(\n dbProvider,\n mutation,\n cb,\n appError,\n );\n if (appError !== undefined) {\n this.#lc.warn?.(\n `Mutation ${mutation.id} for client ${mutation.clientID} was retried after an error`,\n appError,\n );\n return makeAppErrorResponse(mutation, appError);\n }\n\n return ret;\n } catch (error) {\n if (error instanceof OutOfOrderMutation) {\n this.#lc.error?.(error);\n throw error;\n }\n\n if (error instanceof MutationAlreadyProcessedError) {\n this.#lc.warn?.(error);\n return {\n id: {\n clientID: mutation.clientID,\n id: mutation.id,\n },\n result: {\n error: 'alreadyProcessed',\n details: error.message,\n },\n };\n }\n\n if (isApplicationError(error)) {\n appError = error;\n this.#lc.warn?.(\n `Application error processing mutation ${mutation.id} for client ${mutation.clientID}`,\n appError,\n );\n continue;\n }\n\n if (error instanceof DatabaseTransactionError) {\n this.#lc.error?.(\n `Database error processing mutation ${mutation.id} for client ${mutation.clientID}`,\n error,\n );\n } else {\n this.#lc.error?.(\n `Unexpected error processing mutation ${mutation.id} for client ${mutation.clientID}`,\n error,\n );\n }\n\n throw error;\n }\n }\n };\n\n #transactImpl<D extends Database<ExtractTransactionType<D>>>(\n dbProvider: D,\n mutation: CustomMutation,\n cb: TransactFnCallback<D>,\n appError: ApplicationError | undefined,\n ): MaybePromise<MutationResponse> {\n let transactionPhase: DatabaseTransactionPhase = 'open';\n\n return dbProvider\n .transaction(\n async (dbTx, transactionHooks) => {\n // update the transaction phase to 'execute' after the transaction is opened\n transactionPhase = 'execute';\n\n await this.#checkAndIncrementLastMutationID(\n transactionHooks,\n mutation.clientID,\n mutation.id,\n );\n\n if (appError === undefined) {\n try {\n await cb(dbTx, mutation.name, mutation.args[0]);\n } catch (appError) {\n throw wrapWithApplicationError(appError);\n }\n } else {\n const mutationResult = makeAppErrorResponse(mutation, appError);\n await transactionHooks.writeMutationResult(mutationResult);\n }\n\n return {\n id: {\n clientID: mutation.clientID,\n id: mutation.id,\n },\n result: {},\n };\n },\n {\n upstreamSchema: this.#params.schema,\n clientGroupID: this.#req.clientGroupID,\n clientID: mutation.clientID,\n mutationID: mutation.id,\n },\n )\n .catch(error => {\n if (\n isApplicationError(error) ||\n error instanceof OutOfOrderMutation ||\n error instanceof MutationAlreadyProcessedError\n ) {\n throw error;\n }\n\n throw new DatabaseTransactionError(transactionPhase, {cause: error});\n });\n }\n\n async #checkAndIncrementLastMutationID(\n transactionHooks: TransactionProviderHooks,\n clientID: string,\n receivedMutationID: number,\n ) {\n const {lastMutationID} = await transactionHooks.updateClientMutationID();\n\n if (receivedMutationID < lastMutationID) {\n throw new MutationAlreadyProcessedError(\n clientID,\n receivedMutationID,\n lastMutationID,\n );\n } else if (receivedMutationID > lastMutationID) {\n throw new OutOfOrderMutation(\n clientID,\n receivedMutationID,\n lastMutationID,\n );\n }\n }\n}\n\nexport class OutOfOrderMutation extends Error {\n constructor(\n clientID: string,\n receivedMutationID: number,\n lastMutationID: number | bigint,\n ) {\n super(\n `Client ${clientID} sent mutation ID ${receivedMutationID} but expected ${lastMutationID}`,\n );\n }\n}\n\nfunction makeAppErrorResponse(\n m: Mutation,\n error: ApplicationError<ReadonlyJSONValue | undefined>,\n): MutationResponse {\n return {\n id: {\n clientID: m.clientID,\n id: m.id,\n },\n result: {\n error: 'app',\n message: error.message,\n ...(error.details ? {details: error.details} : {}),\n },\n };\n}\n\nexport function getMutation(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n mutators: CustomMutatorDefs<any>,\n name: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n): CustomMutatorImpl<any, any> {\n let path: string[];\n if (name.includes('|')) {\n path = name.split('|');\n } else {\n path = name.split('.');\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n let mutator: any;\n if (path.length === 1) {\n mutator = mutators[path[0]];\n } else {\n const nextMap = mutators[path[0]];\n assert(\n typeof nextMap === 'object' && nextMap !== undefined,\n `could not find mutator map for ${name}`,\n );\n mutator = nextMap[path[1]];\n }\n\n assert(typeof mutator === 'function', () => `could not find mutator ${name}`);\n return mutator;\n}\n\ntype DatabaseTransactionPhase = 'open' | 'execute';\nclass DatabaseTransactionError extends Error {\n constructor(phase: DatabaseTransactionPhase, options?: ErrorOptions) {\n super(\n phase === 'open'\n ? `Failed to open database transaction: ${getErrorMessage(options?.cause)}`\n : `Database transaction failed after opening: ${getErrorMessage(options?.cause)}`,\n options,\n );\n this.name = 'DatabaseTransactionError';\n }\n}\n"],"names":["v.parse","ErrorKind.PushFailed","ErrorOrigin.Server","ErrorReason.Parse","ErrorReason.UnsupportedPushVersion","ErrorReason.OutOfOrderMutation","ErrorReason.Database","ErrorReason.Internal","appError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"process-mutations.js","sources":["../../../../zero-server/src/process-mutations.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {getErrorDetails, getErrorMessage} from '../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport {promiseVoid} from '../../shared/src/resolved-promises.ts';\nimport type {MaybePromise} from '../../shared/src/types.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {MutationAlreadyProcessedError} from '../../zero-cache/src/services/mutagen/error.ts';\nimport type {ApplicationError} from '../../zero-protocol/src/application-error.ts';\nimport {\n isApplicationError,\n wrapWithApplicationError,\n} from '../../zero-protocol/src/application-error.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 type {PushFailedBody} from '../../zero-protocol/src/error.ts';\nimport {\n pushBodySchema,\n pushParamsSchema,\n type CustomMutation,\n type Mutation,\n type MutationID,\n type MutationResponse,\n type PushBody,\n type PushResponse,\n} from '../../zero-protocol/src/push.ts';\nimport type {AnyMutatorRegistry} from '../../zql/src/mutate/mutator-registry.ts';\nimport {isMutator} from '../../zql/src/mutate/mutator.ts';\nimport type {CustomMutatorDefs, CustomMutatorImpl} from './custom.ts';\nimport {createLogContext} from './logging.ts';\n\nexport interface TransactionProviderHooks {\n updateClientMutationID: () => Promise<{lastMutationID: number | bigint}>;\n writeMutationResult: (result: MutationResponse) => Promise<void>;\n}\n\nexport interface TransactionProviderInput {\n upstreamSchema: string;\n clientGroupID: string;\n clientID: string;\n mutationID: number;\n}\n\n/**\n * Defines the abstract interface for a database that PushProcessor can execute\n * transactions against.\n */\nexport interface Database<T> {\n transaction: <R>(\n callback: (\n tx: T,\n transactionHooks: TransactionProviderHooks,\n ) => MaybePromise<R>,\n transactionInput?: TransactionProviderInput,\n ) => Promise<R>;\n}\n\nexport type ExtractTransactionType<D> = D extends Database<infer T> ? T : never;\nexport type Params = v.Infer<typeof pushParamsSchema>;\n\nexport type TransactFn<\n D extends Database<ExtractTransactionType<D>>,\n Context,\n> = (cb: TransactFnCallback<D, Context>) => Promise<MutationResponse>;\n\nexport type TransactFnCallback<\n D extends Database<ExtractTransactionType<D>>,\n Context,\n> = (\n tx: ExtractTransactionType<D>,\n mutatorName: string,\n mutatorArgs: ReadonlyJSONValue | undefined,\n ctx: Context,\n) => Promise<void>;\n\nexport type Parsed<D extends Database<ExtractTransactionType<D>>, Context> = {\n transact: TransactFn<D, Context>;\n mutations: CustomMutation[];\n};\n\ntype MutationPhase = 'preTransaction' | 'transactionPending' | 'postCommit';\n\nconst applicationErrorWrapper = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn();\n } catch (error) {\n if (\n error instanceof DatabaseTransactionError ||\n error instanceof OutOfOrderMutation ||\n error instanceof MutationAlreadyProcessedError ||\n isApplicationError(error)\n ) {\n throw error;\n }\n\n throw wrapWithApplicationError(error);\n }\n};\n\nexport function handleMutationRequest<\n D extends Database<ExtractTransactionType<D>>,\n C = undefined,\n>(\n dbProvider: D,\n cb: (\n transact: TransactFn<D, C>,\n mutation: CustomMutation,\n ) => Promise<MutationResponse>,\n queryString: URLSearchParams | Record<string, string>,\n body: ReadonlyJSONValue,\n context?: C,\n logLevel?: LogLevel,\n): Promise<PushResponse>;\n\nexport function handleMutationRequest<\n D extends Database<ExtractTransactionType<D>>,\n C = undefined,\n>(\n dbProvider: D,\n cb: (\n transact: TransactFn<D, C>,\n mutation: CustomMutation,\n ) => Promise<MutationResponse>,\n request: Request,\n context?: C,\n logLevel?: LogLevel,\n): Promise<PushResponse>;\n\nexport async function handleMutationRequest<\n D extends Database<ExtractTransactionType<D>>,\n C,\n>(\n dbProvider: D,\n cb: (\n transact: TransactFn<D, C>,\n mutation: CustomMutation,\n ) => Promise<MutationResponse>,\n queryStringOrRequest: Request | URLSearchParams | Record<string, string>,\n bodyOrContext: ReadonlyJSONValue | C,\n contextOrLogLevel?: C | LogLevel,\n logLevel?: LogLevel,\n): Promise<PushResponse> {\n // Parse overload arguments\n const isRequestOverload = queryStringOrRequest instanceof Request;\n\n let request: Request | undefined;\n let queryString: URLSearchParams | Record<string, string>;\n let jsonBody: unknown;\n let context: C;\n let lc: LogContext;\n\n if (isRequestOverload) {\n request = queryStringOrRequest;\n context = bodyOrContext as C;\n const level = (contextOrLogLevel as LogLevel | undefined) ?? 'info';\n\n // Create log context early, before extracting JSON from Request\n lc = createLogContext(level).withContext('PushProcessor');\n\n const url = new URL(request.url);\n queryString = url.searchParams;\n\n try {\n jsonBody = await request.json();\n } catch (error) {\n lc.error?.('Failed to parse push body', error);\n const message = `Failed to parse push body: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n mutationIDs: [],\n ...(details ? {details} : {}),\n } as const satisfies PushFailedBody;\n }\n } else {\n queryString = queryStringOrRequest;\n jsonBody = bodyOrContext;\n context = contextOrLogLevel as C;\n const level = logLevel ?? 'info';\n lc = createLogContext(level).withContext('PushProcessor');\n }\n\n let mutationIDs: MutationID[] = [];\n\n let pushBody: PushBody;\n try {\n pushBody = v.parse(jsonBody, pushBodySchema);\n mutationIDs = pushBody.mutations.map(m => ({\n id: m.id,\n clientID: m.clientID,\n }));\n } catch (error) {\n lc.error?.('Failed to parse push body', error);\n const message = `Failed to parse push body: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n mutationIDs,\n ...(details ? {details} : {}),\n } as const satisfies PushFailedBody;\n }\n\n let queryParams: Params;\n try {\n const queryStringObj =\n queryString instanceof URLSearchParams\n ? Object.fromEntries(queryString)\n : queryString;\n queryParams = v.parse(queryStringObj, pushParamsSchema, 'passthrough');\n } catch (error) {\n lc.error?.('Failed to parse push query parameters', error);\n const message = `Failed to parse push query parameters: ${getErrorMessage(error)}`;\n const details = getErrorDetails(error);\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.Parse,\n message,\n mutationIDs,\n ...(details ? {details} : {}),\n } as const satisfies PushFailedBody;\n }\n\n if (pushBody.pushVersion !== 1) {\n const response = {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason: ErrorReason.UnsupportedPushVersion,\n mutationIDs,\n message: `Unsupported push version: ${pushBody.pushVersion}`,\n } as const satisfies PushFailedBody;\n return response;\n }\n\n const responses: MutationResponse[] = [];\n let processedCount = 0;\n\n try {\n const transactor = new Transactor(\n dbProvider,\n pushBody,\n queryParams,\n lc,\n context,\n );\n\n // Each mutation goes through three phases:\n // 1. Pre-transaction: user logic that runs before `transact` is called. If\n // this throws we still advance LMID and persist the failure result.\n // 2. Transaction: the callback passed to `transact`, which can be retried\n // if it fails with an ApplicationError.\n // 3. Post-commit: any logic that runs after `transact` resolves. Failures\n // here are logged but the mutation remains committed.\n for (const m of pushBody.mutations) {\n assert(m.type === 'custom', 'Expected custom mutation');\n lc.debug?.(\n `Processing mutation '${m.name}' (id=${m.id}, clientID=${m.clientID})`,\n m.args,\n );\n\n let mutationPhase: MutationPhase = 'preTransaction';\n\n const transactProxy: TransactFn<D, C> = async innerCb => {\n mutationPhase = 'transactionPending';\n const result = await transactor.transact(m, (tx, name, args) =>\n applicationErrorWrapper(() => innerCb(tx, name, args, context)),\n );\n mutationPhase = 'postCommit';\n return result;\n };\n\n try {\n const res = await applicationErrorWrapper(() => cb(transactProxy, m));\n responses.push(res);\n lc.debug?.(`Mutation '${m.name}' (id=${m.id}) completed successfully`);\n\n processedCount++;\n } catch (error) {\n if (!isApplicationError(error)) {\n throw error;\n }\n\n if (mutationPhase === 'preTransaction') {\n // Pre-transaction\n await transactor.persistPreTransactionFailure(m, error);\n } else if (mutationPhase === 'postCommit') {\n // Post-commit\n lc.error?.(\n `Post-commit mutation handler failed for mutation ${m.id} for client ${m.clientID}`,\n error,\n );\n }\n\n lc.warn?.(\n `Application error processing mutation ${m.id} for client ${m.clientID}`,\n error,\n );\n responses.push(makeAppErrorResponse(m, error));\n\n processedCount++;\n }\n }\n\n return {\n mutations: responses,\n };\n } catch (error) {\n lc.error?.('Failed to process push request', error);\n // only include mutationIDs for mutations that were not processed\n const unprocessedMutationIDs = mutationIDs.slice(processedCount);\n\n const message = getErrorMessage(error);\n const details = getErrorDetails(error);\n\n return {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.Server,\n reason:\n error instanceof OutOfOrderMutation\n ? ErrorReason.OutOfOrderMutation\n : error instanceof DatabaseTransactionError\n ? ErrorReason.Database\n : ErrorReason.Internal,\n message,\n mutationIDs: unprocessedMutationIDs,\n ...(details ? {details} : {}),\n };\n }\n}\n\nclass Transactor<D extends Database<ExtractTransactionType<D>>, Context> {\n readonly #dbProvider: D;\n readonly #req: PushBody;\n readonly #params: Params;\n readonly #lc: LogContext;\n readonly #context: Context;\n\n constructor(\n dbProvider: D,\n req: PushBody,\n params: Params,\n lc: LogContext,\n context: Context,\n ) {\n this.#dbProvider = dbProvider;\n this.#req = req;\n this.#params = params;\n this.#lc = lc;\n this.#context = context;\n }\n\n transact = async (\n mutation: CustomMutation,\n cb: TransactFnCallback<D, Context>,\n ): Promise<MutationResponse> => {\n let appError: ApplicationError | undefined = undefined;\n for (;;) {\n try {\n const ret = await this.#transactImpl(mutation, cb, appError);\n if (appError !== undefined) {\n this.#lc.warn?.(\n `Mutation ${mutation.id} for client ${mutation.clientID} was retried after an error`,\n appError,\n );\n return makeAppErrorResponse(mutation, appError);\n }\n\n return ret;\n } catch (error) {\n if (error instanceof OutOfOrderMutation) {\n this.#lc.error?.(error);\n throw error;\n }\n\n if (error instanceof MutationAlreadyProcessedError) {\n this.#lc.warn?.(error);\n return {\n id: {\n clientID: mutation.clientID,\n id: mutation.id,\n },\n result: {\n error: 'alreadyProcessed',\n details: error.message,\n },\n };\n }\n\n if (isApplicationError(error)) {\n appError = error;\n this.#lc.warn?.(\n `Application error processing mutation ${mutation.id} for client ${mutation.clientID}`,\n appError,\n );\n continue;\n }\n\n this.#lc.error?.(\n `Unexpected error processing mutation ${mutation.id} for client ${mutation.clientID}`,\n error,\n );\n\n throw error;\n }\n }\n };\n\n async persistPreTransactionFailure(\n mutation: CustomMutation,\n appError: ApplicationError<ReadonlyJSONValue | undefined>,\n ): Promise<MutationResponse> {\n // User-land code threw before calling `transact`. We still need to bump the\n // LMID for this mutation and persist the error so that the client knows it failed.\n const ret = await this.#transactImpl(\n mutation,\n // noop callback since there's no transaction to execute\n () => promiseVoid,\n appError,\n );\n return ret;\n }\n\n async #transactImpl(\n mutation: CustomMutation,\n cb: TransactFnCallback<D, Context>,\n appError: ApplicationError | undefined,\n ): Promise<MutationResponse> {\n let transactionPhase: DatabaseTransactionPhase = 'open';\n\n try {\n const ret = await this.#dbProvider.transaction(\n async (dbTx, transactionHooks) => {\n // update the transaction phase to 'execute' after the transaction is opened\n transactionPhase = 'execute';\n\n await this.#checkAndIncrementLastMutationID(\n transactionHooks,\n mutation.clientID,\n mutation.id,\n );\n\n if (appError === undefined) {\n this.#lc.debug?.(\n `Executing mutator '${mutation.name}' (id=${mutation.id})`,\n );\n try {\n await cb(dbTx, mutation.name, mutation.args[0], this.#context);\n } catch (appError) {\n throw wrapWithApplicationError(appError);\n }\n } else {\n const mutationResult = makeAppErrorResponse(mutation, appError);\n await transactionHooks.writeMutationResult(mutationResult);\n }\n\n return {\n id: {\n clientID: mutation.clientID,\n id: mutation.id,\n },\n result: {},\n };\n },\n this.#getTransactionInput(mutation),\n );\n\n return ret;\n } catch (error) {\n if (\n isApplicationError(error) ||\n error instanceof OutOfOrderMutation ||\n error instanceof MutationAlreadyProcessedError\n ) {\n throw error;\n }\n\n throw new DatabaseTransactionError(transactionPhase, {cause: error});\n }\n }\n\n #getTransactionInput(mutation: CustomMutation): TransactionProviderInput {\n return {\n upstreamSchema: this.#params.schema,\n clientGroupID: this.#req.clientGroupID,\n clientID: mutation.clientID,\n mutationID: mutation.id,\n };\n }\n\n async #checkAndIncrementLastMutationID(\n transactionHooks: TransactionProviderHooks,\n clientID: string,\n receivedMutationID: number,\n ) {\n const {lastMutationID} = await transactionHooks.updateClientMutationID();\n\n if (receivedMutationID < lastMutationID) {\n throw new MutationAlreadyProcessedError(\n clientID,\n receivedMutationID,\n lastMutationID,\n );\n } else if (receivedMutationID > lastMutationID) {\n throw new OutOfOrderMutation(\n clientID,\n receivedMutationID,\n lastMutationID,\n );\n }\n }\n}\n\nexport class OutOfOrderMutation extends Error {\n constructor(\n clientID: string,\n receivedMutationID: number,\n lastMutationID: number | bigint,\n ) {\n super(\n `Client ${clientID} sent mutation ID ${receivedMutationID} but expected ${lastMutationID}`,\n );\n }\n}\n\nfunction makeAppErrorResponse(\n m: Mutation,\n error: ApplicationError<ReadonlyJSONValue | undefined>,\n): MutationResponse {\n return {\n id: {\n clientID: m.clientID,\n id: m.id,\n },\n result: {\n error: 'app',\n message: error.message,\n ...(error.details ? {details: error.details} : {}),\n },\n };\n}\n\n/** @deprecated Use getMutator instead */\nexport function getMutation(\n // oxlint-disable-next-line no-explicit-any\n mutators: AnyMutatorRegistry | CustomMutatorDefs<any>,\n name: string,\n // oxlint-disable-next-line no-explicit-any\n): CustomMutatorImpl<any> {\n const path = name.split(/\\.|\\|/);\n const mutator = getObjectAtPath(mutators, path);\n assert(typeof mutator === 'function', `could not find mutator ${name}`);\n\n if (isMutator(mutator)) {\n // mutator needs to be called with {tx, args, ctx}\n // CustomMutatorImpl is called with (tx, args, ctx)\n return (tx, args, ctx) => mutator.fn({args, ctx, tx});\n }\n\n // oxlint-disable-next-line no-explicit-any\n return mutator as CustomMutatorImpl<any>;\n}\n\nfunction getObjectAtPath(\n obj: Record<string, unknown>,\n path: string[],\n): unknown {\n let current: unknown = obj;\n for (const part of path) {\n if (typeof current !== 'object' || current === null || !(part in current)) {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\ntype DatabaseTransactionPhase = 'open' | 'execute';\nclass DatabaseTransactionError extends Error {\n constructor(phase: DatabaseTransactionPhase, options?: ErrorOptions) {\n super(\n phase === 'open'\n ? `Failed to open database transaction: ${getErrorMessage(options?.cause)}`\n : `Database transaction failed after opening: ${getErrorMessage(options?.cause)}`,\n options,\n );\n this.name = 'DatabaseTransactionError';\n }\n}\n"],"names":["ErrorKind.PushFailed","ErrorOrigin.Server","ErrorReason.Parse","v.parse","ErrorReason.UnsupportedPushVersion","ErrorReason.OutOfOrderMutation","ErrorReason.Database","ErrorReason.Internal","appError"],"mappings":"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"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type LogLevel } from '@rocicorp/logger';
|
|
2
2
|
import type { ReadonlyJSONValue } from '../../shared/src/json.ts';
|
|
3
3
|
import { type PushResponse } from '../../zero-protocol/src/push.ts';
|
|
4
|
+
import { type Database, type ExtractTransactionType } from '../../zero-server/src/process-mutations.ts';
|
|
5
|
+
import type { Schema } from '../../zero-types/src/schema.ts';
|
|
6
|
+
import type { AnyMutatorRegistry } from '../../zql/src/mutate/mutator-registry.ts';
|
|
4
7
|
import type { CustomMutatorDefs } from './custom.ts';
|
|
5
|
-
|
|
6
|
-
export declare class PushProcessor<D extends Database<ExtractTransactionType<D>>, MD extends CustomMutatorDefs<ExtractTransactionType<D>>> {
|
|
8
|
+
export declare class PushProcessor<_S extends Schema, D extends Database<ExtractTransactionType<D>>, MD extends AnyMutatorRegistry | CustomMutatorDefs<ExtractTransactionType<D>>, C = undefined> {
|
|
7
9
|
#private;
|
|
8
|
-
constructor(dbProvider: D, logLevel?: LogLevel);
|
|
10
|
+
constructor(dbProvider: D, context?: C, logLevel?: LogLevel);
|
|
9
11
|
/**
|
|
10
12
|
* Processes a push request from zero-cache.
|
|
11
13
|
* This function will parse the request, check the protocol version, and process each mutation in the request.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push-processor.d.ts","sourceRoot":"","sources":["../../../../zero-server/src/push-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"push-processor.d.ts","sourceRoot":"","sources":["../../../../zero-server/src/push-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAGhE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAG5B,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAE3D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,0CAA0C,CAAC;AAEjF,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,aAAa,CAAC;AAEnD,qBAAa,aAAa,CACxB,EAAE,SAAS,MAAM,EACjB,CAAC,SAAS,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,EAC7C,EAAE,SAAS,kBAAkB,GAAG,iBAAiB,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,EAC5E,CAAC,GAAG,SAAS;;gBAMD,UAAU,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAE,QAAiB;IAMnE;;;;;;;;;;OAUG;IACH,OAAO,CACL,QAAQ,EAAE,EAAE,EACZ,WAAW,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrD,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,YAAY,CAAC;IAExB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;CAoD/D"}
|
|
@@ -1,57 +1,49 @@
|
|
|
1
1
|
import "@rocicorp/logger";
|
|
2
2
|
import { assert } from "../../shared/src/asserts.js";
|
|
3
|
+
import { must } from "../../shared/src/must.js";
|
|
4
|
+
import { getValueAtPath } from "../../shared/src/object-traversal.js";
|
|
3
5
|
import "../../zero-protocol/src/push.js";
|
|
4
|
-
import { splitMutatorKey } from "../../zql/src/mutate/custom.js";
|
|
5
6
|
import { handleMutationRequest } from "./process-mutations.js";
|
|
6
|
-
import {
|
|
7
|
+
import { isMutator } from "../../zql/src/mutate/mutator.js";
|
|
7
8
|
class PushProcessor {
|
|
8
9
|
#dbProvider;
|
|
9
10
|
#logLevel;
|
|
10
|
-
|
|
11
|
+
#context;
|
|
12
|
+
constructor(dbProvider, context, logLevel = "info") {
|
|
11
13
|
this.#dbProvider = dbProvider;
|
|
14
|
+
this.#context = context;
|
|
12
15
|
this.#logLevel = logLevel;
|
|
13
16
|
}
|
|
14
17
|
process(mutators, queryOrQueryString, body) {
|
|
15
18
|
if (queryOrQueryString instanceof Request) {
|
|
16
19
|
return handleMutationRequest(
|
|
17
|
-
|
|
20
|
+
this.#dbProvider,
|
|
21
|
+
(transact, mutation) => this.#processMutation(mutators, transact, mutation),
|
|
18
22
|
queryOrQueryString,
|
|
23
|
+
this.#context,
|
|
19
24
|
this.#logLevel
|
|
20
25
|
);
|
|
21
26
|
}
|
|
22
27
|
return handleMutationRequest(
|
|
28
|
+
this.#dbProvider,
|
|
23
29
|
(transact, mutation) => this.#processMutation(mutators, transact, mutation),
|
|
24
30
|
queryOrQueryString,
|
|
25
31
|
must(body),
|
|
32
|
+
this.#context,
|
|
26
33
|
this.#logLevel
|
|
27
34
|
);
|
|
28
35
|
}
|
|
29
36
|
#processMutation(mutators, transact, _mutation) {
|
|
30
37
|
return transact(
|
|
31
|
-
this.#
|
|
32
|
-
(tx, name, args) => this.#dispatchMutation(mutators, tx, name, args)
|
|
38
|
+
(tx, name, args, ctx) => this.#dispatchMutation(mutators, tx, name, args, ctx)
|
|
33
39
|
);
|
|
34
40
|
}
|
|
35
|
-
#dispatchMutation(mutators, dbTx, key, args) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
typeof mutator2 === "function",
|
|
41
|
-
() => `could not find mutator ${key}`
|
|
42
|
-
);
|
|
43
|
-
return mutator2(dbTx, args);
|
|
41
|
+
#dispatchMutation(mutators, dbTx, key, args, ctx) {
|
|
42
|
+
const mutator = getValueAtPath(mutators, key, /\.|\|/);
|
|
43
|
+
assert(typeof mutator === "function", `could not find mutator ${key}`);
|
|
44
|
+
if (isMutator(mutator)) {
|
|
45
|
+
return mutator.fn({ args, ctx, tx: dbTx });
|
|
44
46
|
}
|
|
45
|
-
const mutatorGroup = mutators[namespace];
|
|
46
|
-
assert(
|
|
47
|
-
typeof mutatorGroup === "object",
|
|
48
|
-
() => `could not find mutators for namespace ${namespace}`
|
|
49
|
-
);
|
|
50
|
-
const mutator = mutatorGroup[name];
|
|
51
|
-
assert(
|
|
52
|
-
typeof mutator === "function",
|
|
53
|
-
() => `could not find mutator ${key}`
|
|
54
|
-
);
|
|
55
47
|
return mutator(dbTx, args);
|
|
56
48
|
}
|
|
57
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push-processor.js","sources":["../../../../zero-server/src/push-processor.ts"],"sourcesContent":["import {type LogLevel} from '@rocicorp/logger';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport {\n type CustomMutation,\n type MutationResponse,\n type PushResponse,\n} from '../../zero-protocol/src/push.ts';\nimport {
|
|
1
|
+
{"version":3,"file":"push-processor.js","sources":["../../../../zero-server/src/push-processor.ts"],"sourcesContent":["import {type LogLevel} from '@rocicorp/logger';\nimport {assert} from '../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {getValueAtPath} from '../../shared/src/object-traversal.ts';\nimport {\n type CustomMutation,\n type MutationResponse,\n type PushResponse,\n} from '../../zero-protocol/src/push.ts';\nimport {\n type Database,\n type ExtractTransactionType,\n handleMutationRequest,\n type TransactFn,\n} from '../../zero-server/src/process-mutations.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {Transaction} from '../../zql/src/mutate/custom.ts';\nimport type {AnyMutatorRegistry} from '../../zql/src/mutate/mutator-registry.ts';\nimport {isMutator} from '../../zql/src/mutate/mutator.ts';\nimport type {CustomMutatorDefs} from './custom.ts';\n\nexport class PushProcessor<\n _S extends Schema,\n D extends Database<ExtractTransactionType<D>>,\n MD extends AnyMutatorRegistry | CustomMutatorDefs<ExtractTransactionType<D>>,\n C = undefined,\n> {\n readonly #dbProvider: D;\n readonly #logLevel: LogLevel;\n readonly #context: C;\n\n constructor(dbProvider: D, context?: C, logLevel: LogLevel = 'info') {\n this.#dbProvider = dbProvider;\n this.#context = context as C;\n this.#logLevel = logLevel;\n }\n\n /**\n * Processes a push request from zero-cache.\n * This function will parse the request, check the protocol version, and process each mutation in the request.\n * - If a mutation is out of order: processing will stop and an error will be returned. The zero client will retry the mutation.\n * - If a mutation has already been processed: it will be skipped and the processing will continue.\n * - If a mutation receives an application error: it will be skipped, the error will be returned to the client, and processing will continue.\n *\n * @param mutators the custom mutators for the application\n * @param queryString the query string from the request sent by zero-cache. This will include zero's postgres schema name and appID.\n * @param body the body of the request sent by zero-cache as a JSON object.\n */\n process(\n mutators: MD,\n queryString: URLSearchParams | Record<string, string>,\n body: ReadonlyJSONValue,\n ): Promise<PushResponse>;\n\n /**\n * This override gets the query string and the body from a Request object.\n *\n * @param mutators the custom mutators for the application\n * @param request A `Request` object.\n */\n process(mutators: MD, request: Request): Promise<PushResponse>;\n process(\n mutators: MD,\n queryOrQueryString: Request | URLSearchParams | Record<string, string>,\n body?: ReadonlyJSONValue,\n ): Promise<PushResponse> {\n if (queryOrQueryString instanceof Request) {\n return handleMutationRequest(\n this.#dbProvider,\n (transact, mutation) =>\n this.#processMutation(mutators, transact, mutation),\n queryOrQueryString,\n this.#context,\n this.#logLevel,\n );\n }\n return handleMutationRequest(\n this.#dbProvider,\n (transact, mutation) =>\n this.#processMutation(mutators, transact, mutation),\n queryOrQueryString,\n must(body),\n this.#context,\n this.#logLevel,\n );\n }\n\n #processMutation(\n mutators: MD,\n transact: TransactFn<D, C>,\n _mutation: CustomMutation,\n ): Promise<MutationResponse> {\n return transact((tx, name, args, ctx) =>\n this.#dispatchMutation(mutators, tx, name, args, ctx),\n );\n }\n\n #dispatchMutation(\n mutators: MD,\n dbTx: ExtractTransactionType<D>,\n key: string,\n args: ReadonlyJSONValue | undefined,\n ctx: C,\n ): Promise<void> {\n // Legacy mutators used | as a separator, new mutators use .\n const mutator = getValueAtPath(mutators, key, /\\.|\\|/);\n assert(typeof mutator === 'function', `could not find mutator ${key}`);\n if (isMutator(mutator)) {\n return mutator.fn({args, ctx, tx: dbTx as Transaction<Schema, unknown>});\n }\n return mutator(dbTx, args);\n }\n}\n"],"names":[],"mappings":";;;;;;;AAsBO,MAAM,cAKX;AAAA,EACS;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,YAAe,SAAa,WAAqB,QAAQ;AACnE,SAAK,cAAc;AACnB,SAAK,WAAW;AAChB,SAAK,YAAY;AAAA,EACnB;AAAA,EA0BA,QACE,UACA,oBACA,MACuB;AACvB,QAAI,8BAA8B,SAAS;AACzC,aAAO;AAAA,QACL,KAAK;AAAA,QACL,CAAC,UAAU,aACT,KAAK,iBAAiB,UAAU,UAAU,QAAQ;AAAA,QACpD;AAAA,QACA,KAAK;AAAA,QACL,KAAK;AAAA,MAAA;AAAA,IAET;AACA,WAAO;AAAA,MACL,KAAK;AAAA,MACL,CAAC,UAAU,aACT,KAAK,iBAAiB,UAAU,UAAU,QAAQ;AAAA,MACpD;AAAA,MACA,KAAK,IAAI;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,IAAA;AAAA,EAET;AAAA,EAEA,iBACE,UACA,UACA,WAC2B;AAC3B,WAAO;AAAA,MAAS,CAAC,IAAI,MAAM,MAAM,QAC/B,KAAK,kBAAkB,UAAU,IAAI,MAAM,MAAM,GAAG;AAAA,IAAA;AAAA,EAExD;AAAA,EAEA,kBACE,UACA,MACA,KACA,MACA,KACe;AAEf,UAAM,UAAU,eAAe,UAAU,KAAK,OAAO;AACrD,WAAO,OAAO,YAAY,YAAY,0BAA0B,GAAG,EAAE;AACrE,QAAI,UAAU,OAAO,GAAG;AACtB,aAAO,QAAQ,GAAG,EAAC,MAAM,KAAK,IAAI,MAAqC;AAAA,IACzE;AACA,WAAO,QAAQ,MAAM,IAAI;AAAA,EAC3B;AACF;"}
|
|
@@ -27,7 +27,7 @@ async function transform(cb, schema, requestOrJsonBody, lc, apiName) {
|
|
|
27
27
|
queryIDs = parsed[1].map((r) => r.id);
|
|
28
28
|
} catch (error) {
|
|
29
29
|
lc.error?.(`Failed to parse ${apiName} queries request`, error);
|
|
30
|
-
const message = `Failed to parse
|
|
30
|
+
const message = `Failed to parse ${apiName} queries request: ${getErrorMessage(error)}`;
|
|
31
31
|
const details = getErrorDetails(error);
|
|
32
32
|
return [
|
|
33
33
|
"transformFailed",
|