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.
- package/LICENSE +201 -0
- package/README.md +592 -0
- package/dist/client/_generated/_ignore.d.ts +1 -0
- package/dist/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/client/_generated/_ignore.js +3 -0
- package/dist/client/_generated/_ignore.js.map +1 -0
- package/dist/client/helpers.d.ts +31 -0
- package/dist/client/helpers.d.ts.map +1 -0
- package/dist/client/helpers.js +177 -0
- package/dist/client/helpers.js.map +1 -0
- package/dist/client/index.d.ts +210 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +355 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/tracer-api/index.d.ts +27 -0
- package/dist/client/tracer-api/index.d.ts.map +1 -0
- package/dist/client/tracer-api/index.js +177 -0
- package/dist/client/tracer-api/index.js.map +1 -0
- package/dist/client/tracer-api/types.d.ts +143 -0
- package/dist/client/tracer-api/types.d.ts.map +1 -0
- package/dist/client/tracer-api/types.js +2 -0
- package/dist/client/tracer-api/types.js.map +1 -0
- package/dist/client/types.d.ts +168 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/component/_generated/api.d.ts +36 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +139 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/lib.d.ts +161 -0
- package/dist/component/lib.d.ts.map +1 -0
- package/dist/component/lib.js +349 -0
- package/dist/component/lib.js.map +1 -0
- package/dist/component/schema.d.ts +75 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +46 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/types.d.ts +286 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component/types.js +28 -0
- package/dist/component/types.js.map +1 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +11 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/types.d.ts +8 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/types.js +2 -0
- package/dist/react/types.js.map +1 -0
- package/package.json +121 -0
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/helpers.ts +278 -0
- package/src/client/index.ts +593 -0
- package/src/client/setup.test.ts +26 -0
- package/src/client/tracer-api/index.ts +235 -0
- package/src/client/tracer-api/types.ts +168 -0
- package/src/client/types.ts +257 -0
- package/src/component/_generated/api.ts +52 -0
- package/src/component/_generated/component.ts +199 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +161 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.ts +399 -0
- package/src/component/schema.ts +62 -0
- package/src/component/setup.test.ts +11 -0
- package/src/component/types.ts +38 -0
- package/src/react/index.ts +36 -0
- package/src/react/types.ts +15 -0
- 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", () => {});
|