effect-zero 0.1.0 → 0.2.0

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.
Files changed (66) hide show
  1. package/README.md +138 -95
  2. package/dist/client-transaction.d.ts +22 -0
  3. package/dist/client-transaction.d.ts.map +1 -0
  4. package/dist/client-transaction.js +15 -0
  5. package/dist/client-transaction.js.map +1 -0
  6. package/dist/client.d.ts +10 -38
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +23 -50
  9. package/dist/client.js.map +1 -1
  10. package/dist/internal/server.d.ts +54 -0
  11. package/dist/internal/server.d.ts.map +1 -0
  12. package/dist/internal/server.js +64 -0
  13. package/dist/internal/server.js.map +1 -0
  14. package/dist/internal/utils.d.ts.map +1 -0
  15. package/dist/internal/utils.js.map +1 -0
  16. package/dist/mutators.d.ts +18 -23
  17. package/dist/mutators.d.ts.map +1 -1
  18. package/dist/mutators.js +9 -18
  19. package/dist/mutators.js.map +1 -1
  20. package/dist/query.d.ts +30 -0
  21. package/dist/query.d.ts.map +1 -0
  22. package/dist/query.js +54 -0
  23. package/dist/query.js.map +1 -0
  24. package/dist/server-transaction.d.ts +42 -0
  25. package/dist/server-transaction.d.ts.map +1 -0
  26. package/dist/server-transaction.js +88 -0
  27. package/dist/server-transaction.js.map +1 -0
  28. package/dist/server.d.ts +111 -168
  29. package/dist/server.d.ts.map +1 -1
  30. package/dist/server.js +80 -201
  31. package/dist/server.js.map +1 -1
  32. package/dist/snapshot.d.ts.map +1 -1
  33. package/dist/snapshot.js +3 -1
  34. package/dist/snapshot.js.map +1 -1
  35. package/dist/types/common.d.ts +4 -0
  36. package/dist/types/common.d.ts.map +1 -0
  37. package/dist/types/common.js +4 -0
  38. package/dist/types/common.js.map +1 -0
  39. package/dist/{types.d.ts → types/push.d.ts} +15 -15
  40. package/dist/types/push.d.ts.map +1 -0
  41. package/dist/{types.js → types/push.js} +54 -54
  42. package/dist/types/push.js.map +1 -0
  43. package/dist/types/queries.d.ts +29 -0
  44. package/dist/types/queries.d.ts.map +1 -0
  45. package/dist/types/queries.js +42 -0
  46. package/dist/types/queries.js.map +1 -0
  47. package/package.json +46 -21
  48. package/src/client-transaction.ts +30 -0
  49. package/src/client.ts +66 -116
  50. package/src/internal/server.ts +90 -0
  51. package/src/mutators.ts +42 -44
  52. package/src/query.ts +117 -0
  53. package/src/server-transaction.ts +141 -0
  54. package/src/server.ts +174 -320
  55. package/src/snapshot.ts +0 -2
  56. package/src/types/common.ts +4 -0
  57. package/src/types/push.ts +197 -0
  58. package/src/types/queries.ts +56 -0
  59. package/dist/types.d.ts.map +0 -1
  60. package/dist/types.js.map +0 -1
  61. package/dist/utils.d.ts.map +0 -1
  62. package/dist/utils.js.map +0 -1
  63. package/src/types.ts +0 -201
  64. /package/dist/{utils.d.ts → internal/utils.d.ts} +0 -0
  65. /package/dist/{utils.js → internal/utils.js} +0 -0
  66. /package/src/{utils.ts → internal/utils.ts} +0 -0
package/dist/server.d.ts CHANGED
@@ -1,188 +1,131 @@
1
- import type { Database, TransactionProviderHooks, TransactionProviderInput } from "@rocicorp/zero/pg";
1
+ import type { ReadonlyJSONValue, Schema as ZeroSchema } from "@rocicorp/zero";
2
2
  import * as Cause from "effect/Cause";
3
- import * as Context from "effect/Context";
4
3
  import * as Effect from "effect/Effect";
5
- import * as SynchronizedRef from "effect/SynchronizedRef";
6
- import { type AnyMutators, type ExtractMutatorsRequirements } from "./mutators.js";
7
- export interface ZeroServerTransactionContext {
8
- readonly _tag: unique symbol;
9
- }
10
- export declare const makeServer: <T, I = never>(options: {
11
- database: Database<T>;
12
- clientTransaction?: Context.Tag<I, T>;
13
- }) => {
14
- Transaction: Context.TagClass<ZeroServerTransactionContext, "effect-zero/ZeroServerTransactionContext", {
15
- transaction: T;
16
- transactionHooks: TransactionProviderHooks;
17
- }> & {
18
- execute: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect.AsEffect<Effect.Effect<A, ZeroDatabaseError | UpdateClientMutationIDError | MultipleTransactionsError | OutOfOrderMutationError | MutationAlreadyProcessedError | E, ZeroTransactionInput | ZeroServerMutationContext | Exclude<Exclude<R, ZeroServerTransactionContext>, I>>>;
19
- use: <A>(fn: (transaction: T, options: {
20
- readonly signal: AbortSignal;
21
- }) => PromiseLike<A>) => Effect.Effect<A, unknown, ZeroServerTransactionContext>;
22
- };
23
- processPush: <T_1 extends AnyMutators>(mutators: T_1, params: {
24
- readonly schema: string;
25
- readonly appID: string;
26
- }, request: {
4
+ import type * as ParseResult from "effect/ParseResult";
5
+ import { ServerSynchronizationContext, ServerTransactionInput } from "./internal/server.js";
6
+ import * as Mutators from "./mutators.js";
7
+ import { type MakeQueryResult } from "./query.js";
8
+ import type * as ServerTransaction from "./server-transaction.js";
9
+ type ServerTransactionContext<TSchema extends ZeroSchema, TTransaction> = Omit<ReturnType<typeof ServerTransaction.make<string, TSchema, TTransaction>>, "use">;
10
+ export declare const processPush: <TSchema extends ZeroSchema, TTransaction, TMutators extends Mutators.AnyMutators>(transaction: ServerTransactionContext<TSchema, TTransaction>, mutators: TMutators, params: {
11
+ readonly schema: string;
12
+ readonly appID: string;
13
+ }, request: {
14
+ readonly timestamp: number;
15
+ readonly clientGroupID: string;
16
+ readonly mutations: readonly ({
17
+ readonly name: "_zero_crud";
18
+ readonly type: "crud";
19
+ readonly id: number;
20
+ readonly args: readonly [{
21
+ readonly ops: readonly ({
22
+ readonly value: {
23
+ readonly [x: string]: unknown;
24
+ };
25
+ readonly primaryKey: readonly string[];
26
+ readonly op: "insert";
27
+ readonly tableName: string;
28
+ } | {
29
+ readonly value: {
30
+ readonly [x: string]: unknown;
31
+ };
32
+ readonly primaryKey: readonly string[];
33
+ readonly op: "upsert";
34
+ readonly tableName: string;
35
+ } | {
36
+ readonly value: {
37
+ readonly [x: string]: unknown;
38
+ };
39
+ readonly primaryKey: readonly string[];
40
+ readonly op: "update";
41
+ readonly tableName: string;
42
+ } | {
43
+ readonly value: {
44
+ readonly [x: string]: string | number | boolean;
45
+ };
46
+ readonly primaryKey: readonly string[];
47
+ readonly op: "delete";
48
+ readonly tableName: string;
49
+ })[];
50
+ }];
51
+ readonly clientID: string;
27
52
  readonly timestamp: number;
28
- readonly clientGroupID: string;
29
- readonly mutations: readonly ({
30
- readonly name: "_zero_crud";
31
- readonly type: "crud";
53
+ } | {
54
+ readonly name: string;
55
+ readonly type: "custom";
56
+ readonly id: number;
57
+ readonly args: readonly unknown[];
58
+ readonly clientID: string;
59
+ readonly timestamp: number;
60
+ })[];
61
+ readonly pushVersion: number;
62
+ readonly schemaVersion?: number | undefined;
63
+ readonly requestID: string;
64
+ }) => Effect.Effect<{
65
+ error: "unsupportedPushVersion";
66
+ mutations?: undefined;
67
+ } | {
68
+ mutations: {
69
+ readonly id: {
32
70
  readonly id: number;
33
71
  readonly clientID: string;
34
- readonly args: readonly [{
35
- readonly ops: readonly ({
36
- readonly value: {
37
- readonly [x: string]: unknown;
38
- };
39
- readonly primaryKey: readonly string[];
40
- readonly op: "insert";
41
- readonly tableName: string;
42
- } | {
43
- readonly value: {
44
- readonly [x: string]: unknown;
45
- };
46
- readonly primaryKey: readonly string[];
47
- readonly op: "upsert";
48
- readonly tableName: string;
49
- } | {
50
- readonly value: {
51
- readonly [x: string]: unknown;
52
- };
53
- readonly primaryKey: readonly string[];
54
- readonly op: "update";
55
- readonly tableName: string;
56
- } | {
57
- readonly value: {
58
- readonly [x: string]: string | number | boolean;
59
- };
60
- readonly primaryKey: readonly string[];
61
- readonly op: "delete";
62
- readonly tableName: string;
63
- })[];
64
- }];
65
- readonly timestamp: number;
72
+ };
73
+ readonly result: {
74
+ readonly error: "app";
75
+ readonly details?: unknown;
66
76
  } | {
67
- readonly name: string;
68
- readonly type: "custom";
69
- readonly id: number;
70
- readonly clientID: string;
71
- readonly args: readonly unknown[];
72
- readonly timestamp: number;
73
- })[];
74
- readonly pushVersion: number;
75
- readonly schemaVersion?: number | undefined;
76
- readonly requestID: string;
77
- }) => Effect.Effect<{
78
- error: "unsupportedPushVersion";
79
- mutations?: undefined;
80
- } | {
81
- mutations: {
82
- readonly id: {
83
- readonly id: number;
84
- readonly clientID: string;
85
- };
86
- readonly result: {
87
- readonly error: "app";
88
- readonly details?: unknown;
89
- } | {
90
- readonly error: "oooMutation" | "alreadyProcessed";
91
- readonly details?: unknown;
92
- } | {
93
- readonly data?: unknown;
94
- };
95
- }[];
96
- error?: undefined;
97
- }, CustomMutationExpectedError, Exclude<Exclude<ExtractMutatorsRequirements<T_1>, ZeroServerMutationContext>, ZeroTransactionInput>>;
98
- };
99
- declare const ZeroTransactionInput_base: Context.TagClass<ZeroTransactionInput, "effect-zero/ZeroTransactionInput", TransactionProviderInput>;
100
- declare class ZeroTransactionInput extends ZeroTransactionInput_base {
101
- }
102
- declare const ZeroServerMutationContext_base: Effect.Service.Class<ZeroServerMutationContext, "effect-zero/ZeroServerMutationContext", {
103
- readonly effect: Effect.Effect<{
104
- wasTransactionExecuted: SynchronizedRef.SynchronizedRef<boolean>;
105
- }, never, never>;
106
- }>;
107
- declare class ZeroServerMutationContext extends ZeroServerMutationContext_base {
108
- static wasTransactionExecuted: Effect.Effect<boolean, never, ZeroServerMutationContext>;
109
- static guard: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | MultipleTransactionsError, ZeroServerMutationContext | R>;
110
- }
111
- declare const ZeroDatabaseError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
112
- readonly _tag: "ZeroDatabaseError";
113
- } & Readonly<A>;
114
- declare class ZeroDatabaseError extends ZeroDatabaseError_base<{
115
- cause: Cause.Cause<unknown>;
116
- }> {
117
- }
118
- declare const UpdateClientMutationIDError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
119
- readonly _tag: "UpdateClientMutationIDError";
120
- } & Readonly<A>;
121
- declare class UpdateClientMutationIDError extends UpdateClientMutationIDError_base<{
122
- readonly cause: Cause.Cause<unknown>;
123
- }> {
124
- }
77
+ readonly error: "oooMutation" | "alreadyProcessed";
78
+ readonly details?: unknown;
79
+ } | {
80
+ readonly data?: unknown;
81
+ };
82
+ }[];
83
+ error?: undefined;
84
+ }, CustomMutationExpectedError, Exclude<Exclude<Mutators.ExtractMutatorsRequirements<TMutators>, ServerTransactionInput>, ServerSynchronizationContext>>;
125
85
  declare const CustomMutationExpectedError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
126
86
  readonly _tag: "CustomMutationExpectedError";
127
87
  } & Readonly<A>;
128
- declare class CustomMutationExpectedError extends CustomMutationExpectedError_base<object> {
129
- }
130
- declare const MultipleTransactionsError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
131
- readonly _tag: "MultipleTransactionsError";
132
- } & Readonly<A>;
133
- export declare class MultipleTransactionsError extends MultipleTransactionsError_base {
134
- message: string;
135
- }
136
- declare const NoTransactionError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
137
- readonly _tag: "NoTransactionError";
138
- } & Readonly<A>;
139
- export declare class NoTransactionError extends NoTransactionError_base {
140
- message: string;
141
- }
142
- declare const OutOfOrderMutationErrorTypeId: unique symbol;
143
- declare const OutOfOrderMutationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
144
- readonly _tag: "OutOfOrderMutationError";
145
- } & Readonly<A>;
146
- export declare class OutOfOrderMutationError extends OutOfOrderMutationError_base<{
147
- readonly clientID: string;
148
- readonly receivedMutationID: number;
149
- readonly lastMutationID: number | bigint;
150
- }> {
151
- readonly [OutOfOrderMutationErrorTypeId]: symbol;
152
- get message(): string;
153
- static is(e: unknown): e is OutOfOrderMutationError;
88
+ declare class CustomMutationExpectedError extends CustomMutationExpectedError_base {
154
89
  }
155
- declare const MutationAlreadyProcessedErrorTypeId: unique symbol;
156
- declare const MutationAlreadyProcessedError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
157
- readonly _tag: "MutationAlreadyProcessedError";
90
+ export declare const handleGetQueries: <E, R1, R2>(queries: MakeQueryResult<E, R1, R2>[], schema: ZeroSchema, payload: readonly ["transform", readonly {
91
+ readonly name: string;
92
+ readonly id: string;
93
+ readonly args: readonly unknown[];
94
+ }[]]) => Effect.Effect<["transformed", ({
95
+ id: string;
96
+ name: string;
97
+ ast: import("@rocicorp/zero").AST;
98
+ } | {
99
+ error: "app";
100
+ id: string;
101
+ name: string;
102
+ details: ReadonlyJSONValue;
103
+ } | {
104
+ error: "http";
105
+ id: string;
106
+ name: string;
107
+ status: number;
108
+ details: ReadonlyJSONValue;
109
+ } | {
110
+ error: "zero";
111
+ id: string;
112
+ name: string;
113
+ details: ReadonlyJSONValue;
114
+ })[]], ParseResult.ParseError | E | QueryNotFound | GetQueriesError, R1 | R2>;
115
+ declare const QueryNotFound_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
116
+ readonly _tag: "QueryNotFound";
158
117
  } & Readonly<A>;
159
- export declare class MutationAlreadyProcessedError extends MutationAlreadyProcessedError_base<{
160
- readonly clientID: string;
161
- readonly received: number;
162
- readonly actual: number | bigint;
118
+ declare class QueryNotFound extends QueryNotFound_base<{
119
+ name: string;
163
120
  }> {
164
- readonly [MutationAlreadyProcessedErrorTypeId]: symbol;
165
121
  get message(): string;
166
- static is(e: unknown): e is MutationAlreadyProcessedError;
167
- }
168
- declare const ZeroTransactionUserErrorTypeId: unique symbol;
169
- declare const ZeroTransactionUserError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
170
- readonly _tag: "ZeroTransactionUserError";
171
- } & Readonly<A>;
172
- export declare class ZeroTransactionUserError extends ZeroTransactionUserError_base {
173
- readonly [ZeroTransactionUserErrorTypeId]: symbol;
174
- static is(e: unknown): e is ZeroTransactionUserError;
175
122
  }
176
- declare const ZeroMutationUserErrorTypeId: unique symbol;
177
- declare const ZeroMutationUserError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
178
- readonly _tag: "ZeroMutationUserError";
123
+ declare const GetQueriesError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Cause.YieldableError & {
124
+ readonly _tag: "GetQueriesError";
179
125
  } & Readonly<A>;
180
- export declare class ZeroMutationUserError extends ZeroMutationUserError_base<{
126
+ declare class GetQueriesError extends GetQueriesError_base<{
181
127
  cause: Cause.Cause<unknown>;
182
128
  }> {
183
- readonly [ZeroMutationUserErrorTypeId]: symbol;
184
- static is(e: unknown): e is ZeroMutationUserError;
185
- get message(): string;
186
129
  }
187
130
  export {};
188
131
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG9E,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAKxC,OAAO,KAAK,KAAK,WAAW,MAAM,oBAAoB,CAAC;AAOvD,OAAO,EAGL,4BAA4B,EAC5B,sBAAsB,EACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,KAAK,eAAe,EAAmC,MAAM,YAAY,CAAC;AACnF,OAAO,KAAK,KAAK,iBAAiB,MAAM,yBAAyB,CAAC;AAIlE,KAAK,wBAAwB,CAAC,OAAO,SAAS,UAAU,EAAE,YAAY,IAAI,IAAI,CAC5E,UAAU,CAAC,OAAO,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,EACxE,KAAK,CACN,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,OAAO,SAAS,UAAU,EAC1B,YAAY,EACZ,SAAS,SAAS,QAAQ,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wJAuCgB,CAAC;;;;AA+EzD,cAAM,2BAA4B,SAAQ,gCAA+C;CAAG;AAwB5F,eAAO,MAAM,gBAAgB,GAAwB,CAAC,EAAE,EAAE,EAAE,EAAE;;;;;;;;;;;;;;;;;;;;;;;;6EA+B5D,CAAC;;;;AAEH,cAAM,aAAc,SAAQ,mBAAkC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;IAC7E,IAAa,OAAO,WAEnB;CACF;;;;AAYD,cAAM,eAAgB,SAAQ,qBAAoC;IAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;CAAG"}
package/dist/server.js CHANGED
@@ -1,8 +1,8 @@
1
+ import { handleGetQueriesRequest } from "@rocicorp/zero/server";
2
+ import * as Arr from "effect/Array";
1
3
  import * as Cause from "effect/Cause";
2
4
  import * as Chunk from "effect/Chunk";
3
- import * as Context from "effect/Context";
4
5
  import * as Data from "effect/Data";
5
- import * as Deferred from "effect/Deferred";
6
6
  import * as Effect from "effect/Effect";
7
7
  import * as Exit from "effect/Exit";
8
8
  import * as Fn from "effect/Function";
@@ -14,222 +14,101 @@ import * as Runtime from "effect/Runtime";
14
14
  import * as Schema from "effect/Schema";
15
15
  import * as Stream from "effect/Stream";
16
16
  import * as Str from "effect/String";
17
- import * as SynchronizedRef from "effect/SynchronizedRef";
18
- import { MutatorArgsSchemaSym } from "./mutators.js";
19
- import { ZeroMutationResponse, } from "./types.js";
20
- import { prefixId } from "./utils.js";
21
- // NOTE(zero): Using the "Lifting the Generic" technique.
22
- // NOTE(zero): Could choose to not accept `Database<T>` as a param and instead create some `ZeroDatabase` service inside the
23
- // `make` function, which various other functions require. However, this means that the user will have to manually
24
- // specify the `T` type parameter which seemed a bit clunky.
25
- export const makeServer = (options) => {
26
- const ZeroServerTransactionContext = Context.Tag(prefixId("ZeroServerTransactionContext"))();
27
- // TODO: Maybe prefix this / suffix this with a tag passed in to `make`?
28
- const execute = Effect.fn(function* (effect) {
29
- const runtime =
30
- // TODO: Is there a cleaner way to write this?
31
- yield* Effect.runtime();
32
- const result = yield* Deferred.make();
33
- const transactionInput = yield* ZeroTransactionInput;
34
- yield* Effect.tryPromise({
35
- try: (signal) => options.database.transaction(async (transaction, transactionHooks) => {
36
- const exit = await Effect.zipRight(checkAndIncrementLastMutationID, effect).pipe(Effect.provideService(ZeroServerTransactionContext, { transaction, transactionHooks }), options.clientTransaction
37
- ? Effect.provideService(options.clientTransaction, transaction)
38
- : // TODO: Is there a cleaner way to write this?
39
- (effect) => effect, (effect) => Runtime.runPromiseExit(runtime, effect, { signal }));
40
- Deferred.unsafeDone(result, exit);
41
- return Exit.getOrElse(exit, () => {
42
- // This error's purpose is to differentiate between "external" errors
43
- // that originate from the user-defined mutator code and "internal" errors
44
- // that originate from our own code and the Zero API.
45
- // Both types are caught in the "catch" block below, but at this point we only need to handle
46
- // the "internal" errors wrapping them in a `ZeroDatabaseError`, because "external" errors
47
- // are already covered by passing the Exit result to the Deferred, which is why
48
- // we have the ZeroTransactionUserError silenced below in the pipe.
49
- throw new ZeroTransactionUserError();
50
- });
51
- }, transactionInput),
52
- catch: (error) => {
53
- if (ZeroTransactionUserError.is(error)) {
54
- return error;
55
- }
56
- // This is for errors that occur when calling `database.transaction` despite the provided `effect` succeeding.
57
- // This can be caused by e.g. the database connection timing out or other database-related issues.
58
- return new ZeroDatabaseError({ cause: Cause.fail(error) });
59
- },
60
- }).pipe(Effect.catchTag("ZeroTransactionUserError", () => Effect.void));
61
- return yield* result;
62
- }, ZeroServerMutationContext.guard);
63
- const use = (fn) => Effect.flatMap(ZeroServerTransactionContext, (ctx) => Effect.tryPromise({
64
- try: (signal) => fn(ctx.transaction, { signal }),
65
- catch: Fn.identity,
66
- }));
67
- const checkAndIncrementLastMutationID = Effect.gen(function* () {
68
- const { transactionHooks } = yield* ZeroServerTransactionContext;
69
- const { clientID, mutationID: receivedMutationID } = yield* ZeroTransactionInput;
70
- const { lastMutationID } = yield* Effect.tryPromise({
71
- try: () => transactionHooks.updateClientMutationID(),
72
- catch: (error) => new UpdateClientMutationIDError({ cause: Cause.fail(error) }),
73
- });
74
- if (receivedMutationID < lastMutationID) {
75
- return yield* new MutationAlreadyProcessedError({
76
- clientID,
77
- received: receivedMutationID,
78
- actual: lastMutationID,
79
- });
80
- }
81
- if (receivedMutationID > lastMutationID) {
82
- return yield* new OutOfOrderMutationError({
83
- clientID,
84
- receivedMutationID,
85
- lastMutationID,
86
- });
87
- }
88
- });
89
- const processPush = Effect.fn(function* (mutators, params, request) {
90
- if (request.pushVersion !== 1) {
91
- return { error: "unsupportedPushVersion" };
17
+ import { MutationAlreadyProcessedError, OutOfOrderMutationError, ServerSynchronizationContext, ServerTransactionInput, } from "./internal/server.js";
18
+ import { prefixId } from "./internal/utils.js";
19
+ import * as Mutators from "./mutators.js";
20
+ import { QueryNameSymbol, RunQuerySymbol } from "./query.js";
21
+ import * as Types from "./types/push.js";
22
+ export const processPush = Effect.fn(function* (transaction, mutators, params, request) {
23
+ if (request.pushVersion !== 1) {
24
+ return { error: "unsupportedPushVersion" };
25
+ }
26
+ const responses = yield* Stream.fromIterable(request.mutations).pipe(Stream.mapEffect(Effect.fn(function* (mutation) {
27
+ if (mutation.type !== "custom") {
28
+ return yield* new CustomMutationExpectedError();
92
29
  }
93
- const responses = yield* Stream.fromIterable(request.mutations).pipe(Stream.mapEffect(Effect.fn(function* (mutation) {
94
- if (mutation.type !== "custom") {
95
- return yield* new CustomMutationExpectedError({});
96
- }
97
- return yield* processMutation(mutators, mutation).pipe(Effect.map((result) => ZeroMutationResponse.make({ id: { id: mutation.id, clientID: mutation.clientID }, result })), Effect.provideService(ZeroTransactionInput, {
98
- clientID: mutation.clientID,
99
- mutationID: mutation.id,
100
- clientGroupID: request.clientGroupID,
101
- upstreamSchema: params.schema,
102
- }));
103
- })),
104
- // We only stop processing if the mutation is out of order.
105
- // If the mutation has already been processed or if it returns an application error,
106
- // we continue processing the next mutation.
107
- Stream.takeUntil(({ result }) => Predicate.hasProperty(result, "error") && result.error === "oooMutation"), Stream.runCollect, Effect.annotateLogs({ module: prefixId("ZeroServer"), method: "processPush" }));
108
- return { mutations: Chunk.toArray(responses) };
109
- });
110
- const processMutation = Effect.fn(function* (mutators, mutation) {
111
- // Support both "namespace|name" and "namespace.name" formats, and single-segment names.
112
- const [namespace, name] = mutation.name.includes("|")
113
- ? Str.split(mutation.name, "|")
114
- : Str.split(mutation.name, ".");
115
- const mutator = yield* Fn.pipe(mutators, Rec.get(namespace), Option.flatMap((mutator) => Match.value([mutator, name]).pipe(Match.when([Predicate.isRecord, Predicate.isString], ([mutator, name]) => Rec.get(name)(mutator)), Match.when([Predicate.isFunction, Predicate.isUndefined], ([mutator]) => Option.some(mutator)), Match.orElse(() => Option.none()))), Effect.catchTag("NoSuchElementException", () => new MutatorNotFoundError({ mutationName: mutation.name })));
116
- const args = yield* Schema.decode(mutator[MutatorArgsSchemaSym])(mutation.args[0]).pipe(Effect.catchTag("ParseError", (e) => new ZeroServerArgsParseError({ cause: Cause.fail(e) })));
117
- return yield* mutator(args).pipe(
118
- // Case #2 "One transaction then fail"
119
- // If the transaction was executed successfully, swallow the error and just log it, otherwise re-throw it
120
- Effect.catchAllCause(Effect.fn(function* (e) {
121
- const wasTransactionExecuted = yield* ZeroServerMutationContext.wasTransactionExecuted;
122
- if (wasTransactionExecuted) {
123
- return yield* Effect.logError("Error occurred after transaction execution completed", e);
124
- }
125
- return yield* Effect.failCause(e);
126
- })),
127
- // Case #4 "Zero transactions then succeed"
128
- // Check that the transaction was executed during the mutation
129
- Effect.tap(Effect.gen(function* () {
130
- const wasTransactionExecuted = yield* ZeroServerMutationContext.wasTransactionExecuted;
131
- if (!wasTransactionExecuted) {
132
- return yield* new NoTransactionError();
133
- }
134
- })), Effect.as({}), Effect.catchIf(OutOfOrderMutationError.is, (e) => Effect.logError(e.message).pipe(Effect.as({ error: "oooMutation", details: e.message }))), Effect.catchIf(MutationAlreadyProcessedError.is, (e) => Effect.logWarning(e.message).pipe(Effect.as({ error: "alreadyProcessed", details: e.message }))),
135
- // Case #5 "Zero transactions then fail" / #6 "Fail before transaction"
136
- // Catches all errors that are produced before the transaction is executed
137
- Effect.catchAllCause((cause) => new ZeroMutationUserError({ cause })));
138
- }, Effect.catchAll((e) => processMutationError(e)), Effect.provide(ZeroServerMutationContext.Default));
139
- const processMutationError = Effect.fn(function* (e) {
140
- const { clientID, mutationID } = yield* ZeroTransactionInput;
141
- yield* Effect.logError(`Unexpected error processing mutation ${mutationID} for client ${clientID}`, Cause.fail(e));
142
- const errorMessage = Match.value(e).pipe(Match.when(ZeroMutationUserError.is, (e) => e.message), Match.orElse(() => "Internal error"));
143
- const appError = {
144
- error: "app",
145
- details: errorMessage,
146
- };
147
- yield* execute(Effect.flatMap(ZeroServerTransactionContext, ({ transactionHooks }) => {
148
- return Effect.tryPromise({
149
- try: () => transactionHooks.writeMutationResult({ id: { id: mutationID, clientID }, result: appError }),
150
- catch: (error) => new WriteMutationResultError({ cause: Cause.fail(error) }),
151
- });
152
- })).pipe(Effect.catchAllCause(Effect.logError));
153
- yield* Effect.logWarning(`Mutation ${mutationID} for client ${clientID} was retried after an error: ${e}`);
154
- return appError;
155
- });
156
- return {
157
- Transaction: Object.assign(ZeroServerTransactionContext, { execute, use }),
158
- processPush,
30
+ return yield* processMutation(mutators, mutation).pipe(Effect.catchAll((e) => processMutationError(transaction, e)), Effect.map((result) => Types.MutationResponse.make({ id: { id: mutation.id, clientID: mutation.clientID }, result })), Effect.provideService(ServerTransactionInput, {
31
+ clientID: mutation.clientID,
32
+ mutationID: mutation.id,
33
+ clientGroupID: request.clientGroupID,
34
+ upstreamSchema: params.schema,
35
+ }));
36
+ })),
37
+ // We only stop processing if the mutation is out of order.
38
+ // If the mutation has already been processed or if it returns an application error,
39
+ // we continue processing the next mutation.
40
+ Stream.takeUntil(({ result }) => Predicate.hasProperty(result, "error") && result.error === "oooMutation"), Stream.runCollect);
41
+ return { mutations: Chunk.toArray(responses) };
42
+ }, Effect.provide(ServerSynchronizationContext.Default));
43
+ const processMutation = Effect.fn(function* (mutators, mutation) {
44
+ // Support both "namespace|name" and "namespace.name" formats, and single-segment names.
45
+ const [namespace, name] = mutation.name.includes("|") ? Str.split(mutation.name, "|") : Str.split(mutation.name, ".");
46
+ const mutator = yield* Fn.pipe(mutators, Rec.get(namespace), Option.flatMap((mutator) => Match.value([mutator, name]).pipe(Match.when([Predicate.isRecord, Predicate.isString], ([mutator, name]) => Rec.get(name)(mutator)), Match.when([Predicate.isFunction, Predicate.isUndefined], ([mutator]) => Option.some(mutator)), Match.orElse(() => Option.none()))), Effect.catchTag("NoSuchElementException", () => new MutatorNotFoundError({ name: mutation.name })));
47
+ const args = yield* Schema.decode(mutator[Mutators.MutatorSchemaSymbol])(mutation.args[0]).pipe(Effect.catchTag("ParseError", (e) => new ServerArgsParseError({ cause: Cause.fail(e) })));
48
+ return yield* mutator(args).pipe(ServerSynchronizationContext.finalize, Effect.as({}), Effect.catchIf(OutOfOrderMutationError.is, (e) => Effect.logError(e.message).pipe(Effect.as({ error: "oooMutation", details: e.message }))), Effect.catchIf(MutationAlreadyProcessedError.is, (e) => Effect.logWarning(e.message).pipe(Effect.as({ error: "alreadyProcessed", details: e.message }))),
49
+ // Case #5 "Zero transactions then fail" / #6 "Fail before transaction"
50
+ // Catches all errors that are produced before the transaction is executed
51
+ Effect.catchAllCause((cause) => new MutationUserError({ cause })));
52
+ });
53
+ const processMutationError = Effect.fn(function* (transaction, e) {
54
+ const { clientID, mutationID } = yield* ServerTransactionInput;
55
+ yield* Effect.logError(`Unexpected error processing mutation ${mutationID} for client ${clientID}`, Cause.fail(e));
56
+ const errorMessage = Match.value(e).pipe(Match.when(MutationUserError.is, (e) => e.message), Match.orElse(() => "Internal error"));
57
+ const appError = {
58
+ error: "app",
59
+ details: errorMessage,
159
60
  };
160
- };
161
- class ZeroTransactionInput extends Context.Tag(prefixId("ZeroTransactionInput"))() {
162
- }
163
- class ZeroServerMutationContext extends Effect.Service()(prefixId("ZeroServerMutationContext"), {
164
- effect: Effect.gen(function* () {
165
- return { wasTransactionExecuted: yield* SynchronizedRef.make(false) };
166
- }),
167
- }) {
168
- static wasTransactionExecuted = Effect.flatMap(ZeroServerMutationContext, (ctx) => ctx.wasTransactionExecuted);
169
- // Ensures that only one transaction is executed at a time and checks that another transaction wasn't already executed.
170
- static guard = (effect) => Effect.flatMap(this, ({ wasTransactionExecuted }) => SynchronizedRef.modifyEffect(wasTransactionExecuted, Effect.fn(function* (wasTransactionExecuted) {
171
- if (wasTransactionExecuted) {
172
- return yield* new MultipleTransactionsError();
173
- }
174
- const result = yield* effect;
175
- return [result, true];
176
- })));
177
- }
178
- class ZeroDatabaseError extends Data.TaggedError("ZeroDatabaseError") {
179
- }
180
- class UpdateClientMutationIDError extends Data.TaggedError("UpdateClientMutationIDError") {
181
- }
182
- class MutatorNotFoundError extends Data.TaggedError("MutatorNotFoundError") {
183
- message = `Mutator not found for mutation ${this.mutationName}`;
184
- }
61
+ yield* transaction
62
+ .execute(Effect.flatMap(transaction, ({ transactionHooks }) => {
63
+ return Effect.tryPromise({
64
+ try: () => transactionHooks.writeMutationResult({ id: { id: mutationID, clientID }, result: appError }),
65
+ catch: (error) => new WriteMutationResultError({ cause: Cause.fail(error) }),
66
+ });
67
+ }))
68
+ .pipe(Effect.catchAllCause(Effect.logError));
69
+ yield* Effect.logWarning(`Mutation ${mutationID} for client ${clientID} was retried after an error: ${e}`);
70
+ return appError;
71
+ });
185
72
  class WriteMutationResultError extends Data.TaggedError("WriteMutationResultError") {
186
73
  }
187
74
  class CustomMutationExpectedError extends Data.TaggedError("CustomMutationExpectedError") {
188
75
  }
189
- export class MultipleTransactionsError extends Data.TaggedError("MultipleTransactionsError") {
190
- message = "Multiple transactions detected in a mutation, only one transaction is allowed.";
76
+ class MutatorNotFoundError extends Data.TaggedError("MutatorNotFoundError") {
191
77
  }
192
- export class NoTransactionError extends Data.TaggedError("NoTransactionError") {
193
- message = "No transaction detected in a mutation, a transaction is required.";
78
+ class ServerArgsParseError extends Data.TaggedError("ServerArgsParseError") {
194
79
  }
195
- const OutOfOrderMutationErrorTypeId = Symbol.for(prefixId("OutOfOrderMutationError"));
196
- export class OutOfOrderMutationError extends Data.TaggedError("OutOfOrderMutationError") {
197
- [OutOfOrderMutationErrorTypeId] = OutOfOrderMutationErrorTypeId;
198
- get message() {
199
- return `Client ${this.clientID} sent mutation ID ${this.receivedMutationID} but expected ${this.lastMutationID}`;
200
- }
80
+ const MutationUserErrorTypeId = Symbol.for(prefixId("MutationUserError"));
81
+ class MutationUserError extends Data.TaggedError("MutationUserError") {
82
+ [MutationUserErrorTypeId] = MutationUserErrorTypeId;
201
83
  static is(e) {
202
- return Predicate.hasProperty(e, OutOfOrderMutationErrorTypeId);
84
+ return Predicate.hasProperty(e, MutationUserErrorTypeId);
203
85
  }
204
- }
205
- const MutationAlreadyProcessedErrorTypeId = Symbol.for(prefixId("MutationAlreadyProcessedError"));
206
- export class MutationAlreadyProcessedError extends Data.TaggedError("MutationAlreadyProcessedError") {
207
- [MutationAlreadyProcessedErrorTypeId] = MutationAlreadyProcessedErrorTypeId;
208
86
  get message() {
209
- return `Ignoring mutation from ${this.clientID} with ID ${this.received} as it was already processed. Expected: ${this.actual}`;
210
- }
211
- static is(e) {
212
- return Predicate.hasProperty(e, MutationAlreadyProcessedErrorTypeId);
87
+ const err = Cause.squash(this.cause);
88
+ return err instanceof Error ? err.message : "exception was not of type `Error`";
213
89
  }
214
90
  }
215
- const ZeroTransactionUserErrorTypeId = Symbol.for(prefixId("ZeroTransactionUserError"));
216
- export class ZeroTransactionUserError extends Data.TaggedError("ZeroTransactionUserError") {
217
- [ZeroTransactionUserErrorTypeId] = ZeroTransactionUserErrorTypeId;
218
- static is(e) {
219
- return Predicate.hasProperty(e, ZeroTransactionUserErrorTypeId);
91
+ export const handleGetQueries = Effect.fn(function* (queries, schema, payload) {
92
+ const runtime = yield* Effect.runtime();
93
+ return yield* Effect.tryPromise({
94
+ try: () => handleGetQueriesRequest((name, args) => Arr.findFirst(queries, (q) => q[QueryNameSymbol] === name).pipe(Effect.catchTag("NoSuchElementException", () => new QueryNotFound({ name })), Effect.flatMap((query) => query[RunQuerySymbol]({ _tag: "Encoded", args })), Effect.map((query) => ({ query })), (effect) => Runtime.runPromiseExit(runtime, effect), (exit) => exit.then(Exit.getOrElse((cause) => {
95
+ throw new QueryUserError({ cause });
96
+ }))), schema, payload),
97
+ catch: (e) => Option.liftPredicate(e, (QueryUserError.is)).pipe(Option.flatMap((e) => Cause.failureOption(e.cause)), Option.getOrElse(() => new GetQueriesError({ cause: Cause.fail(e) }))),
98
+ });
99
+ });
100
+ class QueryNotFound extends Data.TaggedError("QueryNotFound") {
101
+ get message() {
102
+ return `Query not found: ${this.name}`;
220
103
  }
221
104
  }
222
- const ZeroMutationUserErrorTypeId = Symbol.for(prefixId("ZeroMutationUserError"));
223
- export class ZeroMutationUserError extends Data.TaggedError("ZeroMutationUserError") {
224
- [ZeroMutationUserErrorTypeId] = ZeroMutationUserErrorTypeId;
105
+ const QueryUserErrorTypeId = Symbol.for(prefixId("QueryUserError"));
106
+ class QueryUserError extends Data.TaggedError("QueryUserError") {
107
+ [QueryUserErrorTypeId] = QueryUserErrorTypeId;
225
108
  static is(e) {
226
- return Predicate.hasProperty(e, ZeroMutationUserErrorTypeId);
227
- }
228
- get message() {
229
- const err = Cause.squash(this.cause);
230
- return err instanceof Error ? err.message : "exception was not of type `Error`";
109
+ return Predicate.hasProperty(e, QueryUserErrorTypeId);
231
110
  }
232
111
  }
233
- class ZeroServerArgsParseError extends Data.TaggedError("ZeroServerArgsParseError") {
112
+ class GetQueriesError extends Data.TaggedError("GetQueriesError") {
234
113
  }
235
114
  //# sourceMappingURL=server.js.map