@gaganref/confect-server 10.3.1-fork.7 → 10.3.1-fork.70

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 (159) 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/ConvexConfigProvider.js +2 -2
  20. package/dist/ConvexFunctionDefinitions.d.ts.map +1 -1
  21. package/dist/ConvexFunctionDefinitions.js +4 -4
  22. package/dist/ConvexFunctionDefinitions.js.map +1 -1
  23. package/dist/ConvexLogger.js +1 -1
  24. package/dist/CronJobs.d.ts.map +1 -1
  25. package/dist/CronJobs.js +7 -6
  26. package/dist/CronJobs.js.map +1 -1
  27. package/dist/DatabaseSchema.js +2 -2
  28. package/dist/DatabaseWriter.d.ts +36 -2
  29. package/dist/DatabaseWriter.d.ts.map +1 -1
  30. package/dist/DatabaseWriter.js +55 -8
  31. package/dist/DatabaseWriter.js.map +1 -1
  32. package/dist/ExecutionMetadata.js +1 -1
  33. package/dist/FunctionImpl.js +2 -2
  34. package/dist/FunctionRegistryItem.d.ts +1 -0
  35. package/dist/FunctionRegistryItem.d.ts.map +1 -1
  36. package/dist/FunctionRegistryItem.js +2 -1
  37. package/dist/FunctionRegistryItem.js.map +1 -1
  38. package/dist/GroupImpl.d.ts +21 -2
  39. package/dist/GroupImpl.d.ts.map +1 -1
  40. package/dist/GroupImpl.js +21 -22
  41. package/dist/GroupImpl.js.map +1 -1
  42. package/dist/GroupRegistry.d.ts +1 -0
  43. package/dist/GroupRegistry.d.ts.map +1 -1
  44. package/dist/GroupRegistry.js +10 -3
  45. package/dist/GroupRegistry.js.map +1 -1
  46. package/dist/Handler.d.ts +8 -4
  47. package/dist/Handler.d.ts.map +1 -1
  48. package/dist/Handler.js.map +1 -1
  49. package/dist/HttpRouter.d.ts +2 -0
  50. package/dist/HttpRouter.d.ts.map +1 -1
  51. package/dist/HttpRouter.js +17 -8
  52. package/dist/HttpRouter.js.map +1 -1
  53. package/dist/MiddlewareImpl.js +1 -1
  54. package/dist/MutationRunner.d.ts +1 -1
  55. package/dist/MutationRunner.d.ts.map +1 -1
  56. package/dist/MutationRunner.js +3 -3
  57. package/dist/MutationRunner.js.map +1 -1
  58. package/dist/MutationTransactionContext.d.ts.map +1 -1
  59. package/dist/MutationTransactionContext.js +4 -3
  60. package/dist/MutationTransactionContext.js.map +1 -1
  61. package/dist/OfficialComponent.d.ts +60 -0
  62. package/dist/OfficialComponent.d.ts.map +1 -0
  63. package/dist/OfficialComponent.js +72 -0
  64. package/dist/OfficialComponent.js.map +1 -0
  65. package/dist/OrderedQuery.d.ts +4 -5
  66. package/dist/OrderedQuery.d.ts.map +1 -1
  67. package/dist/OrderedQuery.js.map +1 -1
  68. package/dist/QueryInitializer.js +2 -2
  69. package/dist/QueryRunner.js +2 -2
  70. package/dist/QueryStream.d.ts +120 -12
  71. package/dist/QueryStream.d.ts.map +1 -1
  72. package/dist/QueryStream.js +143 -41
  73. package/dist/QueryStream.js.map +1 -1
  74. package/dist/QueryStreamCursor.js +1 -1
  75. package/dist/QueryStreamIndexPrefix.js +1 -1
  76. package/dist/QueryStreamIndexRange.js +2 -2
  77. package/dist/QueryStreamKey.js +1 -1
  78. package/dist/QueryStreamKeyBounds.js +2 -2
  79. package/dist/QueryStreamKeyLayout.js +2 -2
  80. package/dist/QueryStreamKeyValues.js +2 -2
  81. package/dist/QueryStreamPagination.js +1 -1
  82. package/dist/QueryStreamReadBudget.d.ts +3 -2
  83. package/dist/QueryStreamReadBudget.d.ts.map +1 -1
  84. package/dist/QueryStreamReadBudget.js +8 -5
  85. package/dist/QueryStreamReadBudget.js.map +1 -1
  86. package/dist/QueryTransactionContext.js +2 -2
  87. package/dist/RegisteredConvexFunction.d.ts +10 -5
  88. package/dist/RegisteredConvexFunction.d.ts.map +1 -1
  89. package/dist/RegisteredConvexFunction.js +45 -29
  90. package/dist/RegisteredConvexFunction.js.map +1 -1
  91. package/dist/RegisteredFunction.d.ts +39 -22
  92. package/dist/RegisteredFunction.d.ts.map +1 -1
  93. package/dist/RegisteredFunction.js +155 -42
  94. package/dist/RegisteredFunction.js.map +1 -1
  95. package/dist/RegisteredNodeFunction.d.ts +1 -1
  96. package/dist/RegisteredNodeFunction.d.ts.map +1 -1
  97. package/dist/RegisteredNodeFunction.js +10 -6
  98. package/dist/RegisteredNodeFunction.js.map +1 -1
  99. package/dist/RegistryItems.d.ts +7 -0
  100. package/dist/RegistryItems.d.ts.map +1 -1
  101. package/dist/RegistryItems.js +8 -3
  102. package/dist/RegistryItems.js.map +1 -1
  103. package/dist/RequestMetadata.js +1 -1
  104. package/dist/Scheduler.d.ts +1 -1
  105. package/dist/Scheduler.d.ts.map +1 -1
  106. package/dist/Scheduler.js +14 -13
  107. package/dist/Scheduler.js.map +1 -1
  108. package/dist/Storage.js +1 -1
  109. package/dist/StorageActionWriter.js +1 -1
  110. package/dist/StorageReader.js +1 -1
  111. package/dist/StorageWriter.js +1 -1
  112. package/dist/TransactionMetadata.js +1 -1
  113. package/dist/VectorSearch.js +1 -1
  114. package/dist/index.d.ts +2 -0
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +20 -18
  117. package/dist/internal/AiGatewayDecisionClient.js +1 -1
  118. package/dist/internal/AiGatewayLanguageClient.js +1 -1
  119. package/dist/internal/AiGatewayServiceToken.js +1 -1
  120. package/dist/internal/DocumentPatch.d.ts +16 -0
  121. package/dist/internal/DocumentPatch.d.ts.map +1 -0
  122. package/dist/internal/DocumentPatch.js +36 -0
  123. package/dist/internal/DocumentPatch.js.map +1 -0
  124. package/dist/internal/observability.d.ts +5 -0
  125. package/dist/internal/observability.d.ts.map +1 -0
  126. package/dist/internal/observability.js +21 -0
  127. package/dist/internal/observability.js.map +1 -0
  128. package/dist/internal/reservedArgument.d.ts +8 -0
  129. package/dist/internal/reservedArgument.d.ts.map +1 -0
  130. package/dist/internal/reservedArgument.js +30 -0
  131. package/dist/internal/reservedArgument.js.map +1 -0
  132. package/package.json +5 -5
  133. package/src/ActionCall.ts +83 -0
  134. package/src/ActionRunner.ts +3 -2
  135. package/src/Auth.ts +49 -1
  136. package/src/Callback.ts +3 -2
  137. package/src/ConvexFunctionDefinitions.ts +2 -1
  138. package/src/CronJobs.ts +2 -1
  139. package/src/DatabaseWriter.ts +183 -25
  140. package/src/FunctionRegistryItem.ts +4 -0
  141. package/src/GroupImpl.ts +61 -8
  142. package/src/GroupRegistry.ts +7 -3
  143. package/src/Handler.ts +21 -5
  144. package/src/HttpRouter.ts +54 -4
  145. package/src/MutationRunner.ts +3 -2
  146. package/src/MutationTransactionContext.ts +2 -1
  147. package/src/OfficialComponent.ts +168 -0
  148. package/src/OrderedQuery.ts +4 -5
  149. package/src/QueryStream.ts +391 -61
  150. package/src/QueryStreamReadBudget.ts +19 -6
  151. package/src/RegisteredConvexFunction.ts +35 -2
  152. package/src/RegisteredFunction.ts +251 -45
  153. package/src/RegisteredNodeFunction.ts +11 -1
  154. package/src/RegistryItems.ts +31 -0
  155. package/src/Scheduler.ts +17 -19
  156. package/src/index.ts +2 -0
  157. package/src/internal/DocumentPatch.ts +87 -0
  158. package/src/internal/observability.ts +27 -0
  159. 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,9 @@ 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,
261
272
  },
262
273
  ),
263
274
  ),
@@ -274,6 +285,7 @@ export const mutationLayer = <Schema extends DatabaseSchema.AnyWithProps>(
274
285
  RequestMetadata.layer(ctx.meta),
275
286
  TransactionMetadata.layer(ctx.meta),
276
287
  Auth.layerForTarget<Schema["target"]>(schema.target, ctx.auth),
288
+ Auth.mutationLayer(ctx.auth),
277
289
  Scheduler.layer<DatabaseSchema.Scope<Schema>>(
278
290
  ctx.scheduler,
279
291
  schema.target.scope,
@@ -313,9 +325,12 @@ const mutationFunction = <
313
325
  error,
314
326
  handler,
315
327
  resolvedMiddlewares,
328
+ observability,
329
+ reservedArgument,
316
330
  }: {
317
331
  databaseSchema: DatabaseSchema_;
318
332
  name: string;
333
+ reservedArgument?: string | undefined;
319
334
  functionVisibility: FunctionVisibility;
320
335
  args: Schema.Codec<Args, ConvexArgs>;
321
336
  returns: Schema.Codec<Returns, ConvexReturns>;
@@ -324,8 +339,14 @@ const mutationFunction = <
324
339
  a: Args,
325
340
  ) => Effect.Effect<Returns, E, MutationServices<DatabaseSchema_>>;
326
341
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware>;
342
+ observability?: Layer.Layer<never> | undefined;
327
343
  }) => ({
328
- args: compileArgsSchema(args, databaseSchema.target.scope),
344
+ args: compileArgsSchema(
345
+ args,
346
+ databaseSchema.target.scope,
347
+ reservedArgument,
348
+ name,
349
+ ),
329
350
  returns: compileReturnsSchema(returns, databaseSchema.target.scope),
330
351
  handler: (
331
352
  ctx: GenericMutationCtx<
@@ -335,7 +356,7 @@ const mutationFunction = <
335
356
  ): Promise<ConvexReturns> =>
336
357
  Effect.gen(function* () {
337
358
  const decodedArgs = yield* pipe(
338
- actualArgs,
359
+ RegisteredFunction.domainArgs(actualArgs, reservedArgument),
339
360
  Schema.decodeUnknownEffect(args),
340
361
  Effect.catchTag("SchemaError", Effect.die),
341
362
  );
@@ -360,6 +381,12 @@ const mutationFunction = <
360
381
  RegisteredFunction.combineErrorSchemas(error, resolvedMiddlewares),
361
382
  {
362
383
  scheduler: new EffectScheduler.MixedScheduler("sync"),
384
+ invocation: RegisteredFunction.invocationMetadata(
385
+ ctx.meta,
386
+ actualArgs,
387
+ reservedArgument,
388
+ ),
389
+ observability,
363
390
  },
364
391
  ),
365
392
  ),
@@ -382,8 +409,11 @@ const convexActionFunction = <
382
409
  error,
383
410
  handler,
384
411
  resolvedMiddlewares,
412
+ observability,
413
+ reservedArgument,
385
414
  }: {
386
415
  name: string;
416
+ reservedArgument?: string | undefined;
387
417
  functionVisibility: FunctionVisibility;
388
418
  args: Schema.Codec<Args, ConvexArgs>;
389
419
  returns: Schema.Codec<Returns, ConvexReturns>;
@@ -396,6 +426,7 @@ const convexActionFunction = <
396
426
  RegisteredFunction.ActionServices<DatabaseSchema_>
397
427
  >;
398
428
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware>;
429
+ observability?: Layer.Layer<never> | undefined;
399
430
  },
400
431
  ) =>
401
432
  RegisteredFunction.actionFunctionBase({
@@ -407,6 +438,8 @@ const convexActionFunction = <
407
438
  error,
408
439
  handler,
409
440
  resolvedMiddlewares,
441
+ observability,
442
+ reservedArgument,
410
443
  createLayer: (ctx) =>
411
444
  Layer.mergeAll(
412
445
  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,8 @@ import {
9
12
  import {
10
13
  type DefaultFunctionArgs,
11
14
  type FunctionVisibility,
15
+ type FunctionMetadata,
16
+ type MutationMeta,
12
17
  type GenericActionCtx,
13
18
  type GenericDataModel,
14
19
  type StorageReader as ConvexStorageReader,
@@ -19,8 +24,15 @@ import {
19
24
  import type { Value } from "convex/values";
20
25
  import { ConvexError } from "convex/values";
21
26
  import { pipe } from "effect/Function";
27
+ import * as Config from "effect/Config";
28
+ import * as Option from "effect/Option";
29
+ import * as ConvexConfigProvider from "./ConvexConfigProvider.js";
30
+ import * as Clock from "effect/Clock";
31
+ import * as Cause from "effect/Cause";
22
32
  import * as Console from "effect/Console";
33
+ import * as Context from "effect/Context";
23
34
  import * as Effect from "effect/Effect";
35
+ import * as Exit from "effect/Exit";
24
36
  import * as Result from "effect/Result";
25
37
  import * as Layer from "effect/Layer";
26
38
  import * as Schema from "effect/Schema";
@@ -41,9 +53,50 @@ import * as Storage from "./Storage.js";
41
53
  import * as StorageActionWriter from "./StorageActionWriter.js";
42
54
  import type * as StorageReader from "./StorageReader.js";
43
55
  import * as StorageWriter from "./StorageWriter.js";
56
+ import * as Tracer from "effect/Tracer";
57
+ import * as ObservabilityRuntime from "./internal/observability.js";
44
58
  import * as VectorSearch from "./VectorSearch.js";
45
59
  import type * as Handler from "./Handler.js";
46
60
 
61
+ export interface ReceivedValue {
62
+ readonly value: unknown;
63
+ readonly requestId: string;
64
+ readonly deployment: string | undefined;
65
+ }
66
+
67
+ export const Received = Context.Reference<ReceivedValue | undefined>(
68
+ "@confect/server/RegisteredFunction/Received",
69
+ { defaultValue: () => undefined },
70
+ );
71
+
72
+ export const invocationMetadata = (
73
+ meta: Pick<MutationMeta, "getFunctionMetadata" | "getRequestMetadata">,
74
+ args: DefaultFunctionArgs,
75
+ reservedArgument?: string,
76
+ ): Effect.Effect<FunctionMetadata & { readonly received?: ReceivedValue }> =>
77
+ Effect.gen(function* () {
78
+ const metadata = yield* Effect.promise(() => meta.getFunctionMetadata());
79
+ if (reservedArgument === undefined) return metadata;
80
+ const { requestId } = yield* Effect.promise(() =>
81
+ meta.getRequestMetadata(),
82
+ );
83
+ const deployment = yield* Config.String("CONVEX_CLOUD_URL").pipe(
84
+ Config.option,
85
+ Effect.map(Option.getOrUndefined),
86
+ Effect.provide(ConvexConfigProvider.layer),
87
+ Effect.orDie,
88
+ );
89
+ return {
90
+ ...metadata,
91
+ received: { value: args[reservedArgument], requestId, deployment },
92
+ };
93
+ });
94
+
95
+ export const Observability = Context.Reference<Layer.Layer<never> | undefined>(
96
+ "@confect/server/Observability",
97
+ { defaultValue: () => undefined },
98
+ );
99
+
47
100
  export type Any =
48
101
  | RegisteredQuery<FunctionVisibility, DefaultFunctionArgs, any>
49
102
  | RegisteredMutation<FunctionVisibility, DefaultFunctionArgs, any>
@@ -183,67 +236,150 @@ export const combineErrorSchemas = (
183
236
  };
184
237
 
185
238
  /**
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`:
239
+ * Run the `Effect` as a `Promise`. Inspect the complete `Exit` before encoding
240
+ * a declared error so a typed failure cannot hide a defect.
188
241
  *
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.
242
+ * Any `Die` reason takes the defect path. Without a `Die`, the first `Fail` in
243
+ * cause order is schema-encoded and thrown as a `ConvexError` outside the
244
+ * Effect runtime so Convex surfaces its data to the client. Other failures and
245
+ * interruptions alongside that `Fail` are ignored. Without an error schema, the
246
+ * selected failure is treated as a defect.
198
247
  *
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.
248
+ * A lone `ConvexError` defect or a selected undeclared `ConvexError` failure is
249
+ * rethrown bare so it keeps Convex's meaning. Pagination and Workpool rely on
250
+ * its data for their control signals. Other causes with a `Die`, interrupt-only
251
+ * or empty causes, and error-encoding failures reject with a plain `Error`
252
+ * whose message renders the complete cause and whose `cause` holds it for
253
+ * server diagnostics.
204
254
  *
205
255
  * A `scheduler` in `runOptions` must be passed here as a run option rather than
206
256
  * provided via `Effect.provideService` inside `effect`: the run option lands in
207
257
  * 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.
258
+ * before the exit/encoding wrappers this function adds. The fiber's op counter
259
+ * survives context pops, so a cooperative yield can fire inside those wrappers.
260
+ * Only a root-context scheduler covers them.
211
261
  */
212
262
  export const runHandlerPromise =
213
263
  (
214
264
  errorSchema: Schema.Codec<any, any> | undefined,
215
265
  runOptions?: {
216
266
  readonly scheduler?: EffectScheduler.Scheduler | undefined;
267
+ readonly clock?: Clock.Clock | undefined;
268
+ readonly invocation?:
269
+ | Effect.Effect<
270
+ FunctionMetadata & { readonly received?: ReceivedValue }
271
+ >
272
+ | undefined;
273
+ readonly observability?: Layer.Layer<never> | undefined;
217
274
  },
218
275
  ) =>
219
276
  <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),
277
+ const { observability, invocation, clock } = runOptions ?? {};
278
+ let encodedError: Exit.Exit<any, any> | undefined;
279
+ let undeclared: { readonly error: unknown } | undefined;
280
+ const observed = Effect.tapCause(effect, (cause) =>
281
+ Effect.gen(function* () {
282
+ const reason =
283
+ cause.reasons.find(Cause.isDieReason) ??
284
+ cause.reasons.find(Cause.isFailReason);
285
+ if (reason?._tag === "Fail" && errorSchema !== undefined) {
286
+ encodedError = yield* Effect.exit(
287
+ Schema.encodeEffect(errorSchema)(reason.error),
288
+ );
289
+ // An encoding failure is a defect, even when it dies with a ConvexError.
290
+ if (Exit.isFailure(encodedError)) {
291
+ const encoding = encodedError.cause;
292
+ return yield* Cause.squash(encoding) instanceof ConvexError
293
+ ? Effect.die(
294
+ new Error(Cause.pretty(encoding), { cause: encoding }),
295
+ )
296
+ : Effect.orDie(encodedError);
297
+ }
298
+ }
299
+ // With no error schema a failure is a defect, so the root span ends as one.
300
+ if (
301
+ reason?._tag === "Fail" &&
302
+ errorSchema === undefined &&
303
+ !(reason.error instanceof ConvexError)
304
+ ) {
305
+ undeclared = { error: reason.error };
306
+ return yield* Effect.die(reason.error);
307
+ }
308
+ }),
227
309
  );
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
- ),
310
+ const withConvexError: Effect.Effect<Result.Result<A, ConvexError<any>>> =
311
+ Effect.gen(function* () {
312
+ const exit = yield* Effect.exit(
313
+ observability === undefined || invocation === undefined
314
+ ? effect
315
+ : Effect.flatMap(
316
+ invocation,
317
+ ({ name, type, componentPath, received }) =>
318
+ Effect.useSpan(
319
+ name,
320
+ {
321
+ root: true,
322
+ attributes: { kind: type, componentPath },
323
+ },
324
+ (span) =>
325
+ Effect.provideService(observed, Tracer.ParentSpan, span),
326
+ ).pipe(Effect.provideService(Received, received)),
240
327
  ),
241
- ),
328
+ );
329
+ if (Exit.isSuccess(exit)) {
330
+ return Result.succeed(exit.value);
331
+ }
332
+ if (undeclared !== undefined)
333
+ return yield* Effect.die(undeclared.error);
334
+
335
+ const cause =
336
+ encodedError !== undefined && Exit.isFailure(encodedError)
337
+ ? encodedError.cause
338
+ : exit.cause;
339
+ const reason =
340
+ cause.reasons.find(Cause.isDieReason) ??
341
+ cause.reasons.find(Cause.isFailReason);
342
+ if (
343
+ encodedError === undefined &&
344
+ reason?._tag === "Die" &&
345
+ cause.reasons.length === 1 &&
346
+ reason.defect instanceof ConvexError
347
+ ) {
348
+ return Result.fail(reason.defect);
349
+ }
350
+
351
+ if (reason?._tag === "Fail") {
352
+ if (errorSchema === undefined) {
353
+ if (reason.error instanceof ConvexError) {
354
+ return Result.fail(reason.error);
355
+ }
356
+ return yield* Effect.die(reason.error);
357
+ }
358
+ const encoded =
359
+ encodedError ??
360
+ (yield* Effect.exit(
361
+ Schema.encodeEffect(errorSchema)(reason.error),
362
+ ));
363
+ if (Exit.isSuccess(encoded)) {
364
+ return Result.fail(new ConvexError(encoded.value));
365
+ }
366
+ return yield* Effect.die(
367
+ new Error(Cause.pretty(encoded.cause), { cause: encoded.cause }),
242
368
  );
369
+ }
370
+ return yield* Effect.die(new Error(Cause.pretty(cause), { cause }));
371
+ });
243
372
  return Effect.runPromise(
244
- Effect.result(rethrowConvexErrorDefects(withConvexError)).pipe(
245
- Effect.provide(ConvexLogger.layer),
373
+ withConvexError.pipe(
374
+ Effect.provide(
375
+ observability === undefined
376
+ ? ConvexLogger.layer
377
+ : ObservabilityRuntime.memoized(observability),
378
+ ),
246
379
  Effect.provideService(Console.Console, globalThis.console),
380
+ clock === undefined
381
+ ? (self) => self
382
+ : Effect.provideService(Clock.Clock, clock),
247
383
  ),
248
384
  runOptions,
249
385
  ).then(
@@ -256,6 +392,16 @@ export const runHandlerPromise =
256
392
  );
257
393
  };
258
394
 
395
+ export const domainArgs = (
396
+ args: DefaultFunctionArgs,
397
+ reservedArgument?: string,
398
+ ): DefaultFunctionArgs =>
399
+ reservedArgument === undefined
400
+ ? args
401
+ : Object.fromEntries(
402
+ Object.entries(args).filter(([key]) => key !== reservedArgument),
403
+ );
404
+
259
405
  export const actionFunctionBase = <
260
406
  Schema extends DatabaseSchema.AnyWithProps,
261
407
  Args,
@@ -274,8 +420,11 @@ export const actionFunctionBase = <
274
420
  resolvedMiddlewares = [],
275
421
  createLayer,
276
422
  scope = IdScope.app,
423
+ observability,
424
+ reservedArgument,
277
425
  }: {
278
426
  name: string;
427
+ reservedArgument?: string | undefined;
279
428
  functionVisibility: FunctionVisibility;
280
429
  args: Schema.Codec<Args, ConvexArgs>;
281
430
  returns: Schema.Codec<Returns, ConvexReturns>;
@@ -286,8 +435,9 @@ export const actionFunctionBase = <
286
435
  ctx: GenericActionCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
287
436
  ) => Layer.Layer<R>;
288
437
  scope?: IdScope.IdScope;
438
+ observability?: Layer.Layer<never> | undefined;
289
439
  }) => ({
290
- args: compileArgsSchema(args, scope),
440
+ args: compileArgsSchema(args, scope, reservedArgument, name),
291
441
  returns: compileReturnsSchema(returns, scope),
292
442
  handler: (
293
443
  ctx: GenericActionCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
@@ -295,10 +445,16 @@ export const actionFunctionBase = <
295
445
  ): Promise<ConvexReturns> =>
296
446
  Effect.gen(function* () {
297
447
  const decodedArgs = yield* pipe(
298
- actualArgs,
448
+ domainArgs(actualArgs, reservedArgument),
299
449
  Schema.decodeUnknownEffect(args),
300
450
  Effect.catchTag("SchemaError", Effect.die),
301
451
  );
452
+ const restricted = resolvedMiddlewares.some(
453
+ ({ middlewareSpec }) =>
454
+ middlewareSpec.key === ActionCallSpec.Middleware.key,
455
+ )
456
+ ? restrict(ctx)
457
+ : undefined;
302
458
  // 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
459
  const decodedReturns = yield* applyMiddleware(
304
460
  handler(decodedArgs),
@@ -309,13 +465,19 @@ export const actionFunctionBase = <
309
465
  functionVisibility,
310
466
  args: decodedArgs,
311
467
  },
312
- ).pipe(Effect.provide(createLayer(ctx)));
468
+ ).pipe(Effect.provide(createLayer(restricted?.ctx ?? ctx)));
469
+ if (restricted !== undefined) yield* restricted.check;
313
470
  return yield* pipe(
314
471
  decodedReturns,
315
472
  Schema.encodeEffect(returns),
316
473
  Effect.catchTag("SchemaError", Effect.die),
317
474
  );
318
- }).pipe(runHandlerPromise(combineErrorSchemas(error, resolvedMiddlewares))),
475
+ }).pipe(
476
+ runHandlerPromise(combineErrorSchemas(error, resolvedMiddlewares), {
477
+ invocation: invocationMetadata(ctx.meta, actualArgs, reservedArgument),
478
+ observability,
479
+ }),
480
+ ),
319
481
  });
320
482
 
321
483
  export type ActionServices<
@@ -389,3 +551,47 @@ export const storageLayer = <Scope extends IdScope.IdScope>(
389
551
  Storage.layer(reader) as Layer.Layer<
390
552
  Storage.Storage | StorageReader.ForScope<Scope>
391
553
  >;
554
+
555
+ /**
556
+ * Shares one dispatch budget across the entire middleware chain.
557
+ */
558
+ const restrict = <Model extends GenericDataModel>(
559
+ ctx: GenericActionCtx<Model>,
560
+ ) => {
561
+ let dispatched = false;
562
+ const claim = () => {
563
+ if (dispatched) throw new ActionCall.ActionCallAlreadyUsed();
564
+ dispatched = true;
565
+ };
566
+ const restricted: GenericActionCtx<Model> = {
567
+ ...ctx,
568
+ scheduler: {
569
+ runAfter: () => {
570
+ throw new ActionCall.SchedulingNotAllowed();
571
+ },
572
+ runAt: () => {
573
+ throw new ActionCall.SchedulingNotAllowed();
574
+ },
575
+ cancel: () => {
576
+ throw new ActionCall.SchedulingNotAllowed();
577
+ },
578
+ },
579
+ runQuery: (...args) => {
580
+ claim();
581
+ return ctx.runQuery(...args);
582
+ },
583
+ runMutation: (...args) => {
584
+ claim();
585
+ return ctx.runMutation(...args);
586
+ },
587
+ runAction: () => {
588
+ throw new ActionCall.NestedActionNotAllowed();
589
+ },
590
+ };
591
+ return {
592
+ ctx: restricted,
593
+ check: Effect.suspend(() =>
594
+ dispatched ? Effect.void : Effect.die(new ActionCall.ActionCallNotUsed()),
595
+ ),
596
+ };
597
+ };
@@ -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),
@@ -38,3 +38,34 @@ export const middlewareKeys = (items: RegistryItems): ReadonlyArray<string> =>
38
38
  Array.filter(MiddlewareRegistryItem.isMiddlewareRegistryItem),
39
39
  Array.map((item) => item.middlewareSpec.key),
40
40
  );
41
+
42
+ export interface MissingMiddleware {
43
+ readonly functionName: string;
44
+ readonly middlewareKey: string;
45
+ }
46
+
47
+ export const missingRequired = (
48
+ items: RegistryItems,
49
+ required: ReadonlyArray<{ readonly key: string }>,
50
+ groupPath = "",
51
+ ): ReadonlyArray<MissingMiddleware> =>
52
+ Object.entries(items)
53
+ .sort(([left], [right]) => left.localeCompare(right))
54
+ .flatMap(([functionName, value]) =>
55
+ FunctionRegistryItem.isFunctionRegistryItem(value)
56
+ ? required.flatMap(({ key }) =>
57
+ value._tag === "Confect" &&
58
+ value.middlewareAttachments.some(({ spec }) => spec.key === key)
59
+ ? []
60
+ : [
61
+ {
62
+ functionName:
63
+ groupPath === ""
64
+ ? functionName
65
+ : `${groupPath}.${functionName}`,
66
+ middlewareKey: key,
67
+ },
68
+ ],
69
+ )
70
+ : [],
71
+ );