effect-zero 0.4.2 → 0.5.0-beta.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 (71) hide show
  1. package/dist/client-transaction.d.ts +77 -7
  2. package/dist/client-transaction.d.ts.map +1 -1
  3. package/dist/client-transaction.js +11 -7
  4. package/dist/client-transaction.js.map +1 -1
  5. package/dist/client.d.ts +119 -7
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +7 -6
  8. package/dist/client.js.map +1 -1
  9. package/dist/internal/server-synchronization-context.d.ts +19 -0
  10. package/dist/internal/server-synchronization-context.d.ts.map +1 -0
  11. package/dist/internal/server-synchronization-context.js +46 -0
  12. package/dist/internal/server-synchronization-context.js.map +1 -0
  13. package/dist/internal/server.d.ts +5 -16
  14. package/dist/internal/server.d.ts.map +1 -1
  15. package/dist/internal/server.js +1 -38
  16. package/dist/internal/server.js.map +1 -1
  17. package/dist/internal/server.test.js +7 -8
  18. package/dist/internal/server.test.js.map +1 -1
  19. package/dist/internal/utils.d.ts +7 -0
  20. package/dist/internal/utils.d.ts.map +1 -1
  21. package/dist/internal/utils.js +7 -0
  22. package/dist/internal/utils.js.map +1 -1
  23. package/dist/mutators.d.ts +1 -1
  24. package/dist/mutators.d.ts.map +1 -1
  25. package/dist/mutators.js +1 -1
  26. package/dist/query.d.ts +3 -5
  27. package/dist/query.d.ts.map +1 -1
  28. package/dist/query.js +6 -12
  29. package/dist/query.js.map +1 -1
  30. package/dist/result.d.ts +15 -15
  31. package/dist/result.d.ts.map +1 -1
  32. package/dist/result.js +11 -11
  33. package/dist/result.js.map +1 -1
  34. package/dist/server-transaction.d.ts +142 -15
  35. package/dist/server-transaction.d.ts.map +1 -1
  36. package/dist/server-transaction.js +24 -19
  37. package/dist/server-transaction.js.map +1 -1
  38. package/dist/server.d.ts +247 -12
  39. package/dist/server.d.ts.map +1 -1
  40. package/dist/server.js +21 -18
  41. package/dist/server.js.map +1 -1
  42. package/dist/types/common.d.ts +1 -1
  43. package/dist/types/common.d.ts.map +1 -1
  44. package/dist/types/error.d.ts +69 -48
  45. package/dist/types/error.d.ts.map +1 -1
  46. package/dist/types/error.js +38 -30
  47. package/dist/types/error.js.map +1 -1
  48. package/dist/types/push.d.ts +149 -149
  49. package/dist/types/push.d.ts.map +1 -1
  50. package/dist/types/push.js +45 -40
  51. package/dist/types/push.js.map +1 -1
  52. package/dist/types/queries.d.ts +42 -42
  53. package/dist/types/queries.d.ts.map +1 -1
  54. package/dist/types/queries.js +7 -7
  55. package/dist/types/queries.js.map +1 -1
  56. package/dist/types.test.js +13 -15
  57. package/dist/types.test.js.map +1 -1
  58. package/package.json +5 -7
  59. package/src/client-transaction.ts +13 -11
  60. package/src/client.ts +17 -19
  61. package/src/internal/server-synchronization-context.ts +64 -0
  62. package/src/internal/server.ts +3 -59
  63. package/src/internal/utils.ts +8 -0
  64. package/src/mutators.ts +2 -2
  65. package/src/query.ts +8 -20
  66. package/src/result.ts +40 -37
  67. package/src/server-transaction.ts +32 -37
  68. package/src/server.ts +34 -37
  69. package/src/types/error.ts +8 -8
  70. package/src/types/push.ts +22 -22
  71. package/src/types/queries.ts +7 -7
package/src/query.ts CHANGED
@@ -4,9 +4,9 @@ import * as Effect from "effect/Effect";
4
4
  import * as Equal from "effect/Equal";
5
5
  import * as Hash from "effect/Hash";
6
6
  import * as Match from "effect/Match";
7
+ import * as Queue from "effect/Queue";
7
8
  import * as Schema from "effect/Schema";
8
9
  import * as Stream from "effect/Stream";
9
- import * as Subscribable from "effect/Subscribable";
10
10
  import * as QueryResult from "./internal/query-result.js";
11
11
  import { prefixId } from "./internal/utils.js";
12
12
  import { deepClone, getDefaultSnapshot, getSnapshot } from "./snapshot.js";
@@ -32,15 +32,15 @@ export const make = <
32
32
  R2,
33
33
  >(options: {
34
34
  name: N;
35
- payload: Schema.Schema<B, A, R1>;
35
+ payload: Schema.Codec<B, A, R1, R1>;
36
36
  query: (args: NoInfer<B>) => Effect.Effect<ZeroQuery<T, S, R>, E, R2>;
37
37
  }) => {
38
38
  const runQuery = Effect.fn(function* (args: QueryArgs<A, B>) {
39
39
  const { encoded, decoded } = yield* Match.valueTags(args, {
40
40
  Encoded: ({ args: encoded }) =>
41
- Effect.map(Schema.decode(options.payload)(encoded), (decoded) => ({ encoded, decoded })),
41
+ Effect.map(Schema.decodeEffect(options.payload)(encoded), (decoded) => ({ encoded, decoded })),
42
42
  Decoded: ({ args: decoded }) =>
43
- Effect.map(Schema.encode(options.payload)(decoded), (encoded) => ({ encoded, decoded })),
43
+ Effect.map(Schema.encodeEffect(options.payload)(decoded), (encoded) => ({ encoded, decoded })),
44
44
  });
45
45
 
46
46
  return yield* options.query(decoded).pipe(
@@ -88,8 +88,8 @@ export const stream = <T extends keyof S["tables"] & string, S extends ZeroSchem
88
88
  (view) => Effect.sync(() => view.destroy()),
89
89
  );
90
90
 
91
- return Stream.asyncEffect<Parameters<Parameters<(typeof view)["addListener"]>[0]>>((emit) =>
92
- Effect.sync(() => view.addListener((...args) => emit.single(args))),
91
+ return Stream.callback<Parameters<Parameters<(typeof view)["addListener"]>[0]>>((queue) =>
92
+ Effect.sync(() => view.addListener((...args) => Queue.offerUnsafe(queue, args))),
93
93
  ).pipe(
94
94
  Stream.mapEffect(([data, resultType, error]) =>
95
95
  Effect.sync(() => {
@@ -102,27 +102,15 @@ export const stream = <T extends keyof S["tables"] & string, S extends ZeroSchem
102
102
  // TODO: Look into this. It seems like this is difficult but not impossible to model in the Effect paradigm.
103
103
  // Likely need some kind of stateful piece between the publisher and and subscriber, allowing the subscriber to swap out
104
104
  // the publisher when this function is called.
105
- () => Effect.dieMessage("retry not available in `effect-zero`"),
105
+ () => Effect.die("retry not available in `effect-zero`"),
106
106
  error,
107
107
  );
108
108
  }),
109
109
  ),
110
110
  Stream.map(QueryResult.make),
111
111
  );
112
- }).pipe(Stream.unwrapScoped);
112
+ }).pipe(Stream.unwrap);
113
113
 
114
114
  export const initialValue = <T extends keyof S["tables"] & string, S extends ZeroSchema, R>(
115
115
  query: ZeroQuery<T, S, R>,
116
116
  ) => QueryResult.make<R>(getDefaultSnapshot(asQueryInternals(query).format.singular));
117
-
118
- export const subscribable = <T extends keyof S["tables"] & string, S extends ZeroSchema, R>(
119
- zero: Zero<S>,
120
- query: ZeroQuery<T, S, R>,
121
- ) => {
122
- return Subscribable.make({
123
- get: Effect.promise(() => zero.run(query, { type: "unknown" })).pipe(
124
- Effect.map((value) => ({ _tag: "Partial", value }) satisfies QueryResult.QueryResult.Partial<R>),
125
- ),
126
- changes: stream(zero, query),
127
- });
128
- };
package/src/result.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { Result as AtomResult } from "@effect-atom/atom";
2
1
  import type { HumanReadable } from "@rocicorp/zero";
3
2
  import * as Cause from "effect/Cause";
4
3
  import * as Data from "effect/Data";
@@ -7,14 +6,15 @@ import * as Match from "effect/Match";
7
6
  import * as Option from "effect/Option";
8
7
  import * as Predicate from "effect/Predicate";
9
8
  import * as Struct from "effect/Struct";
9
+ import { AsyncResult } from "effect/unstable/reactivity";
10
10
  import type { QueryResult } from "./internal/query-result.js";
11
11
 
12
12
  /*
13
13
  ### Introduction
14
14
 
15
- When using effect-atom with effect-zero, you will often have atoms which contain a type in the form of
15
+ When using `effect/unstable/reactivity` (atoms) with effect-zero, you will often have atoms which contain a type in the form of
16
16
 
17
- `@effect-atom/atom`.Result<`@rocicorp/zero/react`.QueryResult<A>, E>
17
+ `AsyncResult<QueryResult<A>, E>`
18
18
 
19
19
  However, such a type is not easy to work with.
20
20
  The purpose of `effect-zero`.Result is to provide a more convenient type for this use case.
@@ -22,8 +22,8 @@ The purpose of `effect-zero`.Result is to provide a more convenient type for thi
22
22
  ### Background
23
23
 
24
24
  To start, let's analyze the possible states of the original type:
25
- - The outer `@effect-atom/atom`.Result may be `Initial<_, _>`, `Failure<_, E>`, or `Success<QueryResult, _>`.
26
- - The inner `@rocicorp/zero/react`.QueryResult may be `unknown<A>`, `complete<A>`, or `error`.
25
+ - The outer `AsyncResult` may be `Initial<_, _>`, `Failure<_, E>`, or `Success<QueryResult, _>`.
26
+ - The inner `QueryResult` may be `unknown<A>`, `complete<A>`, or `error`.
27
27
 
28
28
  This means that there are 5 total possible states:
29
29
  - Initial, representing the state before the query has been executed on either the client or the server.
@@ -43,45 +43,48 @@ On the other hand, `effect-zero`.Result has the following states:
43
43
 
44
44
  This has a few key benefits:
45
45
  - It allows you to deal with errors in a more streamlined way, as now all error states are represented as a `Failure(E | QueryError)`,
46
- thus you can use the builtin facilities of `effect-atom`.Result to work with the possible error states.
46
+ thus you can use the builtin facilities of `AsyncResult` to work with the possible error states.
47
47
  - We provide a method `acceptPartialMode` which allows for simplifying the type further by specifying the situations in which you want to accept
48
48
  partial results, resulting a further simplified type of `Initial | Success(A) | Failure(E | QueryError)`.
49
49
  */
50
50
 
51
- export type Result<A, E = never> = AtomResult.Result<QueryResult.Partial<A> | QueryResult.Complete<A>, E | QueryError>;
51
+ export type Result<A, E = never> = AsyncResult.AsyncResult<
52
+ QueryResult.Partial<A> | QueryResult.Complete<A>,
53
+ E | QueryError
54
+ >;
52
55
  export namespace Result {
53
- export type Initial<A, E = never> = AtomResult.Initial<
56
+ export type Initial<A, E = never> = AsyncResult.Initial<
54
57
  QueryResult.Partial<A> | QueryResult.Complete<A>,
55
58
  E | QueryError
56
59
  >;
57
- export type Failure<A, E = never> = AtomResult.Failure<
60
+ export type Failure<A, E = never> = AsyncResult.Failure<
58
61
  QueryResult.Partial<A> | QueryResult.Complete<A>,
59
62
  E | QueryError
60
63
  >;
61
- export type Success<A, E = never> = AtomResult.Success<
64
+ export type Success<A, E = never> = AsyncResult.Success<
62
65
  QueryResult.Partial<A> | QueryResult.Complete<A>,
63
66
  E | QueryError
64
67
  >;
65
68
 
66
69
  // More granular Partial and Complete types
67
- export type Partial<A, E = never> = AtomResult.Success<QueryResult.Partial<A>, E | QueryError>;
68
- export type Complete<A, E = never> = AtomResult.Success<QueryResult.Complete<A>, E | QueryError>;
70
+ export type Partial<A, E = never> = AsyncResult.Success<QueryResult.Partial<A>, E | QueryError>;
71
+ export type Complete<A, E = never> = AsyncResult.Success<QueryResult.Complete<A>, E | QueryError>;
69
72
  }
70
73
 
71
74
  export class QueryError extends Data.TaggedError("QueryError")<{
72
75
  readonly details: QueryResult.Error["details"];
73
76
  }> {}
74
77
 
75
- export const make = <A, E>(value: AtomResult.Result<QueryResult<A>, E>): Result<A, E> =>
78
+ export const make = <A, E>(value: AsyncResult.AsyncResult<QueryResult<A>, E>): Result<A, E> =>
76
79
  Match.valueTags(value, {
77
- Initial: () => AtomResult.initial(),
80
+ Initial: () => AsyncResult.initial(),
78
81
  Success: ({ value }) =>
79
82
  Match.valueTags(value, {
80
- Partial: (value) => AtomResult.success(value),
81
- Complete: (value) => AtomResult.success(value),
82
- Error: (e) => AtomResult.failure(Cause.fail(new QueryError(e))),
83
+ Partial: (value) => AsyncResult.success(value),
84
+ Complete: (value) => AsyncResult.success(value),
85
+ Error: (e): Result<A, E> => AsyncResult.failure(Cause.fail(new QueryError(e))),
83
86
  }),
84
- Failure: (e) => AtomResult.failure(e.cause),
87
+ Failure: (e): Result<A, E> => AsyncResult.failure(e.cause),
85
88
  });
86
89
 
87
90
  // based on: https://github.com/tim-smart/effect-atom/blob/04c15cacda42dd230782f52c0e978400793a502c/packages/atom/src/Result.ts#L424
@@ -124,34 +127,34 @@ export const match: {
124
127
  );
125
128
 
126
129
  export const mapSuccess: {
127
- <A, B>(f: (value: A) => B): <E = never>(self: AtomResult.Success<A, E>) => AtomResult.Success<B, E>;
128
- <A, B, E = never>(self: AtomResult.Success<A, E>, f: (value: A) => B): AtomResult.Success<B, E>;
129
- } = Fn.dual(2, <A, B, E = never>(self: AtomResult.Success<A, E>, f: (value: A) => B) => AtomResult.map(self, f));
130
+ <A, B>(f: (value: A) => B): <E = never>(self: AsyncResult.Success<A, E>) => AsyncResult.Success<B, E>;
131
+ <A, B, E = never>(self: AsyncResult.Success<A, E>, f: (value: A) => B): AsyncResult.Success<B, E>;
132
+ } = Fn.dual(2, <A, B, E = never>(self: AsyncResult.Success<A, E>, f: (value: A) => B) => AsyncResult.map(self, f));
130
133
 
131
134
  export const acceptPartialWith: {
132
135
  <A, E = never>(
133
136
  self: Result<A, E>,
134
137
  predicate: (value: HumanReadable<A>) => boolean,
135
138
  ):
136
- | AtomResult.Initial<HumanReadable<A>, E>
137
- | AtomResult.Success<HumanReadable<A>, QueryError | E>
138
- | AtomResult.Failure<HumanReadable<A>, QueryError | E>;
139
+ | AsyncResult.Initial<HumanReadable<A>, E>
140
+ | AsyncResult.Success<HumanReadable<A>, QueryError | E>
141
+ | AsyncResult.Failure<HumanReadable<A>, QueryError | E>;
139
142
  <A, E = never>(
140
143
  self: Result<A, E>,
141
144
  ): (
142
145
  predicate: (value: HumanReadable<A>) => boolean,
143
146
  ) =>
144
- | AtomResult.Initial<HumanReadable<A>, E>
145
- | AtomResult.Success<HumanReadable<A>, QueryError | E>
146
- | AtomResult.Failure<HumanReadable<A>, QueryError | E>;
147
+ | AsyncResult.Initial<HumanReadable<A>, E>
148
+ | AsyncResult.Success<HumanReadable<A>, QueryError | E>
149
+ | AsyncResult.Failure<HumanReadable<A>, QueryError | E>;
147
150
  } = Fn.dual(2, <A, E = never>(self: Result<A, E>, predicate: (value: HumanReadable<A>) => boolean) =>
148
- AtomResult.match(self, {
151
+ AsyncResult.match(self, {
149
152
  onSuccess: (success) =>
150
153
  Predicate.isTagged(success.value, "Partial") && !predicate(success.value.value)
151
- ? AtomResult.initial<HumanReadable<A>, E>(success.waiting)
154
+ ? AsyncResult.initial(success.waiting)
152
155
  : mapSuccess(success, Struct.get("value")),
153
156
  onFailure: (failure) =>
154
- AtomResult.failure<E | QueryError, HumanReadable<A>>(failure.cause, {
157
+ AsyncResult.failure(failure.cause, {
155
158
  waiting: failure.waiting,
156
159
  previousSuccess: failure.previousSuccess.pipe(
157
160
  Option.flatMap((success) =>
@@ -161,7 +164,7 @@ export const acceptPartialWith: {
161
164
  ),
162
165
  ),
163
166
  }),
164
- onInitial: (initial) => AtomResult.initial<HumanReadable<A>, E>(initial.waiting),
167
+ onInitial: (initial) => AsyncResult.initial(initial.waiting),
165
168
  }),
166
169
  );
167
170
 
@@ -182,9 +185,9 @@ export const acceptPartialMode: {
182
185
  **/
183
186
  mode: AcceptPartialMode,
184
187
  ):
185
- | AtomResult.Initial<HumanReadable<A>, E>
186
- | AtomResult.Success<HumanReadable<A>, QueryError | E>
187
- | AtomResult.Failure<HumanReadable<A>, QueryError | E>;
188
+ | AsyncResult.Initial<HumanReadable<A>, E>
189
+ | AsyncResult.Success<HumanReadable<A>, QueryError | E>
190
+ | AsyncResult.Failure<HumanReadable<A>, QueryError | E>;
188
191
  <A, E>(
189
192
  /**
190
193
  * - `acceptCached` -> Will resolve to the partial result if it is available
@@ -200,9 +203,9 @@ export const acceptPartialMode: {
200
203
  ): (
201
204
  self: Result<A, E>,
202
205
  ) =>
203
- | AtomResult.Initial<HumanReadable<A>, E>
204
- | AtomResult.Success<HumanReadable<A>, QueryError | E>
205
- | AtomResult.Failure<HumanReadable<A>, QueryError | E>;
206
+ | AsyncResult.Initial<HumanReadable<A>, E>
207
+ | AsyncResult.Success<HumanReadable<A>, QueryError | E>
208
+ | AsyncResult.Failure<HumanReadable<A>, QueryError | E>;
206
209
  } = Fn.dual(2, <A, E>(self: Result<A, E>, mode: AcceptPartialMode) =>
207
210
  Match.value(mode).pipe(
208
211
  Match.when("waitForServer", () => acceptPartialWith(self, () => false)),
@@ -1,42 +1,35 @@
1
- import type {
2
- Schema as ZeroSchema,
3
- ServerTransaction as ZeroServerTransaction,
4
- Transaction as ZeroTransaction,
5
- } from "@rocicorp/zero";
1
+ import type { Schema as ZeroSchema, ServerTransaction as ZeroServerTransaction } from "@rocicorp/zero";
6
2
  import type { TransactionProviderHooks, ZQLDatabase } from "@rocicorp/zero/server";
7
3
  import * as Cause from "effect/Cause";
8
- import * as Context from "effect/Context";
4
+ import * as Ctx from "effect/Context";
9
5
  import * as Data from "effect/Data";
10
6
  import * as Deferred from "effect/Deferred";
11
7
  import * as Effect from "effect/Effect";
12
8
  import * as Exit from "effect/Exit";
9
+ import type { NodeInspectSymbol } from "effect/Inspectable";
13
10
  import * as Layer from "effect/Layer";
14
11
  import * as Predicate from "effect/Predicate";
15
- import * as Runtime from "effect/Runtime";
16
12
  import type * as ClientTransaction from "./client-transaction.js";
17
- import {
18
- MutationAlreadyProcessedError,
19
- OutOfOrderMutationError,
20
- ServerSynchronizationContext,
21
- ServerTransactionInput,
22
- } from "./internal/server.js";
13
+ import { MutationAlreadyProcessedError, OutOfOrderMutationError, ServerTransactionInput } from "./internal/server.js";
14
+ import * as ServerSynchronizationContext from "./internal/server-synchronization-context.js";
23
15
  import { prefixId } from "./internal/utils.js";
24
16
 
25
17
  // Updated to: https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/push-processor.ts
26
18
 
27
- export interface ServerTransactionContext {
28
- readonly _tag: unique symbol;
29
- }
19
+ type _ = NodeInspectSymbol;
20
+
21
+ // biome-ignore lint/suspicious/noExplicitAny: any client transaction (its Id literal is irrelevant here)
22
+ export type Context<TSchema extends ZeroSchema, TTransaction> = ReturnType<typeof make<any, TSchema, TTransaction>>;
30
23
 
31
24
  export const make = <const Id extends string, TSchema extends ZeroSchema, TTransaction>(
32
25
  id: Id,
33
26
  database: ZQLDatabase<TSchema, TTransaction>,
34
- clientTransaction: Context.TagClass<ClientTransaction.ClientTransaction, string, ZeroTransaction<TSchema>>,
27
+ clientTransaction: ClientTransaction.Context<TSchema>,
35
28
  ) => {
36
- const ServerTransactionContext = Context.Tag(`${id}/ServerTransactionContext` as const)<
37
- ServerTransactionContext,
29
+ class Context extends Ctx.Service<
30
+ Context,
38
31
  { transaction: ZeroServerTransaction<TSchema, TTransaction>; transactionHooks: TransactionProviderHooks }
39
- >();
32
+ >()(`${id as string}/ServerTransactionContext` as const) {}
40
33
 
41
34
  const use = <A>(
42
35
  fn: (
@@ -44,32 +37,33 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
44
37
  options: { readonly signal: AbortSignal },
45
38
  ) => PromiseLike<A>,
46
39
  ) =>
47
- Effect.flatMap(ServerTransactionContext, (ctx) =>
48
- Effect.tryPromise({
40
+ Effect.gen(function* () {
41
+ const ctx = yield* Context;
42
+ return yield* Effect.tryPromise({
49
43
  try: (signal) => fn(ctx.transaction, { signal }),
50
44
  catch: (error) => new ServerTransactionError({ cause: Cause.fail(error) }),
51
- }),
52
- );
45
+ });
46
+ });
53
47
 
54
48
  const execute = Effect.fn(function* <A, E, R>(effect: Effect.Effect<A, E, R>) {
55
- const runtime = yield* Effect.runtime<
56
- ServerTransactionInput | Exclude<R, ClientTransaction.ClientTransaction | ServerTransactionContext>
49
+ const ctx = yield* Effect.context<
50
+ ServerTransactionInput | Exclude<R, (typeof clientTransaction.Context)["Identifier"] | Context>
57
51
  >();
58
- const result = yield* Deferred.make<A, E | Effect.Effect.Error<typeof checkAndIncrementLastMutationId>>();
52
+ const result = yield* Deferred.make<A, E | Effect.Error<typeof checkAndIncrementLastMutationId>>();
59
53
 
60
54
  const transactionInput = yield* ServerTransactionInput;
61
55
  yield* Effect.tryPromise({
62
56
  try: (signal) =>
63
57
  database.transaction(async (transaction, transactionHooks) => {
64
- const exit = await Effect.zipRight(checkAndIncrementLastMutationId, effect).pipe(
58
+ const exit = await Effect.flatMap(checkAndIncrementLastMutationId, () => effect).pipe(
65
59
  Effect.provide([
66
- Layer.succeed(ServerTransactionContext, { transaction, transactionHooks }),
67
- Layer.succeed(clientTransaction, transaction),
60
+ Layer.succeed(Context, { transaction, transactionHooks }),
61
+ Layer.succeed(clientTransaction.Context, transaction),
68
62
  ]),
69
- (effect) => Runtime.runPromiseExit(runtime, effect, { signal }),
63
+ (effect) => Effect.runPromiseExitWith(ctx)(effect, { signal }),
70
64
  );
71
- Deferred.unsafeDone(result, exit);
72
- return Exit.getOrElse(exit, () => {
65
+ Deferred.doneUnsafe(result, exit);
66
+ if (Exit.isFailure(exit)) {
73
67
  // This error's purpose is to differentiate between "external" errors
74
68
  // that originate from the user-defined mutator code and "internal" errors
75
69
  // that originate from our own code and the Zero API.
@@ -78,7 +72,8 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
78
72
  // are already covered by passing the Exit result to the Deferred, which is why
79
73
  // we have the ServerTransactionUserError silenced below in the pipe.
80
74
  throw new ServerTransactionUserError();
81
- });
75
+ }
76
+ return exit.value;
82
77
  }, transactionInput),
83
78
  catch: (error) => {
84
79
  if (ServerTransactionUserError.is(error)) {
@@ -90,11 +85,11 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
90
85
  },
91
86
  }).pipe(Effect.catchTag("ServerTransactionUserError", () => Effect.void));
92
87
 
93
- return yield* result;
88
+ return yield* Deferred.await(result);
94
89
  }, ServerSynchronizationContext.guard);
95
90
 
96
91
  const checkAndIncrementLastMutationId = Effect.gen(function* () {
97
- const { transactionHooks } = yield* ServerTransactionContext;
92
+ const { transactionHooks } = yield* Context;
98
93
  const { clientID, mutationID: receivedMutationID } = yield* ServerTransactionInput;
99
94
 
100
95
  const { lastMutationID } = yield* Effect.tryPromise({
@@ -118,7 +113,7 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
118
113
  }
119
114
  });
120
115
 
121
- return Object.assign(ServerTransactionContext, { use, execute });
116
+ return { Context, use, execute };
122
117
  };
123
118
 
124
119
  class ServerTransactionError extends Data.TaggedError("ServerTransactionError")<{
package/src/server.ts CHANGED
@@ -2,27 +2,21 @@ import type { ReadonlyJSONValue, Schema as ZeroSchema } from "@rocicorp/zero";
2
2
  import { handleQueryRequest } from "@rocicorp/zero/server";
3
3
  import * as Arr from "effect/Array";
4
4
  import * as Cause from "effect/Cause";
5
- import * as Chunk from "effect/Chunk";
6
5
  import * as Data from "effect/Data";
7
6
  import * as Effect from "effect/Effect";
8
7
  import * as Exit from "effect/Exit";
9
8
  import * as Fn from "effect/Function";
9
+ import type { NodeInspectSymbol } from "effect/Inspectable";
10
10
  import * as Match from "effect/Match";
11
11
  import * as Option from "effect/Option";
12
- import type * as ParseResult from "effect/ParseResult";
13
12
  import * as Predicate from "effect/Predicate";
14
13
  import * as Rec from "effect/Record";
15
- import * as Runtime from "effect/Runtime";
16
14
  import * as Schema from "effect/Schema";
17
15
  import * as Stream from "effect/Stream";
18
16
  import * as Str from "effect/String";
19
- import {
20
- MutationAlreadyProcessedError,
21
- OutOfOrderMutationError,
22
- ServerSynchronizationContext,
23
- ServerTransactionInput,
24
- } from "./internal/server.js";
25
- import { prefixId } from "./internal/utils.js";
17
+ import { MutationAlreadyProcessedError, OutOfOrderMutationError, ServerTransactionInput } from "./internal/server.js";
18
+ import * as ServerSynchronizationContext from "./internal/server-synchronization-context.js";
19
+ import { normalizeArgs, prefixId } from "./internal/utils.js";
26
20
  import * as Mutators from "./mutators.js";
27
21
  import { type MakeQueryResult, QueryNameSymbol, RunQuerySymbol } from "./query.js";
28
22
  import type * as ServerTransaction from "./server-transaction.js";
@@ -33,17 +27,14 @@ import type { TransformRequestMessage } from "./types/queries.js";
33
27
  // https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/push-processor.ts
34
28
  // https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/process-mutations.ts
35
29
 
36
- type ServerTransactionContext<TSchema extends ZeroSchema, TTransaction> = Omit<
37
- ReturnType<typeof ServerTransaction.make<string, TSchema, TTransaction>>,
38
- "use"
39
- >;
30
+ type _ = NodeInspectSymbol;
40
31
 
41
32
  export const processPush = Effect.fn(function* <
42
33
  TSchema extends ZeroSchema,
43
34
  TTransaction,
44
35
  TMutators extends Mutators.AnyMutators,
45
36
  >(
46
- transaction: ServerTransactionContext<TSchema, TTransaction>,
37
+ transaction: ServerTransaction.Context<TSchema, TTransaction>,
47
38
  mutators: TMutators,
48
39
  params: Types.PushParams,
49
40
  request: Types.PushBody,
@@ -60,7 +51,7 @@ export const processPush = Effect.fn(function* <
60
51
  }
61
52
 
62
53
  return yield* processMutation<Mutators.ExtractMutatorsRequirements<TMutators>>(mutators, mutation).pipe(
63
- Effect.catchAll((e) => processMutationError(transaction, e)),
54
+ Effect.catch((e) => processMutationError(transaction, e)),
64
55
  Effect.map((result) =>
65
56
  Types.MutationResponse.make({ id: { id: mutation.id, clientID: mutation.clientID }, result }),
66
57
  ),
@@ -70,7 +61,7 @@ export const processPush = Effect.fn(function* <
70
61
  clientGroupID: request.clientGroupID,
71
62
  upstreamSchema: params.schema,
72
63
  }),
73
- Effect.provide(ServerSynchronizationContext.Default),
64
+ Effect.provide(ServerSynchronizationContext.layer),
74
65
  );
75
66
  }),
76
67
  ),
@@ -81,7 +72,7 @@ export const processPush = Effect.fn(function* <
81
72
  Stream.runCollect,
82
73
  );
83
74
 
84
- return { mutations: Chunk.toArray(responses) } satisfies Types.PushResponse;
75
+ return { mutations: responses } satisfies Types.PushResponse;
85
76
  });
86
77
 
87
78
  const processMutation = Effect.fn(function* <R>(mutators: Mutators.AnyMutators<R>, mutation: Types.Mutation) {
@@ -93,17 +84,18 @@ const processMutation = Effect.fn(function* <R>(mutators: Mutators.AnyMutators<R
93
84
  Rec.get<string>(namespace),
94
85
  Option.flatMap((mutator) =>
95
86
  Match.value([mutator, name]).pipe(
96
- Match.when([Predicate.isRecord, Predicate.isString], ([mutator, name]) => Rec.get<string>(name)(mutator)),
87
+ Match.when([Predicate.isObject, Predicate.isString], ([mutator, name]) => Rec.get<string>(name)(mutator)),
97
88
  Match.when([Predicate.isFunction, Predicate.isUndefined], ([mutator]) => Option.some(mutator)),
98
89
  Match.orElse(() => Option.none()),
99
90
  ),
100
91
  ),
101
- Effect.catchTag("NoSuchElementException", () => new MutatorNotFoundError({ name: mutation.name })),
92
+ Effect.fromOption,
93
+ Effect.catchTag("NoSuchElementError", () => Effect.fail(new MutatorNotFoundError({ name: mutation.name }))),
102
94
  );
103
95
 
104
- const args = yield* Schema.decode(mutator[Mutators.MutatorSchemaSymbol])(mutation.args[0]).pipe(
105
- Effect.catchTag("ParseError", (e) => new ServerArgsParseError({ cause: Cause.fail(e) })),
106
- );
96
+ const args = yield* Schema.decodeUnknownEffect(mutator[Mutators.MutatorSchemaSymbol])(
97
+ normalizeArgs(mutation.args[0]),
98
+ ).pipe(Effect.catchTag("SchemaError", (e) => Effect.fail(new ServerArgsParseError({ cause: Cause.fail(e) }))));
107
99
 
108
100
  return yield* mutator(args).pipe(
109
101
  ServerSynchronizationContext.finalize,
@@ -120,12 +112,12 @@ const processMutation = Effect.fn(function* <R>(mutators: Mutators.AnyMutators<R
120
112
  ),
121
113
  // Case #5 "Zero transactions then fail" / #6 "Fail before transaction"
122
114
  // Catches all errors that are produced before the transaction is executed
123
- Effect.catchAllCause((cause) => new MutationUserError({ cause })),
115
+ Effect.catchCause((cause) => Effect.fail(new MutationUserError({ cause }))),
124
116
  );
125
117
  });
126
118
 
127
119
  const processMutationError = Effect.fn(function* <TSchema extends ZeroSchema, TTransaction>(
128
- transaction: ServerTransactionContext<TSchema, TTransaction>,
120
+ transaction: ServerTransaction.Context<TSchema, TTransaction>,
129
121
  e: unknown,
130
122
  ) {
131
123
  const { clientID, mutationID } = yield* ServerTransactionInput;
@@ -144,14 +136,15 @@ const processMutationError = Effect.fn(function* <TSchema extends ZeroSchema, TT
144
136
 
145
137
  yield* transaction
146
138
  .execute(
147
- Effect.flatMap(transaction, ({ transactionHooks }) => {
148
- return Effect.tryPromise({
139
+ Effect.gen(function* () {
140
+ const { transactionHooks } = yield* transaction.Context;
141
+ return yield* Effect.tryPromise({
149
142
  try: () => transactionHooks.writeMutationResult({ id: { id: mutationID, clientID }, result: appError }),
150
143
  catch: (error) => new WriteMutationResultError({ cause: Cause.fail(error) }),
151
144
  });
152
145
  }),
153
146
  )
154
- .pipe(Effect.catchAllCause(Effect.logError));
147
+ .pipe(Effect.catchCause(Effect.logError));
155
148
 
156
149
  yield* Effect.logWarning(`Mutation ${mutationID} for client ${clientID} was retried after an error: ${e}`);
157
150
 
@@ -167,7 +160,7 @@ class MutatorNotFoundError extends Data.TaggedError("MutatorNotFoundError")<{
167
160
  }> {}
168
161
 
169
162
  class ServerArgsParseError extends Data.TaggedError("ServerArgsParseError")<{
170
- readonly cause: Cause.Cause<ParseResult.ParseError>;
163
+ readonly cause: Cause.Cause<Schema.SchemaError>;
171
164
  }> {}
172
165
 
173
166
  const MutationUserErrorTypeId = Symbol.for(prefixId("MutationUserError"));
@@ -190,25 +183,29 @@ export const handleQuery = Effect.fn(function* <E, R1, R2>(
190
183
  schema: ZeroSchema,
191
184
  payload: TransformRequestMessage,
192
185
  ) {
193
- const runtime = yield* Effect.runtime<R1 | R2>();
186
+ const ctx = yield* Effect.context<R1 | R2>();
194
187
  return yield* Effect.tryPromise({
195
188
  try: () =>
196
189
  handleQueryRequest(
197
190
  (name, args) =>
198
191
  Arr.findFirst(queries, (q) => q[QueryNameSymbol] === name).pipe(
199
- Effect.catchTag("NoSuchElementException", () => new QueryNotFound({ name })),
192
+ Effect.fromOption,
193
+ Effect.catchTag("NoSuchElementError", () => Effect.fail(new QueryNotFound({ name }))),
200
194
  Effect.flatMap((query) => query[RunQuerySymbol]({ _tag: "Encoded", args })),
201
- (effect) => Runtime.runSyncExit(runtime, effect),
202
- Exit.getOrElse((cause) => {
203
- throw new QueryUserError<E>({ cause });
204
- }),
195
+ Effect.runSyncExitWith(ctx),
196
+ (exit) => {
197
+ if (Exit.isFailure(exit)) {
198
+ throw new QueryUserError<E>({ cause: exit.cause });
199
+ }
200
+ return exit.value;
201
+ },
205
202
  ),
206
203
  schema,
207
204
  payload as ReadonlyJSONValue,
208
205
  ),
209
206
  catch: (e) =>
210
207
  Option.liftPredicate(e, QueryUserError.is<E>).pipe(
211
- Option.flatMap((e) => Cause.failureOption(e.cause)),
208
+ Option.flatMap((e) => Cause.findErrorOption(e.cause)),
212
209
  Option.getOrElse(() => new QueryRequestError({ cause: Cause.fail(e) })),
213
210
  ),
214
211
  });
@@ -222,7 +219,7 @@ class QueryNotFound extends Data.TaggedError("QueryNotFound")<{ name: string }>
222
219
 
223
220
  const QueryUserErrorTypeId = Symbol.for(prefixId("QueryUserError"));
224
221
  class QueryUserError<E> extends Data.TaggedError("QueryUserError")<{
225
- cause: Cause.Cause<E | ParseResult.ParseError | QueryNotFound>;
222
+ cause: Cause.Cause<E | Schema.SchemaError | QueryNotFound>;
226
223
  }> {
227
224
  readonly [QueryUserErrorTypeId] = QueryUserErrorTypeId;
228
225
  static is<E>(e: unknown): e is QueryUserError<E> {
@@ -20,11 +20,11 @@ const PushFailedBase = Schema.Struct({
20
20
  message: Schema.String,
21
21
  });
22
22
 
23
- export const PushFailedBody = Schema.Union(
23
+ export const PushFailedBody = Schema.Union([
24
24
  Schema.Struct({
25
25
  ...PushFailedBase.fields,
26
26
  origin: Schema.Literal("server"),
27
- reason: Schema.Literal("database", "parse", "oooMutation", "unsupportedPushVersion", "internal"),
27
+ reason: Schema.Literals(["database", "parse", "oooMutation", "unsupportedPushVersion", "internal"]),
28
28
  }),
29
29
  Schema.Struct({
30
30
  ...PushFailedBase.fields,
@@ -36,9 +36,9 @@ export const PushFailedBody = Schema.Union(
36
36
  Schema.Struct({
37
37
  ...PushFailedBase.fields,
38
38
  origin: Schema.Literal("zeroCache"),
39
- reason: Schema.Literal("timeout", "parse", "internal"),
39
+ reason: Schema.Literals(["timeout", "parse", "internal"]),
40
40
  }),
41
- );
41
+ ]);
42
42
 
43
43
  const TransformFailedBase = Schema.Struct({
44
44
  kind: Schema.Literal("TransformFailed"),
@@ -50,11 +50,11 @@ const TransformFailedBase = Schema.Struct({
50
50
  message: Schema.String,
51
51
  });
52
52
 
53
- export const TransformFailedBody = Schema.Union(
53
+ export const TransformFailedBody = Schema.Union([
54
54
  Schema.Struct({
55
55
  ...TransformFailedBase.fields,
56
56
  origin: Schema.Literal("server"),
57
- reason: Schema.Literal("database", "parse", "internal"),
57
+ reason: Schema.Literals(["database", "parse", "internal"]),
58
58
  }),
59
59
  Schema.Struct({
60
60
  ...TransformFailedBase.fields,
@@ -66,6 +66,6 @@ export const TransformFailedBody = Schema.Union(
66
66
  Schema.Struct({
67
67
  ...TransformFailedBase.fields,
68
68
  origin: Schema.Literal("zeroCache"),
69
- reason: Schema.Literal("timeout", "parse", "internal"),
69
+ reason: Schema.Literals(["timeout", "parse", "internal"]),
70
70
  }),
71
- );
71
+ ]);