effect-zero 0.4.2 → 0.5.0-beta.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.
Files changed (71) hide show
  1. package/dist/client-transaction.d.ts +85 -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 +130 -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 +156 -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 +287 -29
  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 +29 -31
  59. package/src/client-transaction.ts +15 -11
  60. package/src/client.ts +19 -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 +34 -37
  68. package/src/server.ts +36 -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/mutators.ts CHANGED
@@ -15,7 +15,7 @@ export type AnyMutatorFns<R = any> = {
15
15
  };
16
16
 
17
17
  // biome-ignore lint/suspicious/noExplicitAny: upper bound to allow everything
18
- export type AnyMutatorSchema = Schema.Schema<any, any, never>;
18
+ export type AnyMutatorSchema = Schema.Codec<any, any>;
19
19
  export type AnyMutatorSchemas = Record<string, AnyMutatorSchema | Record<string, AnyMutatorSchema>>;
20
20
 
21
21
  export type MutatorFns<TSchema extends AnyMutatorSchemas> = {
@@ -71,7 +71,7 @@ export function make(schema: AnyMutatorSchemas, mutators: AnyMutatorFns): AnyMut
71
71
  return Rec.map(mutators, (v, name) => {
72
72
  return Match.value([v, schema[name]]).pipe(
73
73
  Match.when([Predicate.isFunction, Schema.isSchema], ([mutator, schema]) => makeMutator(schema, mutator)),
74
- Match.when([Predicate.isRecord, Predicate.isRecord], ([mutator, schema]) =>
74
+ Match.when([Predicate.isObject, Predicate.isObject], ([mutator, schema]) =>
75
75
  Rec.map(mutator, (mutator, name) => makeMutator(Option.getOrThrow(Rec.get(schema, name)), mutator)),
76
76
  ),
77
77
  Match.orElseAbsurd,
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,37 @@
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";
12
+ import type * as Unify from "effect/Unify";
16
13
  import type * as ClientTransaction from "./client-transaction.js";
17
- import {
18
- MutationAlreadyProcessedError,
19
- OutOfOrderMutationError,
20
- ServerSynchronizationContext,
21
- ServerTransactionInput,
22
- } from "./internal/server.js";
14
+ import { MutationAlreadyProcessedError, OutOfOrderMutationError, ServerTransactionInput } from "./internal/server.js";
15
+ import * as ServerSynchronizationContext from "./internal/server-synchronization-context.js";
23
16
  import { prefixId } from "./internal/utils.js";
24
17
 
25
18
  // Updated to: https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/push-processor.ts
26
19
 
27
- export interface ServerTransactionContext {
28
- readonly _tag: unique symbol;
29
- }
20
+ type _ = NodeInspectSymbol;
21
+ type _Unify = Unify.typeSymbol | Unify.unifySymbol | Unify.ignoreSymbol;
22
+
23
+ // biome-ignore lint/suspicious/noExplicitAny: any client transaction (its Id literal is irrelevant here)
24
+ export type Context<TSchema extends ZeroSchema, TTransaction> = ReturnType<typeof make<any, TSchema, TTransaction>>;
30
25
 
31
26
  export const make = <const Id extends string, TSchema extends ZeroSchema, TTransaction>(
32
27
  id: Id,
33
28
  database: ZQLDatabase<TSchema, TTransaction>,
34
- clientTransaction: Context.TagClass<ClientTransaction.ClientTransaction, string, ZeroTransaction<TSchema>>,
29
+ clientTransaction: ClientTransaction.Context<TSchema>,
35
30
  ) => {
36
- const ServerTransactionContext = Context.Tag(`${id}/ServerTransactionContext` as const)<
37
- ServerTransactionContext,
31
+ class Context extends Ctx.Service<
32
+ Context,
38
33
  { transaction: ZeroServerTransaction<TSchema, TTransaction>; transactionHooks: TransactionProviderHooks }
39
- >();
34
+ >()(`${id as string}/ServerTransactionContext` as const) {}
40
35
 
41
36
  const use = <A>(
42
37
  fn: (
@@ -44,32 +39,33 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
44
39
  options: { readonly signal: AbortSignal },
45
40
  ) => PromiseLike<A>,
46
41
  ) =>
47
- Effect.flatMap(ServerTransactionContext, (ctx) =>
48
- Effect.tryPromise({
42
+ Effect.gen(function* () {
43
+ const ctx = yield* Context;
44
+ return yield* Effect.tryPromise({
49
45
  try: (signal) => fn(ctx.transaction, { signal }),
50
46
  catch: (error) => new ServerTransactionError({ cause: Cause.fail(error) }),
51
- }),
52
- );
47
+ });
48
+ });
53
49
 
54
50
  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>
51
+ const ctx = yield* Effect.context<
52
+ ServerTransactionInput | Exclude<R, (typeof clientTransaction.Context)["Identifier"] | Context>
57
53
  >();
58
- const result = yield* Deferred.make<A, E | Effect.Effect.Error<typeof checkAndIncrementLastMutationId>>();
54
+ const result = yield* Deferred.make<A, E | Effect.Error<typeof checkAndIncrementLastMutationId>>();
59
55
 
60
56
  const transactionInput = yield* ServerTransactionInput;
61
57
  yield* Effect.tryPromise({
62
58
  try: (signal) =>
63
59
  database.transaction(async (transaction, transactionHooks) => {
64
- const exit = await Effect.zipRight(checkAndIncrementLastMutationId, effect).pipe(
60
+ const exit = await Effect.flatMap(checkAndIncrementLastMutationId, () => effect).pipe(
65
61
  Effect.provide([
66
- Layer.succeed(ServerTransactionContext, { transaction, transactionHooks }),
67
- Layer.succeed(clientTransaction, transaction),
62
+ Layer.succeed(Context, { transaction, transactionHooks }),
63
+ Layer.succeed(clientTransaction.Context, transaction),
68
64
  ]),
69
- (effect) => Runtime.runPromiseExit(runtime, effect, { signal }),
65
+ (effect) => Effect.runPromiseExitWith(ctx)(effect, { signal }),
70
66
  );
71
- Deferred.unsafeDone(result, exit);
72
- return Exit.getOrElse(exit, () => {
67
+ Deferred.doneUnsafe(result, exit);
68
+ if (Exit.isFailure(exit)) {
73
69
  // This error's purpose is to differentiate between "external" errors
74
70
  // that originate from the user-defined mutator code and "internal" errors
75
71
  // that originate from our own code and the Zero API.
@@ -78,7 +74,8 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
78
74
  // are already covered by passing the Exit result to the Deferred, which is why
79
75
  // we have the ServerTransactionUserError silenced below in the pipe.
80
76
  throw new ServerTransactionUserError();
81
- });
77
+ }
78
+ return exit.value;
82
79
  }, transactionInput),
83
80
  catch: (error) => {
84
81
  if (ServerTransactionUserError.is(error)) {
@@ -90,11 +87,11 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
90
87
  },
91
88
  }).pipe(Effect.catchTag("ServerTransactionUserError", () => Effect.void));
92
89
 
93
- return yield* result;
90
+ return yield* Deferred.await(result);
94
91
  }, ServerSynchronizationContext.guard);
95
92
 
96
93
  const checkAndIncrementLastMutationId = Effect.gen(function* () {
97
- const { transactionHooks } = yield* ServerTransactionContext;
94
+ const { transactionHooks } = yield* Context;
98
95
  const { clientID, mutationID: receivedMutationID } = yield* ServerTransactionInput;
99
96
 
100
97
  const { lastMutationID } = yield* Effect.tryPromise({
@@ -118,7 +115,7 @@ export const make = <const Id extends string, TSchema extends ZeroSchema, TTrans
118
115
  }
119
116
  });
120
117
 
121
- return Object.assign(ServerTransactionContext, { use, execute });
118
+ return { Context, use, execute };
122
119
  };
123
120
 
124
121
  class ServerTransactionError extends Data.TaggedError("ServerTransactionError")<{
package/src/server.ts CHANGED
@@ -2,27 +2,22 @@ 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 type * as Unify from "effect/Unify";
18
+ import { MutationAlreadyProcessedError, OutOfOrderMutationError, ServerTransactionInput } from "./internal/server.js";
19
+ import * as ServerSynchronizationContext from "./internal/server-synchronization-context.js";
20
+ import { normalizeArgs, prefixId } from "./internal/utils.js";
26
21
  import * as Mutators from "./mutators.js";
27
22
  import { type MakeQueryResult, QueryNameSymbol, RunQuerySymbol } from "./query.js";
28
23
  import type * as ServerTransaction from "./server-transaction.js";
@@ -33,17 +28,15 @@ import type { TransformRequestMessage } from "./types/queries.js";
33
28
  // https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/push-processor.ts
34
29
  // https://github.com/rocicorp/mono/blob/3082c9fa061891067b4bd7dc9fe74f798270d8d7/packages/zero-server/src/process-mutations.ts
35
30
 
36
- type ServerTransactionContext<TSchema extends ZeroSchema, TTransaction> = Omit<
37
- ReturnType<typeof ServerTransaction.make<string, TSchema, TTransaction>>,
38
- "use"
39
- >;
31
+ type _ = NodeInspectSymbol;
32
+ type _Unify = Unify.typeSymbol | Unify.unifySymbol | Unify.ignoreSymbol;
40
33
 
41
34
  export const processPush = Effect.fn(function* <
42
35
  TSchema extends ZeroSchema,
43
36
  TTransaction,
44
37
  TMutators extends Mutators.AnyMutators,
45
38
  >(
46
- transaction: ServerTransactionContext<TSchema, TTransaction>,
39
+ transaction: ServerTransaction.Context<TSchema, TTransaction>,
47
40
  mutators: TMutators,
48
41
  params: Types.PushParams,
49
42
  request: Types.PushBody,
@@ -60,7 +53,7 @@ export const processPush = Effect.fn(function* <
60
53
  }
61
54
 
62
55
  return yield* processMutation<Mutators.ExtractMutatorsRequirements<TMutators>>(mutators, mutation).pipe(
63
- Effect.catchAll((e) => processMutationError(transaction, e)),
56
+ Effect.catch((e) => processMutationError(transaction, e)),
64
57
  Effect.map((result) =>
65
58
  Types.MutationResponse.make({ id: { id: mutation.id, clientID: mutation.clientID }, result }),
66
59
  ),
@@ -70,7 +63,7 @@ export const processPush = Effect.fn(function* <
70
63
  clientGroupID: request.clientGroupID,
71
64
  upstreamSchema: params.schema,
72
65
  }),
73
- Effect.provide(ServerSynchronizationContext.Default),
66
+ Effect.provide(ServerSynchronizationContext.layer),
74
67
  );
75
68
  }),
76
69
  ),
@@ -81,7 +74,7 @@ export const processPush = Effect.fn(function* <
81
74
  Stream.runCollect,
82
75
  );
83
76
 
84
- return { mutations: Chunk.toArray(responses) } satisfies Types.PushResponse;
77
+ return { mutations: responses } satisfies Types.PushResponse;
85
78
  });
86
79
 
87
80
  const processMutation = Effect.fn(function* <R>(mutators: Mutators.AnyMutators<R>, mutation: Types.Mutation) {
@@ -93,17 +86,18 @@ const processMutation = Effect.fn(function* <R>(mutators: Mutators.AnyMutators<R
93
86
  Rec.get<string>(namespace),
94
87
  Option.flatMap((mutator) =>
95
88
  Match.value([mutator, name]).pipe(
96
- Match.when([Predicate.isRecord, Predicate.isString], ([mutator, name]) => Rec.get<string>(name)(mutator)),
89
+ Match.when([Predicate.isObject, Predicate.isString], ([mutator, name]) => Rec.get<string>(name)(mutator)),
97
90
  Match.when([Predicate.isFunction, Predicate.isUndefined], ([mutator]) => Option.some(mutator)),
98
91
  Match.orElse(() => Option.none()),
99
92
  ),
100
93
  ),
101
- Effect.catchTag("NoSuchElementException", () => new MutatorNotFoundError({ name: mutation.name })),
94
+ Effect.fromOption,
95
+ Effect.catchTag("NoSuchElementError", () => Effect.fail(new MutatorNotFoundError({ name: mutation.name }))),
102
96
  );
103
97
 
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
- );
98
+ const args = yield* Schema.decodeUnknownEffect(mutator[Mutators.MutatorSchemaSymbol])(
99
+ normalizeArgs(mutation.args[0]),
100
+ ).pipe(Effect.catchTag("SchemaError", (e) => Effect.fail(new ServerArgsParseError({ cause: Cause.fail(e) }))));
107
101
 
108
102
  return yield* mutator(args).pipe(
109
103
  ServerSynchronizationContext.finalize,
@@ -120,12 +114,12 @@ const processMutation = Effect.fn(function* <R>(mutators: Mutators.AnyMutators<R
120
114
  ),
121
115
  // Case #5 "Zero transactions then fail" / #6 "Fail before transaction"
122
116
  // Catches all errors that are produced before the transaction is executed
123
- Effect.catchAllCause((cause) => new MutationUserError({ cause })),
117
+ Effect.catchCause((cause) => Effect.fail(new MutationUserError({ cause }))),
124
118
  );
125
119
  });
126
120
 
127
121
  const processMutationError = Effect.fn(function* <TSchema extends ZeroSchema, TTransaction>(
128
- transaction: ServerTransactionContext<TSchema, TTransaction>,
122
+ transaction: ServerTransaction.Context<TSchema, TTransaction>,
129
123
  e: unknown,
130
124
  ) {
131
125
  const { clientID, mutationID } = yield* ServerTransactionInput;
@@ -144,14 +138,15 @@ const processMutationError = Effect.fn(function* <TSchema extends ZeroSchema, TT
144
138
 
145
139
  yield* transaction
146
140
  .execute(
147
- Effect.flatMap(transaction, ({ transactionHooks }) => {
148
- return Effect.tryPromise({
141
+ Effect.gen(function* () {
142
+ const { transactionHooks } = yield* transaction.Context;
143
+ return yield* Effect.tryPromise({
149
144
  try: () => transactionHooks.writeMutationResult({ id: { id: mutationID, clientID }, result: appError }),
150
145
  catch: (error) => new WriteMutationResultError({ cause: Cause.fail(error) }),
151
146
  });
152
147
  }),
153
148
  )
154
- .pipe(Effect.catchAllCause(Effect.logError));
149
+ .pipe(Effect.catchCause(Effect.logError));
155
150
 
156
151
  yield* Effect.logWarning(`Mutation ${mutationID} for client ${clientID} was retried after an error: ${e}`);
157
152
 
@@ -167,7 +162,7 @@ class MutatorNotFoundError extends Data.TaggedError("MutatorNotFoundError")<{
167
162
  }> {}
168
163
 
169
164
  class ServerArgsParseError extends Data.TaggedError("ServerArgsParseError")<{
170
- readonly cause: Cause.Cause<ParseResult.ParseError>;
165
+ readonly cause: Cause.Cause<Schema.SchemaError>;
171
166
  }> {}
172
167
 
173
168
  const MutationUserErrorTypeId = Symbol.for(prefixId("MutationUserError"));
@@ -190,25 +185,29 @@ export const handleQuery = Effect.fn(function* <E, R1, R2>(
190
185
  schema: ZeroSchema,
191
186
  payload: TransformRequestMessage,
192
187
  ) {
193
- const runtime = yield* Effect.runtime<R1 | R2>();
188
+ const ctx = yield* Effect.context<R1 | R2>();
194
189
  return yield* Effect.tryPromise({
195
190
  try: () =>
196
191
  handleQueryRequest(
197
192
  (name, args) =>
198
193
  Arr.findFirst(queries, (q) => q[QueryNameSymbol] === name).pipe(
199
- Effect.catchTag("NoSuchElementException", () => new QueryNotFound({ name })),
194
+ Effect.fromOption,
195
+ Effect.catchTag("NoSuchElementError", () => Effect.fail(new QueryNotFound({ name }))),
200
196
  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
- }),
197
+ Effect.runSyncExitWith(ctx),
198
+ (exit) => {
199
+ if (Exit.isFailure(exit)) {
200
+ throw new QueryUserError<E>({ cause: exit.cause });
201
+ }
202
+ return exit.value;
203
+ },
205
204
  ),
206
205
  schema,
207
206
  payload as ReadonlyJSONValue,
208
207
  ),
209
208
  catch: (e) =>
210
209
  Option.liftPredicate(e, QueryUserError.is<E>).pipe(
211
- Option.flatMap((e) => Cause.failureOption(e.cause)),
210
+ Option.flatMap((e) => Cause.findErrorOption(e.cause)),
212
211
  Option.getOrElse(() => new QueryRequestError({ cause: Cause.fail(e) })),
213
212
  ),
214
213
  });
@@ -222,7 +221,7 @@ class QueryNotFound extends Data.TaggedError("QueryNotFound")<{ name: string }>
222
221
 
223
222
  const QueryUserErrorTypeId = Symbol.for(prefixId("QueryUserError"));
224
223
  class QueryUserError<E> extends Data.TaggedError("QueryUserError")<{
225
- cause: Cause.Cause<E | ParseResult.ParseError | QueryNotFound>;
224
+ cause: Cause.Cause<E | Schema.SchemaError | QueryNotFound>;
226
225
  }> {
227
226
  readonly [QueryUserErrorTypeId] = QueryUserErrorTypeId;
228
227
  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
+ ]);