@confect/server 6.0.0 → 8.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +157 -1
  2. package/dist/ActionRunner.d.ts +3 -5
  3. package/dist/ActionRunner.d.ts.map +1 -1
  4. package/dist/ActionRunner.js.map +1 -1
  5. package/dist/DatabaseReader.d.ts +1 -1
  6. package/dist/DatabaseSchema.d.ts +5 -10
  7. package/dist/DatabaseSchema.d.ts.map +1 -1
  8. package/dist/DatabaseSchema.js +5 -6
  9. package/dist/DatabaseSchema.js.map +1 -1
  10. package/dist/Document.d.ts.map +1 -1
  11. package/dist/Document.js +35 -23
  12. package/dist/Document.js.map +1 -1
  13. package/dist/Handler.d.ts +1 -1
  14. package/dist/Handler.d.ts.map +1 -1
  15. package/dist/Handler.js.map +1 -1
  16. package/dist/MutationRunner.d.ts +5 -16
  17. package/dist/MutationRunner.d.ts.map +1 -1
  18. package/dist/MutationRunner.js +2 -12
  19. package/dist/MutationRunner.js.map +1 -1
  20. package/dist/QueryRunner.d.ts +3 -5
  21. package/dist/QueryRunner.d.ts.map +1 -1
  22. package/dist/QueryRunner.js.map +1 -1
  23. package/dist/RegisteredConvexFunction.d.ts +6 -6
  24. package/dist/RegisteredConvexFunction.d.ts.map +1 -1
  25. package/dist/RegisteredConvexFunction.js +36 -15
  26. package/dist/RegisteredConvexFunction.js.map +1 -1
  27. package/dist/RegisteredFunction.d.ts +24 -5
  28. package/dist/RegisteredFunction.d.ts.map +1 -1
  29. package/dist/RegisteredFunction.js +34 -5
  30. package/dist/RegisteredFunction.js.map +1 -1
  31. package/dist/RegisteredNodeFunction.js +3 -1
  32. package/dist/RegisteredNodeFunction.js.map +1 -1
  33. package/dist/SchemaToValidator.d.ts +8 -8
  34. package/dist/StorageActionWriter.d.ts +1 -1
  35. package/package.json +22 -18
  36. package/src/ActionRunner.ts +5 -1
  37. package/src/DatabaseSchema.ts +7 -10
  38. package/src/Document.ts +90 -58
  39. package/src/Handler.ts +5 -1
  40. package/src/MutationRunner.ts +6 -16
  41. package/src/QueryRunner.ts +5 -1
  42. package/src/RegisteredConvexFunction.ts +111 -94
  43. package/src/RegisteredFunction.ts +67 -22
  44. package/src/RegisteredNodeFunction.ts +4 -0
@@ -10,6 +10,7 @@ import {
10
10
  mutationGeneric,
11
11
  queryGeneric,
12
12
  } from "convex/server";
13
+ import type { Value } from "convex/values";
13
14
  import { Clock, Effect, Layer, Match, pipe, Schema } from "effect";
14
15
  import type * as Api from "./Api";
15
16
  import * as Auth from "./Auth";
@@ -49,9 +50,11 @@ export const make = <Api_ extends Api.AnyWithPropsWithRuntime<"Convex">>(
49
50
  );
50
51
 
51
52
  return genericFunction(
52
- queryFunction(api.databaseSchema, {
53
+ queryFunction({
54
+ databaseSchema: api.databaseSchema,
53
55
  args: functionProvenance.args,
54
56
  returns: functionProvenance.returns,
57
+ error: functionProvenance.error,
55
58
  handler: handler as Handler.AnyConfectProvenance,
56
59
  }),
57
60
  );
@@ -64,9 +67,11 @@ export const make = <Api_ extends Api.AnyWithPropsWithRuntime<"Convex">>(
64
67
  );
65
68
 
66
69
  return genericFunction(
67
- mutationFunction(api.databaseSchema, {
70
+ mutationFunction({
71
+ databaseSchema: api.databaseSchema,
68
72
  args: functionProvenance.args,
69
73
  returns: functionProvenance.returns,
74
+ error: functionProvenance.error,
70
75
  handler: handler as Handler.AnyConfectProvenance,
71
76
  }),
72
77
  );
@@ -82,6 +87,7 @@ export const make = <Api_ extends Api.AnyWithPropsWithRuntime<"Convex">>(
82
87
  convexActionFunction(api.databaseSchema, {
83
88
  args: functionProvenance.args,
84
89
  returns: functionProvenance.returns,
90
+ error: functionProvenance.error,
85
91
  handler: handler as Handler.AnyConfectProvenance,
86
92
  }),
87
93
  );
@@ -92,27 +98,28 @@ export const make = <Api_ extends Api.AnyWithPropsWithRuntime<"Convex">>(
92
98
  Match.exhaustive,
93
99
  );
94
100
 
95
- // Convex's query cache is invalidated by any Date.now() call during handler
96
- // execution. Effect's unsafeFork calls Date.now() when constructing a
97
- // FiberId.Runtime, which trips the cache for every confect-wrapped query. We
98
- // stub Date.now to 0 for the span of the handler; queries are forbidden from
99
- // relying on real time for correctness anyway.
100
- //
101
- // Users who explicitly want the real timestamp can still reach it via Effect's
102
- // Clock service (Clock.currentTimeMillis / Clock.currentTimeNanos). We provide
103
- // a Clock layer whose methods close over the *original* Date.now, so opting in
104
- // to Clock is an opt-in to worse caching but caching is not broken by default.
101
+ /**
102
+ * Convex's query cache is invalidated by any Date.now() call during handler
103
+ * execution. Effect's unsafeFork calls Date.now() when constructing a
104
+ * FiberId.Runtime, which trips the cache for every confect-wrapped query. We
105
+ * stub Date.now to 0 for the span of the handler; queries are forbidden from
106
+ * relying on real time for correctness anyway.
107
+ *
108
+ * Users who explicitly want the real timestamp can still reach it via Effect's
109
+ * Clock service (Clock.currentTimeMillis/Clock.currentTimeNanos). We provide a
110
+ * Clock whose user-facing Effects call realDateNow (Convex's tracker) directly,
111
+ * making Clock an explicit opt-in to cache invalidation. The unsafe methods
112
+ * used internally by Effect (logging, span events, scheduler) return constants
113
+ * so they never touch the tracker—caching is not broken by default.
114
+ */
105
115
  const unpatchedClock = (realDateNow: () => number): Clock.Clock => {
106
- const bigint1e6 = BigInt(1_000_000);
107
- const unsafeCurrentTimeMillis = () => realDateNow();
108
- const unsafeCurrentTimeNanos = () => BigInt(realDateNow()) * bigint1e6;
109
116
  const defaultClock = Clock.make();
110
117
  return {
111
118
  ...defaultClock,
112
- unsafeCurrentTimeMillis,
113
- unsafeCurrentTimeNanos,
114
- currentTimeMillis: Effect.sync(unsafeCurrentTimeMillis),
115
- currentTimeNanos: Effect.sync(unsafeCurrentTimeNanos),
119
+ unsafeCurrentTimeMillis: () => 0,
120
+ unsafeCurrentTimeNanos: () => 0n,
121
+ currentTimeMillis: Effect.sync(() => realDateNow()),
122
+ currentTimeNanos: Effect.sync(() => BigInt(realDateNow()) * 1_000_000n),
116
123
  };
117
124
  };
118
125
 
@@ -136,30 +143,31 @@ const queryFunction = <
136
143
  Returns,
137
144
  ConvexReturns,
138
145
  E,
139
- >(
140
- databaseSchema: DatabaseSchema_,
141
- {
142
- args,
143
- returns,
144
- handler,
145
- }: {
146
- args: Schema.Schema<Args, ConvexArgs>;
147
- returns: Schema.Schema<Returns, ConvexReturns>;
148
- handler: (
149
- a: Args,
150
- ) => Effect.Effect<
151
- Returns,
152
- E,
153
- | DatabaseReader.DatabaseReader<DatabaseSchema_>
154
- | Auth.Auth
155
- | StorageReader
156
- | QueryRunner.QueryRunner
157
- | QueryCtx.QueryCtx<
158
- DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
159
- >
160
- >;
161
- },
162
- ) => ({
146
+ >({
147
+ databaseSchema,
148
+ args,
149
+ returns,
150
+ error,
151
+ handler,
152
+ }: {
153
+ databaseSchema: DatabaseSchema_;
154
+ args: Schema.Schema<Args, ConvexArgs>;
155
+ returns: Schema.Schema<Returns, ConvexReturns>;
156
+ error: Schema.Schema<Error, Value> | undefined;
157
+ handler: (
158
+ a: Args,
159
+ ) => Effect.Effect<
160
+ Returns,
161
+ E,
162
+ | DatabaseReader.DatabaseReader<DatabaseSchema_>
163
+ | Auth.Auth
164
+ | StorageReader
165
+ | QueryRunner.QueryRunner
166
+ | QueryCtx.QueryCtx<
167
+ DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
168
+ >
169
+ >;
170
+ }) => ({
163
171
  args: SchemaToValidator.compileArgsSchema(args),
164
172
  returns: SchemaToValidator.compileReturnsSchema(returns),
165
173
  handler: (
@@ -169,35 +177,37 @@ const queryFunction = <
169
177
  actualArgs: ConvexArgs,
170
178
  ): Promise<ConvexReturns> =>
171
179
  withStubbedDateNow((clock) =>
172
- pipe(
173
- actualArgs,
174
- Schema.decode(args),
175
- Effect.orDie,
176
- Effect.andThen((decodedArgs) =>
177
- pipe(
178
- handler(decodedArgs),
179
- Effect.provide(
180
- Layer.mergeAll(
181
- DatabaseReader.layer(databaseSchema, ctx.db),
182
- Auth.layer(ctx.auth),
183
- StorageReader.layer(ctx.storage),
184
- QueryRunner.layer(ctx.runQuery),
185
- Layer.succeed(
186
- QueryCtx.QueryCtx<
187
- DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
188
- >(),
189
- ctx,
190
- ),
191
- Layer.setConfigProvider(ConvexConfigProvider.make()),
180
+ Effect.gen(function* () {
181
+ const decodedArgs = yield* pipe(
182
+ actualArgs,
183
+ Schema.decode(args),
184
+ Effect.orDie,
185
+ );
186
+ const decodedReturns = yield* handler(decodedArgs).pipe(
187
+ Effect.provide(
188
+ Layer.mergeAll(
189
+ DatabaseReader.layer(databaseSchema, ctx.db),
190
+ Auth.layer(ctx.auth),
191
+ StorageReader.layer(ctx.storage),
192
+ QueryRunner.layer(ctx.runQuery),
193
+ Layer.succeed(
194
+ QueryCtx.QueryCtx<
195
+ DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
196
+ >(),
197
+ ctx,
192
198
  ),
199
+ Layer.setConfigProvider(ConvexConfigProvider.make()),
193
200
  ),
194
201
  ),
195
- ),
196
- Effect.andThen((convexReturns) =>
197
- Schema.encodeUnknown(returns)(convexReturns),
198
- ),
202
+ );
203
+ return yield* pipe(
204
+ decodedReturns,
205
+ Schema.encode(returns),
206
+ Effect.orDie,
207
+ );
208
+ }).pipe(
199
209
  Effect.withClock(clock),
200
- Effect.runPromise,
210
+ RegisteredFunction.runHandlerPromise(error),
201
211
  ),
202
212
  ),
203
213
  });
@@ -236,42 +246,46 @@ export type MutationServices<Schema extends DatabaseSchema.AnyWithProps> =
236
246
  | MutationCtx.MutationCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>;
237
247
 
238
248
  const mutationFunction = <
239
- Schema extends DatabaseSchema.AnyWithProps,
249
+ DatabaseSchema_ extends DatabaseSchema.AnyWithProps,
240
250
  Args,
241
251
  ConvexArgs extends DefaultFunctionArgs,
242
252
  Returns,
243
253
  ConvexReturns,
244
254
  E,
245
- >(
246
- schema: Schema,
247
- {
248
- args,
249
- returns,
250
- handler,
251
- }: {
252
- args: Schema.Schema<Args, ConvexArgs>;
253
- returns: Schema.Schema<Returns, ConvexReturns>;
254
- handler: (a: Args) => Effect.Effect<Returns, E, MutationServices<Schema>>;
255
- },
256
- ) => ({
255
+ >({
256
+ databaseSchema,
257
+ args,
258
+ returns,
259
+ error,
260
+ handler,
261
+ }: {
262
+ databaseSchema: DatabaseSchema_;
263
+ args: Schema.Schema<Args, ConvexArgs>;
264
+ returns: Schema.Schema<Returns, ConvexReturns>;
265
+ error: Schema.Schema<Error, Value> | undefined;
266
+ handler: (
267
+ a: Args,
268
+ ) => Effect.Effect<Returns, E, MutationServices<DatabaseSchema_>>;
269
+ }) => ({
257
270
  args: SchemaToValidator.compileArgsSchema(args),
258
271
  returns: SchemaToValidator.compileReturnsSchema(returns),
259
272
  handler: (
260
- ctx: GenericMutationCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
273
+ ctx: GenericMutationCtx<
274
+ DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
275
+ >,
261
276
  actualArgs: ConvexArgs,
262
277
  ): Promise<ConvexReturns> =>
263
- pipe(
264
- actualArgs,
265
- Schema.decode(args),
266
- Effect.orDie,
267
- Effect.andThen((decodedArgs) =>
268
- handler(decodedArgs).pipe(Effect.provide(mutationLayer(schema, ctx))),
269
- ),
270
- Effect.andThen((convexReturns) =>
271
- Schema.encodeUnknown(returns)(convexReturns),
272
- ),
273
- Effect.runPromise,
274
- ),
278
+ Effect.gen(function* () {
279
+ const decodedArgs = yield* pipe(
280
+ actualArgs,
281
+ Schema.decode(args),
282
+ Effect.orDie,
283
+ );
284
+ const decodedReturns = yield* handler(decodedArgs).pipe(
285
+ Effect.provide(mutationLayer(databaseSchema, ctx)),
286
+ );
287
+ return yield* pipe(decodedReturns, Schema.encode(returns), Effect.orDie);
288
+ }).pipe(RegisteredFunction.runHandlerPromise(error)),
275
289
  });
276
290
 
277
291
  const convexActionFunction = <
@@ -286,10 +300,12 @@ const convexActionFunction = <
286
300
  {
287
301
  args,
288
302
  returns,
303
+ error,
289
304
  handler,
290
305
  }: {
291
306
  args: Schema.Schema<Args, ConvexArgs>;
292
307
  returns: Schema.Schema<Returns, ConvexReturns>;
308
+ error: Schema.Schema.AnyNoContext | undefined;
293
309
  handler: (
294
310
  a: Args,
295
311
  ) => Effect.Effect<
@@ -302,6 +318,7 @@ const convexActionFunction = <
302
318
  RegisteredFunction.actionFunctionBase({
303
319
  args,
304
320
  returns,
321
+ error,
305
322
  handler,
306
323
  createLayer: (ctx) =>
307
324
  Layer.mergeAll(
@@ -8,7 +8,9 @@ import {
8
8
  type RegisteredMutation,
9
9
  type RegisteredQuery,
10
10
  } from "convex/server";
11
- import { Effect, Layer, pipe, Schema } from "effect";
11
+ import type { Value } from "convex/values";
12
+ import { ConvexError } from "convex/values";
13
+ import { Effect, Either, Layer, pipe, Schema } from "effect";
12
14
  import * as ActionCtx from "./ActionCtx";
13
15
  import * as ActionRunner from "./ActionRunner";
14
16
  import * as Auth from "./Auth";
@@ -18,9 +20,9 @@ import * as MutationRunner from "./MutationRunner";
18
20
  import * as QueryRunner from "./QueryRunner";
19
21
  import * as Scheduler from "./Scheduler";
20
22
  import * as SchemaToValidator from "./SchemaToValidator";
21
- import { StorageActionWriter } from "./StorageActionWriter";
22
- import { StorageReader } from "./StorageReader";
23
- import { StorageWriter } from "./StorageWriter";
23
+ import * as StorageActionWriter from "./StorageActionWriter";
24
+ import * as StorageReader from "./StorageReader";
25
+ import * as StorageWriter from "./StorageWriter";
24
26
  import * as VectorSearch from "./VectorSearch";
25
27
 
26
28
  export type Any =
@@ -111,6 +113,48 @@ export type RegisteredFunction<
111
113
  ? ConfectRegisteredFunction<FunctionSpec_>
112
114
  : never;
113
115
 
116
+ /**
117
+ * Run the `Effect` as a `Promise`. The error schema acts as an allowlist of
118
+ * failures that may be surfaced to the client as a `ConvexError`:
119
+ *
120
+ * - With a schema: typed errors are schema-encoded and wrapped in a
121
+ * `ConvexError`, then thrown so Convex surfaces the data to the client.
122
+ * `Effect.either` escapes the failure channel before `runPromise` so the thrown
123
+ * `ConvexError` retains its `Symbol.for("ConvexError")` identity instead of
124
+ * being wrapped in Effect's `FiberFailure`.
125
+ *
126
+ * - Without a schema: every failure is converted to a defect via
127
+ * `Effect.orDie`, so nothing—not even a `ConvexError` the handler placed in its
128
+ * error channel—reaches the client as a `ConvexError`. The fiber dies and
129
+ * `runPromise` rejects with a generic failure.
130
+ */
131
+ export const runHandlerPromise =
132
+ (errorSchema: Schema.Schema.AnyNoContext | undefined) =>
133
+ <A, E>(effect: Effect.Effect<A, E>): Promise<A> => {
134
+ if (errorSchema === undefined) {
135
+ return Effect.runPromise(Effect.orDie(effect));
136
+ }
137
+ const withConvexError = effect.pipe(
138
+ Effect.catchAll((typedError) =>
139
+ pipe(
140
+ Schema.encode(errorSchema)(typedError),
141
+ Effect.orDie,
142
+ Effect.andThen((encodedError) =>
143
+ Effect.fail(new ConvexError(encodedError)),
144
+ ),
145
+ ),
146
+ ),
147
+ );
148
+ return Effect.runPromise(Effect.either(withConvexError)).then(
149
+ Either.match({
150
+ onLeft: (error) => {
151
+ throw error;
152
+ },
153
+ onRight: (value) => value,
154
+ }),
155
+ );
156
+ };
157
+
114
158
  export const actionFunctionBase = <
115
159
  Schema extends DatabaseSchema.AnyWithProps,
116
160
  Args,
@@ -122,11 +166,13 @@ export const actionFunctionBase = <
122
166
  >({
123
167
  args,
124
168
  returns,
169
+ error,
125
170
  handler,
126
171
  createLayer,
127
172
  }: {
128
173
  args: Schema.Schema<Args, ConvexArgs>;
129
174
  returns: Schema.Schema<Returns, ConvexReturns>;
175
+ error: Schema.Schema<Error, Value> | undefined;
130
176
  handler: (a: Args) => Effect.Effect<Returns, E, R>;
131
177
  createLayer: (
132
178
  ctx: GenericActionCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
@@ -138,18 +184,17 @@ export const actionFunctionBase = <
138
184
  ctx: GenericActionCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
139
185
  actualArgs: ConvexArgs,
140
186
  ): Promise<ConvexReturns> =>
141
- pipe(
142
- actualArgs,
143
- Schema.decode(args),
144
- Effect.orDie,
145
- Effect.andThen((decodedArgs) =>
146
- handler(decodedArgs).pipe(Effect.provide(createLayer(ctx))),
147
- ),
148
- Effect.andThen((convexReturns) =>
149
- Schema.encodeUnknown(returns)(convexReturns),
150
- ),
151
- Effect.runPromise,
152
- ),
187
+ Effect.gen(function* () {
188
+ const decodedArgs = yield* pipe(
189
+ actualArgs,
190
+ Schema.decode(args),
191
+ Effect.orDie,
192
+ );
193
+ const decodedReturns = yield* handler(decodedArgs).pipe(
194
+ Effect.provide(createLayer(ctx)),
195
+ );
196
+ return yield* pipe(decodedReturns, Schema.encode(returns), Effect.orDie);
197
+ }).pipe(runHandlerPromise(error)),
153
198
  });
154
199
 
155
200
  export type ActionServices<
@@ -157,9 +202,9 @@ export type ActionServices<
157
202
  > =
158
203
  | Scheduler.Scheduler
159
204
  | Auth.Auth
160
- | StorageReader
161
- | StorageWriter
162
- | StorageActionWriter
205
+ | StorageReader.StorageReader
206
+ | StorageWriter.StorageWriter
207
+ | StorageActionWriter.StorageActionWriter
163
208
  | QueryRunner.QueryRunner
164
209
  | MutationRunner.MutationRunner
165
210
  | ActionRunner.ActionRunner
@@ -179,9 +224,9 @@ export const actionLayer = <
179
224
  Layer.mergeAll(
180
225
  Scheduler.layer(ctx.scheduler),
181
226
  Auth.layer(ctx.auth),
182
- StorageReader.layer(ctx.storage),
183
- StorageWriter.layer(ctx.storage),
184
- StorageActionWriter.layer(ctx.storage),
227
+ StorageReader.StorageReader.layer(ctx.storage),
228
+ StorageWriter.StorageWriter.layer(ctx.storage),
229
+ StorageActionWriter.StorageActionWriter.layer(ctx.storage),
185
230
  QueryRunner.layer(ctx.runQuery),
186
231
  MutationRunner.layer(ctx.runMutation),
187
232
  ActionRunner.layer(ctx.runAction),
@@ -33,6 +33,7 @@ export const make = <Api_ extends Api.AnyWithPropsWithRuntime<"Node">>(
33
33
  nodeActionFunction(api.databaseSchema, {
34
34
  args: functionProvenance.args,
35
35
  returns: functionProvenance.returns,
36
+ error: functionProvenance.error,
36
37
  handler: handler as Handler.AnyConfectProvenance,
37
38
  }),
38
39
  );
@@ -52,10 +53,12 @@ const nodeActionFunction = <
52
53
  {
53
54
  args,
54
55
  returns,
56
+ error,
55
57
  handler,
56
58
  }: {
57
59
  args: Schema.Schema<Args, ConvexArgs>;
58
60
  returns: Schema.Schema<Returns, ConvexReturns>;
61
+ error: Schema.Schema.AnyNoContext | undefined;
59
62
  handler: (
60
63
  a: Args,
61
64
  ) => Effect.Effect<
@@ -69,6 +72,7 @@ const nodeActionFunction = <
69
72
  RegisteredFunction.actionFunctionBase({
70
73
  args,
71
74
  returns,
75
+ error,
72
76
  handler,
73
77
  createLayer: (ctx) =>
74
78
  Layer.mergeAll(