autotel-tanstack 1.13.35 → 1.13.36
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/package.json +4 -5
- package/src/auto.test.ts +0 -114
- package/src/auto.ts +0 -60
- package/src/browser/context.ts +0 -88
- package/src/browser/debug-headers.ts +0 -19
- package/src/browser/error-reporting.ts +0 -64
- package/src/browser/handlers.ts +0 -23
- package/src/browser/index.ts +0 -66
- package/src/browser/loaders.ts +0 -62
- package/src/browser/metrics.ts +0 -86
- package/src/browser/middleware.ts +0 -77
- package/src/browser/server-functions.ts +0 -31
- package/src/browser/testing.ts +0 -130
- package/src/browser/types.ts +0 -100
- package/src/context.test.ts +0 -90
- package/src/context.ts +0 -145
- package/src/debug-headers.ts +0 -109
- package/src/env.ts +0 -56
- package/src/error-reporting.ts +0 -204
- package/src/handlers.ts +0 -306
- package/src/index.ts +0 -97
- package/src/instrument.test.ts +0 -131
- package/src/instrument.ts +0 -97
- package/src/loaders.test.ts +0 -123
- package/src/loaders.ts +0 -356
- package/src/metrics.ts +0 -184
- package/src/middleware.test.ts +0 -198
- package/src/middleware.ts +0 -435
- package/src/route-filter.test.ts +0 -28
- package/src/route-filter.ts +0 -40
- package/src/server-functions.test.ts +0 -86
- package/src/server-functions.ts +0 -188
- package/src/testing.test.ts +0 -205
- package/src/testing.ts +0 -430
- package/src/types.test.ts +0 -46
- package/src/types.ts +0 -182
package/src/middleware.ts
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import { context, SpanStatusCode, type Attributes } from '@opentelemetry/api';
|
|
2
|
-
import { trace, type TraceContext } from 'autotel';
|
|
3
|
-
import { extractContextFromRequest } from './context';
|
|
4
|
-
import { isServerSide } from './env';
|
|
5
|
-
import { isExcludedPath } from './route-filter';
|
|
6
|
-
import {
|
|
7
|
-
type TracingMiddlewareConfig,
|
|
8
|
-
DEFAULT_CONFIG,
|
|
9
|
-
SPAN_ATTRIBUTES,
|
|
10
|
-
} from './types';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Build span attributes for HTTP requests
|
|
14
|
-
*/
|
|
15
|
-
function buildRequestAttributes(
|
|
16
|
-
request: Request,
|
|
17
|
-
config: Required<
|
|
18
|
-
Omit<TracingMiddlewareConfig, 'customAttributes' | 'service' | 'type'>
|
|
19
|
-
>,
|
|
20
|
-
): Attributes {
|
|
21
|
-
const url = new URL(request.url);
|
|
22
|
-
const attrs: Attributes = {
|
|
23
|
-
[SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,
|
|
24
|
-
[SPAN_ATTRIBUTES.URL_PATH]: url.pathname,
|
|
25
|
-
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'request',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
if (url.search) {
|
|
29
|
-
attrs[SPAN_ATTRIBUTES.URL_QUERY] = url.search;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Capture configured headers
|
|
33
|
-
if (config.captureHeaders) {
|
|
34
|
-
for (const header of config.captureHeaders) {
|
|
35
|
-
const value = request.headers.get(header);
|
|
36
|
-
if (value) {
|
|
37
|
-
attrs[`http.request.header.${header.toLowerCase()}`] = value;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return attrs;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Build span attributes for server functions
|
|
47
|
-
*/
|
|
48
|
-
function buildServerFnAttributes(
|
|
49
|
-
functionName: string,
|
|
50
|
-
method: string,
|
|
51
|
-
args: unknown,
|
|
52
|
-
config: Required<
|
|
53
|
-
Omit<TracingMiddlewareConfig, 'customAttributes' | 'service' | 'type'>
|
|
54
|
-
>,
|
|
55
|
-
): Attributes {
|
|
56
|
-
const attrs: Attributes = {
|
|
57
|
-
[SPAN_ATTRIBUTES.RPC_SYSTEM]: 'tanstack-start',
|
|
58
|
-
[SPAN_ATTRIBUTES.RPC_METHOD]: functionName,
|
|
59
|
-
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'serverFn',
|
|
60
|
-
[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_NAME]: functionName,
|
|
61
|
-
[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_METHOD]: method,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
if (config.captureArgs && args !== undefined) {
|
|
65
|
-
try {
|
|
66
|
-
attrs[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS] = JSON.stringify(args);
|
|
67
|
-
} catch {
|
|
68
|
-
attrs[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS] = '[non-serializable]';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return attrs;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Generic middleware handler type (compatible with TanStack's middleware pattern)
|
|
77
|
-
*
|
|
78
|
-
* This type represents the shape of TanStack middleware handlers.
|
|
79
|
-
* We use a generic type to avoid direct dependency on TanStack packages.
|
|
80
|
-
*/
|
|
81
|
-
export interface MiddlewareHandler<TContext = unknown> {
|
|
82
|
-
(opts: {
|
|
83
|
-
next: (ctx?: Partial<TContext>) => Promise<TContext>;
|
|
84
|
-
context: TContext;
|
|
85
|
-
request?: Request;
|
|
86
|
-
pathname?: string;
|
|
87
|
-
data?: unknown;
|
|
88
|
-
method?: string;
|
|
89
|
-
filename?: string;
|
|
90
|
-
functionId?: string;
|
|
91
|
-
signal?: AbortSignal;
|
|
92
|
-
}): Promise<TContext>;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Create a TanStack-compatible tracing middleware
|
|
97
|
-
*
|
|
98
|
-
* This creates middleware that automatically traces all requests/server functions
|
|
99
|
-
* with OpenTelemetry spans. Use with TanStack Start's middleware system.
|
|
100
|
-
*
|
|
101
|
-
* @param config - Configuration options
|
|
102
|
-
* @returns Middleware handler compatible with TanStack Start
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```typescript
|
|
106
|
-
* // Global request middleware in app/start.ts
|
|
107
|
-
* import { createStart } from '@tanstack/react-start';
|
|
108
|
-
* import { createTracingMiddleware } from 'autotel-tanstack/middleware';
|
|
109
|
-
*
|
|
110
|
-
* export const startInstance = createStart(() => ({
|
|
111
|
-
* requestMiddleware: [
|
|
112
|
-
* createTracingMiddleware({
|
|
113
|
-
* captureHeaders: ['x-request-id', 'user-agent'],
|
|
114
|
-
* excludePaths: ['/health', '/metrics'],
|
|
115
|
-
* }),
|
|
116
|
-
* ],
|
|
117
|
-
* }));
|
|
118
|
-
* ```
|
|
119
|
-
*
|
|
120
|
-
* @example
|
|
121
|
-
* ```typescript
|
|
122
|
-
* // Server function middleware
|
|
123
|
-
* import { createServerFn } from '@tanstack/react-start';
|
|
124
|
-
* import { createTracingMiddleware } from 'autotel-tanstack/middleware';
|
|
125
|
-
*
|
|
126
|
-
* export const getUser = createServerFn({ method: 'GET' })
|
|
127
|
-
* .middleware([createTracingMiddleware({ type: 'function' })])
|
|
128
|
-
* .handler(async ({ data: id }) => {
|
|
129
|
-
* return await db.users.findUnique({ where: { id } });
|
|
130
|
-
* });
|
|
131
|
-
* ```
|
|
132
|
-
*/
|
|
133
|
-
export function createTracingMiddleware<TContext = unknown>(
|
|
134
|
-
config?: TracingMiddlewareConfig,
|
|
135
|
-
): MiddlewareHandler<TContext> {
|
|
136
|
-
const mergedConfig = {
|
|
137
|
-
...DEFAULT_CONFIG,
|
|
138
|
-
...config,
|
|
139
|
-
type: config?.type ?? 'request',
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
return async function tracingMiddleware(opts) {
|
|
143
|
-
// If we're in the browser, return a no-op middleware
|
|
144
|
-
// This prevents autotel (which uses Node.js APIs) from being bundled/executed in the browser
|
|
145
|
-
if (!isServerSide()) {
|
|
146
|
-
return opts.next();
|
|
147
|
-
}
|
|
148
|
-
const { next, request, pathname, data, functionId } = opts;
|
|
149
|
-
|
|
150
|
-
// For function middleware
|
|
151
|
-
if (mergedConfig.type === 'function') {
|
|
152
|
-
const fnName = functionId || 'unknown';
|
|
153
|
-
const method = (opts as { method?: string }).method || 'POST';
|
|
154
|
-
|
|
155
|
-
return trace(`tanstack.serverFn.${fnName}`, async (ctx: TraceContext) => {
|
|
156
|
-
const attrs = buildServerFnAttributes(
|
|
157
|
-
fnName,
|
|
158
|
-
method,
|
|
159
|
-
data,
|
|
160
|
-
mergedConfig,
|
|
161
|
-
);
|
|
162
|
-
ctx.setAttributes(attrs as Record<string, string | number | boolean>);
|
|
163
|
-
|
|
164
|
-
// Add custom attributes if provided
|
|
165
|
-
if (config?.customAttributes) {
|
|
166
|
-
const customAttrs = config.customAttributes({
|
|
167
|
-
type: 'serverFn',
|
|
168
|
-
name: fnName,
|
|
169
|
-
args: data,
|
|
170
|
-
});
|
|
171
|
-
ctx.setAttributes(
|
|
172
|
-
customAttrs as Record<string, string | number | boolean>,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const result = await next();
|
|
178
|
-
|
|
179
|
-
// Capture result if configured
|
|
180
|
-
if (mergedConfig.captureResults && result !== undefined) {
|
|
181
|
-
try {
|
|
182
|
-
ctx.setAttribute(
|
|
183
|
-
SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,
|
|
184
|
-
JSON.stringify(result),
|
|
185
|
-
);
|
|
186
|
-
} catch {
|
|
187
|
-
ctx.setAttribute(
|
|
188
|
-
SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,
|
|
189
|
-
'[non-serializable]',
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
195
|
-
return result;
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (mergedConfig.captureErrors) {
|
|
198
|
-
if ('recordError' in ctx && typeof ctx.recordError === 'function') {
|
|
199
|
-
ctx.recordError(error);
|
|
200
|
-
} else if (
|
|
201
|
-
'recordException' in ctx &&
|
|
202
|
-
typeof ctx.recordException === 'function'
|
|
203
|
-
) {
|
|
204
|
-
ctx.recordException(error);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Report error to error store
|
|
208
|
-
try {
|
|
209
|
-
const { reportError } = await import('./error-reporting');
|
|
210
|
-
reportError(error as Error, {
|
|
211
|
-
type: 'serverFn',
|
|
212
|
-
name: fnName,
|
|
213
|
-
method,
|
|
214
|
-
});
|
|
215
|
-
} catch {
|
|
216
|
-
// Error reporting not available, skip
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
throw error;
|
|
220
|
-
}
|
|
221
|
-
}) as Promise<TContext>;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// For request middleware
|
|
225
|
-
if (!request) {
|
|
226
|
-
// No request available, just pass through
|
|
227
|
-
return next();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const url = new URL(request.url);
|
|
231
|
-
|
|
232
|
-
// Check if path should be excluded
|
|
233
|
-
if (isExcludedPath(url.pathname, mergedConfig.excludePaths)) {
|
|
234
|
-
return next();
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Extract parent context from request headers
|
|
238
|
-
const parentContext = extractContextFromRequest(request);
|
|
239
|
-
|
|
240
|
-
// Run within parent context for distributed tracing
|
|
241
|
-
return context.with(parentContext, async () => {
|
|
242
|
-
const spanName = `${request.method} ${pathname || url.pathname}`;
|
|
243
|
-
|
|
244
|
-
return trace(spanName, async (ctx: TraceContext) => {
|
|
245
|
-
const attrs = buildRequestAttributes(request, mergedConfig);
|
|
246
|
-
ctx.setAttributes(attrs as Record<string, string | number | boolean>);
|
|
247
|
-
|
|
248
|
-
// Add custom attributes if provided
|
|
249
|
-
if (config?.customAttributes) {
|
|
250
|
-
const customAttrs = config.customAttributes({
|
|
251
|
-
type: 'request',
|
|
252
|
-
name: spanName,
|
|
253
|
-
request,
|
|
254
|
-
});
|
|
255
|
-
ctx.setAttributes(
|
|
256
|
-
customAttrs as Record<string, string | number | boolean>,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const startTime = Date.now();
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
const result = await next();
|
|
264
|
-
|
|
265
|
-
const duration = Date.now() - startTime;
|
|
266
|
-
ctx.setAttribute(
|
|
267
|
-
SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,
|
|
268
|
-
duration,
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
// Record timing in metrics collector
|
|
272
|
-
try {
|
|
273
|
-
const { metricsCollector } = await import('./metrics');
|
|
274
|
-
metricsCollector.recordTiming(spanName, duration);
|
|
275
|
-
} catch {
|
|
276
|
-
// Metrics not available, skip
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Try to get response status from result if it's a Response
|
|
280
|
-
if (result && typeof result === 'object' && 'status' in result) {
|
|
281
|
-
ctx.setAttribute(
|
|
282
|
-
SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE,
|
|
283
|
-
(result as { status: number }).status,
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
288
|
-
return result;
|
|
289
|
-
} catch (error) {
|
|
290
|
-
const duration = Date.now() - startTime;
|
|
291
|
-
ctx.setAttribute(
|
|
292
|
-
SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,
|
|
293
|
-
duration,
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
if (mergedConfig.captureErrors) {
|
|
297
|
-
if ('recordError' in ctx && typeof ctx.recordError === 'function') {
|
|
298
|
-
ctx.recordError(error);
|
|
299
|
-
} else if (
|
|
300
|
-
'recordException' in ctx &&
|
|
301
|
-
typeof ctx.recordException === 'function'
|
|
302
|
-
) {
|
|
303
|
-
ctx.recordException(error);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Report error to error store
|
|
307
|
-
try {
|
|
308
|
-
const { reportError } = await import('./error-reporting');
|
|
309
|
-
reportError(error as Error, {
|
|
310
|
-
type: 'request',
|
|
311
|
-
method: request.method,
|
|
312
|
-
pathname: url.pathname,
|
|
313
|
-
});
|
|
314
|
-
} catch {
|
|
315
|
-
// Error reporting not available, skip
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
}) as Promise<TContext>;
|
|
321
|
-
});
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Pre-configured tracing middleware with sensible defaults
|
|
327
|
-
*
|
|
328
|
-
* Convenience export for quick setup. Uses adaptive sampling,
|
|
329
|
-
* captures x-request-id header, and excludes common health check paths.
|
|
330
|
-
*
|
|
331
|
-
* @param config - Optional configuration overrides
|
|
332
|
-
* @returns Middleware handler
|
|
333
|
-
*
|
|
334
|
-
* @example
|
|
335
|
-
* ```typescript
|
|
336
|
-
* import { createStart } from '@tanstack/react-start';
|
|
337
|
-
* import { tracingMiddleware } from 'autotel-tanstack/middleware';
|
|
338
|
-
*
|
|
339
|
-
* export const startInstance = createStart(() => ({
|
|
340
|
-
* requestMiddleware: [tracingMiddleware()],
|
|
341
|
-
* }));
|
|
342
|
-
* ```
|
|
343
|
-
*/
|
|
344
|
-
export function tracingMiddleware<TContext = unknown>(
|
|
345
|
-
config?: TracingMiddlewareConfig,
|
|
346
|
-
): MiddlewareHandler<TContext> {
|
|
347
|
-
return createTracingMiddleware({
|
|
348
|
-
sampling: 'adaptive',
|
|
349
|
-
captureHeaders: ['x-request-id', 'user-agent'],
|
|
350
|
-
excludePaths: ['/health', '/healthz', '/ready', '/metrics', '/_ping'],
|
|
351
|
-
...config,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Create function-specific tracing middleware
|
|
357
|
-
*
|
|
358
|
-
* Convenience wrapper for server function middleware.
|
|
359
|
-
*
|
|
360
|
-
* @param config - Optional configuration
|
|
361
|
-
* @returns Middleware handler for server functions
|
|
362
|
-
*
|
|
363
|
-
* @example
|
|
364
|
-
* ```typescript
|
|
365
|
-
* import { createServerFn } from '@tanstack/react-start';
|
|
366
|
-
* import { functionTracingMiddleware } from 'autotel-tanstack/middleware';
|
|
367
|
-
*
|
|
368
|
-
* export const getUser = createServerFn({ method: 'GET' })
|
|
369
|
-
* .middleware([functionTracingMiddleware()])
|
|
370
|
-
* .handler(async ({ data: id }) => {
|
|
371
|
-
* return await db.users.findUnique({ where: { id } });
|
|
372
|
-
* });
|
|
373
|
-
* ```
|
|
374
|
-
*/
|
|
375
|
-
export function functionTracingMiddleware<TContext = unknown>(
|
|
376
|
-
config?: Omit<TracingMiddlewareConfig, 'type'>,
|
|
377
|
-
): MiddlewareHandler<TContext> {
|
|
378
|
-
return createTracingMiddleware({
|
|
379
|
-
...config,
|
|
380
|
-
type: 'function',
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Create a tracing handler for use with TanStack's native createMiddleware()
|
|
386
|
-
*
|
|
387
|
-
* This provides the raw tracing logic that you can pass to createMiddleware().server().
|
|
388
|
-
* Use this when you want full control over the middleware builder pattern.
|
|
389
|
-
*
|
|
390
|
-
* The handler accepts TanStack's middleware signature `{ next, context, request }`
|
|
391
|
-
* and internally adapts it to our more flexible MiddlewareHandler interface.
|
|
392
|
-
*
|
|
393
|
-
* @param config - Configuration options
|
|
394
|
-
* @returns Server handler function compatible with createMiddleware().server()
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* ```typescript
|
|
398
|
-
* import { createStart, createMiddleware } from '@tanstack/react-start';
|
|
399
|
-
* import { createTracingServerHandler } from 'autotel-tanstack/middleware';
|
|
400
|
-
*
|
|
401
|
-
* // TanStack-native middleware creation
|
|
402
|
-
* const requestTracingMiddleware = createMiddleware().server(
|
|
403
|
-
* createTracingServerHandler({ captureHeaders: ['x-request-id'] })
|
|
404
|
-
* );
|
|
405
|
-
*
|
|
406
|
-
* export const start = createStart(() => ({
|
|
407
|
-
* requestMiddleware: [requestTracingMiddleware],
|
|
408
|
-
* }));
|
|
409
|
-
* ```
|
|
410
|
-
*
|
|
411
|
-
* @example
|
|
412
|
-
* ```typescript
|
|
413
|
-
* // For server functions - use createMiddleware({ type: 'function' })
|
|
414
|
-
* import { createStart, createMiddleware } from '@tanstack/react-start';
|
|
415
|
-
* import { createTracingServerHandler } from 'autotel-tanstack/middleware';
|
|
416
|
-
*
|
|
417
|
-
* const functionTracingMiddleware = createMiddleware({ type: 'function' }).server(
|
|
418
|
-
* createTracingServerHandler({ type: 'function', captureArgs: true })
|
|
419
|
-
* );
|
|
420
|
-
*
|
|
421
|
-
* export const start = createStart(() => ({
|
|
422
|
-
* functionMiddleware: [functionTracingMiddleware],
|
|
423
|
-
* }));
|
|
424
|
-
* ```
|
|
425
|
-
*/
|
|
426
|
-
export function createTracingServerHandler<TContext = unknown>(
|
|
427
|
-
config?: TracingMiddlewareConfig,
|
|
428
|
-
): (opts: any) => any {
|
|
429
|
-
const handler = createTracingMiddleware<TContext>(config);
|
|
430
|
-
|
|
431
|
-
// Adapt TanStack's signature to our handler
|
|
432
|
-
return async (opts: any) => {
|
|
433
|
-
return handler(opts);
|
|
434
|
-
};
|
|
435
|
-
}
|
package/src/route-filter.test.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { isExcludedPath } from './route-filter';
|
|
3
|
-
|
|
4
|
-
describe('route-filter', () => {
|
|
5
|
-
it('matches plain string paths as prefix for backwards compatibility', () => {
|
|
6
|
-
expect(isExcludedPath('/health', ['/health'])).toBe(true);
|
|
7
|
-
expect(isExcludedPath('/healthz', ['/health'])).toBe(true);
|
|
8
|
-
expect(isExcludedPath('/api/users', ['/health'])).toBe(false);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('matches glob patterns through shared autotel-edge matcher', () => {
|
|
12
|
-
expect(isExcludedPath('/api/internal/debug', ['/api/internal/*'])).toBe(
|
|
13
|
-
true,
|
|
14
|
-
);
|
|
15
|
-
expect(isExcludedPath('/api/public/debug', ['/api/internal/*'])).toBe(
|
|
16
|
-
false,
|
|
17
|
-
);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('matches regex patterns', () => {
|
|
21
|
-
expect(isExcludedPath('/api/v2/health', [/^\/api\/v\d+\/health$/])).toBe(
|
|
22
|
-
true,
|
|
23
|
-
);
|
|
24
|
-
expect(isExcludedPath('/api/v2/users', [/^\/api\/v\d+\/health$/])).toBe(
|
|
25
|
-
false,
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
});
|
package/src/route-filter.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { shouldInstrumentPath } from 'autotel-edge';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TanStack historically supported:
|
|
5
|
-
* - glob strings (`/api/internal/*`)
|
|
6
|
-
* - regex values
|
|
7
|
-
* - plain-string prefix matching (`/health` matches `/healthz`)
|
|
8
|
-
*
|
|
9
|
-
* This helper keeps those semantics while delegating glob matching to
|
|
10
|
-
* autotel-edge's shared middleware toolkit.
|
|
11
|
-
*/
|
|
12
|
-
export function isExcludedPath(
|
|
13
|
-
pathname: string,
|
|
14
|
-
excludePaths: Array<string | RegExp>,
|
|
15
|
-
): boolean {
|
|
16
|
-
for (const pattern of excludePaths) {
|
|
17
|
-
if (pattern instanceof RegExp) {
|
|
18
|
-
if (pattern.test(pathname)) return true;
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (pattern.includes('*') || pattern.includes('?')) {
|
|
23
|
-
if (
|
|
24
|
-
!shouldInstrumentPath(pathname, {
|
|
25
|
-
include: undefined,
|
|
26
|
-
exclude: [pattern],
|
|
27
|
-
})
|
|
28
|
-
) {
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (pathname === pattern || pathname.startsWith(pattern)) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { traceServerFn, createTracedServerFnFactory } from './server-functions';
|
|
3
|
-
|
|
4
|
-
// Mock autotel
|
|
5
|
-
vi.mock('autotel', () => ({
|
|
6
|
-
trace: vi.fn((name, fn) =>
|
|
7
|
-
fn({
|
|
8
|
-
setAttributes: vi.fn(),
|
|
9
|
-
setAttribute: vi.fn(),
|
|
10
|
-
setStatus: vi.fn(),
|
|
11
|
-
recordException: vi.fn(),
|
|
12
|
-
}),
|
|
13
|
-
),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
describe('server-functions', () => {
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
vi.clearAllMocks();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('traceServerFn', () => {
|
|
22
|
-
it('should wrap a server function', async () => {
|
|
23
|
-
const originalFn = vi.fn().mockResolvedValue({ id: '123', name: 'Test' });
|
|
24
|
-
const tracedFn = traceServerFn(originalFn, { name: 'getUser' });
|
|
25
|
-
|
|
26
|
-
const result = await tracedFn({ id: '123' });
|
|
27
|
-
|
|
28
|
-
expect(originalFn).toHaveBeenCalledWith({ id: '123' });
|
|
29
|
-
expect(result).toEqual({ id: '123', name: 'Test' });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should use function name if no name provided', async () => {
|
|
33
|
-
async function namedFunction() {
|
|
34
|
-
return 'result';
|
|
35
|
-
}
|
|
36
|
-
const tracedFn = traceServerFn(namedFunction);
|
|
37
|
-
|
|
38
|
-
await tracedFn();
|
|
39
|
-
expect(tracedFn).toBeDefined();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should propagate errors', async () => {
|
|
43
|
-
const error = new Error('Test error');
|
|
44
|
-
const originalFn = vi.fn().mockRejectedValue(error);
|
|
45
|
-
const tracedFn = traceServerFn(originalFn, { name: 'failingFn' });
|
|
46
|
-
|
|
47
|
-
await expect(tracedFn()).rejects.toThrow('Test error');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should preserve function properties', () => {
|
|
51
|
-
const originalFn = Object.assign(vi.fn().mockResolvedValue('result'), {
|
|
52
|
-
customProp: 'value',
|
|
53
|
-
});
|
|
54
|
-
const tracedFn = traceServerFn(originalFn, { name: 'testFn' });
|
|
55
|
-
|
|
56
|
-
expect((tracedFn as any).customProp).toBe('value');
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('createTracedServerFnFactory', () => {
|
|
61
|
-
it('should create a factory that wraps createServerFn', () => {
|
|
62
|
-
const mockCreateServerFn = vi.fn(() => ({
|
|
63
|
-
handler: vi.fn((fn) => fn),
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
const tracedFactory = createTracedServerFnFactory(mockCreateServerFn);
|
|
67
|
-
expect(tracedFactory).toBeDefined();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should wrap handler method', () => {
|
|
71
|
-
const _handlerFn = vi.fn().mockResolvedValue('result');
|
|
72
|
-
const mockResult = {
|
|
73
|
-
handler: vi.fn((fn) => {
|
|
74
|
-
// Simulate returning a callable
|
|
75
|
-
return async (...args: unknown[]) => fn(...args);
|
|
76
|
-
}),
|
|
77
|
-
};
|
|
78
|
-
const mockCreateServerFn = vi.fn(() => mockResult);
|
|
79
|
-
|
|
80
|
-
const tracedFactory = createTracedServerFnFactory(mockCreateServerFn);
|
|
81
|
-
const builder = tracedFactory({ method: 'GET' });
|
|
82
|
-
|
|
83
|
-
expect(builder.handler).toBeDefined();
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|