convex-tracer 0.1.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 (86) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +592 -0
  3. package/dist/client/_generated/_ignore.d.ts +1 -0
  4. package/dist/client/_generated/_ignore.d.ts.map +1 -0
  5. package/dist/client/_generated/_ignore.js +3 -0
  6. package/dist/client/_generated/_ignore.js.map +1 -0
  7. package/dist/client/helpers.d.ts +31 -0
  8. package/dist/client/helpers.d.ts.map +1 -0
  9. package/dist/client/helpers.js +177 -0
  10. package/dist/client/helpers.js.map +1 -0
  11. package/dist/client/index.d.ts +210 -0
  12. package/dist/client/index.d.ts.map +1 -0
  13. package/dist/client/index.js +355 -0
  14. package/dist/client/index.js.map +1 -0
  15. package/dist/client/tracer-api/index.d.ts +27 -0
  16. package/dist/client/tracer-api/index.d.ts.map +1 -0
  17. package/dist/client/tracer-api/index.js +177 -0
  18. package/dist/client/tracer-api/index.js.map +1 -0
  19. package/dist/client/tracer-api/types.d.ts +143 -0
  20. package/dist/client/tracer-api/types.d.ts.map +1 -0
  21. package/dist/client/tracer-api/types.js +2 -0
  22. package/dist/client/tracer-api/types.js.map +1 -0
  23. package/dist/client/types.d.ts +168 -0
  24. package/dist/client/types.d.ts.map +1 -0
  25. package/dist/client/types.js +2 -0
  26. package/dist/client/types.js.map +1 -0
  27. package/dist/component/_generated/api.d.ts +36 -0
  28. package/dist/component/_generated/api.d.ts.map +1 -0
  29. package/dist/component/_generated/api.js +31 -0
  30. package/dist/component/_generated/api.js.map +1 -0
  31. package/dist/component/_generated/component.d.ts +139 -0
  32. package/dist/component/_generated/component.d.ts.map +1 -0
  33. package/dist/component/_generated/component.js +11 -0
  34. package/dist/component/_generated/component.js.map +1 -0
  35. package/dist/component/_generated/dataModel.d.ts +46 -0
  36. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  37. package/dist/component/_generated/dataModel.js +11 -0
  38. package/dist/component/_generated/dataModel.js.map +1 -0
  39. package/dist/component/_generated/server.d.ts +121 -0
  40. package/dist/component/_generated/server.d.ts.map +1 -0
  41. package/dist/component/_generated/server.js +78 -0
  42. package/dist/component/_generated/server.js.map +1 -0
  43. package/dist/component/convex.config.d.ts +3 -0
  44. package/dist/component/convex.config.d.ts.map +1 -0
  45. package/dist/component/convex.config.js +3 -0
  46. package/dist/component/convex.config.js.map +1 -0
  47. package/dist/component/lib.d.ts +161 -0
  48. package/dist/component/lib.d.ts.map +1 -0
  49. package/dist/component/lib.js +349 -0
  50. package/dist/component/lib.js.map +1 -0
  51. package/dist/component/schema.d.ts +75 -0
  52. package/dist/component/schema.d.ts.map +1 -0
  53. package/dist/component/schema.js +46 -0
  54. package/dist/component/schema.js.map +1 -0
  55. package/dist/component/types.d.ts +286 -0
  56. package/dist/component/types.d.ts.map +1 -0
  57. package/dist/component/types.js +28 -0
  58. package/dist/component/types.js.map +1 -0
  59. package/dist/react/index.d.ts +6 -0
  60. package/dist/react/index.d.ts.map +1 -0
  61. package/dist/react/index.js +11 -0
  62. package/dist/react/index.js.map +1 -0
  63. package/dist/react/types.d.ts +8 -0
  64. package/dist/react/types.d.ts.map +1 -0
  65. package/dist/react/types.js +2 -0
  66. package/dist/react/types.js.map +1 -0
  67. package/package.json +121 -0
  68. package/src/client/_generated/_ignore.ts +1 -0
  69. package/src/client/helpers.ts +278 -0
  70. package/src/client/index.ts +593 -0
  71. package/src/client/setup.test.ts +26 -0
  72. package/src/client/tracer-api/index.ts +235 -0
  73. package/src/client/tracer-api/types.ts +168 -0
  74. package/src/client/types.ts +257 -0
  75. package/src/component/_generated/api.ts +52 -0
  76. package/src/component/_generated/component.ts +199 -0
  77. package/src/component/_generated/dataModel.ts +60 -0
  78. package/src/component/_generated/server.ts +161 -0
  79. package/src/component/convex.config.ts +3 -0
  80. package/src/component/lib.ts +399 -0
  81. package/src/component/schema.ts +62 -0
  82. package/src/component/setup.test.ts +11 -0
  83. package/src/component/types.ts +38 -0
  84. package/src/react/index.ts +36 -0
  85. package/src/react/types.ts +15 -0
  86. package/src/test.ts +18 -0
@@ -0,0 +1,593 @@
1
+ import type { FunctionType, GenericQueryCtx } from "convex/server";
2
+ import {
3
+ actionGeneric,
4
+ type GenericActionCtx,
5
+ type GenericDataModel,
6
+ type GenericMutationCtx,
7
+ internalActionGeneric,
8
+ internalMutationGeneric,
9
+ mutationGeneric,
10
+ type RegisteredAction,
11
+ type RegisteredMutation,
12
+ } from "convex/server";
13
+ import type { Infer, PropertyValidators } from "convex/values";
14
+ import { v } from "convex/values";
15
+ import type { ComponentApi } from "../component/_generated/component";
16
+ import { statusValidator } from "../component/schema";
17
+ import type { CompleteTrace, Trace } from "../component/types";
18
+ import type { EmptyObject } from "../react/types";
19
+ import {
20
+ executeTracedHandler,
21
+ extractTraceContext,
22
+ prepareLogArgs,
23
+ setupTraceContext,
24
+ } from "./helpers";
25
+ import TracingAPI from "./tracer-api";
26
+ import type {
27
+ ActionCtxWithTracer,
28
+ AnyFunctionReference,
29
+ ExtractOutput,
30
+ GenericFunctionContext,
31
+ MutationCtxWithTracer,
32
+ QueryCtxWithTracer,
33
+ TraceContext,
34
+ TracedFunctionConfig,
35
+ TracedFunctionContext,
36
+ TracedFunctionTypes,
37
+ TracedResult,
38
+ TracerArgs,
39
+ TracerConfig,
40
+ TracerHandler,
41
+ } from "./types";
42
+
43
+ export * from "../component/types";
44
+
45
+ const DEFAULT_CONFIG: Required<TracerConfig> = {
46
+ sampleRate: 0.1,
47
+ preserveErrors: true,
48
+ retentionMinutes: 120,
49
+ };
50
+
51
+ const __traceContext = v.optional(
52
+ v.object({
53
+ traceId: v.string(),
54
+ spanId: v.string(),
55
+ sampleRate: v.optional(v.number()),
56
+ retentionMinutes: v.optional(v.number()),
57
+ preserveErrors: v.optional(v.boolean()),
58
+ }),
59
+ );
60
+
61
+ /**
62
+ * @example
63
+ * ```typescript
64
+ * import { components } from "./_generated/api";
65
+ * import { mutation, action } from "./_generated/server";
66
+ * import { Tracer } from "@convex-dev/tracer";
67
+ *
68
+ * export const { tracedQuery, tracedMutation, tracedAction } = new Tracer(components.tracer, {
69
+ * sampleRate: 0.1,
70
+ * preserveErrors: true,
71
+ * });
72
+ *
73
+ * export const createPost = tracedMutation({
74
+ * name: "createPost",
75
+ * args: { title: v.string() },
76
+ * handler: async (ctx, args) => {
77
+ * await ctx.tracer.info("Creating post");
78
+ * return await ctx.db.insert("posts", args);
79
+ * },
80
+ * });
81
+ * ```
82
+ */
83
+ export class Tracer<DataModel extends GenericDataModel> {
84
+ public readonly sampleRate: number;
85
+ public readonly preserveErrors: boolean;
86
+ public readonly retentionMinutes: number;
87
+
88
+ constructor(
89
+ public readonly component: ComponentApi,
90
+ config: TracerConfig = {},
91
+ ) {
92
+ this.sampleRate = config.sampleRate ?? DEFAULT_CONFIG.sampleRate;
93
+ this.preserveErrors =
94
+ config.preserveErrors ?? DEFAULT_CONFIG.preserveErrors;
95
+ this.retentionMinutes =
96
+ config.retentionMinutes ?? DEFAULT_CONFIG.retentionMinutes;
97
+ }
98
+
99
+ private createRunTracedFunction<
100
+ Ctx extends GenericFunctionContext<DataModel>,
101
+ >(ctx: Ctx, traceContext: TraceContext, type: TracedFunctionTypes) {
102
+ return async <FuncRef extends AnyFunctionReference>(
103
+ funcRef: FuncRef,
104
+ args: Exclude<FuncRef["_args"], "__traceContext">,
105
+ ): Promise<FuncRef["_returnType"]> => {
106
+ const argsWithTrace = {
107
+ ...args,
108
+ __traceContext: traceContext,
109
+ };
110
+
111
+ if (type === "action") {
112
+ return await (ctx as GenericActionCtx<DataModel>).runAction(
113
+ funcRef,
114
+ argsWithTrace,
115
+ );
116
+ }
117
+
118
+ return await (ctx as GenericMutationCtx<DataModel>).runMutation(
119
+ funcRef,
120
+ argsWithTrace,
121
+ );
122
+ };
123
+ }
124
+
125
+ private createRestrictedQueryContext(
126
+ ctx: GenericMutationCtx<DataModel>,
127
+ traceContext: TraceContext,
128
+ ): QueryCtxWithTracer<DataModel> {
129
+ const tracerConfig: Required<TracerConfig> = {
130
+ sampleRate: this.sampleRate,
131
+ preserveErrors: this.preserveErrors,
132
+ retentionMinutes: this.retentionMinutes,
133
+ };
134
+
135
+ const { db, storage, ...restOfCtx } = ctx;
136
+
137
+ const { get, query, normalizeId, system } = db;
138
+ const { getUrl, getMetadata } = storage;
139
+
140
+ const queryCtx = {
141
+ ...restOfCtx,
142
+ db: { get, query, normalizeId, system },
143
+ storage: { getUrl, getMetadata },
144
+ } satisfies GenericQueryCtx<DataModel>;
145
+
146
+ return {
147
+ ...queryCtx,
148
+ tracer: new TracingAPI(
149
+ ctx as any,
150
+ this.component,
151
+ traceContext.traceId,
152
+ traceContext.spanId,
153
+ tracerConfig,
154
+ ),
155
+ runTracedQuery: this.createRunTracedFunction(
156
+ ctx,
157
+ traceContext,
158
+ "mutation",
159
+ ),
160
+ } as QueryCtxWithTracer<DataModel>;
161
+ }
162
+
163
+ private createEnhancedContext(
164
+ ctx: GenericFunctionContext<DataModel>,
165
+ traceContext: TraceContext,
166
+ type: FunctionType,
167
+ ): TracedFunctionContext<DataModel> {
168
+ const tracerConfig: Required<TracerConfig> = {
169
+ sampleRate: this.sampleRate,
170
+ preserveErrors: this.preserveErrors,
171
+ retentionMinutes: this.retentionMinutes,
172
+ };
173
+
174
+ if (type === "query") {
175
+ return this.createRestrictedQueryContext(
176
+ ctx as GenericMutationCtx<DataModel>,
177
+ traceContext,
178
+ );
179
+ }
180
+
181
+ const baseCtx = {
182
+ ...ctx,
183
+ tracer: new TracingAPI(
184
+ ctx as any,
185
+ this.component,
186
+ traceContext.traceId,
187
+ traceContext.spanId,
188
+ tracerConfig,
189
+ ),
190
+ runTracedQuery: this.createRunTracedFunction(
191
+ ctx,
192
+ traceContext,
193
+ "mutation",
194
+ ),
195
+ runTracedMutation: this.createRunTracedFunction(
196
+ ctx,
197
+ traceContext,
198
+ "mutation",
199
+ ),
200
+ };
201
+
202
+ if (type === "mutation") {
203
+ return baseCtx as MutationCtxWithTracer<DataModel>;
204
+ }
205
+
206
+ if (type === "action") {
207
+ return {
208
+ ...baseCtx,
209
+ runTracedAction: this.createRunTracedFunction(
210
+ ctx,
211
+ traceContext,
212
+ "action",
213
+ ),
214
+ } as ActionCtxWithTracer<DataModel>;
215
+ }
216
+
217
+ throw new Error(`Unexpected function type: ${type}`);
218
+ }
219
+
220
+ private createTracedHandler<
221
+ EnhancedCtx extends TracedFunctionContext<DataModel>,
222
+ Args extends PropertyValidators,
223
+ Handler extends TracerHandler<EnhancedCtx, Args>,
224
+ Output extends ExtractOutput<Handler>,
225
+ >(
226
+ tConfig: TracedFunctionConfig<EnhancedCtx, Args, Handler, Output>,
227
+ functionType: FunctionType,
228
+ defaultName: string,
229
+ ) {
230
+ const functionName = tConfig.name || defaultName;
231
+
232
+ return async (
233
+ ctx: GenericFunctionContext<DataModel>,
234
+ allArgs: any,
235
+ ): Promise<TracedResult<Output>> => {
236
+ const startTime = Date.now();
237
+
238
+ const { existingContext, args } = extractTraceContext(allArgs);
239
+
240
+ const { traceId, spanId, traceContext, isRoot } = await setupTraceContext(
241
+ ctx as any,
242
+ this.component,
243
+ existingContext,
244
+ startTime,
245
+ functionName,
246
+ tConfig.sampleRate ?? this.sampleRate,
247
+ tConfig.retentionMinutes ?? this.retentionMinutes,
248
+ tConfig.preserveErrors ?? this.preserveErrors,
249
+ {
250
+ functionName,
251
+ args: prepareLogArgs(args, tConfig.logArgs as any),
252
+ },
253
+ );
254
+
255
+ const enhancedCtx = this.createEnhancedContext(
256
+ ctx,
257
+ traceContext,
258
+ functionType,
259
+ ) as EnhancedCtx;
260
+
261
+ return await executeTracedHandler<Args, Output, EnhancedCtx>({
262
+ ctx: ctx as any,
263
+ component: this.component,
264
+ traceId,
265
+ spanId,
266
+ startTime,
267
+ config: tConfig,
268
+ args,
269
+ handler: tConfig.handler,
270
+ enhancedCtx,
271
+ isRoot,
272
+ });
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Creates a traced query (runs as mutation internally).
278
+ * @example
279
+ * ```ts
280
+ * export const getUser = tracedQuery({
281
+ * name: "getUser",
282
+ * args: { userId: v.id("users") },
283
+ * onSuccess: async (ctx, args, result) => {
284
+ * await ctx.tracer.info("Succeeded user fetch", { userId: args.userId });
285
+ * },
286
+ * handler: async (ctx, args) => {
287
+ * await ctx.tracer.info("fetching user", { title: args.userId });
288
+ * const user = await ctx.db.get(args.userId);
289
+ * await ctx.tracer.info("user fetched", { userId: args.userId });
290
+ * return user;
291
+ * },
292
+ * });
293
+ * ```
294
+ */
295
+ tracedQuery = <
296
+ Ctx extends QueryCtxWithTracer<DataModel>,
297
+ Args extends PropertyValidators | EmptyObject,
298
+ Handler extends TracerHandler<Ctx, Args>,
299
+ Output extends ExtractOutput<Handler>,
300
+ >(
301
+ tConfig: TracedFunctionConfig<Ctx, Args, Handler, Output>,
302
+ ): RegisteredMutation<
303
+ "public",
304
+ TracerArgs<Args>,
305
+ TracedResult<ExtractOutput<Handler>>
306
+ > => {
307
+ return mutationGeneric({
308
+ args: {
309
+ ...tConfig.args,
310
+ __traceContext,
311
+ },
312
+ handler: this.createTracedHandler<Ctx, Args, Handler, Output>(
313
+ tConfig,
314
+ "query",
315
+ "anonymous-query",
316
+ ),
317
+ });
318
+ };
319
+
320
+ /**
321
+ * Creates a traced query (runs as mutation internally).
322
+ * @example
323
+ * ```ts
324
+ * export const getUser = internalTracedQuery({
325
+ * name: "getUser",
326
+ * args: { userId: v.id("users") },
327
+ * onSuccess: async (ctx, args, result) => {
328
+ * await ctx.tracer.info("Succeeded user fetch", { userId: args.userId });
329
+ * },
330
+ * handler: async (ctx, args) => {
331
+ * await ctx.tracer.info("fetching user", { title: args.userId });
332
+ * const user = await ctx.db.get(args.userId);
333
+ * await ctx.tracer.info("user fetched", { userId: args.userId });
334
+ * return user;
335
+ * },
336
+ * });
337
+ * ```
338
+ */
339
+ internalTracedQuery = <
340
+ Ctx extends QueryCtxWithTracer<DataModel>,
341
+ Args extends PropertyValidators | EmptyObject,
342
+ Handler extends TracerHandler<Ctx, Args>,
343
+ Output extends ExtractOutput<Handler>,
344
+ >(
345
+ tConfig: TracedFunctionConfig<Ctx, Args, Handler, Output>,
346
+ ): RegisteredMutation<
347
+ "internal",
348
+ TracerArgs<Args>,
349
+ TracedResult<ExtractOutput<Handler>>
350
+ > => {
351
+ return internalMutationGeneric({
352
+ args: {
353
+ ...tConfig.args,
354
+ __traceContext,
355
+ },
356
+ handler: this.createTracedHandler<Ctx, Args, Handler, Output>(
357
+ tConfig,
358
+ "query",
359
+ "anonymous-internal-query",
360
+ ),
361
+ });
362
+ };
363
+
364
+ /**
365
+ * Creates a traced mutation.
366
+ * @example
367
+ * ```ts
368
+ * export const createUser = tracedMutation({
369
+ * name: "createUser",
370
+ * args: { user: v.object({ name: v.string(), email: v.string() }) },
371
+ * handler: async (ctx, args) => {
372
+ * const existing = await ctx.db
373
+ * .query("users")
374
+ * .withIndex("by_email", (q) => q.eq("email", args.user.email))
375
+ * .first();
376
+ *
377
+ * if (existing) {
378
+ * ctx.tracer.info("User already exists", { user: args.user });
379
+ * return existing._id;
380
+ * }
381
+ *
382
+ * ctx.tracer.info("Adding user", { ...args.user });
383
+ *
384
+ * const userId = await ctx.db.insert("users", { ...args.user });
385
+ * ctx.tracer.info("User added", { userId });
386
+ *
387
+ * await ctx.tracer.withSpan("syncUser", async (span) => {
388
+ * span.info("Syncing user");
389
+ * // do something
390
+ * span.info("User synced");
391
+ * });
392
+ *
393
+ * return userId;
394
+ * },
395
+ * });
396
+ * ```
397
+ */
398
+ tracedMutation = <
399
+ Ctx extends MutationCtxWithTracer<DataModel>,
400
+ Args extends PropertyValidators | EmptyObject,
401
+ Handler extends TracerHandler<Ctx, Args>,
402
+ Output extends ExtractOutput<Handler>,
403
+ >(
404
+ tConfig: TracedFunctionConfig<Ctx, Args, Handler, Output>,
405
+ ): RegisteredMutation<
406
+ "public",
407
+ TracerArgs<Args>,
408
+ TracedResult<ExtractOutput<Handler>>
409
+ > => {
410
+ return mutationGeneric({
411
+ args: {
412
+ ...tConfig.args,
413
+ __traceContext,
414
+ },
415
+ handler: this.createTracedHandler<Ctx, Args, Handler, Output>(
416
+ tConfig,
417
+ "mutation",
418
+ "anonymous-mutation",
419
+ ),
420
+ });
421
+ };
422
+
423
+ /**
424
+ * Creates a traced mutation.
425
+ * @example
426
+ * ```ts
427
+ * export const createUser = internalTracedMutation({
428
+ * name: "createUser",
429
+ * args: { user: v.object({ name: v.string(), email: v.string() }) },
430
+ * handler: async (ctx, args) => {
431
+ * const existing = await ctx.db
432
+ * .query("users")
433
+ * .withIndex("by_email", (q) => q.eq("email", args.user.email))
434
+ * .first();
435
+ *
436
+ * if (existing) {
437
+ * await ctx.tracer.info("User already exists", { user: args.user });
438
+ * return existing._id;
439
+ * }
440
+ *
441
+ * await ctx.tracer.info("Adding user", { ...args.user });
442
+ *
443
+ * const userId = await ctx.db.insert("users", { ...args.user });
444
+ * ctx.tracer.info("User added", { userId });
445
+ *
446
+ * await ctx.tracer.withSpan("syncUser", async (span) => {
447
+ * await span.info("Syncing user");
448
+ * // do something
449
+ * await span.info("User synced");
450
+ * });
451
+ *
452
+ * return userId;
453
+ * },
454
+ * });
455
+ * ```
456
+ */
457
+ internalTracedMutation = <
458
+ Ctx extends MutationCtxWithTracer<DataModel>,
459
+ Args extends PropertyValidators | EmptyObject,
460
+ Handler extends TracerHandler<Ctx, Args>,
461
+ Output extends ExtractOutput<Handler>,
462
+ >(
463
+ tConfig: TracedFunctionConfig<Ctx, Args, Handler, Output>,
464
+ ): RegisteredMutation<
465
+ "internal",
466
+ TracerArgs<Args>,
467
+ TracedResult<ExtractOutput<Handler>>
468
+ > => {
469
+ return internalMutationGeneric({
470
+ args: {
471
+ ...tConfig.args,
472
+ __traceContext,
473
+ },
474
+ handler: this.createTracedHandler<Ctx, Args, Handler, Output>(
475
+ tConfig,
476
+ "mutation",
477
+ "anonymous-internal-mutation",
478
+ ),
479
+ });
480
+ };
481
+
482
+ /**
483
+ * Creates a traced action.
484
+ * @example
485
+ * ```ts
486
+ * export const someAction = tracedAction({
487
+ * name: "someAction",
488
+ * args: { userId: v.id("users") },
489
+ * handler: async (ctx, args) => {
490
+ * // do something
491
+ * },
492
+ * });
493
+ * ```
494
+ */
495
+ tracedAction = <
496
+ Ctx extends ActionCtxWithTracer<DataModel>,
497
+ Args extends PropertyValidators | EmptyObject,
498
+ Handler extends TracerHandler<Ctx, Args>,
499
+ Output extends ExtractOutput<Handler>,
500
+ >(
501
+ tConfig: TracedFunctionConfig<Ctx, Args, Handler, Output>,
502
+ ): RegisteredAction<"public", TracerArgs<Args>, TracedResult<Output>> => {
503
+ return actionGeneric({
504
+ args: {
505
+ ...tConfig.args,
506
+ __traceContext,
507
+ },
508
+ handler: this.createTracedHandler<Ctx, Args, Handler, Output>(
509
+ tConfig,
510
+ "action",
511
+ "anonymous-action",
512
+ ),
513
+ });
514
+ };
515
+
516
+ /**
517
+ * Creates a traced action.
518
+ * @example
519
+ * ```ts
520
+ * export const someAction = internalTracedAction({
521
+ * name: "someAction",
522
+ * args: { userId: v.id("users") },
523
+ * handler: async (ctx, args) => {
524
+ * // do something
525
+ * },
526
+ * });
527
+ * ```
528
+ */
529
+ internalTracedAction = <
530
+ Ctx extends ActionCtxWithTracer<DataModel>,
531
+ Args extends PropertyValidators | EmptyObject,
532
+ Handler extends TracerHandler<Ctx, Args>,
533
+ Output extends ExtractOutput<Handler>,
534
+ >(
535
+ tConfig: TracedFunctionConfig<Ctx, Args, Handler, Output>,
536
+ ): RegisteredAction<"internal", TracerArgs<Args>, TracedResult<Output>> => {
537
+ return internalActionGeneric({
538
+ args: {
539
+ ...tConfig.args,
540
+ __traceContext,
541
+ },
542
+ handler: this.createTracedHandler<Ctx, Args, Handler, Output>(
543
+ tConfig,
544
+ "action",
545
+ "anonymous-internal-action",
546
+ ),
547
+ });
548
+ };
549
+
550
+ get tracer() {
551
+ return {
552
+ /**
553
+ * Retrieves a trace by its ID.
554
+ * @param traceId - The ID of the trace to retrieve.
555
+ * @example
556
+ * ```ts
557
+ * // In a convex function (query, mutation, or action)
558
+ * // Or in a traced function
559
+ * const trace = await tracer.getTrace("123")
560
+ * ```
561
+ */
562
+ getTrace: async (
563
+ ctx: GenericFunctionContext<DataModel>,
564
+ traceId: string,
565
+ ): Promise<CompleteTrace | null> => {
566
+ return await ctx.runQuery(this.component.lib.getTrace, { traceId });
567
+ },
568
+
569
+ /**
570
+ * Lists traces with optional filtering by status.
571
+ * @param status - The status of the traces to retrieve.
572
+ * @param limit - The maximum number of traces to retrieve.
573
+ * @param userId - The ID of the user to retrieve traces for.
574
+ * @example
575
+ * ```ts
576
+ * // In a convex function (query, mutation, or action)
577
+ * // Or in a traced function
578
+ * const traces = await tracer.listTraces({ status: "success", limit: 10 })
579
+ * ```
580
+ */
581
+ listTraces: async (
582
+ ctx: GenericFunctionContext<DataModel>,
583
+ args?: {
584
+ status?: Infer<typeof statusValidator>;
585
+ limit?: number;
586
+ userId?: string;
587
+ },
588
+ ): Promise<Trace[]> => {
589
+ return await ctx.runQuery(this.component.lib.listTraces, { ...args });
590
+ },
591
+ };
592
+ }
593
+ }
@@ -0,0 +1,26 @@
1
+ /// <reference types="vite/client" />
2
+ import { convexTest } from "convex-test";
3
+ import { test } from "vitest";
4
+ export const modules = import.meta.glob("./**/*.*s");
5
+
6
+ import {
7
+ componentsGeneric,
8
+ defineSchema,
9
+ type GenericSchema,
10
+ type SchemaDefinition,
11
+ } from "convex/server";
12
+ import { type ComponentApi } from "../component/_generated/component.js";
13
+ import { register } from "../test.js";
14
+
15
+ export function initConvexTest<
16
+ Schema extends SchemaDefinition<GenericSchema, boolean>,
17
+ >(schema?: Schema) {
18
+ const t = convexTest(schema ?? defineSchema({}), modules);
19
+ register(t);
20
+ return t;
21
+ }
22
+ export const components = componentsGeneric() as unknown as {
23
+ timeline: ComponentApi;
24
+ };
25
+
26
+ test("setup", () => {});