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,278 @@
1
+ import type { GenericDataModel } from "convex/server";
2
+ import type { ObjectType, PropertyValidators } from "convex/values";
3
+ import type { ComponentApi } from "../component/_generated/component";
4
+ import TracerAPI from "./tracer-api/index";
5
+ import type {
6
+ ArgsWithTraceContext,
7
+ LogArgs,
8
+ OptionalArgsObject,
9
+ StrippedGenericFunctionContext,
10
+ TraceContext,
11
+ TracedFunctionOptions,
12
+ TracedResult,
13
+ TracerConfig,
14
+ TracerHandler,
15
+ } from "./types";
16
+
17
+ function pick<T extends Record<string, any>, Keys extends (keyof T)[]>(
18
+ obj: T,
19
+ keys: Keys,
20
+ ) {
21
+ return Object.fromEntries(
22
+ Object.entries(obj).filter(([k]) => keys.includes(k as Keys[number])),
23
+ ) as {
24
+ [K in Keys[number]]: T[K];
25
+ };
26
+ }
27
+
28
+ export function extractTraceContext<Args extends Record<string, unknown>>(
29
+ allArgs: ArgsWithTraceContext<Args>,
30
+ ): { existingContext?: TraceContext; args: Args } {
31
+ const existingContext = allArgs.__traceContext;
32
+ const args = { ...allArgs };
33
+ delete (args as ArgsWithTraceContext<Args>).__traceContext;
34
+ return { existingContext, args };
35
+ }
36
+
37
+ export function prepareLogArgs<Args extends PropertyValidators>(
38
+ args: ObjectType<Args>,
39
+ logArgs: LogArgs<Args>,
40
+ ): unknown | undefined {
41
+ if (!logArgs) return undefined;
42
+ if (logArgs === true) return args;
43
+ if (Array.isArray(logArgs)) {
44
+ return pick(args, logArgs);
45
+ }
46
+ return undefined;
47
+ }
48
+
49
+ export async function setupTraceContext(
50
+ ctx: StrippedGenericFunctionContext<GenericDataModel>,
51
+ component: ComponentApi,
52
+ existingContext: TraceContext | undefined,
53
+ startTime: number,
54
+ functionName: string,
55
+ sampleRate: number,
56
+ retentionMinutes: number,
57
+ preserveErrors: boolean,
58
+ spanData: { functionName?: string; args?: unknown },
59
+ ): Promise<{
60
+ traceId: string;
61
+ spanId: string;
62
+ traceContext: TraceContext;
63
+ isRoot: boolean;
64
+ }> {
65
+ if (existingContext?.traceId) {
66
+ const spanId = await ctx.runMutation(component.lib.createSpan, {
67
+ traceId: existingContext.traceId,
68
+ span: {
69
+ parentSpanId: existingContext.spanId,
70
+ spanName: functionName,
71
+ source: "backend",
72
+ startTime,
73
+ status: "pending",
74
+ functionName: spanData.functionName,
75
+ args: spanData.args,
76
+ },
77
+ });
78
+
79
+ return {
80
+ traceId: existingContext.traceId,
81
+ spanId,
82
+ traceContext: {
83
+ traceId: existingContext.traceId,
84
+ spanId,
85
+ sampleRate: existingContext.sampleRate,
86
+ retentionMinutes: existingContext.retentionMinutes,
87
+ preserveErrors: existingContext.preserveErrors,
88
+ },
89
+ isRoot: false,
90
+ };
91
+ }
92
+
93
+ const traceId = await ctx.runMutation(component.lib.createTrace, {
94
+ status: "pending",
95
+ sampleRate,
96
+ metadata: {},
97
+ source: "backend",
98
+ });
99
+
100
+ const spanId = await ctx.runMutation(component.lib.createSpan, {
101
+ traceId,
102
+ span: {
103
+ spanName: functionName,
104
+ source: "backend",
105
+ startTime: Date.now(),
106
+ status: "pending",
107
+ functionName: spanData.functionName,
108
+ args: spanData.args,
109
+ },
110
+ });
111
+
112
+ return {
113
+ traceId,
114
+ spanId,
115
+ traceContext: {
116
+ traceId,
117
+ spanId,
118
+ sampleRate,
119
+ retentionMinutes,
120
+ preserveErrors,
121
+ },
122
+ isRoot: true,
123
+ };
124
+ }
125
+
126
+ export async function executeTracedHandler<
127
+ Args extends PropertyValidators,
128
+ Output,
129
+ EnhancedCtx,
130
+ >(params: {
131
+ ctx: StrippedGenericFunctionContext<GenericDataModel>;
132
+ component: ComponentApi;
133
+ traceId: string;
134
+ spanId: string;
135
+ startTime: number;
136
+ config: TracedFunctionOptions<EnhancedCtx, Args, Output> & TracerConfig;
137
+ args: OptionalArgsObject<Args>;
138
+ handler: TracerHandler<EnhancedCtx, Args>;
139
+ enhancedCtx: EnhancedCtx;
140
+ isRoot: boolean;
141
+ }): Promise<TracedResult<Output>> {
142
+ const {
143
+ ctx,
144
+ component,
145
+ traceId,
146
+ spanId,
147
+ startTime,
148
+ config,
149
+ args,
150
+ handler,
151
+ enhancedCtx,
152
+ isRoot,
153
+ } = params;
154
+
155
+ const defaultConfig = (enhancedCtx as any).tracer
156
+ .config as Required<TracerConfig>;
157
+
158
+ try {
159
+ // Reject any functions that pass a traceId that doesn't exist
160
+ // These params can be passed from the frontend to break tracing
161
+ // Running a tracedFunction without passing ids will create a new trace
162
+ if (traceId) {
163
+ const traceExists = await ctx.runQuery(component.lib.verifyTrace, {
164
+ traceId,
165
+ });
166
+
167
+ if (!traceExists)
168
+ throw new Error("Cannot pass a traceId for a trace that doesn't exist");
169
+ }
170
+
171
+ // Reject any functions that pass a spanId that doesn't exist
172
+ // These params can be passed from the frontend to break tracing
173
+ if (spanId) {
174
+ const spanExists = await ctx.runQuery(component.lib.verifySpan, {
175
+ spanId,
176
+ });
177
+
178
+ if (!spanExists)
179
+ throw new Error("Cannot pass a spanId for a span that doesn't exist");
180
+ }
181
+
182
+ if (config.onStart) {
183
+ await config.onStart(enhancedCtx, args);
184
+ }
185
+
186
+ const result = await handler(enhancedCtx, args);
187
+
188
+ if (config.onSuccess) {
189
+ await config.onSuccess(enhancedCtx, args, result);
190
+ }
191
+
192
+ const now = Date.now();
193
+
194
+ await ctx
195
+ .runMutation(component.lib.completeSpan, {
196
+ spanId,
197
+ endTime: now,
198
+ duration: now - startTime,
199
+ status: "success",
200
+ result: config.logReturn ? result : undefined,
201
+ })
202
+ .catch((err) =>
203
+ console.error("[Tracer] Failed to complete span with success:", err),
204
+ );
205
+
206
+ if (isRoot) {
207
+ await ctx
208
+ .runMutation(component.lib.updateTraceStatus, {
209
+ traceId,
210
+ status: "success",
211
+ })
212
+ .catch((err) =>
213
+ console.error("[Tracer] Failed to update trace status:", err),
214
+ );
215
+ }
216
+
217
+ return { success: true, data: result, error: undefined };
218
+ } catch (e) {
219
+ const error = e as unknown as Error;
220
+
221
+ if (config.onError) {
222
+ await config.onError(enhancedCtx, args, error);
223
+ }
224
+
225
+ const preserveErrors =
226
+ config.preserveErrors ?? defaultConfig.preserveErrors;
227
+
228
+ if (preserveErrors) {
229
+ const tracerAPI = new TracerAPI(ctx, component, traceId, spanId, config);
230
+ await tracerAPI.preserve();
231
+ }
232
+
233
+ await ctx
234
+ .runMutation(component.lib.completeSpan, {
235
+ spanId,
236
+ endTime: Date.now(),
237
+ duration: Date.now() - startTime,
238
+ status: "error",
239
+ error: error.message,
240
+ })
241
+ .catch((err) =>
242
+ console.error("[Tracer] Failed to complete span with error:", err),
243
+ );
244
+
245
+ if (isRoot) {
246
+ await ctx
247
+ .runMutation(component.lib.updateTraceStatus, {
248
+ traceId,
249
+ status: "error",
250
+ })
251
+ .catch((err) =>
252
+ console.error("[Tracer] Failed to update trace status:", err),
253
+ );
254
+ }
255
+
256
+ return { success: false, data: undefined, error: error.message };
257
+ } finally {
258
+ const retMins = config.retentionMinutes ?? defaultConfig.retentionMinutes;
259
+
260
+ if (!retMins) {
261
+ console.error("[Tracer] retentionMinutes is not defined");
262
+ }
263
+
264
+ const sampleRate = config.sampleRate ?? defaultConfig.sampleRate;
265
+ if (!sampleRate) {
266
+ console.error("[Tracer] sampleRate is not defined");
267
+ } else if (sampleRate && sampleRate < 1) {
268
+ const MINUTE = 60 * 1000;
269
+ const delayMins = retMins ?? 120;
270
+
271
+ const delay = delayMins * MINUTE;
272
+
273
+ await ctx.scheduler.runAfter(delay, component.lib.cleanupTrace, {
274
+ traceId,
275
+ });
276
+ }
277
+ }
278
+ }