@gaganref/confect-server 10.3.1-fork.38 → 10.3.1-fork.39

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.
@@ -11,6 +11,7 @@ import {
11
11
  import {
12
12
  type DefaultFunctionArgs,
13
13
  type FunctionVisibility,
14
+ type FunctionMetadata,
14
15
  type GenericActionCtx,
15
16
  type GenericDataModel,
16
17
  type StorageReader as ConvexStorageReader,
@@ -21,8 +22,10 @@ import {
21
22
  import type { Value } from "convex/values";
22
23
  import { ConvexError } from "convex/values";
23
24
  import { pipe } from "effect/Function";
25
+ import * as Clock from "effect/Clock";
24
26
  import * as Cause from "effect/Cause";
25
27
  import * as Console from "effect/Console";
28
+ import * as Context from "effect/Context";
26
29
  import * as Effect from "effect/Effect";
27
30
  import * as Exit from "effect/Exit";
28
31
  import * as Result from "effect/Result";
@@ -45,9 +48,16 @@ import * as Storage from "./Storage.js";
45
48
  import * as StorageActionWriter from "./StorageActionWriter.js";
46
49
  import type * as StorageReader from "./StorageReader.js";
47
50
  import * as StorageWriter from "./StorageWriter.js";
51
+ import * as Tracer from "effect/Tracer";
52
+ import * as ObservabilityRuntime from "./internal/observability.js";
48
53
  import * as VectorSearch from "./VectorSearch.js";
49
54
  import type * as Handler from "./Handler.js";
50
55
 
56
+ export const Observability = Context.Reference<Layer.Layer<never> | undefined>(
57
+ "@confect/server/Observability",
58
+ { defaultValue: () => undefined },
59
+ );
60
+
51
61
  export type Any =
52
62
  | RegisteredQuery<FunctionVisibility, DefaultFunctionArgs, any>
53
63
  | RegisteredMutation<FunctionVisibility, DefaultFunctionArgs, any>
@@ -215,21 +225,58 @@ export const runHandlerPromise =
215
225
  errorSchema: Schema.Codec<any, any> | undefined,
216
226
  runOptions?: {
217
227
  readonly scheduler?: EffectScheduler.Scheduler | undefined;
228
+ readonly clock?: Clock.Clock | undefined;
229
+ readonly invocation?: Effect.Effect<FunctionMetadata> | undefined;
230
+ readonly observability?: Layer.Layer<never> | undefined;
218
231
  },
219
232
  ) =>
220
233
  <A, E>(effect: Effect.Effect<A, E>): Promise<A> => {
234
+ const { observability, invocation, clock } = runOptions ?? {};
235
+ let encodedError: Exit.Exit<any, any> | undefined;
236
+ const observed = Effect.tapCause(effect, (cause) =>
237
+ Effect.gen(function* () {
238
+ const reason =
239
+ cause.reasons.find(Cause.isDieReason) ??
240
+ cause.reasons.find(Cause.isFailReason);
241
+ if (reason?._tag === "Fail" && errorSchema !== undefined) {
242
+ encodedError = yield* Effect.exit(
243
+ Schema.encodeEffect(errorSchema)(reason.error),
244
+ );
245
+ if (Exit.isFailure(encodedError))
246
+ return yield* Effect.orDie(encodedError);
247
+ }
248
+ }),
249
+ );
221
250
  const withConvexError: Effect.Effect<Result.Result<A, ConvexError<any>>> =
222
251
  Effect.gen(function* () {
223
- const exit = yield* Effect.exit(effect);
252
+ const exit = yield* Effect.exit(
253
+ observability === undefined || invocation === undefined
254
+ ? effect
255
+ : Effect.flatMap(invocation, ({ name, type, componentPath }) =>
256
+ Effect.useSpan(
257
+ name,
258
+ {
259
+ root: true,
260
+ attributes: { kind: type, componentPath },
261
+ },
262
+ (span) =>
263
+ Effect.provideService(observed, Tracer.ParentSpan, span),
264
+ ),
265
+ ),
266
+ );
224
267
  if (Exit.isSuccess(exit)) {
225
268
  return Result.succeed(exit.value);
226
269
  }
227
270
 
228
- const { cause } = exit;
271
+ const cause =
272
+ encodedError !== undefined && Exit.isFailure(encodedError)
273
+ ? encodedError.cause
274
+ : exit.cause;
229
275
  const reason =
230
276
  cause.reasons.find(Cause.isDieReason) ??
231
277
  cause.reasons.find(Cause.isFailReason);
232
278
  if (
279
+ encodedError === undefined &&
233
280
  reason?._tag === "Die" &&
234
281
  cause.reasons.length === 1 &&
235
282
  reason.defect instanceof ConvexError
@@ -244,9 +291,11 @@ export const runHandlerPromise =
244
291
  }
245
292
  return yield* Effect.die(reason.error);
246
293
  }
247
- const encoded = yield* Effect.exit(
248
- Schema.encodeEffect(errorSchema)(reason.error),
249
- );
294
+ const encoded =
295
+ encodedError ??
296
+ (yield* Effect.exit(
297
+ Schema.encodeEffect(errorSchema)(reason.error),
298
+ ));
250
299
  if (Exit.isSuccess(encoded)) {
251
300
  return Result.fail(new ConvexError(encoded.value));
252
301
  }
@@ -258,8 +307,15 @@ export const runHandlerPromise =
258
307
  });
259
308
  return Effect.runPromise(
260
309
  withConvexError.pipe(
261
- Effect.provide(ConvexLogger.layer),
310
+ Effect.provide(
311
+ observability === undefined
312
+ ? ConvexLogger.layer
313
+ : ObservabilityRuntime.memoized(observability),
314
+ ),
262
315
  Effect.provideService(Console.Console, globalThis.console),
316
+ clock === undefined
317
+ ? (self) => self
318
+ : Effect.provideService(Clock.Clock, clock),
263
319
  ),
264
320
  runOptions,
265
321
  ).then(
@@ -290,6 +346,7 @@ export const actionFunctionBase = <
290
346
  resolvedMiddlewares = [],
291
347
  createLayer,
292
348
  scope = IdScope.app,
349
+ observability,
293
350
  }: {
294
351
  name: string;
295
352
  functionVisibility: FunctionVisibility;
@@ -302,6 +359,7 @@ export const actionFunctionBase = <
302
359
  ctx: GenericActionCtx<DataModel.ToConvex<DataModel.FromSchema<Schema>>>,
303
360
  ) => Layer.Layer<R>;
304
361
  scope?: IdScope.IdScope;
362
+ observability?: Layer.Layer<never> | undefined;
305
363
  }) => ({
306
364
  args: compileArgsSchema(args, scope),
307
365
  returns: compileReturnsSchema(returns, scope),
@@ -338,7 +396,12 @@ export const actionFunctionBase = <
338
396
  Schema.encodeEffect(returns),
339
397
  Effect.catchTag("SchemaError", Effect.die),
340
398
  );
341
- }).pipe(runHandlerPromise(combineErrorSchemas(error, resolvedMiddlewares))),
399
+ }).pipe(
400
+ runHandlerPromise(combineErrorSchemas(error, resolvedMiddlewares), {
401
+ invocation: Effect.promise(() => ctx.meta.getFunctionMetadata()),
402
+ observability,
403
+ }),
404
+ ),
342
405
  });
343
406
 
344
407
  export type ActionServices<
@@ -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,7 @@ export const make = (
59
61
  return genericFunction(
60
62
  nodeActionFunction(databaseSchema, {
61
63
  name: item.name,
64
+ observability,
62
65
  functionVisibility: item.functionVisibility,
63
66
  args: item.args,
64
67
  returns: item.returns,
@@ -86,6 +89,7 @@ const nodeActionFunction = <
86
89
  error,
87
90
  handler,
88
91
  resolvedMiddlewares,
92
+ observability,
89
93
  }: {
90
94
  name: string;
91
95
  functionVisibility: FunctionVisibility;
@@ -101,6 +105,7 @@ const nodeActionFunction = <
101
105
  | NodeServices.NodeServices
102
106
  >;
103
107
  resolvedMiddlewares: ReadonlyArray<ResolvedMiddleware.ResolvedMiddleware>;
108
+ observability?: Layer.Layer<never> | undefined;
104
109
  },
105
110
  ) =>
106
111
  RegisteredFunction.actionFunctionBase({
@@ -112,6 +117,7 @@ const nodeActionFunction = <
112
117
  error,
113
118
  handler,
114
119
  resolvedMiddlewares,
120
+ observability,
115
121
  createLayer: (ctx) =>
116
122
  Layer.mergeAll(
117
123
  RegisteredFunction.actionLayer(databaseSchema, ctx),
@@ -0,0 +1,27 @@
1
+ import * as Duration from "effect/Duration";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Scope from "effect/Scope";
6
+ import * as ConvexConfigProvider from "../ConvexConfigProvider.js";
7
+
8
+ const memoMap = Layer.makeMemoMapUnsafe();
9
+ export const scope = Scope.makeUnsafe();
10
+ const layers = new WeakMap<Layer.Layer<never>, Layer.Layer<never>>();
11
+
12
+ export const memoized = (layer: Layer.Layer<never>): Layer.Layer<never> => {
13
+ const cached = layers.get(layer);
14
+ if (cached !== undefined) return cached;
15
+ const built = Layer.buildWithMemoMap(layer, memoMap, scope).pipe(
16
+ Effect.provide(ConvexConfigProvider.layer),
17
+ // A failed first build is retried on the next invocation instead of failing every later one.
18
+ Effect.cachedWithTTL((exit) =>
19
+ Exit.isSuccess(exit) ? Duration.infinity : Duration.zero,
20
+ ),
21
+ Effect.runSync,
22
+ Effect.uninterruptible,
23
+ Layer.effectContext,
24
+ );
25
+ layers.set(layer, built);
26
+ return built;
27
+ };