@confect/core 6.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,7 +131,8 @@ 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;
@@ -131,11 +146,23 @@ export type Returns<Ref_> =
131
146
  infer _RuntimeAndFunctionType,
132
147
  infer _FunctionVisibility,
133
148
  infer _Args,
134
- infer Returns_
149
+ infer Returns_,
150
+ infer _Error
135
151
  >
136
152
  ? Returns_
137
153
  : never;
138
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
+
139
166
  export type FunctionReference<Ref_ extends Any> = ConvexFunctionReference<
140
167
  GetFunctionType<Ref_>,
141
168
  GetFunctionVisibility<Ref_>
@@ -146,7 +173,8 @@ export type FromFunctionSpec<FunctionSpec_ extends FunctionSpec.AnyWithProps> =
146
173
  FunctionSpec.GetRuntimeAndFunctionType<FunctionSpec_>,
147
174
  FunctionSpec.GetFunctionVisibility<FunctionSpec_>,
148
175
  FunctionSpec.Args<FunctionSpec_>,
149
- FunctionSpec.Returns<FunctionSpec_>
176
+ FunctionSpec.Returns<FunctionSpec_>,
177
+ FunctionSpec.Error<FunctionSpec_>
150
178
  >;
151
179
 
152
180
  export const make = <FunctionSpec_ extends FunctionSpec.AnyWithProps>(
@@ -167,12 +195,25 @@ export const getFunctionReference = <Ref_ extends Any>(
167
195
  ): FunctionReference<Ref_> =>
168
196
  makeFunctionReference(getConvexFunctionName(ref)) as FunctionReference<Ref_>;
169
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
+
170
209
  export const encodeArgs = <Ref_ extends Any>(
171
210
  ref: Ref_,
172
211
  args: Args<Ref_>,
173
212
  ): Effect.Effect<unknown, ParseResult.ParseError> =>
174
213
  Match.value(ref.functionSpec.functionProvenance).pipe(
175
- Match.tag("Confect", (c) => Schema.encode(c.args)(args)),
214
+ Match.tag("Confect", (confectFunctionProvenance) =>
215
+ Schema.encode(confectFunctionProvenance.args)(args),
216
+ ),
176
217
  Match.tag("Convex", () => Effect.succeed(args)),
177
218
  Match.exhaustive,
178
219
  );
@@ -182,7 +223,9 @@ export const decodeReturns = <Ref_ extends Any>(
182
223
  returns: unknown,
183
224
  ): Effect.Effect<Returns<Ref_>, ParseResult.ParseError> =>
184
225
  Match.value(ref.functionSpec.functionProvenance).pipe(
185
- Match.tag("Confect", (c) => Schema.decode(c.returns)(returns)),
226
+ Match.tag("Confect", (confectFunctionProvenance) =>
227
+ Schema.decode(confectFunctionProvenance.returns)(returns),
228
+ ),
186
229
  Match.tag("Convex", () => Effect.succeed(returns)),
187
230
  Match.exhaustive,
188
231
  );
@@ -192,7 +235,9 @@ export const encodeArgsSync = <Ref_ extends Any>(
192
235
  args: Args<Ref_>,
193
236
  ): unknown =>
194
237
  Match.value(ref.functionSpec.functionProvenance).pipe(
195
- Match.tag("Confect", (c) => Schema.encodeSync(c.args)(args)),
238
+ Match.tag("Confect", (confectFunctionProvenance) =>
239
+ Schema.encodeSync(confectFunctionProvenance.args)(args),
240
+ ),
196
241
  Match.tag("Convex", () => args),
197
242
  Match.exhaustive,
198
243
  );
@@ -202,52 +247,155 @@ export const decodeReturnsSync = <Ref_ extends Any>(
202
247
  encodedReturns: unknown,
203
248
  ): Returns<Ref_> =>
204
249
  Match.value(ref.functionSpec.functionProvenance).pipe(
205
- Match.tag("Confect", (c) => Schema.decodeSync(c.returns)(encodedReturns)),
250
+ Match.tag("Confect", (confectFunctionProvenance) =>
251
+ Schema.decodeSync(confectFunctionProvenance.returns)(encodedReturns),
252
+ ),
206
253
  Match.tag("Convex", () => encodedReturns),
207
254
  Match.exhaustive,
208
255
  ) as Returns<Ref_>;
209
256
 
210
- export const runWithCodec: {
211
- <Ref_ extends Any, E>(
212
- ref: Ref_,
213
- args: Args<Ref_>,
214
- f: (
215
- functionReference: FunctionReference<Ref_>,
216
- encodedArgs: unknown,
217
- ) => Effect.Effect<unknown, E>,
218
- ): Effect.Effect<Returns<Ref_>, E | ParseResult.ParseError>;
219
- <Ref_ extends Any>(
220
- ref: Ref_,
221
- args: Args<Ref_>,
222
- f: (
223
- functionReference: FunctionReference<Ref_>,
224
- encodedArgs: unknown,
225
- ) => PromiseLike<unknown>,
226
- ): Effect.Effect<Returns<Ref_>, ParseResult.ParseError>;
227
- } = <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>(
228
357
  ref: Ref_,
229
358
  args: Args<Ref_>,
230
- f: (
359
+ call: (
231
360
  functionReference: FunctionReference<Ref_>,
232
361
  encodedArgs: unknown,
233
- ) => Effect.Effect<unknown, E> | PromiseLike<unknown>,
234
- ): 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> =>
235
365
  Effect.gen(function* () {
236
366
  const functionReference = getFunctionReference(ref);
237
367
  const functionProvenance = ref.functionSpec.functionProvenance;
238
- const call = (encodedArgs: unknown) => {
239
- const result = f(functionReference, encodedArgs);
240
- return Effect.isEffect(result) ? result : Effect.promise(() => result);
241
- };
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
+ });
242
386
  return yield* Match.value(functionProvenance).pipe(
243
- Match.tag("Confect", (confect) =>
387
+ Match.tag("Confect", (confectFunctionProvenance) =>
244
388
  Effect.gen(function* () {
245
- const encodedArgs = yield* Schema.encode(confect.args)(args);
246
- const encodedReturns = yield* call(encodedArgs);
247
- 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
+ );
248
396
  }),
249
397
  ),
250
- Match.tag("Convex", () => call(args)),
398
+ Match.tag("Convex", () => invoke(args)),
251
399
  Match.exhaustive,
252
400
  );
253
401
  });