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,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal mutations for managing traces, spans, and logs.
|
|
3
|
+
* These are called automatically by the tracing system to persist data immediately.
|
|
4
|
+
*/
|
|
5
|
+
import { v } from "convex/values";
|
|
6
|
+
import type { Id } from "./_generated/dataModel.js";
|
|
7
|
+
import { mutation, query, type MutationCtx } from "./_generated/server.js";
|
|
8
|
+
import schema, {
|
|
9
|
+
severityValidator,
|
|
10
|
+
sourceValidator,
|
|
11
|
+
statusValidator,
|
|
12
|
+
} from "./schema.js";
|
|
13
|
+
import { vCompleteTrace } from "./types.js";
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Trace Operations
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new trace in the database.
|
|
21
|
+
* Called automatically when a traced function is invoked without an existing trace context.
|
|
22
|
+
*/
|
|
23
|
+
export const createTrace = mutation({
|
|
24
|
+
args: {
|
|
25
|
+
status: statusValidator,
|
|
26
|
+
sampleRate: v.number(),
|
|
27
|
+
metadata: v.optional(v.record(v.string(), v.any())),
|
|
28
|
+
source: sourceValidator,
|
|
29
|
+
},
|
|
30
|
+
returns: v.id("traces"),
|
|
31
|
+
handler: async (ctx, args): Promise<Id<"traces">> => {
|
|
32
|
+
return await ctx.db.insert("traces", {
|
|
33
|
+
status: args.status,
|
|
34
|
+
sampleRate: args.sampleRate,
|
|
35
|
+
updatedAt: Date.now(),
|
|
36
|
+
metadata: args.metadata,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Updates the status of an existing trace.
|
|
43
|
+
* Called when a root traced function completes or errors.
|
|
44
|
+
*/
|
|
45
|
+
export const updateTraceStatus = mutation({
|
|
46
|
+
args: {
|
|
47
|
+
traceId: v.string(),
|
|
48
|
+
status: statusValidator,
|
|
49
|
+
},
|
|
50
|
+
returns: v.null(),
|
|
51
|
+
handler: async (ctx, args): Promise<void> => {
|
|
52
|
+
await ctx.db.patch("traces", args.traceId as Id<"traces">, {
|
|
53
|
+
status: args.status,
|
|
54
|
+
updatedAt: Date.now(),
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Updates the preserve flag on a trace.
|
|
61
|
+
* Called when tracer.preserve(), tracer.discard(), or tracer.sample() is invoked.
|
|
62
|
+
*/
|
|
63
|
+
export const updateTracePreserve = mutation({
|
|
64
|
+
args: {
|
|
65
|
+
traceId: v.string(),
|
|
66
|
+
preserve: v.optional(v.boolean()),
|
|
67
|
+
sampleRate: v.optional(v.number()),
|
|
68
|
+
},
|
|
69
|
+
returns: v.null(),
|
|
70
|
+
handler: async (ctx, { sampleRate, traceId, preserve }): Promise<void> => {
|
|
71
|
+
const srUpdate = {} as any;
|
|
72
|
+
if (sampleRate) srUpdate.sampleRate = sampleRate;
|
|
73
|
+
|
|
74
|
+
await ctx.db.patch("traces", traceId as Id<"traces">, {
|
|
75
|
+
preserve: preserve,
|
|
76
|
+
updatedAt: Date.now(),
|
|
77
|
+
...srUpdate,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Updates the trace metadata.
|
|
84
|
+
* Called when tracer.setMetadata() is invoked.
|
|
85
|
+
*/
|
|
86
|
+
export const updateTraceMetadata = mutation({
|
|
87
|
+
args: {
|
|
88
|
+
traceId: v.string(),
|
|
89
|
+
metadata: v.record(v.string(), v.any()),
|
|
90
|
+
},
|
|
91
|
+
returns: v.null(),
|
|
92
|
+
handler: async (ctx, args): Promise<void> => {
|
|
93
|
+
const trace = await ctx.db.get("traces", args.traceId as Id<"traces">);
|
|
94
|
+
if (!trace) throw new Error(`Trace not found: ${args.traceId}`);
|
|
95
|
+
|
|
96
|
+
await ctx.db.patch("traces", args.traceId as Id<"traces">, {
|
|
97
|
+
metadata: {
|
|
98
|
+
...trace.metadata,
|
|
99
|
+
...args.metadata,
|
|
100
|
+
},
|
|
101
|
+
updatedAt: Date.now(),
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Span Operations
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a new span in the database.
|
|
112
|
+
* Called automatically when a traced function starts or when withSpan() is called.
|
|
113
|
+
*/
|
|
114
|
+
export const createSpan = mutation({
|
|
115
|
+
args: {
|
|
116
|
+
traceId: v.string(),
|
|
117
|
+
span: v.object({
|
|
118
|
+
parentSpanId: v.optional(v.string()),
|
|
119
|
+
spanName: v.string(),
|
|
120
|
+
source: sourceValidator,
|
|
121
|
+
startTime: v.number(),
|
|
122
|
+
status: statusValidator,
|
|
123
|
+
functionName: v.optional(v.string()),
|
|
124
|
+
args: v.optional(v.any()),
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
returns: v.id("spans"),
|
|
128
|
+
handler: async (ctx, args): Promise<Id<"spans">> => {
|
|
129
|
+
return await ctx.db.insert("spans", {
|
|
130
|
+
...args.span,
|
|
131
|
+
traceId: args.traceId as Id<"traces">,
|
|
132
|
+
parentSpanId: args.span.parentSpanId
|
|
133
|
+
? (args.span.parentSpanId as Id<"spans">)
|
|
134
|
+
: undefined,
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Completes a span by updating its end time, duration, status, and optional result/error.
|
|
141
|
+
* Called automatically when a traced function completes or when withSpan() finishes.
|
|
142
|
+
*/
|
|
143
|
+
export const completeSpan = mutation({
|
|
144
|
+
args: {
|
|
145
|
+
spanId: v.string(),
|
|
146
|
+
endTime: v.number(),
|
|
147
|
+
duration: v.number(),
|
|
148
|
+
status: v.union(v.literal("success"), v.literal("error")),
|
|
149
|
+
result: v.optional(v.any()),
|
|
150
|
+
error: v.optional(v.string()),
|
|
151
|
+
},
|
|
152
|
+
returns: v.null(),
|
|
153
|
+
handler: async (ctx, args): Promise<void> => {
|
|
154
|
+
await ctx.db.patch("spans", args.spanId as Id<"spans">, {
|
|
155
|
+
endTime: args.endTime,
|
|
156
|
+
duration: args.duration,
|
|
157
|
+
status: args.status,
|
|
158
|
+
result: args.result,
|
|
159
|
+
error: args.error,
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Updates the metadata of a span.
|
|
166
|
+
* Called when span.setMetadata() is invoked within withSpan().
|
|
167
|
+
*/
|
|
168
|
+
export const updateSpanMetadata = mutation({
|
|
169
|
+
args: {
|
|
170
|
+
spanId: v.string(),
|
|
171
|
+
metadata: v.record(v.string(), v.any()),
|
|
172
|
+
},
|
|
173
|
+
returns: v.null(),
|
|
174
|
+
handler: async (ctx, args): Promise<void> => {
|
|
175
|
+
const span = await ctx.db.get("spans", args.spanId as Id<"spans">);
|
|
176
|
+
if (!span) throw new Error(`Span not found: ${args.spanId}`);
|
|
177
|
+
|
|
178
|
+
await ctx.db.patch(span._id, {
|
|
179
|
+
metadata: {
|
|
180
|
+
...span.metadata,
|
|
181
|
+
...args.metadata,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ============================================================================
|
|
188
|
+
// Log Operations
|
|
189
|
+
// ============================================================================
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Adds a log entry to a specific span.
|
|
193
|
+
* Called when tracer.info(), tracer.warn(), or tracer.error() is invoked.
|
|
194
|
+
*/
|
|
195
|
+
export const addLog = mutation({
|
|
196
|
+
args: {
|
|
197
|
+
spanId: v.string(),
|
|
198
|
+
log: v.object({
|
|
199
|
+
timestamp: v.number(),
|
|
200
|
+
severity: severityValidator,
|
|
201
|
+
message: v.string(),
|
|
202
|
+
metadata: v.optional(v.record(v.string(), v.any())),
|
|
203
|
+
}),
|
|
204
|
+
},
|
|
205
|
+
returns: v.id("logs"),
|
|
206
|
+
handler: async (ctx, args): Promise<Id<"logs">> => {
|
|
207
|
+
const span = await ctx.db.get("spans", args.spanId as Id<"spans">);
|
|
208
|
+
if (!span) throw new Error(`Span not found: ${args.spanId}`);
|
|
209
|
+
|
|
210
|
+
return await ctx.db.insert("logs", {
|
|
211
|
+
spanId: span._id,
|
|
212
|
+
...args.log,
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// Query Operations
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Verify trace exists
|
|
223
|
+
*/
|
|
224
|
+
export const verifyTrace = query({
|
|
225
|
+
args: { traceId: v.string() },
|
|
226
|
+
returns: v.boolean(),
|
|
227
|
+
handler: async (ctx, { traceId }) => {
|
|
228
|
+
const trace = await ctx.db.get("traces", traceId as Id<"traces">);
|
|
229
|
+
if (!trace) return false;
|
|
230
|
+
|
|
231
|
+
return true;
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
export const verifySpan = query({
|
|
236
|
+
args: { spanId: v.string() },
|
|
237
|
+
returns: v.boolean(),
|
|
238
|
+
handler: async (ctx, { spanId }) => {
|
|
239
|
+
const span = await ctx.db.get("spans", spanId as Id<"spans">);
|
|
240
|
+
if (!span) return false;
|
|
241
|
+
|
|
242
|
+
return true;
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Retrieves a complete trace with all its spans and logs.
|
|
248
|
+
*/
|
|
249
|
+
export const getTrace = query({
|
|
250
|
+
args: { traceId: v.string() },
|
|
251
|
+
returns: v.union(v.null(), vCompleteTrace),
|
|
252
|
+
handler: async (ctx, { traceId }) => {
|
|
253
|
+
const trace = await ctx.db.get("traces", traceId as Id<"traces">);
|
|
254
|
+
if (!trace) return null;
|
|
255
|
+
|
|
256
|
+
const spans = await ctx.db
|
|
257
|
+
.query("spans")
|
|
258
|
+
.withIndex("by_traceId", (q) => q.eq("traceId", traceId as Id<"traces">))
|
|
259
|
+
.collect();
|
|
260
|
+
|
|
261
|
+
const spansWithLogs = await Promise.all(
|
|
262
|
+
spans.map(async (span) => ({
|
|
263
|
+
...span,
|
|
264
|
+
children: [],
|
|
265
|
+
logs: await ctx.db
|
|
266
|
+
.query("logs")
|
|
267
|
+
.withIndex("by_spanId", (q) => q.eq("spanId", span._id))
|
|
268
|
+
.collect(),
|
|
269
|
+
})),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const spanMap = new Map(spansWithLogs.map((span) => [span._id, span]));
|
|
273
|
+
|
|
274
|
+
spansWithLogs.forEach((span) => {
|
|
275
|
+
if (span.parentSpanId) {
|
|
276
|
+
const parentSpan = spanMap.get(span.parentSpanId);
|
|
277
|
+
if (parentSpan) {
|
|
278
|
+
(parentSpan.children as any[]).push(span);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const sortSpanChildren = (span: (typeof spansWithLogs)[0]) => {
|
|
284
|
+
if (span.children.length > 0) {
|
|
285
|
+
(span.children as any[]).sort(
|
|
286
|
+
(a, b) => a._creationTime - b._creationTime,
|
|
287
|
+
);
|
|
288
|
+
span.children.forEach(sortSpanChildren);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const rootSpans = spansWithLogs
|
|
293
|
+
.filter((span) => !span.parentSpanId)
|
|
294
|
+
.sort((a, b) => a._creationTime - b._creationTime);
|
|
295
|
+
|
|
296
|
+
rootSpans.forEach(sortSpanChildren);
|
|
297
|
+
|
|
298
|
+
return { ...trace, spans: rootSpans };
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Lists traces with optional filtering by status.
|
|
304
|
+
*/
|
|
305
|
+
export const listTraces = query({
|
|
306
|
+
args: {
|
|
307
|
+
status: v.optional(statusValidator),
|
|
308
|
+
limit: v.optional(v.number()),
|
|
309
|
+
userId: v.optional(v.string()),
|
|
310
|
+
},
|
|
311
|
+
returns: v.array(
|
|
312
|
+
schema.tables.traces.validator.extend({
|
|
313
|
+
_id: v.id("traces"),
|
|
314
|
+
_creationTime: v.number(),
|
|
315
|
+
}),
|
|
316
|
+
),
|
|
317
|
+
handler: async (ctx, { status, limit, userId }) => {
|
|
318
|
+
const query =
|
|
319
|
+
status && !userId
|
|
320
|
+
? ctx.db
|
|
321
|
+
.query("traces")
|
|
322
|
+
.withIndex("by_status", (q) => q.eq("status", status))
|
|
323
|
+
: !status && userId
|
|
324
|
+
? ctx.db
|
|
325
|
+
.query("traces")
|
|
326
|
+
.withIndex("by_userId", (q) => q.eq("userId", userId))
|
|
327
|
+
: status && userId
|
|
328
|
+
? ctx.db
|
|
329
|
+
.query("traces")
|
|
330
|
+
.withIndex("by_status_and_userId", (q) =>
|
|
331
|
+
q.eq("status", status).eq("userId", userId),
|
|
332
|
+
)
|
|
333
|
+
: ctx.db.query("traces");
|
|
334
|
+
|
|
335
|
+
return await query.order("desc").take(limit ?? 100);
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ============================================================================
|
|
340
|
+
// Cleanup Operations
|
|
341
|
+
// ============================================================================
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Cleans up old traces based on retention policy and sampling.
|
|
345
|
+
* Should be called periodically by a Convex scheduler.
|
|
346
|
+
*/
|
|
347
|
+
export const cleanupTrace = mutation({
|
|
348
|
+
args: {
|
|
349
|
+
traceId: v.string(),
|
|
350
|
+
},
|
|
351
|
+
returns: v.null(),
|
|
352
|
+
handler: async (ctx, args) => {
|
|
353
|
+
const trace = await ctx.db.get("traces", args.traceId as Id<"traces">);
|
|
354
|
+
|
|
355
|
+
if (!trace) return;
|
|
356
|
+
|
|
357
|
+
// Always keep explicitly preserved traces
|
|
358
|
+
if (trace.preserve === true) return;
|
|
359
|
+
|
|
360
|
+
// Always delete explicitly discarded traces
|
|
361
|
+
if (trace.preserve === false) {
|
|
362
|
+
return await deleteTrace(ctx, trace._id);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Apply sampling for undefined preserve status
|
|
366
|
+
const random = Math.random();
|
|
367
|
+
if (random >= trace.sampleRate) await deleteTrace(ctx, trace._id);
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Deletes a trace and all its associated spans and logs.
|
|
373
|
+
*/
|
|
374
|
+
async function deleteTrace(
|
|
375
|
+
ctx: MutationCtx,
|
|
376
|
+
traceId: Id<"traces">,
|
|
377
|
+
): Promise<void> {
|
|
378
|
+
const spans = await ctx.db
|
|
379
|
+
.query("spans")
|
|
380
|
+
.withIndex("by_traceId", (q) => q.eq("traceId", traceId))
|
|
381
|
+
.collect();
|
|
382
|
+
|
|
383
|
+
const logsRequest = spans.map((span) =>
|
|
384
|
+
ctx.db
|
|
385
|
+
.query("logs")
|
|
386
|
+
.withIndex("by_spanId", (q) => q.eq("spanId", span._id))
|
|
387
|
+
.collect(),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const logs = await Promise.all(logsRequest);
|
|
391
|
+
|
|
392
|
+
const deletionRequests = [
|
|
393
|
+
...logs.flat().map((log) => ctx.db.delete(log._id)),
|
|
394
|
+
...spans.map((span) => ctx.db.delete(span._id)),
|
|
395
|
+
ctx.db.delete(traceId),
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
await Promise.all(deletionRequests);
|
|
399
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
export const statusValidator = v.union(
|
|
5
|
+
v.literal("pending"),
|
|
6
|
+
v.literal("success"),
|
|
7
|
+
v.literal("error"),
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export const sourceValidator = v.union(
|
|
11
|
+
v.literal("frontend"),
|
|
12
|
+
v.literal("backend"),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const severityValidator = v.union(
|
|
16
|
+
v.literal("info"),
|
|
17
|
+
v.literal("warn"),
|
|
18
|
+
v.literal("error"),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export default defineSchema({
|
|
22
|
+
traces: defineTable({
|
|
23
|
+
status: statusValidator,
|
|
24
|
+
sampleRate: v.number(),
|
|
25
|
+
preserve: v.optional(v.boolean()),
|
|
26
|
+
updatedAt: v.number(),
|
|
27
|
+
metadata: v.optional(v.record(v.string(), v.any())),
|
|
28
|
+
userId: v.optional(v.string()),
|
|
29
|
+
})
|
|
30
|
+
.index("by_status", ["status"])
|
|
31
|
+
.index("by_userId", ["userId"])
|
|
32
|
+
.index("by_status_and_userId", ["status", "userId"]),
|
|
33
|
+
|
|
34
|
+
spans: defineTable({
|
|
35
|
+
traceId: v.id("traces"),
|
|
36
|
+
parentSpanId: v.optional(v.id("spans")),
|
|
37
|
+
spanName: v.string(),
|
|
38
|
+
source: sourceValidator,
|
|
39
|
+
startTime: v.number(),
|
|
40
|
+
endTime: v.optional(v.number()),
|
|
41
|
+
duration: v.optional(v.number()),
|
|
42
|
+
status: statusValidator,
|
|
43
|
+
functionName: v.optional(v.string()),
|
|
44
|
+
args: v.optional(v.any()),
|
|
45
|
+
result: v.optional(v.any()),
|
|
46
|
+
error: v.optional(v.string()),
|
|
47
|
+
metadata: v.optional(v.record(v.string(), v.any())),
|
|
48
|
+
})
|
|
49
|
+
.index("by_traceId", ["traceId"])
|
|
50
|
+
.index("by_parentSpanId", ["parentSpanId"])
|
|
51
|
+
.index("by_status", ["status"]),
|
|
52
|
+
|
|
53
|
+
logs: defineTable({
|
|
54
|
+
spanId: v.id("spans"),
|
|
55
|
+
timestamp: v.number(),
|
|
56
|
+
severity: severityValidator,
|
|
57
|
+
message: v.string(),
|
|
58
|
+
metadata: v.optional(v.record(v.string(), v.any())),
|
|
59
|
+
})
|
|
60
|
+
.index("by_spanId", ["spanId"])
|
|
61
|
+
.index("by_severity", ["severity"]),
|
|
62
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
import { convexTest } from "convex-test";
|
|
3
|
+
import { test } from "vitest";
|
|
4
|
+
import schema from "./schema.js";
|
|
5
|
+
export const modules = import.meta.glob("./**/*.*s");
|
|
6
|
+
|
|
7
|
+
export function initConvexTest() {
|
|
8
|
+
const t = convexTest(schema, modules);
|
|
9
|
+
return t;
|
|
10
|
+
}
|
|
11
|
+
test("setup", () => {});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v, type Infer } from "convex/values";
|
|
2
|
+
import schema from "./schema";
|
|
3
|
+
|
|
4
|
+
export const vTrace = schema.tables.traces.validator.extend({
|
|
5
|
+
_id: v.string(),
|
|
6
|
+
_creationTime: v.number(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const vSpan = schema.tables.spans.validator
|
|
10
|
+
.omit("parentSpanId")
|
|
11
|
+
.omit("traceId")
|
|
12
|
+
.extend({
|
|
13
|
+
_id: v.string(),
|
|
14
|
+
traceId: v.string(),
|
|
15
|
+
parentSpanId: v.optional(v.string()),
|
|
16
|
+
_creationTime: v.number(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const vLog = schema.tables.logs.validator.omit("spanId").extend({
|
|
20
|
+
_id: v.string(),
|
|
21
|
+
spanId: v.string(),
|
|
22
|
+
_creationTime: v.number(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const vSpanWithLogs = vSpan.extend({
|
|
26
|
+
logs: v.optional(v.array(vLog)),
|
|
27
|
+
children: v.optional(v.array(v.any())),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const vCompleteTrace = vTrace.extend({
|
|
31
|
+
spans: v.array(vSpanWithLogs),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type Trace = Infer<typeof vTrace>;
|
|
35
|
+
export type Span = Infer<typeof vSpan>;
|
|
36
|
+
export type Log = Infer<typeof vLog>;
|
|
37
|
+
export type SpanWithLogs = Infer<typeof vSpanWithLogs>;
|
|
38
|
+
export type CompleteTrace = Infer<typeof vCompleteTrace>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useAction, useMutation } from "convex/react";
|
|
2
|
+
|
|
3
|
+
import type { FunctionReference, FunctionReturnType } from "convex/server";
|
|
4
|
+
import type { EmptyObject, OptionalTracedArgs } from "./types";
|
|
5
|
+
|
|
6
|
+
export function useTracedQuery<TQuery extends FunctionReference<"mutation">>(
|
|
7
|
+
fnRef: TQuery,
|
|
8
|
+
): OptionalTracedArgs<TQuery> extends [args?: EmptyObject]
|
|
9
|
+
? (args?: EmptyObject) => Promise<FunctionReturnType<TQuery>>
|
|
10
|
+
: OptionalTracedArgs<TQuery> extends [args: infer Args]
|
|
11
|
+
? (args: Args) => Promise<FunctionReturnType<TQuery>>
|
|
12
|
+
: never {
|
|
13
|
+
return useMutation(fnRef) as any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useTracedMutation<
|
|
17
|
+
TMutation extends FunctionReference<"mutation">,
|
|
18
|
+
>(
|
|
19
|
+
fnRef: TMutation,
|
|
20
|
+
): OptionalTracedArgs<TMutation> extends [args?: EmptyObject]
|
|
21
|
+
? (args?: EmptyObject) => Promise<FunctionReturnType<TMutation>>
|
|
22
|
+
: OptionalTracedArgs<TMutation> extends [args: infer Args]
|
|
23
|
+
? (args: Args) => Promise<FunctionReturnType<TMutation>>
|
|
24
|
+
: never {
|
|
25
|
+
return useMutation(fnRef) as any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useTracedAction<TAction extends FunctionReference<"action">>(
|
|
29
|
+
fnRef: TAction,
|
|
30
|
+
): OptionalTracedArgs<TAction> extends [args?: EmptyObject]
|
|
31
|
+
? (args?: EmptyObject) => Promise<FunctionReturnType<TAction>>
|
|
32
|
+
: OptionalTracedArgs<TAction> extends [args: infer Args]
|
|
33
|
+
? (args: Args) => Promise<FunctionReturnType<TAction>>
|
|
34
|
+
: never {
|
|
35
|
+
return useAction(fnRef) as any;
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AnyFunctionReference } from "../client/types";
|
|
2
|
+
|
|
3
|
+
export type EmptyObject = Record<string, never>;
|
|
4
|
+
|
|
5
|
+
export type OmitTraceContext<T> = T extends { __traceContext?: any }
|
|
6
|
+
? Omit<T, "__traceContext">
|
|
7
|
+
: T;
|
|
8
|
+
|
|
9
|
+
export type ArgsWithoutTrace<Args> =
|
|
10
|
+
Args extends Record<string, any> ? OmitTraceContext<Args> : Args;
|
|
11
|
+
|
|
12
|
+
export type OptionalTracedArgs<FuncRef extends AnyFunctionReference> =
|
|
13
|
+
keyof OmitTraceContext<FuncRef["_args"]> extends never
|
|
14
|
+
? [args?: EmptyObject]
|
|
15
|
+
: [args: OmitTraceContext<FuncRef["_args"]>];
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
import type { TestConvex } from "convex-test";
|
|
3
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
4
|
+
import schema from "./component/schema.js";
|
|
5
|
+
const modules = import.meta.glob("./component/**/*.ts");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register the component with the test convex instance.
|
|
9
|
+
* @param t - The test convex instance, e.g. from calling `convexTest`.
|
|
10
|
+
* @param name - The name of the component, as registered in convex.config.ts.
|
|
11
|
+
*/
|
|
12
|
+
export function register(
|
|
13
|
+
t: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
|
|
14
|
+
name: string = "sampleComponent",
|
|
15
|
+
) {
|
|
16
|
+
t.registerComponent(name, schema, modules);
|
|
17
|
+
}
|
|
18
|
+
export default { register, schema, modules };
|