effect-zero 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -16
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/internal/query-result.d.ts +2 -1
- package/dist/internal/query-result.d.ts.map +1 -1
- package/dist/internal/query-result.js.map +1 -1
- package/dist/query.d.ts +12 -13
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +15 -13
- package/dist/query.js.map +1 -1
- package/dist/query.test.js +4 -2
- package/dist/query.test.js.map +1 -1
- package/dist/result.d.ts +1 -1
- package/dist/result.d.ts.map +1 -1
- package/dist/server.d.ts +35 -14
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -8
- package/dist/server.js.map +1 -1
- package/dist/snapshot.d.ts +3 -3
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +66 -8
- package/dist/snapshot.js.map +1 -1
- package/dist/types/error.d.ts +59 -0
- package/dist/types/error.d.ts.map +1 -0
- package/dist/types/error.js +58 -0
- package/dist/types/error.js.map +1 -0
- package/dist/types/push.d.ts +53 -1
- package/dist/types/push.d.ts.map +1 -1
- package/dist/types/push.js +63 -10
- package/dist/types/push.js.map +1 -1
- package/dist/types/queries.d.ts +28 -9
- package/dist/types/queries.d.ts.map +1 -1
- package/dist/types/queries.js +14 -15
- package/dist/types/queries.js.map +1 -1
- package/dist/types.test.d.ts +2 -0
- package/dist/types.test.d.ts.map +1 -0
- package/dist/types.test.js +268 -0
- package/dist/types.test.js.map +1 -0
- package/package.json +32 -26
- package/src/client.ts +3 -3
- package/src/internal/query-result.ts +2 -5
- package/src/query.ts +36 -34
- package/src/result.ts +1 -1
- package/src/server.ts +16 -14
- package/src/snapshot.ts +71 -12
- package/src/types/error.ts +71 -0
- package/src/types/push.ts +69 -10
- package/src/types/queries.ts +16 -16
package/src/query.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HumanReadable, ReadonlyJSONValue, Zero, Query as ZeroQuery, Schema as ZeroSchema } from "@rocicorp/zero";
|
|
2
|
+
import { asQueryInternals } from "@rocicorp/zero/bindings";
|
|
2
3
|
import * as Effect from "effect/Effect";
|
|
3
4
|
import * as Equal from "effect/Equal";
|
|
4
5
|
import * as Hash from "effect/Hash";
|
|
@@ -10,15 +11,8 @@ import * as QueryResult from "./internal/query-result.js";
|
|
|
10
11
|
import { prefixId } from "./internal/utils.js";
|
|
11
12
|
import { deepClone, getDefaultSnapshot, getSnapshot } from "./snapshot.js";
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type QueryArgs<
|
|
16
|
-
A extends ReadonlyJSONValue[],
|
|
17
|
-
B extends unknown[],
|
|
18
|
-
> = // biome-ignore lint/suspicious/noExplicitAny: handles "any query" case
|
|
19
|
-
| { _tag: "Encoded"; args: any extends A ? any : readonly [...A] }
|
|
20
|
-
// biome-ignore lint/suspicious/noExplicitAny: handles "any query" case
|
|
21
|
-
| { _tag: "Decoded"; args: any extends B ? any : readonly [...B] };
|
|
14
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: necessary to allow Schema.Void
|
|
15
|
+
type QueryArgs<A extends ReadonlyJSONValue | void, B> = { _tag: "Encoded"; args: A } | { _tag: "Decoded"; args: B };
|
|
22
16
|
|
|
23
17
|
export const RunQuerySymbol = Symbol.for(prefixId("RunQuery"));
|
|
24
18
|
export const QueryNameSymbol = Symbol.for(prefixId("QueryName"));
|
|
@@ -26,19 +20,20 @@ export const QueryNameSymbol = Symbol.for(prefixId("QueryName"));
|
|
|
26
20
|
export const make = <
|
|
27
21
|
N extends string,
|
|
28
22
|
// The encoded format that is sent over the wire, must conform to JSON
|
|
29
|
-
|
|
23
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: necessary to allow Schema.Void
|
|
24
|
+
A extends ReadonlyJSONValue | void,
|
|
30
25
|
// The decoded format, can be anything
|
|
31
|
-
B
|
|
32
|
-
S extends ZeroSchema,
|
|
26
|
+
B,
|
|
33
27
|
T extends keyof S["tables"] & string,
|
|
28
|
+
S extends ZeroSchema,
|
|
34
29
|
R,
|
|
35
30
|
E,
|
|
36
31
|
R1,
|
|
37
32
|
R2,
|
|
38
33
|
>(options: {
|
|
39
34
|
name: N;
|
|
40
|
-
payload: Schema.Schema<
|
|
41
|
-
query: (
|
|
35
|
+
payload: Schema.Schema<B, A, R1>;
|
|
36
|
+
query: (args: NoInfer<B>) => Effect.Effect<ZeroQuery<T, S, R>, E, R2>;
|
|
42
37
|
}) => {
|
|
43
38
|
const runQuery = Effect.fn(function* (args: QueryArgs<A, B>) {
|
|
44
39
|
const { encoded, decoded } = yield* Match.valueTags(args, {
|
|
@@ -48,14 +43,14 @@ export const make = <
|
|
|
48
43
|
Effect.map(Schema.encode(options.payload)(decoded), (encoded) => ({ encoded, decoded })),
|
|
49
44
|
});
|
|
50
45
|
|
|
51
|
-
return yield* options.query(
|
|
46
|
+
return yield* options.query(decoded).pipe(
|
|
52
47
|
Effect.map((rawQuery) => {
|
|
53
|
-
const query = rawQuery.nameAndArgs(
|
|
48
|
+
const query = asQueryInternals(rawQuery).nameAndArgs(
|
|
54
49
|
options.name,
|
|
55
50
|
// We pass the encoded `args` to the `nameAndArgs` method, as that is the wire format which
|
|
56
51
|
// is sent to the server.
|
|
57
|
-
encoded,
|
|
58
|
-
) as
|
|
52
|
+
encoded !== undefined ? [encoded] : [],
|
|
53
|
+
) as ZeroQuery<T, S, R> & Equal.Equal;
|
|
59
54
|
// Adapted from https://github.com/rocicorp/mono/blob/17171f975e61f7ec93c61569da7bda1d962ac962/packages/zero-protocol/src/query-hash.ts#L17
|
|
60
55
|
query[Hash.symbol] = () => Hash.string(`${options.name}:${JSON.stringify(encoded)}`);
|
|
61
56
|
query[Equal.symbol] = function (that) {
|
|
@@ -70,7 +65,7 @@ export const make = <
|
|
|
70
65
|
});
|
|
71
66
|
|
|
72
67
|
// We return a callable object for ease of use on the frontend.
|
|
73
|
-
return Object.assign((
|
|
68
|
+
return Object.assign((args: B) => runQuery({ _tag: "Decoded", args }), {
|
|
74
69
|
[QueryNameSymbol]: options.name,
|
|
75
70
|
// Internally, we include the full `runQuery` function for use in library's server logic.
|
|
76
71
|
[RunQuerySymbol]: runQuery,
|
|
@@ -80,13 +75,12 @@ export const make = <
|
|
|
80
75
|
// biome-ignore lint/suspicious/noExplicitAny: accept any query
|
|
81
76
|
export type MakeQueryResult<E = any, R1 = any, R2 = any> = ReturnType<
|
|
82
77
|
// biome-ignore lint/suspicious/noExplicitAny: accept any query
|
|
83
|
-
typeof make<string, any, any,
|
|
78
|
+
typeof make<string, any, any, string, ZeroSchema, Record<string, any> | undefined, E, R1, R2>
|
|
84
79
|
>;
|
|
85
80
|
|
|
86
|
-
export const stream = <
|
|
81
|
+
export const stream = <T extends keyof S["tables"] & string, S extends ZeroSchema, R>(
|
|
87
82
|
zero: Zero<S>,
|
|
88
|
-
|
|
89
|
-
query: ZeroQuery<S, T, R> | Query<S, T, R>,
|
|
83
|
+
query: ZeroQuery<T, S, R>,
|
|
90
84
|
) =>
|
|
91
85
|
Effect.gen(function* () {
|
|
92
86
|
const view = yield* Effect.acquireRelease(
|
|
@@ -97,28 +91,36 @@ export const stream = <S extends ZeroSchema, T extends keyof S["tables"] & strin
|
|
|
97
91
|
return Stream.asyncEffect<Parameters<Parameters<(typeof view)["addListener"]>[0]>>((emit) =>
|
|
98
92
|
Effect.sync(() => view.addListener((...args) => emit.single(args))),
|
|
99
93
|
).pipe(
|
|
100
|
-
Stream.mapEffect(([data, resultType]) =>
|
|
94
|
+
Stream.mapEffect(([data, resultType, error]) =>
|
|
101
95
|
Effect.sync(() => {
|
|
102
|
-
// logic here borrowed from: https://github.com/rocicorp/mono/blob/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
// logic here borrowed from: https://github.com/rocicorp/mono/blob/8e0f600fb3a9185facf60cfd4971d260b266690e/packages/zero-react/src/use-query.tsx#L543
|
|
97
|
+
const cloned = data === undefined ? data : (deepClone(data as ReadonlyJSONValue) as HumanReadable<R>);
|
|
98
|
+
return getSnapshot<R>(
|
|
99
|
+
asQueryInternals(query).format.singular,
|
|
100
|
+
cloned,
|
|
101
|
+
resultType,
|
|
102
|
+
// TODO: Look into this. It seems like this is difficult but not impossible to model in the Effect paradigm.
|
|
103
|
+
// Likely need some kind of stateful piece between the publisher and and subscriber, allowing the subscriber to swap out
|
|
104
|
+
// the publisher when this function is called.
|
|
105
|
+
() => Effect.dieMessage("retry not available in `effect-zero`"),
|
|
106
|
+
error,
|
|
107
|
+
);
|
|
106
108
|
}),
|
|
107
109
|
),
|
|
108
110
|
Stream.map(QueryResult.make),
|
|
109
111
|
);
|
|
110
112
|
}).pipe(Stream.unwrapScoped);
|
|
111
113
|
|
|
112
|
-
export const initialValue = <
|
|
113
|
-
query: ZeroQuery<
|
|
114
|
-
) => QueryResult.make<R>(getDefaultSnapshot(query.format.singular));
|
|
114
|
+
export const initialValue = <T extends keyof S["tables"] & string, S extends ZeroSchema, R>(
|
|
115
|
+
query: ZeroQuery<T, S, R>,
|
|
116
|
+
) => QueryResult.make<R>(getDefaultSnapshot(asQueryInternals(query).format.singular));
|
|
115
117
|
|
|
116
|
-
export const subscribable = <
|
|
118
|
+
export const subscribable = <T extends keyof S["tables"] & string, S extends ZeroSchema, R>(
|
|
117
119
|
zero: Zero<S>,
|
|
118
|
-
query: ZeroQuery<
|
|
120
|
+
query: ZeroQuery<T, S, R>,
|
|
119
121
|
) => {
|
|
120
122
|
return Subscribable.make({
|
|
121
|
-
get: Effect.promise(() => zero.run(query
|
|
123
|
+
get: Effect.promise(() => zero.run(query, { type: "unknown" })).pipe(
|
|
122
124
|
Effect.map((value) => ({ _tag: "Partial", value }) satisfies QueryResult.QueryResult.Partial<R>),
|
|
123
125
|
),
|
|
124
126
|
changes: stream(zero, query),
|
package/src/result.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Result as AtomResult } from "@effect-atom/atom";
|
|
2
|
-
import type { HumanReadable } from "@rocicorp/zero
|
|
2
|
+
import type { HumanReadable } from "@rocicorp/zero";
|
|
3
3
|
import * as Cause from "effect/Cause";
|
|
4
4
|
import * as Data from "effect/Data";
|
|
5
5
|
import * as Fn from "effect/Function";
|
package/src/server.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReadonlyJSONValue, Schema as ZeroSchema } from "@rocicorp/zero";
|
|
2
|
-
import {
|
|
2
|
+
import { handleQueryRequest } from "@rocicorp/zero/server";
|
|
3
3
|
import * as Arr from "effect/Array";
|
|
4
4
|
import * as Cause from "effect/Cause";
|
|
5
5
|
import * as Chunk from "effect/Chunk";
|
|
@@ -29,6 +29,10 @@ import type * as ServerTransaction from "./server-transaction.js";
|
|
|
29
29
|
import * as Types from "./types/push.js";
|
|
30
30
|
import type { TransformRequestMessage } from "./types/queries.js";
|
|
31
31
|
|
|
32
|
+
// Updated to:
|
|
33
|
+
// https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/push-processor.ts
|
|
34
|
+
// https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/process-mutations.ts
|
|
35
|
+
|
|
32
36
|
type ServerTransactionContext<TSchema extends ZeroSchema, TTransaction> = Omit<
|
|
33
37
|
ReturnType<typeof ServerTransaction.make<string, TSchema, TTransaction>>,
|
|
34
38
|
"use"
|
|
@@ -54,6 +58,7 @@ export const processPush = Effect.fn(function* <
|
|
|
54
58
|
if (mutation.type !== "custom") {
|
|
55
59
|
return yield* new CustomMutationExpectedError();
|
|
56
60
|
}
|
|
61
|
+
|
|
57
62
|
return yield* processMutation<Mutators.ExtractMutatorsRequirements<TMutators>>(mutators, mutation).pipe(
|
|
58
63
|
Effect.catchAll((e) => processMutationError(transaction, e)),
|
|
59
64
|
Effect.map((result) =>
|
|
@@ -65,6 +70,7 @@ export const processPush = Effect.fn(function* <
|
|
|
65
70
|
clientGroupID: request.clientGroupID,
|
|
66
71
|
upstreamSchema: params.schema,
|
|
67
72
|
}),
|
|
73
|
+
Effect.provide(ServerSynchronizationContext.Default),
|
|
68
74
|
);
|
|
69
75
|
}),
|
|
70
76
|
),
|
|
@@ -76,7 +82,7 @@ export const processPush = Effect.fn(function* <
|
|
|
76
82
|
);
|
|
77
83
|
|
|
78
84
|
return { mutations: Chunk.toArray(responses) } satisfies Types.PushResponse;
|
|
79
|
-
}
|
|
85
|
+
});
|
|
80
86
|
|
|
81
87
|
const processMutation = Effect.fn(function* <R>(mutators: Mutators.AnyMutators<R>, mutation: Types.Mutation) {
|
|
82
88
|
// Support both "namespace|name" and "namespace.name" formats, and single-segment names.
|
|
@@ -179,7 +185,7 @@ class MutationUserError extends Data.TaggedError("MutationUserError")<{
|
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
|
|
182
|
-
export const
|
|
188
|
+
export const handleQuery = Effect.fn(function* <E, R1, R2>(
|
|
183
189
|
queries: MakeQueryResult<E, R1, R2>[],
|
|
184
190
|
schema: ZeroSchema,
|
|
185
191
|
payload: TransformRequestMessage,
|
|
@@ -187,19 +193,15 @@ export const handleGetQueries = Effect.fn(function* <E, R1, R2>(
|
|
|
187
193
|
const runtime = yield* Effect.runtime<R1 | R2>();
|
|
188
194
|
return yield* Effect.tryPromise({
|
|
189
195
|
try: () =>
|
|
190
|
-
|
|
196
|
+
handleQueryRequest(
|
|
191
197
|
(name, args) =>
|
|
192
198
|
Arr.findFirst(queries, (q) => q[QueryNameSymbol] === name).pipe(
|
|
193
199
|
Effect.catchTag("NoSuchElementException", () => new QueryNotFound({ name })),
|
|
194
200
|
Effect.flatMap((query) => query[RunQuerySymbol]({ _tag: "Encoded", args })),
|
|
195
|
-
|
|
196
|
-
(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
Exit.getOrElse((cause) => {
|
|
200
|
-
throw new QueryUserError<E>({ cause });
|
|
201
|
-
}),
|
|
202
|
-
),
|
|
201
|
+
(effect) => Runtime.runSyncExit(runtime, effect),
|
|
202
|
+
Exit.getOrElse((cause) => {
|
|
203
|
+
throw new QueryUserError<E>({ cause });
|
|
204
|
+
}),
|
|
203
205
|
),
|
|
204
206
|
schema,
|
|
205
207
|
payload as ReadonlyJSONValue,
|
|
@@ -207,7 +209,7 @@ export const handleGetQueries = Effect.fn(function* <E, R1, R2>(
|
|
|
207
209
|
catch: (e) =>
|
|
208
210
|
Option.liftPredicate(e, QueryUserError.is<E>).pipe(
|
|
209
211
|
Option.flatMap((e) => Cause.failureOption(e.cause)),
|
|
210
|
-
Option.getOrElse(() => new
|
|
212
|
+
Option.getOrElse(() => new QueryRequestError({ cause: Cause.fail(e) })),
|
|
211
213
|
),
|
|
212
214
|
});
|
|
213
215
|
});
|
|
@@ -228,4 +230,4 @@ class QueryUserError<E> extends Data.TaggedError("QueryUserError")<{
|
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
class
|
|
233
|
+
class QueryRequestError extends Data.TaggedError("QueryRequestError")<{ cause: Cause.Cause<unknown> }> {}
|
package/src/snapshot.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
// Code borrowed from:
|
|
2
|
-
// https://github.com/rocicorp/mono/blob/
|
|
3
|
-
// https://github.com/rocicorp/mono/blob/
|
|
2
|
+
// https://github.com/rocicorp/mono/blob/8e0f600fb3a9185facf60cfd4971d260b266690e/packages/zero-react/src/use-query.tsx
|
|
3
|
+
// https://github.com/rocicorp/mono/blob/8e0f600fb3a9185facf60cfd4971d260b266690e/packages/shared/src/deep-clone.ts
|
|
4
4
|
|
|
5
|
-
import type { JSONValue, ReadonlyJSONValue } from "@rocicorp/zero";
|
|
6
|
-
import type {
|
|
5
|
+
import type { ErroredQuery, HumanReadable, JSONValue, QueryErrorDetails, ReadonlyJSONValue, ResultType } from "@rocicorp/zero";
|
|
6
|
+
import type { QueryResult } from "@rocicorp/zero/react";
|
|
7
7
|
|
|
8
8
|
const emptyArray: unknown[] = [];
|
|
9
9
|
|
|
10
10
|
const resultTypeUnknown = { type: "unknown" } as const;
|
|
11
11
|
const resultTypeComplete = { type: "complete" } as const;
|
|
12
|
+
const resultTypeError = {type: 'error'} as const;
|
|
12
13
|
|
|
13
14
|
const emptySnapshotSingularUnknown = [undefined, resultTypeUnknown] as const;
|
|
14
15
|
const emptySnapshotSingularComplete = [undefined, resultTypeComplete] as const;
|
|
16
|
+
const emptySnapshotSingularErrorUnknown = [undefined, resultTypeError] as const;
|
|
15
17
|
const emptySnapshotPluralUnknown = [emptyArray, resultTypeUnknown] as const;
|
|
16
18
|
const emptySnapshotPluralComplete = [emptyArray, resultTypeComplete] as const;
|
|
19
|
+
const emptySnapshotErrorUnknown = [emptyArray, resultTypeError] as const;
|
|
17
20
|
|
|
18
21
|
export function getDefaultSnapshot<TReturn>(singular: boolean): QueryResult<TReturn> {
|
|
19
22
|
return (singular ? emptySnapshotSingularUnknown : emptySnapshotPluralUnknown) as QueryResult<TReturn>;
|
|
@@ -26,21 +29,77 @@ export function getDefaultSnapshot<TReturn>(singular: boolean): QueryResult<TRet
|
|
|
26
29
|
export function getSnapshot<TReturn>(
|
|
27
30
|
singular: boolean,
|
|
28
31
|
data: HumanReadable<TReturn>,
|
|
29
|
-
resultType:
|
|
32
|
+
resultType: ResultType,
|
|
33
|
+
retryFn: () => void,
|
|
34
|
+
error?: ErroredQuery,
|
|
30
35
|
): QueryResult<TReturn> {
|
|
31
36
|
if (singular && data === undefined) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
switch (resultType) {
|
|
38
|
+
case 'error':
|
|
39
|
+
if (error) {
|
|
40
|
+
return [
|
|
41
|
+
undefined,
|
|
42
|
+
makeError(retryFn, error),
|
|
43
|
+
] as unknown as QueryResult<TReturn>;
|
|
44
|
+
}
|
|
45
|
+
return emptySnapshotSingularErrorUnknown as unknown as QueryResult<TReturn>;
|
|
46
|
+
case 'complete':
|
|
47
|
+
return emptySnapshotSingularComplete as unknown as QueryResult<TReturn>;
|
|
48
|
+
case 'unknown':
|
|
49
|
+
return emptySnapshotSingularUnknown as unknown as QueryResult<TReturn>;
|
|
50
|
+
}
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
if (!singular && (data as unknown[]).length === 0) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
switch (resultType) {
|
|
55
|
+
case 'error':
|
|
56
|
+
if (error) {
|
|
57
|
+
return [
|
|
58
|
+
emptyArray,
|
|
59
|
+
makeError(retryFn, error),
|
|
60
|
+
] as unknown as QueryResult<TReturn>;
|
|
61
|
+
}
|
|
62
|
+
return emptySnapshotErrorUnknown as unknown as QueryResult<TReturn>;
|
|
63
|
+
case 'complete':
|
|
64
|
+
return emptySnapshotPluralComplete as unknown as QueryResult<TReturn>;
|
|
65
|
+
case 'unknown':
|
|
66
|
+
return emptySnapshotPluralUnknown as unknown as QueryResult<TReturn>;
|
|
67
|
+
}
|
|
41
68
|
}
|
|
42
69
|
|
|
43
|
-
|
|
70
|
+
switch (resultType) {
|
|
71
|
+
case 'error':
|
|
72
|
+
if (error) {
|
|
73
|
+
return [data, makeError(retryFn, error)];
|
|
74
|
+
}
|
|
75
|
+
return [
|
|
76
|
+
data,
|
|
77
|
+
makeError(retryFn, {
|
|
78
|
+
error: 'app',
|
|
79
|
+
id: 'unknown',
|
|
80
|
+
name: 'unknown',
|
|
81
|
+
message: 'An unknown error occurred',
|
|
82
|
+
}),
|
|
83
|
+
];
|
|
84
|
+
case 'complete':
|
|
85
|
+
return [data, resultTypeComplete];
|
|
86
|
+
case 'unknown':
|
|
87
|
+
return [data, resultTypeUnknown];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function makeError(retry: () => void, error: ErroredQuery): QueryErrorDetails {
|
|
92
|
+
const message = error.message ?? 'An unknown error occurred';
|
|
93
|
+
return {
|
|
94
|
+
type: 'error',
|
|
95
|
+
retry,
|
|
96
|
+
refetch: retry,
|
|
97
|
+
error: {
|
|
98
|
+
type: error.error,
|
|
99
|
+
message,
|
|
100
|
+
...(error.details ? {details: error.details} : {}),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
44
103
|
}
|
|
45
104
|
|
|
46
105
|
export function deepClone(value: ReadonlyJSONValue): JSONValue {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { JsonSchema } from "./common.js";
|
|
3
|
+
|
|
4
|
+
// The code below was converted to Effect Schema from:
|
|
5
|
+
// https://github.com/rocicorp/mono/blob/8e0f600fb3a9185facf60cfd4971d260b266690e/packages/zero-protocol/src/error.ts
|
|
6
|
+
|
|
7
|
+
const MutationId = Schema.Struct({
|
|
8
|
+
id: Schema.Number,
|
|
9
|
+
clientID: Schema.String,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const PushFailedBase = Schema.Struct({
|
|
13
|
+
kind: Schema.Literal("PushFailed"),
|
|
14
|
+
details: Schema.optional(JsonSchema),
|
|
15
|
+
/**
|
|
16
|
+
* The mutationIDs of the mutations that failed to process.
|
|
17
|
+
* This can be a subset of the mutationIDs in the request.
|
|
18
|
+
*/
|
|
19
|
+
mutationIDs: Schema.Array(MutationId),
|
|
20
|
+
message: Schema.String,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const PushFailedBody = Schema.Union(
|
|
24
|
+
Schema.Struct({
|
|
25
|
+
...PushFailedBase.fields,
|
|
26
|
+
origin: Schema.Literal("server"),
|
|
27
|
+
reason: Schema.Literal("database", "parse", "oooMutation", "unsupportedPushVersion", "internal"),
|
|
28
|
+
}),
|
|
29
|
+
Schema.Struct({
|
|
30
|
+
...PushFailedBase.fields,
|
|
31
|
+
origin: Schema.Literal("zeroCache"),
|
|
32
|
+
reason: Schema.Literal("http"),
|
|
33
|
+
status: Schema.Number,
|
|
34
|
+
bodyPreview: Schema.optional(Schema.String),
|
|
35
|
+
}),
|
|
36
|
+
Schema.Struct({
|
|
37
|
+
...PushFailedBase.fields,
|
|
38
|
+
origin: Schema.Literal("zeroCache"),
|
|
39
|
+
reason: Schema.Literal("timeout", "parse", "internal"),
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const TransformFailedBase = Schema.Struct({
|
|
44
|
+
kind: Schema.Literal("TransformFailed"),
|
|
45
|
+
details: Schema.optional(JsonSchema),
|
|
46
|
+
/**
|
|
47
|
+
* The queryIDs of the queries that failed to transform.
|
|
48
|
+
*/
|
|
49
|
+
queryIDs: Schema.Array(Schema.String),
|
|
50
|
+
message: Schema.String,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const TransformFailedBody = Schema.Union(
|
|
54
|
+
Schema.Struct({
|
|
55
|
+
...TransformFailedBase.fields,
|
|
56
|
+
origin: Schema.Literal("server"),
|
|
57
|
+
reason: Schema.Literal("database", "parse", "internal"),
|
|
58
|
+
}),
|
|
59
|
+
Schema.Struct({
|
|
60
|
+
...TransformFailedBase.fields,
|
|
61
|
+
origin: Schema.Literal("zeroCache"),
|
|
62
|
+
reason: Schema.Literal("http"),
|
|
63
|
+
status: Schema.Number,
|
|
64
|
+
bodyPreview: Schema.optional(Schema.String),
|
|
65
|
+
}),
|
|
66
|
+
Schema.Struct({
|
|
67
|
+
...TransformFailedBase.fields,
|
|
68
|
+
origin: Schema.Literal("zeroCache"),
|
|
69
|
+
reason: Schema.Literal("timeout", "parse", "internal"),
|
|
70
|
+
}),
|
|
71
|
+
);
|
package/src/types/push.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
2
|
import { JsonSchema } from "./common.js";
|
|
3
|
+
import { PushFailedBody } from "./error.js";
|
|
3
4
|
|
|
4
5
|
// The code below was converted to Effect Schema from:
|
|
5
|
-
// https://github.com/rocicorp/mono/blob/
|
|
6
|
+
// https://github.com/rocicorp/mono/blob/8e0f600fb3a9185facf60cfd4971d260b266690e/packages/zero-protocol/src/push.ts
|
|
6
7
|
|
|
7
8
|
const PrimaryKey = Schema.Array(Schema.String);
|
|
8
9
|
|
|
@@ -15,6 +16,32 @@ const Row = Schema.Record({ key: Schema.String, value: Value });
|
|
|
15
16
|
|
|
16
17
|
const CRUD_MUTATION_NAME = "_zero_crud";
|
|
17
18
|
|
|
19
|
+
// biome-ignore lint/correctness/noUnusedVariables: borrowed code
|
|
20
|
+
const CLEANUP_RESULTS_MUTATION_NAME = "_zero_cleanupResults";
|
|
21
|
+
|
|
22
|
+
// biome-ignore lint/correctness/noUnusedVariables: borrowed code
|
|
23
|
+
const CleanupResultsArg = Schema.Union(
|
|
24
|
+
// Legacy format (no type field) - treat as single
|
|
25
|
+
Schema.Struct({
|
|
26
|
+
clientGroupID: Schema.String,
|
|
27
|
+
clientID: Schema.String,
|
|
28
|
+
upToMutationID: Schema.Number,
|
|
29
|
+
}),
|
|
30
|
+
// Explicit single: delete up to a specific mutation ID for one client
|
|
31
|
+
Schema.Struct({
|
|
32
|
+
type: Schema.Literal("single"),
|
|
33
|
+
clientGroupID: Schema.String,
|
|
34
|
+
clientID: Schema.String,
|
|
35
|
+
upToMutationID: Schema.Number,
|
|
36
|
+
}),
|
|
37
|
+
// Bulk: delete all mutations for multiple clients
|
|
38
|
+
Schema.Struct({
|
|
39
|
+
type: Schema.Literal("bulk"),
|
|
40
|
+
clientGroupID: Schema.String,
|
|
41
|
+
clientIDs: Schema.NonEmptyArray(Schema.String),
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
|
|
18
45
|
/**
|
|
19
46
|
* Inserts if entity with id does not already exist.
|
|
20
47
|
*/
|
|
@@ -97,6 +124,11 @@ export const PushBody = Schema.Struct({
|
|
|
97
124
|
schemaVersion: Schema.optional(Schema.Number),
|
|
98
125
|
timestamp: Schema.Number,
|
|
99
126
|
requestID: Schema.String,
|
|
127
|
+
/**
|
|
128
|
+
* @deprecated auth is managed at client-group scope via connect/updateAuth
|
|
129
|
+
* and should not be included in push messages.
|
|
130
|
+
*/
|
|
131
|
+
auth: Schema.optional(Schema.String),
|
|
100
132
|
});
|
|
101
133
|
export type PushBody = typeof PushBody.Type;
|
|
102
134
|
|
|
@@ -111,13 +143,18 @@ const MutationId = Schema.Struct({
|
|
|
111
143
|
export const AppError = Schema.Struct({
|
|
112
144
|
error: Schema.Literal("app"),
|
|
113
145
|
// The user can return any additional data here
|
|
146
|
+
message: Schema.optional(Schema.String),
|
|
114
147
|
details: Schema.optional(JsonSchema),
|
|
115
148
|
});
|
|
116
149
|
|
|
117
150
|
export type AppError = typeof AppError.Type;
|
|
118
151
|
|
|
119
152
|
export const ZeroError = Schema.Struct({
|
|
120
|
-
error: Schema.Union(
|
|
153
|
+
error: Schema.Union(
|
|
154
|
+
/** @deprecated push oooMutation errors are now represented as ['error', { ... }] messages */
|
|
155
|
+
Schema.Literal("oooMutation"),
|
|
156
|
+
Schema.Literal("alreadyProcessed"),
|
|
157
|
+
),
|
|
121
158
|
details: Schema.optional(JsonSchema),
|
|
122
159
|
});
|
|
123
160
|
|
|
@@ -147,42 +184,64 @@ const PushOk = Schema.Struct({
|
|
|
147
184
|
mutations: Schema.Array(MutationResponse),
|
|
148
185
|
});
|
|
149
186
|
|
|
187
|
+
/**
|
|
188
|
+
* @deprecated push errors are now represented as ['error', { ... }] messages
|
|
189
|
+
*/
|
|
150
190
|
const UnsupportedPushVersion = Schema.Struct({
|
|
191
|
+
/** @deprecated */
|
|
151
192
|
error: Schema.Literal("unsupportedPushVersion"),
|
|
152
|
-
|
|
153
|
-
// This field is included so the client knows which mutations
|
|
154
|
-
// were not processed by the server.
|
|
193
|
+
/** @deprecated */
|
|
155
194
|
mutationIDs: Schema.optional(Schema.Array(MutationId)),
|
|
156
195
|
});
|
|
157
196
|
|
|
197
|
+
/**
|
|
198
|
+
* @deprecated push errors are now represented as ['error', { ... }] messages
|
|
199
|
+
*/
|
|
158
200
|
const UnsupportedSchemaVersion = Schema.Struct({
|
|
201
|
+
/** @deprecated */
|
|
159
202
|
error: Schema.Literal("unsupportedSchemaVersion"),
|
|
160
|
-
|
|
161
|
-
// This field is included so the client knows which mutations
|
|
162
|
-
// were not processed by the server.
|
|
203
|
+
/** @deprecated */
|
|
163
204
|
mutationIDs: Schema.optional(Schema.Array(MutationId)),
|
|
164
205
|
});
|
|
165
206
|
|
|
207
|
+
/**
|
|
208
|
+
* @deprecated push http errors are now represented as ['error', { ... }] messages
|
|
209
|
+
*/
|
|
166
210
|
const HttpError = Schema.Struct({
|
|
211
|
+
/** @deprecated */
|
|
167
212
|
error: Schema.Literal("http"),
|
|
213
|
+
/** @deprecated */
|
|
168
214
|
status: Schema.Number,
|
|
215
|
+
/** @deprecated */
|
|
169
216
|
details: Schema.String,
|
|
217
|
+
/** @deprecated */
|
|
170
218
|
mutationIDs: Schema.optional(Schema.Array(MutationId)),
|
|
171
219
|
});
|
|
172
220
|
|
|
221
|
+
/**
|
|
222
|
+
* @deprecated push zero errors are now represented as ['error', { ... }] messages
|
|
223
|
+
*/
|
|
173
224
|
const ZeroPusherError = Schema.Struct({
|
|
225
|
+
/** @deprecated */
|
|
174
226
|
error: Schema.Literal("zeroPusher"),
|
|
227
|
+
/** @deprecated */
|
|
175
228
|
details: Schema.String,
|
|
229
|
+
/** @deprecated */
|
|
176
230
|
mutationIDs: Schema.optional(Schema.Array(MutationId)),
|
|
177
231
|
});
|
|
178
232
|
|
|
233
|
+
/**
|
|
234
|
+
* @deprecated push errors are now represented as ['error', { ... }] messages
|
|
235
|
+
*/
|
|
179
236
|
const PushError = Schema.Union(UnsupportedPushVersion, UnsupportedSchemaVersion, HttpError, ZeroPusherError);
|
|
180
237
|
|
|
181
|
-
|
|
238
|
+
const PushResponseBody = Schema.Union(PushOk, PushError);
|
|
239
|
+
|
|
240
|
+
export const PushResponse = Schema.Union(PushResponseBody, PushFailedBody);
|
|
182
241
|
export type PushResponse = typeof PushResponse.Type;
|
|
183
242
|
|
|
184
243
|
// biome-ignore lint/correctness/noUnusedVariables: borrowed code
|
|
185
|
-
const PushResponseMessage = Schema.Tuple(Schema.Literal("pushResponse"),
|
|
244
|
+
const PushResponseMessage = Schema.Tuple(Schema.Literal("pushResponse"), PushResponseBody);
|
|
186
245
|
|
|
187
246
|
// biome-ignore lint/correctness/noUnusedVariables: borrowed code
|
|
188
247
|
const AckMutationResponsesMessage = Schema.Tuple(Schema.Literal("ackMutationResponses"), MutationId);
|
package/src/types/queries.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
2
|
import { JsonSchema } from "./common.js";
|
|
3
|
+
import { TransformFailedBody } from "./error.js";
|
|
3
4
|
|
|
4
5
|
// The code below was converted to Effect Schema from:
|
|
5
|
-
// https://github.com/rocicorp/mono/blob/
|
|
6
|
+
// https://github.com/rocicorp/mono/blob/8e0f600fb3a9185facf60cfd4971d260b266690e/packages/zero-protocol/src/custom-queries.ts
|
|
6
7
|
|
|
7
8
|
/** TODO: Defined as unknown for now */
|
|
8
9
|
const Ast = Schema.Unknown;
|
|
@@ -21,29 +22,24 @@ const TransformedQuery = Schema.Struct({
|
|
|
21
22
|
ast: Ast,
|
|
22
23
|
});
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
+
const AppErroredQuery = Schema.Struct({
|
|
25
26
|
error: Schema.Literal("app"),
|
|
26
27
|
id: Schema.String,
|
|
27
28
|
name: Schema.String,
|
|
28
|
-
|
|
29
|
+
// optional for backwards compatibility
|
|
30
|
+
message: Schema.optional(Schema.String),
|
|
31
|
+
details: Schema.optional(JsonSchema),
|
|
29
32
|
});
|
|
30
33
|
|
|
31
|
-
const
|
|
32
|
-
error: Schema.Literal("
|
|
34
|
+
const ParseErroredQuery = Schema.Struct({
|
|
35
|
+
error: Schema.Literal("parse"),
|
|
33
36
|
id: Schema.String,
|
|
34
37
|
name: Schema.String,
|
|
35
|
-
|
|
38
|
+
message: Schema.String,
|
|
39
|
+
details: Schema.optional(JsonSchema),
|
|
36
40
|
});
|
|
37
41
|
|
|
38
|
-
const
|
|
39
|
-
error: Schema.Literal("http"),
|
|
40
|
-
id: Schema.String,
|
|
41
|
-
name: Schema.String,
|
|
42
|
-
status: Schema.Number,
|
|
43
|
-
details: JsonSchema,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const ErroredQuery = Schema.Union(AppQueryError, HttpQueryError, ErrorSchema);
|
|
42
|
+
const ErroredQuery = Schema.Union(AppErroredQuery, ParseErroredQuery);
|
|
47
43
|
|
|
48
44
|
const TransformResponseBody = Schema.Array(Schema.Union(TransformedQuery, ErroredQuery));
|
|
49
45
|
|
|
@@ -53,4 +49,8 @@ export type TransformRequestMessage = typeof TransformRequestMessage.Type;
|
|
|
53
49
|
// biome-ignore lint/correctness/noUnusedVariables: borrowed code
|
|
54
50
|
const TransformErrorMessage = Schema.Tuple(Schema.Literal("transformError"), Schema.Array(ErroredQuery));
|
|
55
51
|
|
|
56
|
-
|
|
52
|
+
const TransformFailedMessage = Schema.Tuple(Schema.Literal("transformFailed"), TransformFailedBody);
|
|
53
|
+
|
|
54
|
+
const TransformOkMessage = Schema.Tuple(Schema.Literal("transformed"), TransformResponseBody);
|
|
55
|
+
|
|
56
|
+
export const TransformResponseMessage = Schema.Union(TransformOkMessage, TransformFailedMessage);
|