@gaganref/confect-server 10.3.1-fork.9 → 10.3.1-fork.90

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 (183) hide show
  1. package/dist/ActionCall.d.ts +30 -0
  2. package/dist/ActionCall.d.ts.map +1 -0
  3. package/dist/ActionCall.js +41 -0
  4. package/dist/ActionCall.js.map +1 -0
  5. package/dist/ActionRunner.d.ts +1 -1
  6. package/dist/ActionRunner.d.ts.map +1 -1
  7. package/dist/ActionRunner.js +3 -3
  8. package/dist/ActionRunner.js.map +1 -1
  9. package/dist/AiGatewayDecisionClient.js +1 -1
  10. package/dist/AiGatewayLanguageClient.js +1 -1
  11. package/dist/Auth.d.ts +31 -2
  12. package/dist/Auth.d.ts.map +1 -1
  13. package/dist/Auth.js +23 -3
  14. package/dist/Auth.js.map +1 -1
  15. package/dist/Callback.d.ts +1 -1
  16. package/dist/Callback.d.ts.map +1 -1
  17. package/dist/Callback.js +6 -6
  18. package/dist/Callback.js.map +1 -1
  19. package/dist/ComponentCtx.d.ts +4 -0
  20. package/dist/ComponentCtx.d.ts.map +1 -1
  21. package/dist/ConvexConfigProvider.js +2 -2
  22. package/dist/ConvexFunctionDefinitions.d.ts.map +1 -1
  23. package/dist/ConvexFunctionDefinitions.js +4 -4
  24. package/dist/ConvexFunctionDefinitions.js.map +1 -1
  25. package/dist/ConvexLogger.js +1 -1
  26. package/dist/CronJobs.d.ts.map +1 -1
  27. package/dist/CronJobs.js +7 -6
  28. package/dist/CronJobs.js.map +1 -1
  29. package/dist/DatabaseSchema.js +2 -2
  30. package/dist/DatabaseWriter.d.ts +44 -2
  31. package/dist/DatabaseWriter.d.ts.map +1 -1
  32. package/dist/DatabaseWriter.js +59 -9
  33. package/dist/DatabaseWriter.js.map +1 -1
  34. package/dist/ExecutionMetadata.js +1 -1
  35. package/dist/FunctionImpl.js +2 -2
  36. package/dist/FunctionRegistryItem.d.ts +1 -0
  37. package/dist/FunctionRegistryItem.d.ts.map +1 -1
  38. package/dist/FunctionRegistryItem.js +2 -1
  39. package/dist/FunctionRegistryItem.js.map +1 -1
  40. package/dist/GroupImpl.d.ts +21 -2
  41. package/dist/GroupImpl.d.ts.map +1 -1
  42. package/dist/GroupImpl.js +21 -22
  43. package/dist/GroupImpl.js.map +1 -1
  44. package/dist/GroupRegistry.d.ts +1 -0
  45. package/dist/GroupRegistry.d.ts.map +1 -1
  46. package/dist/GroupRegistry.js +10 -3
  47. package/dist/GroupRegistry.js.map +1 -1
  48. package/dist/Handler.d.ts +8 -4
  49. package/dist/Handler.d.ts.map +1 -1
  50. package/dist/Handler.js.map +1 -1
  51. package/dist/HttpRouter.d.ts +3 -0
  52. package/dist/HttpRouter.d.ts.map +1 -1
  53. package/dist/HttpRouter.js +29 -10
  54. package/dist/HttpRouter.js.map +1 -1
  55. package/dist/MiddlewareImpl.js +1 -1
  56. package/dist/MountInit.d.ts +31 -0
  57. package/dist/MountInit.d.ts.map +1 -0
  58. package/dist/MountInit.js +49 -0
  59. package/dist/MountInit.js.map +1 -0
  60. package/dist/MutationRunner.d.ts +1 -1
  61. package/dist/MutationRunner.d.ts.map +1 -1
  62. package/dist/MutationRunner.js +3 -3
  63. package/dist/MutationRunner.js.map +1 -1
  64. package/dist/MutationTransactionContext.d.ts.map +1 -1
  65. package/dist/MutationTransactionContext.js +4 -3
  66. package/dist/MutationTransactionContext.js.map +1 -1
  67. package/dist/OfficialComponent.d.ts +60 -0
  68. package/dist/OfficialComponent.d.ts.map +1 -0
  69. package/dist/OfficialComponent.js +72 -0
  70. package/dist/OfficialComponent.js.map +1 -0
  71. package/dist/OrderedQuery.d.ts +4 -5
  72. package/dist/OrderedQuery.d.ts.map +1 -1
  73. package/dist/OrderedQuery.js.map +1 -1
  74. package/dist/QueryInitializer.js +2 -2
  75. package/dist/QueryRunner.js +2 -2
  76. package/dist/QueryStream.d.ts +120 -12
  77. package/dist/QueryStream.d.ts.map +1 -1
  78. package/dist/QueryStream.js +143 -41
  79. package/dist/QueryStream.js.map +1 -1
  80. package/dist/QueryStreamCursor.js +1 -1
  81. package/dist/QueryStreamIndexPrefix.js +1 -1
  82. package/dist/QueryStreamIndexRange.js +2 -2
  83. package/dist/QueryStreamKey.js +1 -1
  84. package/dist/QueryStreamKeyBounds.js +2 -2
  85. package/dist/QueryStreamKeyLayout.js +2 -2
  86. package/dist/QueryStreamKeyValues.js +2 -2
  87. package/dist/QueryStreamPagination.js +1 -1
  88. package/dist/QueryStreamReadBudget.d.ts +3 -2
  89. package/dist/QueryStreamReadBudget.d.ts.map +1 -1
  90. package/dist/QueryStreamReadBudget.js +8 -5
  91. package/dist/QueryStreamReadBudget.js.map +1 -1
  92. package/dist/QueryTransactionContext.js +2 -2
  93. package/dist/RegisteredConvexFunction.d.ts +10 -5
  94. package/dist/RegisteredConvexFunction.d.ts.map +1 -1
  95. package/dist/RegisteredConvexFunction.js +49 -29
  96. package/dist/RegisteredConvexFunction.js.map +1 -1
  97. package/dist/RegisteredFunction.d.ts +48 -22
  98. package/dist/RegisteredFunction.d.ts.map +1 -1
  99. package/dist/RegisteredFunction.js +163 -42
  100. package/dist/RegisteredFunction.js.map +1 -1
  101. package/dist/RegisteredNodeFunction.d.ts +1 -1
  102. package/dist/RegisteredNodeFunction.d.ts.map +1 -1
  103. package/dist/RegisteredNodeFunction.js +10 -6
  104. package/dist/RegisteredNodeFunction.js.map +1 -1
  105. package/dist/RegistryItems.d.ts +7 -0
  106. package/dist/RegistryItems.d.ts.map +1 -1
  107. package/dist/RegistryItems.js +8 -3
  108. package/dist/RegistryItems.js.map +1 -1
  109. package/dist/RequestMetadata.js +1 -1
  110. package/dist/Scheduler.d.ts +1 -1
  111. package/dist/Scheduler.d.ts.map +1 -1
  112. package/dist/Scheduler.js +14 -13
  113. package/dist/Scheduler.js.map +1 -1
  114. package/dist/Storage.js +1 -1
  115. package/dist/StorageActionWriter.js +1 -1
  116. package/dist/StorageReader.js +1 -1
  117. package/dist/StorageWriter.js +1 -1
  118. package/dist/Table.d.ts +6 -6
  119. package/dist/Table.d.ts.map +1 -1
  120. package/dist/TableInfo.d.ts +7 -7
  121. package/dist/TableInfo.d.ts.map +1 -1
  122. package/dist/TableInfo.js.map +1 -1
  123. package/dist/TransactionMetadata.js +1 -1
  124. package/dist/VectorSearch.js +1 -1
  125. package/dist/index.d.ts +3 -0
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +21 -18
  128. package/dist/internal/AiGatewayDecisionClient.js +1 -1
  129. package/dist/internal/AiGatewayLanguageClient.js +1 -1
  130. package/dist/internal/AiGatewayServiceToken.js +1 -1
  131. package/dist/internal/DocumentPatch.d.ts +16 -0
  132. package/dist/internal/DocumentPatch.d.ts.map +1 -0
  133. package/dist/internal/DocumentPatch.js +36 -0
  134. package/dist/internal/DocumentPatch.js.map +1 -0
  135. package/dist/internal/httpBody.d.ts +13 -0
  136. package/dist/internal/httpBody.d.ts.map +1 -0
  137. package/dist/internal/httpBody.js +47 -0
  138. package/dist/internal/httpBody.js.map +1 -0
  139. package/dist/internal/httpOptions.d.ts +4 -0
  140. package/dist/internal/httpOptions.d.ts.map +1 -0
  141. package/dist/internal/httpOptions.js +83 -0
  142. package/dist/internal/httpOptions.js.map +1 -0
  143. package/dist/internal/observability.d.ts +5 -0
  144. package/dist/internal/observability.d.ts.map +1 -0
  145. package/dist/internal/observability.js +21 -0
  146. package/dist/internal/observability.js.map +1 -0
  147. package/dist/internal/reservedArgument.d.ts +8 -0
  148. package/dist/internal/reservedArgument.d.ts.map +1 -0
  149. package/dist/internal/reservedArgument.js +30 -0
  150. package/dist/internal/reservedArgument.js.map +1 -0
  151. package/package.json +5 -5
  152. package/src/ActionCall.ts +83 -0
  153. package/src/ActionRunner.ts +3 -2
  154. package/src/Auth.ts +49 -1
  155. package/src/Callback.ts +3 -2
  156. package/src/ComponentCtx.ts +2 -0
  157. package/src/ConvexFunctionDefinitions.ts +2 -1
  158. package/src/CronJobs.ts +2 -1
  159. package/src/DatabaseWriter.ts +190 -26
  160. package/src/FunctionRegistryItem.ts +4 -0
  161. package/src/GroupImpl.ts +61 -8
  162. package/src/GroupRegistry.ts +7 -3
  163. package/src/Handler.ts +21 -5
  164. package/src/HttpRouter.ts +69 -5
  165. package/src/MountInit.ts +80 -0
  166. package/src/MutationRunner.ts +3 -2
  167. package/src/MutationTransactionContext.ts +2 -1
  168. package/src/OfficialComponent.ts +168 -0
  169. package/src/OrderedQuery.ts +4 -5
  170. package/src/QueryStream.ts +391 -61
  171. package/src/QueryStreamReadBudget.ts +19 -6
  172. package/src/RegisteredConvexFunction.ts +39 -2
  173. package/src/RegisteredFunction.ts +305 -49
  174. package/src/RegisteredNodeFunction.ts +11 -1
  175. package/src/RegistryItems.ts +31 -0
  176. package/src/Scheduler.ts +17 -19
  177. package/src/TableInfo.ts +18 -22
  178. package/src/index.ts +3 -0
  179. package/src/internal/DocumentPatch.ts +87 -0
  180. package/src/internal/httpBody.ts +72 -0
  181. package/src/internal/httpOptions.ts +154 -0
  182. package/src/internal/observability.ts +27 -0
  183. package/src/internal/reservedArgument.ts +56 -0
@@ -61,12 +61,14 @@ export const prepare = (
61
61
  databaseSchema: DatabaseSchema.AnyWithProps,
62
62
  item: FunctionRegistryItem.ConfectFunctionRegistryItem,
63
63
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware> = [],
64
+ observability?: Layer.Layer<never>,
64
65
  ) =>
65
66
  Match.value(item.functionType).pipe(
66
67
  Match.when("query", () => {
67
68
  const definition = queryFunction({
68
69
  databaseSchema,
69
70
  name: item.name,
71
+ observability,
70
72
  functionVisibility: item.functionVisibility,
71
73
  args: item.args,
72
74
  returns: item.returns,
@@ -84,6 +86,8 @@ export const prepare = (
84
86
  const definition = mutationFunction({
85
87
  databaseSchema,
86
88
  name: item.name,
89
+ reservedArgument: item.reservedArgument,
90
+ observability,
87
91
  functionVisibility: item.functionVisibility,
88
92
  args: item.args,
89
93
  returns: item.returns,
@@ -100,6 +104,8 @@ export const prepare = (
100
104
  Match.when("action", () => {
101
105
  const definition = convexActionFunction(databaseSchema, {
102
106
  name: item.name,
107
+ reservedArgument: item.reservedArgument,
108
+ observability,
103
109
  functionVisibility: item.functionVisibility,
104
110
  args: item.args,
105
111
  returns: item.returns,
@@ -188,6 +194,7 @@ const queryFunction = <
188
194
  error,
189
195
  handler,
190
196
  resolvedMiddlewares,
197
+ observability,
191
198
  }: {
192
199
  databaseSchema: DatabaseSchema_;
193
200
  name: string;
@@ -199,6 +206,7 @@ const queryFunction = <
199
206
  a: Args,
200
207
  ) => Effect.Effect<Returns, E, Handler.QueryServices<DatabaseSchema_>>;
201
208
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware>;
209
+ observability?: Layer.Layer<never> | undefined;
202
210
  }) => ({
203
211
  args: compileArgsSchema(args, databaseSchema.target.scope),
204
212
  returns: compileReturnsSchema(returns, databaseSchema.target.scope),
@@ -258,6 +266,11 @@ const queryFunction = <
258
266
  RegisteredFunction.combineErrorSchemas(error, resolvedMiddlewares),
259
267
  {
260
268
  scheduler: new EffectScheduler.MixedScheduler("sync"),
269
+ invocation: Effect.promise(() => ctx.meta.getFunctionMetadata()),
270
+ clock: observability === undefined ? undefined : queryClock,
271
+ observability,
272
+ meta: ctx.meta,
273
+ functionType: "query",
261
274
  },
262
275
  ),
263
276
  ),
@@ -274,6 +287,7 @@ export const mutationLayer = <Schema extends DatabaseSchema.AnyWithProps>(
274
287
  RequestMetadata.layer(ctx.meta),
275
288
  TransactionMetadata.layer(ctx.meta),
276
289
  Auth.layerForTarget<Schema["target"]>(schema.target, ctx.auth),
290
+ Auth.mutationLayer(ctx.auth),
277
291
  Scheduler.layer<DatabaseSchema.Scope<Schema>>(
278
292
  ctx.scheduler,
279
293
  schema.target.scope,
@@ -313,9 +327,12 @@ const mutationFunction = <
313
327
  error,
314
328
  handler,
315
329
  resolvedMiddlewares,
330
+ observability,
331
+ reservedArgument,
316
332
  }: {
317
333
  databaseSchema: DatabaseSchema_;
318
334
  name: string;
335
+ reservedArgument?: string | undefined;
319
336
  functionVisibility: FunctionVisibility;
320
337
  args: Schema.Codec<Args, ConvexArgs>;
321
338
  returns: Schema.Codec<Returns, ConvexReturns>;
@@ -324,8 +341,14 @@ const mutationFunction = <
324
341
  a: Args,
325
342
  ) => Effect.Effect<Returns, E, MutationServices<DatabaseSchema_>>;
326
343
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware>;
344
+ observability?: Layer.Layer<never> | undefined;
327
345
  }) => ({
328
- args: compileArgsSchema(args, databaseSchema.target.scope),
346
+ args: compileArgsSchema(
347
+ args,
348
+ databaseSchema.target.scope,
349
+ reservedArgument,
350
+ name,
351
+ ),
329
352
  returns: compileReturnsSchema(returns, databaseSchema.target.scope),
330
353
  handler: (
331
354
  ctx: GenericMutationCtx<
@@ -335,7 +358,7 @@ const mutationFunction = <
335
358
  ): Promise<ConvexReturns> =>
336
359
  Effect.gen(function* () {
337
360
  const decodedArgs = yield* pipe(
338
- actualArgs,
361
+ RegisteredFunction.domainArgs(actualArgs, reservedArgument),
339
362
  Schema.decodeUnknownEffect(args),
340
363
  Effect.catchTag("SchemaError", Effect.die),
341
364
  );
@@ -360,6 +383,14 @@ const mutationFunction = <
360
383
  RegisteredFunction.combineErrorSchemas(error, resolvedMiddlewares),
361
384
  {
362
385
  scheduler: new EffectScheduler.MixedScheduler("sync"),
386
+ invocation: RegisteredFunction.invocationMetadata(
387
+ ctx.meta,
388
+ actualArgs,
389
+ reservedArgument,
390
+ ),
391
+ observability,
392
+ meta: ctx.meta,
393
+ functionType: "mutation",
363
394
  },
364
395
  ),
365
396
  ),
@@ -382,8 +413,11 @@ const convexActionFunction = <
382
413
  error,
383
414
  handler,
384
415
  resolvedMiddlewares,
416
+ observability,
417
+ reservedArgument,
385
418
  }: {
386
419
  name: string;
420
+ reservedArgument?: string | undefined;
387
421
  functionVisibility: FunctionVisibility;
388
422
  args: Schema.Codec<Args, ConvexArgs>;
389
423
  returns: Schema.Codec<Returns, ConvexReturns>;
@@ -396,6 +430,7 @@ const convexActionFunction = <
396
430
  RegisteredFunction.ActionServices<DatabaseSchema_>
397
431
  >;
398
432
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware>;
433
+ observability?: Layer.Layer<never> | undefined;
399
434
  },
400
435
  ) =>
401
436
  RegisteredFunction.actionFunctionBase({
@@ -407,6 +442,8 @@ const convexActionFunction = <
407
442
  error,
408
443
  handler,
409
444
  resolvedMiddlewares,
445
+ observability,
446
+ reservedArgument,
410
447
  createLayer: (ctx) =>
411
448
  Layer.mergeAll(
412
449
  RegisteredFunction.actionLayer(schema, ctx),
@@ -1,3 +1,6 @@
1
+ export { Forward } from "./internal/reservedArgument.js";
2
+ import * as ActionCallSpec from "@confect/core/ActionCall";
3
+ import * as ActionCall from "./ActionCall.js";
1
4
  import type { FunctionSpec, RuntimeAndFunctionType } from "@confect/core";
2
5
  import * as IdScope from "@confect/core/IdScope";
3
6
  import type * as FunctionProvenance from "@confect/core/FunctionProvenance";
@@ -9,6 +12,9 @@ import {
9
12
  import {
10
13
  type DefaultFunctionArgs,
11
14
  type FunctionVisibility,
15
+ type FunctionMetadata,
16
+ type MutationMeta,
17
+ type QueryMeta,
12
18
  type GenericActionCtx,
13
19
  type GenericDataModel,
14
20
  type StorageReader as ConvexStorageReader,
@@ -19,8 +25,15 @@ import {
19
25
  import type { Value } from "convex/values";
20
26
  import { ConvexError } from "convex/values";
21
27
  import { pipe } from "effect/Function";
28
+ import * as Config from "effect/Config";
29
+ import * as Option from "effect/Option";
30
+ import * as ConvexConfigProvider from "./ConvexConfigProvider.js";
31
+ import * as Clock from "effect/Clock";
32
+ import * as Cause from "effect/Cause";
22
33
  import * as Console from "effect/Console";
34
+ import * as Context from "effect/Context";
23
35
  import * as Effect from "effect/Effect";
36
+ import * as Exit from "effect/Exit";
24
37
  import * as Result from "effect/Result";
25
38
  import * as Layer from "effect/Layer";
26
39
  import * as Schema from "effect/Schema";
@@ -41,9 +54,58 @@ import * as Storage from "./Storage.js";
41
54
  import * as StorageActionWriter from "./StorageActionWriter.js";
42
55
  import type * as StorageReader from "./StorageReader.js";
43
56
  import * as StorageWriter from "./StorageWriter.js";
57
+ import * as Tracer from "effect/Tracer";
58
+ import * as ObservabilityRuntime from "./internal/observability.js";
44
59
  import * as VectorSearch from "./VectorSearch.js";
45
60
  import type * as Handler from "./Handler.js";
46
61
 
62
+ export interface ReceivedValue {
63
+ readonly value: unknown;
64
+ readonly requestId: string;
65
+ readonly deployment: string | undefined;
66
+ readonly visibility?: FunctionVisibility;
67
+ }
68
+
69
+ export const Received = Context.Reference<ReceivedValue | undefined>(
70
+ "@confect/server/RegisteredFunction/Received",
71
+ { defaultValue: () => undefined },
72
+ );
73
+
74
+ export const invocationMetadata = (
75
+ meta: Pick<MutationMeta, "getFunctionMetadata" | "getRequestMetadata">,
76
+ args: DefaultFunctionArgs,
77
+ reservedArgument?: string,
78
+ ): Effect.Effect<FunctionMetadata & { readonly received?: ReceivedValue }> =>
79
+ Effect.gen(function* () {
80
+ const metadata = yield* Effect.promise(() => meta.getFunctionMetadata());
81
+ if (reservedArgument === undefined) return metadata;
82
+ const { requestId } = yield* Effect.promise(() =>
83
+ meta.getRequestMetadata(),
84
+ );
85
+ const deployment = yield* Config.String("CONVEX_CLOUD_URL").pipe(
86
+ Config.option,
87
+ Effect.map(Option.getOrUndefined),
88
+ Effect.provide(ConvexConfigProvider.layer),
89
+ Effect.orDie,
90
+ );
91
+ return {
92
+ ...metadata,
93
+ received: {
94
+ value: args[reservedArgument],
95
+ requestId,
96
+ deployment,
97
+ ...(metadata.visibility === undefined
98
+ ? {}
99
+ : { visibility: metadata.visibility }),
100
+ },
101
+ };
102
+ });
103
+
104
+ export const Observability = Context.Reference<Layer.Layer<never> | undefined>(
105
+ "@confect/server/Observability",
106
+ { defaultValue: () => undefined },
107
+ );
108
+
47
109
  export type Any =
48
110
  | RegisteredQuery<FunctionVisibility, DefaultFunctionArgs, any>
49
111
  | RegisteredMutation<FunctionVisibility, DefaultFunctionArgs, any>
@@ -182,71 +244,193 @@ export const combineErrorSchemas = (
182
244
  : Schema.Union(schemas);
183
245
  };
184
246
 
247
+ type InvocationMeta = Pick<QueryMeta, "getFunctionMetadata"> &
248
+ Partial<Pick<MutationMeta, "getRequestMetadata">>;
249
+
250
+ type InvocationFunctionType = "query" | "mutation" | "action";
251
+
185
252
  /**
186
- * Run the `Effect` as a `Promise`. The error schema acts as an allowlist of
187
- * failures that may be surfaced to the client as a `ConvexError`:
253
+ * What `@confect/test` installs on `globalThis` under {@link testInvocation}.
254
+ * `start` runs `run` with the services of the test invocation the handler
255
+ * belongs to, with the handler's `Date`, `Math.random` and
256
+ * `crypto.getRandomValues` pinned to that invocation, or answers `undefined`
257
+ * when none is bound. `functionType` decides how `Date` reads: a query or
258
+ * mutation keeps its start time, an action follows the clock. Production never
259
+ * installs it.
260
+ */
261
+ type TestInvocation = {
262
+ readonly start: <A>(
263
+ meta: InvocationMeta,
264
+ run: (services: Context.Context<never>) => Promise<A>,
265
+ functionType: InvocationFunctionType,
266
+ ) => Promise<A> | undefined;
267
+ };
268
+
269
+ const testInvocation = Symbol.for("@confect/test/Invocation");
270
+
271
+ /**
272
+ * Run the `Effect` as a `Promise`. Inspect the complete `Exit` before encoding
273
+ * a declared error so a typed failure cannot hide a defect.
188
274
  *
189
- * - With a schema: typed errors are schema-encoded and wrapped in a
190
- * `ConvexError`, then thrown so Convex surfaces the data to the client.
191
- * `Effect.either` escapes the failure channel before `runPromise` so the
192
- * thrown `ConvexError` retains its `Symbol.for("ConvexError")` identity
193
- * instead of being wrapped in Effect's `FiberFailure`.
194
- * - Without a schema: every failure is converted to a defect via `Effect.orDie`,
195
- * so nothing—not even a `ConvexError` the handler placed in its error
196
- * channel—reaches the client as a `ConvexError`. The fiber dies and
197
- * `runPromise` rejects with a generic failure.
275
+ * Any `Die` reason takes the defect path. Without a `Die`, the first `Fail` in
276
+ * cause order is schema-encoded and thrown as a `ConvexError` outside the
277
+ * Effect runtime so Convex surfaces its data to the client. Other failures and
278
+ * interruptions alongside that `Fail` are ignored. Without an error schema, the
279
+ * selected failure is treated as a defect.
198
280
  *
199
- * Either way, a `ConvexError` _defect_—thrown imperatively rather than placed
200
- * in the error channel, e.g. the pagination protocol's `InvalidCursor` signal
201
- * from `QueryStream.paginate`—is rethrown bare so it retains its identity and
202
- * Convex serializes its `data` to the client, matching how a thrown
203
- * `ConvexError` behaves in a plain Convex handler.
281
+ * A lone `ConvexError` defect or a selected undeclared `ConvexError` failure is
282
+ * rethrown bare so it keeps Convex's meaning. Pagination and Workpool rely on
283
+ * its data for their control signals. Other causes with a `Die`, interrupt-only
284
+ * or empty causes, and error-encoding failures reject with a plain `Error`
285
+ * whose message renders the complete cause and whose `cause` holds it for
286
+ * server diagnostics.
204
287
  *
205
288
  * A `scheduler` in `runOptions` must be passed here as a run option rather than
206
289
  * provided via `Effect.provideService` inside `effect`: the run option lands in
207
290
  * the fiber's root context, while a service provided within `effect` pops
208
- * before the `orDie`/`catch`/`result` wrappers this function adds. The fiber's
209
- * op counter survives context pops, so a cooperative yield can fire inside
210
- * those wrappers—only a root-context scheduler covers them.
291
+ * before the exit/encoding wrappers this function adds. The fiber's op counter
292
+ * survives context pops, so a cooperative yield can fire inside those wrappers.
293
+ * Only a root-context scheduler covers them.
294
+ *
295
+ * Under `@confect/test`, a handler reached from a bound test invocation starts
296
+ * with that invocation's services through `Effect.runPromiseWith`, on a fiber
297
+ * of its own. Every other handler starts with `Effect.runPromise`.
211
298
  */
212
299
  export const runHandlerPromise =
213
300
  (
214
301
  errorSchema: Schema.Codec<any, any> | undefined,
215
302
  runOptions?: {
216
303
  readonly scheduler?: EffectScheduler.Scheduler | undefined;
304
+ readonly clock?: Clock.Clock | undefined;
305
+ readonly invocation?:
306
+ | Effect.Effect<
307
+ FunctionMetadata & { readonly received?: ReceivedValue }
308
+ >
309
+ | undefined;
310
+ readonly observability?: Layer.Layer<never> | undefined;
311
+ readonly meta?: InvocationMeta | undefined;
312
+ readonly functionType?: InvocationFunctionType | undefined;
217
313
  },
218
314
  ) =>
219
315
  <A, E>(effect: Effect.Effect<A, E>): Promise<A> => {
220
- // A `ConvexError` defect escapes into the (escaped) failure channel so
221
- // the `throw` below rethrows it with its identity intact.
222
- const rethrowConvexErrorDefects = Effect.catchDefect(
223
- (defect: unknown): Effect.Effect<never, ConvexError<any>> =>
224
- defect instanceof ConvexError
225
- ? Effect.fail(defect)
226
- : Effect.die(defect),
316
+ const { observability, invocation, clock, meta, functionType } =
317
+ runOptions ?? {};
318
+ let encodedError: Exit.Exit<any, any> | undefined;
319
+ let undeclared: { readonly error: unknown } | undefined;
320
+ const observed = Effect.tapCause(effect, (cause) =>
321
+ Effect.gen(function* () {
322
+ const reason =
323
+ cause.reasons.find(Cause.isDieReason) ??
324
+ cause.reasons.find(Cause.isFailReason);
325
+ if (reason?._tag === "Fail" && errorSchema !== undefined) {
326
+ encodedError = yield* Effect.exit(
327
+ Schema.encodeEffect(errorSchema)(reason.error),
328
+ );
329
+ // An encoding failure is a defect, even when it dies with a ConvexError.
330
+ if (Exit.isFailure(encodedError)) {
331
+ const encoding = encodedError.cause;
332
+ return yield* Cause.squash(encoding) instanceof ConvexError
333
+ ? Effect.die(
334
+ new Error(Cause.pretty(encoding), { cause: encoding }),
335
+ )
336
+ : Effect.orDie(encodedError);
337
+ }
338
+ }
339
+ // With no error schema a failure is a defect, so the root span ends as one.
340
+ if (
341
+ reason?._tag === "Fail" &&
342
+ errorSchema === undefined &&
343
+ !(reason.error instanceof ConvexError)
344
+ ) {
345
+ undeclared = { error: reason.error };
346
+ return yield* Effect.die(reason.error);
347
+ }
348
+ }),
227
349
  );
228
-
229
- const withConvexError =
230
- errorSchema === undefined
231
- ? Effect.orDie(effect)
232
- : effect.pipe(
233
- Effect.catch((typedError) =>
234
- pipe(
235
- Schema.encodeEffect(errorSchema)(typedError),
236
- Effect.catchTag("SchemaError", Effect.die),
237
- Effect.andThen((encodedError) =>
238
- Effect.fail(new ConvexError(encodedError)),
239
- ),
350
+ const withConvexError: Effect.Effect<Result.Result<A, ConvexError<any>>> =
351
+ Effect.gen(function* () {
352
+ const exit = yield* Effect.exit(
353
+ observability === undefined || invocation === undefined
354
+ ? effect
355
+ : Effect.flatMap(
356
+ invocation,
357
+ ({ name, type, componentPath, received }) =>
358
+ Effect.useSpan(
359
+ name,
360
+ {
361
+ root: true,
362
+ attributes: { kind: type, componentPath },
363
+ },
364
+ (span) =>
365
+ Effect.provideService(observed, Tracer.ParentSpan, span),
366
+ ).pipe(Effect.provideService(Received, received)),
240
367
  ),
241
- ),
368
+ );
369
+ if (Exit.isSuccess(exit)) {
370
+ return Result.succeed(exit.value);
371
+ }
372
+ if (undeclared !== undefined)
373
+ return yield* Effect.die(undeclared.error);
374
+
375
+ const cause =
376
+ encodedError !== undefined && Exit.isFailure(encodedError)
377
+ ? encodedError.cause
378
+ : exit.cause;
379
+ const reason =
380
+ cause.reasons.find(Cause.isDieReason) ??
381
+ cause.reasons.find(Cause.isFailReason);
382
+ if (
383
+ encodedError === undefined &&
384
+ reason?._tag === "Die" &&
385
+ cause.reasons.length === 1 &&
386
+ reason.defect instanceof ConvexError
387
+ ) {
388
+ return Result.fail(reason.defect);
389
+ }
390
+
391
+ if (reason?._tag === "Fail") {
392
+ if (errorSchema === undefined) {
393
+ if (reason.error instanceof ConvexError) {
394
+ return Result.fail(reason.error);
395
+ }
396
+ return yield* Effect.die(reason.error);
397
+ }
398
+ const encoded =
399
+ encodedError ??
400
+ (yield* Effect.exit(
401
+ Schema.encodeEffect(errorSchema)(reason.error),
402
+ ));
403
+ if (Exit.isSuccess(encoded)) {
404
+ return Result.fail(new ConvexError(encoded.value));
405
+ }
406
+ return yield* Effect.die(
407
+ new Error(Cause.pretty(encoded.cause), { cause: encoded.cause }),
242
408
  );
243
- return Effect.runPromise(
244
- Effect.result(rethrowConvexErrorDefects(withConvexError)).pipe(
245
- Effect.provide(ConvexLogger.layer),
246
- Effect.provideService(Console.Console, globalThis.console),
409
+ }
410
+ return yield* Effect.die(new Error(Cause.pretty(cause), { cause }));
411
+ });
412
+ const program = withConvexError.pipe(
413
+ Effect.provide(
414
+ observability === undefined
415
+ ? ConvexLogger.layer
416
+ : ObservabilityRuntime.memoized(observability),
247
417
  ),
248
- runOptions,
249
- ).then(
418
+ Effect.provideService(Console.Console, globalThis.console),
419
+ clock === undefined
420
+ ? (self) => self
421
+ : Effect.provideService(Clock.Clock, clock),
422
+ );
423
+ const started =
424
+ meta === undefined || functionType === undefined
425
+ ? undefined
426
+ : (globalThis as { [testInvocation]?: TestInvocation })[
427
+ testInvocation
428
+ ]?.start(
429
+ meta,
430
+ (services) => Effect.runPromiseWith(services)(program, runOptions),
431
+ functionType,
432
+ );
433
+ return (started ?? Effect.runPromise(program, runOptions)).then(
250
434
  Result.match({
251
435
  onFailure: (error) => {
252
436
  throw error;
@@ -256,6 +440,16 @@ export const runHandlerPromise =
256
440
  );
257
441
  };
258
442
 
443
+ export const domainArgs = (
444
+ args: DefaultFunctionArgs,
445
+ reservedArgument?: string,
446
+ ): DefaultFunctionArgs =>
447
+ reservedArgument === undefined
448
+ ? args
449
+ : Object.fromEntries(
450
+ Object.entries(args).filter(([key]) => key !== reservedArgument),
451
+ );
452
+
259
453
  export const actionFunctionBase = <
260
454
  Schema extends DatabaseSchema.AnyWithProps,
261
455
  Args,
@@ -274,8 +468,11 @@ export const actionFunctionBase = <
274
468
  resolvedMiddlewares = [],
275
469
  createLayer,
276
470
  scope = IdScope.app,
471
+ observability,
472
+ reservedArgument,
277
473
  }: {
278
474
  name: string;
475
+ reservedArgument?: string | undefined;
279
476
  functionVisibility: FunctionVisibility;
280
477
  args: Schema.Codec<Args, ConvexArgs>;
281
478
  returns: Schema.Codec<Returns, ConvexReturns>;
@@ -286,8 +483,9 @@ export const actionFunctionBase = <
286
483
  ctx: GenericActionCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
287
484
  ) => Layer.Layer<R>;
288
485
  scope?: IdScope.IdScope;
486
+ observability?: Layer.Layer<never> | undefined;
289
487
  }) => ({
290
- args: compileArgsSchema(args, scope),
488
+ args: compileArgsSchema(args, scope, reservedArgument, name),
291
489
  returns: compileReturnsSchema(returns, scope),
292
490
  handler: (
293
491
  ctx: GenericActionCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
@@ -295,10 +493,16 @@ export const actionFunctionBase = <
295
493
  ): Promise<ConvexReturns> =>
296
494
  Effect.gen(function* () {
297
495
  const decodedArgs = yield* pipe(
298
- actualArgs,
496
+ domainArgs(actualArgs, reservedArgument),
299
497
  Schema.decodeUnknownEffect(args),
300
498
  Effect.catchTag("SchemaError", Effect.die),
301
499
  );
500
+ const restricted = resolvedMiddlewares.some(
501
+ ({ middlewareSpec }) =>
502
+ middlewareSpec.key === ActionCallSpec.Middleware.key,
503
+ )
504
+ ? restrict(ctx)
505
+ : undefined;
302
506
  // oxlint-disable-next-line effecttsgo/any-unknown-in-error-context -- Middleware errors are intentionally erased here and validated by runHandlerPromise against the combined error schema below.
303
507
  const decodedReturns = yield* applyMiddleware(
304
508
  handler(decodedArgs),
@@ -309,13 +513,21 @@ export const actionFunctionBase = <
309
513
  functionVisibility,
310
514
  args: decodedArgs,
311
515
  },
312
- ).pipe(Effect.provide(createLayer(ctx)));
516
+ ).pipe(Effect.provide(createLayer(restricted?.ctx ?? ctx)));
517
+ if (restricted !== undefined) yield* restricted.check;
313
518
  return yield* pipe(
314
519
  decodedReturns,
315
520
  Schema.encodeEffect(returns),
316
521
  Effect.catchTag("SchemaError", Effect.die),
317
522
  );
318
- }).pipe(runHandlerPromise(combineErrorSchemas(error, resolvedMiddlewares))),
523
+ }).pipe(
524
+ runHandlerPromise(combineErrorSchemas(error, resolvedMiddlewares), {
525
+ invocation: invocationMetadata(ctx.meta, actualArgs, reservedArgument),
526
+ observability,
527
+ meta: ctx.meta,
528
+ functionType: "action",
529
+ }),
530
+ ),
319
531
  });
320
532
 
321
533
  export type ActionServices<
@@ -389,3 +601,47 @@ export const storageLayer = <Scope extends IdScope.IdScope>(
389
601
  Storage.layer(reader) as Layer.Layer<
390
602
  Storage.Storage | StorageReader.ForScope<Scope>
391
603
  >;
604
+
605
+ /**
606
+ * Shares one dispatch budget across the entire middleware chain.
607
+ */
608
+ const restrict = <Model extends GenericDataModel>(
609
+ ctx: GenericActionCtx<Model>,
610
+ ) => {
611
+ let dispatched = false;
612
+ const claim = () => {
613
+ if (dispatched) throw new ActionCall.ActionCallAlreadyUsed();
614
+ dispatched = true;
615
+ };
616
+ const restricted: GenericActionCtx<Model> = {
617
+ ...ctx,
618
+ scheduler: {
619
+ runAfter: () => {
620
+ throw new ActionCall.SchedulingNotAllowed();
621
+ },
622
+ runAt: () => {
623
+ throw new ActionCall.SchedulingNotAllowed();
624
+ },
625
+ cancel: () => {
626
+ throw new ActionCall.SchedulingNotAllowed();
627
+ },
628
+ },
629
+ runQuery: (...args) => {
630
+ claim();
631
+ return ctx.runQuery(...args);
632
+ },
633
+ runMutation: (...args) => {
634
+ claim();
635
+ return ctx.runMutation(...args);
636
+ },
637
+ runAction: () => {
638
+ throw new ActionCall.NestedActionNotAllowed();
639
+ },
640
+ };
641
+ return {
642
+ ctx: restricted,
643
+ check: Effect.suspend(() =>
644
+ dispatched ? Effect.void : Effect.die(new ActionCall.ActionCallNotUsed()),
645
+ ),
646
+ };
647
+ };
@@ -23,7 +23,7 @@ export const buildForGroup = <Group extends GroupSpec.AnyWithProps>(
23
23
  databaseSchema: DatabaseSchema.AnyWithProps,
24
24
  groupLayer: Layer.Layer<GroupImpl.GroupImpl<"Finalized">>,
25
25
  ): RegisteredFunctions.RegisteredFunctionsForGroupSpec<Group> => {
26
- const { functionRegistryItems, middlewareRegistryItems } =
26
+ const { functionRegistryItems, middlewareRegistryItems, observability } =
27
27
  GroupRegistry.make(groupLayer);
28
28
  return mapLeaves<FunctionRegistryItem.AnyWithProps, RegisteredFunction.Any>(
29
29
  functionRegistryItems as {
@@ -38,6 +38,7 @@ export const buildForGroup = <Group extends GroupSpec.AnyWithProps>(
38
38
  databaseSchema,
39
39
  confect,
40
40
  ResolvedMiddleware.resolve(confect, middlewareRegistryItems),
41
+ observability,
41
42
  ),
42
43
  ),
43
44
  Match.exhaustive,
@@ -49,6 +50,7 @@ export const make = (
49
50
  databaseSchema: DatabaseSchema.AnyWithProps,
50
51
  item: FunctionRegistryItem.ConfectFunctionRegistryItem,
51
52
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware> = [],
53
+ observability?: Layer.Layer<never>,
52
54
  ): RegisteredFunction.Any => {
53
55
  const genericFunction = Match.value(item.functionVisibility).pipe(
54
56
  Match.when("public", () => actionGeneric),
@@ -59,6 +61,8 @@ export const make = (
59
61
  return genericFunction(
60
62
  nodeActionFunction(databaseSchema, {
61
63
  name: item.name,
64
+ reservedArgument: item.reservedArgument,
65
+ observability,
62
66
  functionVisibility: item.functionVisibility,
63
67
  args: item.args,
64
68
  returns: item.returns,
@@ -86,8 +90,11 @@ const nodeActionFunction = <
86
90
  error,
87
91
  handler,
88
92
  resolvedMiddlewares,
93
+ observability,
94
+ reservedArgument,
89
95
  }: {
90
96
  name: string;
97
+ reservedArgument?: string | undefined;
91
98
  functionVisibility: FunctionVisibility;
92
99
  args: Schema.Codec<Args, ConvexArgs>;
93
100
  returns: Schema.Codec<Returns, ConvexReturns>;
@@ -101,6 +108,7 @@ const nodeActionFunction = <
101
108
  | NodeServices.NodeServices
102
109
  >;
103
110
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware>;
111
+ observability?: Layer.Layer<never> | undefined;
104
112
  },
105
113
  ) =>
106
114
  RegisteredFunction.actionFunctionBase({
@@ -112,6 +120,8 @@ const nodeActionFunction = <
112
120
  error,
113
121
  handler,
114
122
  resolvedMiddlewares,
123
+ observability,
124
+ reservedArgument,
115
125
  createLayer: (ctx) =>
116
126
  Layer.mergeAll(
117
127
  RegisteredFunction.actionLayer(databaseSchema, ctx),