@confect/core 5.0.0 → 7.0.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.
package/src/Ref.ts CHANGED
@@ -3,8 +3,10 @@ import type {
3
3
  FunctionVisibility,
4
4
  } from "convex/server";
5
5
  import { makeFunctionReference } from "convex/server";
6
+ import type { Value } from "convex/values";
7
+ import { ConvexError } from "convex/values";
6
8
  import type { ParseResult } from "effect";
7
- import { Effect, Match, Schema } from "effect";
9
+ import { Effect, Match, Option, Schema } from "effect";
8
10
  import type * as FunctionSpec from "./FunctionSpec";
9
11
  import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
10
12
 
@@ -13,27 +15,30 @@ export interface Ref<
13
15
  _FunctionVisibility extends FunctionVisibility,
14
16
  _Args,
15
17
  _Returns,
18
+ _Error = never,
16
19
  > {
17
20
  readonly _RuntimeAndFunctionType?: _RuntimeAndFunctionType;
18
21
  readonly _FunctionVisibility?: _FunctionVisibility;
19
22
  readonly _Args?: _Args;
20
23
  readonly _Returns?: _Returns;
24
+ readonly _Error?: _Error;
21
25
  /** @internal */
22
26
  readonly functionSpec: FunctionSpec.AnyWithProps;
23
27
  /** @internal */
24
28
  readonly functionNamespace: string;
25
29
  }
26
30
 
27
- export interface Any extends Ref<any, any, any, any> {}
31
+ export interface Any extends Ref<any, any, any, any, any> {}
28
32
 
29
- export interface AnyInternal extends Ref<any, "internal", any, any> {}
33
+ export interface AnyInternal extends Ref<any, "internal", any, any, any> {}
30
34
 
31
- export interface AnyPublic extends Ref<any, "public", any, any> {}
35
+ export interface AnyPublic extends Ref<any, "public", any, any, any> {}
32
36
 
33
37
  export interface AnyQuery extends Ref<
34
38
  RuntimeAndFunctionType.AnyQuery,
35
39
  FunctionVisibility,
36
40
  any,
41
+ any,
37
42
  any
38
43
  > {}
39
44
 
@@ -41,6 +46,7 @@ export interface AnyMutation extends Ref<
41
46
  RuntimeAndFunctionType.AnyMutation,
42
47
  FunctionVisibility,
43
48
  any,
49
+ any,
44
50
  any
45
51
  > {}
46
52
 
@@ -48,6 +54,7 @@ export interface AnyAction extends Ref<
48
54
  RuntimeAndFunctionType.AnyAction,
49
55
  FunctionVisibility,
50
56
  any,
57
+ any,
51
58
  any
52
59
  > {}
53
60
 
@@ -55,6 +62,7 @@ export interface AnyPublicQuery extends Ref<
55
62
  RuntimeAndFunctionType.AnyQuery,
56
63
  "public",
57
64
  any,
65
+ any,
58
66
  any
59
67
  > {}
60
68
 
@@ -62,6 +70,7 @@ export interface AnyPublicMutation extends Ref<
62
70
  RuntimeAndFunctionType.AnyMutation,
63
71
  "public",
64
72
  any,
73
+ any,
65
74
  any
66
75
  > {}
67
76
 
@@ -69,6 +78,7 @@ export interface AnyPublicAction extends Ref<
69
78
  RuntimeAndFunctionType.AnyAction,
70
79
  "public",
71
80
  any,
81
+ any,
72
82
  any
73
83
  > {}
74
84
 
@@ -77,7 +87,8 @@ export type GetRuntimeAndFunctionType<Ref_> =
77
87
  infer RuntimeAndFunctionType_,
78
88
  infer _FunctionVisibility,
79
89
  infer _Args,
80
- infer _Returns
90
+ infer _Returns,
91
+ infer _Error
81
92
  >
82
93
  ? RuntimeAndFunctionType_
83
94
  : never;
@@ -87,7 +98,8 @@ export type GetRuntime<Ref_> =
87
98
  infer RuntimeAndFunctionType_,
88
99
  infer _FunctionVisibility,
89
100
  infer _Args,
90
- infer _Returns
101
+ infer _Returns,
102
+ infer _Error
91
103
  >
92
104
  ? RuntimeAndFunctionType.GetRuntime<RuntimeAndFunctionType_>
93
105
  : never;
@@ -97,7 +109,8 @@ export type GetFunctionType<Ref_> =
97
109
  infer RuntimeAndFunctionType_,
98
110
  infer _FunctionVisibility,
99
111
  infer _Args,
100
- infer _Returns
112
+ infer _Returns,
113
+ infer _Error
101
114
  >
102
115
  ? RuntimeAndFunctionType.GetFunctionType<RuntimeAndFunctionType_>
103
116
  : never;
@@ -107,7 +120,8 @@ export type GetFunctionVisibility<Ref_> =
107
120
  infer _RuntimeAndFunctionType,
108
121
  infer FunctionVisibility_,
109
122
  infer _Args,
110
- infer _Returns
123
+ infer _Returns,
124
+ infer _Error
111
125
  >
112
126
  ? FunctionVisibility_
113
127
  : never;
@@ -117,21 +131,38 @@ export type Args<Ref_> =
117
131
  infer _RuntimeAndFunctionType,
118
132
  infer _FunctionVisibility,
119
133
  infer Args_,
120
- infer _Returns
134
+ infer _Returns,
135
+ infer _Error
121
136
  >
122
137
  ? Args_
123
138
  : never;
124
139
 
140
+ export type OptionalArgs<Ref_ extends Any> = keyof Args<Ref_> extends never
141
+ ? [args?: Args<Ref_>]
142
+ : [args: Args<Ref_>];
143
+
125
144
  export type Returns<Ref_> =
126
145
  Ref_ extends Ref<
127
146
  infer _RuntimeAndFunctionType,
128
147
  infer _FunctionVisibility,
129
148
  infer _Args,
130
- infer Returns_
149
+ infer Returns_,
150
+ infer _Error
131
151
  >
132
152
  ? Returns_
133
153
  : never;
134
154
 
155
+ export type Error<Ref_> =
156
+ Ref_ extends Ref<
157
+ infer _RuntimeAndFunctionType,
158
+ infer _FunctionVisibility,
159
+ infer _Args,
160
+ infer _Returns,
161
+ infer Error_
162
+ >
163
+ ? Error_
164
+ : never;
165
+
135
166
  export type FunctionReference<Ref_ extends Any> = ConvexFunctionReference<
136
167
  GetFunctionType<Ref_>,
137
168
  GetFunctionVisibility<Ref_>
@@ -142,7 +173,8 @@ export type FromFunctionSpec<FunctionSpec_ extends FunctionSpec.AnyWithProps> =
142
173
  FunctionSpec.GetRuntimeAndFunctionType<FunctionSpec_>,
143
174
  FunctionSpec.GetFunctionVisibility<FunctionSpec_>,
144
175
  FunctionSpec.Args<FunctionSpec_>,
145
- FunctionSpec.Returns<FunctionSpec_>
176
+ FunctionSpec.Returns<FunctionSpec_>,
177
+ FunctionSpec.Error<FunctionSpec_>
146
178
  >;
147
179
 
148
180
  export const make = <FunctionSpec_ extends FunctionSpec.AnyWithProps>(
@@ -163,12 +195,25 @@ export const getFunctionReference = <Ref_ extends Any>(
163
195
  ): FunctionReference<Ref_> =>
164
196
  makeFunctionReference(getConvexFunctionName(ref)) as FunctionReference<Ref_>;
165
197
 
198
+ export const hasErrorSchema = (ref: Any): boolean =>
199
+ Match.value(ref.functionSpec.functionProvenance).pipe(
200
+ Match.tag(
201
+ "Confect",
202
+ (confectFunctionProvenance) =>
203
+ confectFunctionProvenance.error !== undefined,
204
+ ),
205
+ Match.tag("Convex", () => false),
206
+ Match.exhaustive,
207
+ );
208
+
166
209
  export const encodeArgs = <Ref_ extends Any>(
167
210
  ref: Ref_,
168
211
  args: Args<Ref_>,
169
212
  ): Effect.Effect<unknown, ParseResult.ParseError> =>
170
213
  Match.value(ref.functionSpec.functionProvenance).pipe(
171
- Match.tag("Confect", (c) => Schema.encode(c.args)(args)),
214
+ Match.tag("Confect", (confectFunctionProvenance) =>
215
+ Schema.encode(confectFunctionProvenance.args)(args),
216
+ ),
172
217
  Match.tag("Convex", () => Effect.succeed(args)),
173
218
  Match.exhaustive,
174
219
  );
@@ -178,7 +223,9 @@ export const decodeReturns = <Ref_ extends Any>(
178
223
  returns: unknown,
179
224
  ): Effect.Effect<Returns<Ref_>, ParseResult.ParseError> =>
180
225
  Match.value(ref.functionSpec.functionProvenance).pipe(
181
- Match.tag("Confect", (c) => Schema.decode(c.returns)(returns)),
226
+ Match.tag("Confect", (confectFunctionProvenance) =>
227
+ Schema.decode(confectFunctionProvenance.returns)(returns),
228
+ ),
182
229
  Match.tag("Convex", () => Effect.succeed(returns)),
183
230
  Match.exhaustive,
184
231
  );
@@ -188,7 +235,9 @@ export const encodeArgsSync = <Ref_ extends Any>(
188
235
  args: Args<Ref_>,
189
236
  ): unknown =>
190
237
  Match.value(ref.functionSpec.functionProvenance).pipe(
191
- Match.tag("Confect", (c) => Schema.encodeSync(c.args)(args)),
238
+ Match.tag("Confect", (confectFunctionProvenance) =>
239
+ Schema.encodeSync(confectFunctionProvenance.args)(args),
240
+ ),
192
241
  Match.tag("Convex", () => args),
193
242
  Match.exhaustive,
194
243
  );
@@ -198,52 +247,155 @@ export const decodeReturnsSync = <Ref_ extends Any>(
198
247
  encodedReturns: unknown,
199
248
  ): Returns<Ref_> =>
200
249
  Match.value(ref.functionSpec.functionProvenance).pipe(
201
- Match.tag("Confect", (c) => Schema.decodeSync(c.returns)(encodedReturns)),
250
+ Match.tag("Confect", (confectFunctionProvenance) =>
251
+ Schema.decodeSync(confectFunctionProvenance.returns)(encodedReturns),
252
+ ),
202
253
  Match.tag("Convex", () => encodedReturns),
203
254
  Match.exhaustive,
204
255
  ) as Returns<Ref_>;
205
256
 
206
- export const runWithCodec: {
207
- <Ref_ extends Any, E>(
208
- ref: Ref_,
209
- args: Args<Ref_>,
210
- f: (
211
- functionReference: FunctionReference<Ref_>,
212
- encodedArgs: unknown,
213
- ) => Effect.Effect<unknown, E>,
214
- ): Effect.Effect<Returns<Ref_>, E | ParseResult.ParseError>;
215
- <Ref_ extends Any>(
216
- ref: Ref_,
217
- args: Args<Ref_>,
218
- f: (
219
- functionReference: FunctionReference<Ref_>,
220
- encodedArgs: unknown,
221
- ) => PromiseLike<unknown>,
222
- ): Effect.Effect<Returns<Ref_>, ParseResult.ParseError>;
223
- } = <Ref_ extends Any, E>(
257
+ const ConvexErrorIdentifier = Symbol.for("ConvexError");
258
+
259
+ export const isConvexError = (error: unknown): error is ConvexError<Value> =>
260
+ error instanceof ConvexError ||
261
+ (typeof error === "object" &&
262
+ error !== null &&
263
+ ConvexErrorIdentifier in error);
264
+
265
+ /**
266
+ * Build a callback-style handler that decodes the ref's typed error from a
267
+ * caught `ConvexError`, or else forwards the value to `mapUnknownError`. The
268
+ * fallback is also invoked when the input *is* a `ConvexError` but the ref
269
+ * doesn't declare a typed-error schema—by definition such a value falls
270
+ * outside the ref's error contract. Useful when adapting non-Effect APIs (e.g.
271
+ * emitter callbacks for streamed subscriptions) to the same error semantics
272
+ * that `runWithCodec` provides.
273
+ */
274
+ export const decodeErrorOrElse =
275
+ <Ref_ extends Any, E>(ref: Ref_, mapUnknownError: (error: unknown) => E) =>
276
+ (error: unknown): Error<Ref_> | E => {
277
+ if (isConvexError(error)) {
278
+ const decoded = decodeErrorSync(ref, error.data);
279
+ if (Option.isSome(decoded)) {
280
+ return decoded.value;
281
+ }
282
+ }
283
+ return mapUnknownError(error);
284
+ };
285
+
286
+ /**
287
+ * Decode `encodedError` against the ref's error schema. Returns `None` if the
288
+ * ref doesn't declare a typed error (Confect ref without an `error` schema, or
289
+ * a Convex-provenance ref)—by definition there's nothing to decode the value
290
+ * into, and the caller is responsible for deciding what to do (typically:
291
+ * surface the original value as a defect).
292
+ */
293
+ export const decodeError = <Ref_ extends Any>(
294
+ ref: Ref_,
295
+ encodedError: unknown,
296
+ ): Effect.Effect<Option.Option<Error<Ref_>>, ParseResult.ParseError> =>
297
+ Match.value(ref.functionSpec.functionProvenance).pipe(
298
+ Match.tag("Confect", (confectFunctionProvenance) =>
299
+ confectFunctionProvenance.error !== undefined
300
+ ? Effect.map(
301
+ Schema.decode(confectFunctionProvenance.error)(encodedError),
302
+ Option.some,
303
+ )
304
+ : Effect.succeed(Option.none<Error<Ref_>>()),
305
+ ),
306
+ Match.tag("Convex", () => Effect.succeed(Option.none<Error<Ref_>>())),
307
+ Match.exhaustive,
308
+ );
309
+
310
+ /**
311
+ * Synchronous counterpart to `decodeError`. Throws on schema decode failure;
312
+ * returns `None` when the ref doesn't declare a typed error.
313
+ */
314
+ export const decodeErrorSync = <Ref_ extends Any>(
315
+ ref: Ref_,
316
+ encodedError: unknown,
317
+ ): Option.Option<Error<Ref_>> =>
318
+ Match.value(ref.functionSpec.functionProvenance).pipe(
319
+ Match.tag("Confect", (confectFunctionProvenance) =>
320
+ confectFunctionProvenance.error !== undefined
321
+ ? Option.some(
322
+ Schema.decodeSync(confectFunctionProvenance.error)(
323
+ encodedError,
324
+ ) as Error<Ref_>,
325
+ )
326
+ : Option.none<Error<Ref_>>(),
327
+ ),
328
+ Match.tag("Convex", () => Option.none<Error<Ref_>>()),
329
+ Match.exhaustive,
330
+ );
331
+
332
+ export const maybeDecodeErrorSync = <Ref_ extends Any>(
333
+ ref: Ref_,
334
+ error: unknown,
335
+ ): unknown =>
336
+ isConvexError(error)
337
+ ? Match.value(ref.functionSpec.functionProvenance).pipe(
338
+ Match.tag("Confect", (confectFunctionProvenance) =>
339
+ confectFunctionProvenance.error !== undefined
340
+ ? Schema.decodeSync(confectFunctionProvenance.error)(error.data)
341
+ : error,
342
+ ),
343
+ Match.tag("Convex", () => error),
344
+ Match.exhaustive,
345
+ )
346
+ : error;
347
+
348
+ /**
349
+ * Encode args via the ref's args schema, invoke `call`, decode returns via the
350
+ * ref's returns schema, and translate any thrown `ConvexError` into the ref's
351
+ * typed error. Anything else the Promise rejects with—network failures,
352
+ * server-side runtime errors, validation failures, etc.—is passed to
353
+ * `mapUnknownError` to be turned into a typed `E`, or surfaced as a defect when
354
+ * no handler is provided.
355
+ */
356
+ export const runWithCodec = <Ref_ extends Any, E = never>(
224
357
  ref: Ref_,
225
358
  args: Args<Ref_>,
226
- f: (
359
+ call: (
227
360
  functionReference: FunctionReference<Ref_>,
228
361
  encodedArgs: unknown,
229
- ) => Effect.Effect<unknown, E> | PromiseLike<unknown>,
230
- ): Effect.Effect<Returns<Ref_>, E | ParseResult.ParseError> =>
362
+ ) => PromiseLike<unknown>,
363
+ mapUnknownError?: (error: unknown) => E,
364
+ ): Effect.Effect<Returns<Ref_>, E | Error<Ref_> | ParseResult.ParseError> =>
231
365
  Effect.gen(function* () {
232
366
  const functionReference = getFunctionReference(ref);
233
367
  const functionProvenance = ref.functionSpec.functionProvenance;
234
- const call = (encodedArgs: unknown) => {
235
- const result = f(functionReference, encodedArgs);
236
- return Effect.isEffect(result) ? result : Effect.promise(() => result);
237
- };
368
+ const invoke = (
369
+ encodedArgs: unknown,
370
+ ): Effect.Effect<unknown, Error<Ref_> | E> =>
371
+ Effect.tryPromise({
372
+ try: () => Promise.resolve(call(functionReference, encodedArgs)),
373
+ catch: (error): Error<Ref_> | E => {
374
+ if (isConvexError(error)) {
375
+ const decoded = decodeErrorSync(ref, error.data);
376
+ if (Option.isSome(decoded)) {
377
+ return decoded.value;
378
+ }
379
+ }
380
+ if (mapUnknownError !== undefined) {
381
+ return mapUnknownError(error);
382
+ }
383
+ throw error;
384
+ },
385
+ });
238
386
  return yield* Match.value(functionProvenance).pipe(
239
- Match.tag("Confect", (confect) =>
387
+ Match.tag("Confect", (confectFunctionProvenance) =>
240
388
  Effect.gen(function* () {
241
- const encodedArgs = yield* Schema.encode(confect.args)(args);
242
- const encodedReturns = yield* call(encodedArgs);
243
- return yield* Schema.decode(confect.returns)(encodedReturns);
389
+ const encodedArgs = yield* Schema.encode(
390
+ confectFunctionProvenance.args,
391
+ )(args);
392
+ const encodedReturns = yield* invoke(encodedArgs);
393
+ return yield* Schema.decode(confectFunctionProvenance.returns)(
394
+ encodedReturns,
395
+ );
244
396
  }),
245
397
  ),
246
- Match.tag("Convex", () => call(args)),
398
+ Match.tag("Convex", () => invoke(args)),
247
399
  Match.exhaustive,
248
400
  );
249
401
  });
package/src/Types.ts CHANGED
@@ -58,7 +58,7 @@ export type IsRecord<T> = [T] extends [never]
58
58
  export type DeepMutable<T> =
59
59
  IsAny<T> extends true
60
60
  ? any
61
- : T extends Brand.Brand<any> | GenericId<any>
61
+ : T extends NonRecursiveLeaf
62
62
  ? T
63
63
  : T extends ReadonlyMap<infer K, infer V>
64
64
  ? Map<DeepMutable<K>, DeepMutable<V>>
@@ -74,25 +74,29 @@ export type TypeDefect<Message extends string, T = never> = [Message, T];
74
74
 
75
75
  export type IsRecursive<T> = true extends DetectCycle<T> ? true : false;
76
76
 
77
+ type NonRecursiveLeaf = Brand.Brand<any> | GenericId<any> | ArrayBuffer;
78
+
77
79
  type DetectCycle<T, Cache extends any[] = []> =
78
80
  IsAny<T> extends true
79
81
  ? false
80
82
  : [T] extends [any]
81
- ? T extends Cache[number]
82
- ? true
83
- : T extends Array<infer U>
84
- ? DetectCycle<U, [...Cache, T]>
85
- : T extends Map<infer _U, infer V>
86
- ? DetectCycle<V, [...Cache, T]>
87
- : T extends Set<infer U>
88
- ? DetectCycle<U, [...Cache, T]>
89
- : T extends object
90
- ? true extends {
91
- [K in keyof T]: DetectCycle<T[K], [...Cache, T]>;
92
- }[keyof T]
93
- ? true
83
+ ? T extends NonRecursiveLeaf
84
+ ? false
85
+ : T extends Cache[number]
86
+ ? true
87
+ : T extends Array<infer U>
88
+ ? DetectCycle<U, [...Cache, T]>
89
+ : T extends Map<infer _U, infer V>
90
+ ? DetectCycle<V, [...Cache, T]>
91
+ : T extends Set<infer U>
92
+ ? DetectCycle<U, [...Cache, T]>
93
+ : T extends object
94
+ ? true extends {
95
+ [K in keyof T]: DetectCycle<T[K], [...Cache, T]>;
96
+ }[keyof T]
97
+ ? true
98
+ : false
94
99
  : false
95
- : false
96
100
  : never;
97
101
 
98
102
  //////////////////////////////////