autotel-tanstack 1.5.1 → 1.7.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/dist/auto.d.ts +1 -0
- package/dist/auto.js +3 -3
- package/dist/browser/error-reporting.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/{chunk-TNOQTZ3N.js → chunk-5JJXFTG3.js} +2 -2
- package/dist/chunk-5JJXFTG3.js.map +1 -0
- package/dist/{chunk-HKM7LMO6.js → chunk-5RYSHDCO.js} +96 -32
- package/dist/chunk-5RYSHDCO.js.map +1 -0
- package/dist/{chunk-HIQYW2HB.js → chunk-EFSKEYDJ.js} +2 -2
- package/dist/{chunk-HIQYW2HB.js.map → chunk-EFSKEYDJ.js.map} +1 -1
- package/dist/{chunk-JSI6QG7M.js → chunk-G526TOMY.js} +2 -2
- package/dist/chunk-G526TOMY.js.map +1 -0
- package/dist/{chunk-ETD6SGPH.js → chunk-NP4OJO7T.js} +2 -2
- package/dist/{chunk-ETD6SGPH.js.map → chunk-NP4OJO7T.js.map} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -4
- package/dist/loaders.d.ts +26 -34
- package/dist/loaders.js +1 -1
- package/dist/metrics.d.ts +1 -1
- package/dist/metrics.js +1 -1
- package/dist/middleware.d.ts +1 -5
- package/dist/middleware.js +1 -1
- package/dist/server-functions.d.ts +2 -2
- package/dist/server-functions.js +1 -1
- package/package.json +8 -7
- package/src/browser/error-reporting.ts +2 -1
- package/src/loaders.ts +159 -76
- package/src/metrics.ts +4 -3
- package/src/middleware.ts +2 -6
- package/src/server-functions.ts +5 -4
- package/dist/chunk-HKM7LMO6.js.map +0 -1
- package/dist/chunk-JSI6QG7M.js.map +0 -1
- package/dist/chunk-TNOQTZ3N.js.map +0 -1
package/dist/auto.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { traceServerFn } from './server-functions.js';
|
|
|
3
3
|
export { traceBeforeLoad, traceLoader } from './loaders.js';
|
|
4
4
|
import './types-C37KSxMN.js';
|
|
5
5
|
import '@opentelemetry/api';
|
|
6
|
+
import '@tanstack/react-router';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Zero-config auto-instrumentation for TanStack Start
|
package/dist/auto.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { functionTracingMiddleware, tracingMiddleware } from './chunk-
|
|
2
|
-
export { traceServerFn } from './chunk-
|
|
3
|
-
export { traceBeforeLoad, traceLoader } from './chunk-
|
|
1
|
+
export { functionTracingMiddleware, tracingMiddleware } from './chunk-NP4OJO7T.js';
|
|
2
|
+
export { traceServerFn } from './chunk-5JJXFTG3.js';
|
|
3
|
+
export { traceBeforeLoad, traceLoader } from './chunk-5RYSHDCO.js';
|
|
4
4
|
import './chunk-EUYFVNYE.js';
|
|
5
5
|
import './chunk-I4LX3LOG.js';
|
|
6
6
|
import './chunk-NTY64BKS.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { clearErrors, createErrorReportingHandler, getRecentErrors, reportError, withErrorReporting } from '../chunk-
|
|
1
|
+
export { clearErrors, createErrorReportingHandler, getRecentErrors, reportError, withErrorReporting } from '../chunk-EFSKEYDJ.js';
|
|
2
2
|
//# sourceMappingURL=error-reporting.js.map
|
|
3
3
|
//# sourceMappingURL=error-reporting.js.map
|
package/dist/browser/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createMetricsHandler, getMetrics, metricsCollector, recordError, recordTiming, resetMetrics } from '../chunk-UMEJU65Q.js';
|
|
2
|
-
export { clearErrors, createErrorReportingHandler, getRecentErrors, reportError, withErrorReporting } from '../chunk-
|
|
2
|
+
export { clearErrors, createErrorReportingHandler, getRecentErrors, reportError, withErrorReporting } from '../chunk-EFSKEYDJ.js';
|
|
3
3
|
export { DEFAULT_CONFIG, SPAN_ATTRIBUTES } from '../chunk-MFYOV2SF.js';
|
|
4
4
|
export { createTracingMiddleware, createTracingServerHandler, functionTracingMiddleware, tracingMiddleware } from '../chunk-A7WMQ2BC.js';
|
|
5
5
|
export { createTracedServerFnFactory, traceServerFn } from '../chunk-CSFIPJC2.js';
|
|
@@ -88,5 +88,5 @@ function createTracedServerFnFactory(createServerFnOriginal, defaultConfig = {})
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
export { createTracedServerFnFactory, traceServerFn };
|
|
91
|
-
//# sourceMappingURL=chunk-
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
91
|
+
//# sourceMappingURL=chunk-5JJXFTG3.js.map
|
|
92
|
+
//# sourceMappingURL=chunk-5JJXFTG3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server-functions.ts"],"names":[],"mappings":"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","file":"chunk-5JJXFTG3.js","sourcesContent":["import { SpanStatusCode } from '@opentelemetry/api';\nimport { trace, type TraceContext } from 'autotel';\nimport { isServerSide } from './env';\nimport { type TraceServerFnConfig, SPAN_ATTRIBUTES } from './types';\n\n/**\n * Wrap a TanStack server function with OpenTelemetry tracing\n *\n * This function wraps a server function to automatically create spans\n * for each invocation. It captures function name, arguments (optionally),\n * results (optionally), and errors.\n *\n * @param serverFn - The server function to wrap\n * @param config - Configuration options\n * @returns Wrapped server function with tracing\n *\n * @example\n * ```typescript\n * import { createServerFn } from '@tanstack/react-start';\n * import { traceServerFn } from 'autotel-tanstack/server-functions';\n *\n * const getUserBase = createServerFn({ method: 'GET' })\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n *\n * export const getUser = traceServerFn(getUserBase, { name: 'getUser' });\n * ```\n *\n * @example\n * ```typescript\n * // With argument and result capture (careful with PII!)\n * export const createUser = traceServerFn(\n * createServerFn({ method: 'POST' })\n * .handler(async ({ data }) => {\n * return await db.users.create({ data });\n * }),\n * {\n * name: 'createUser',\n * captureArgs: true,\n * captureResults: false, // Don't capture for PII reasons\n * }\n * );\n * ```\n */\nexport function traceServerFn<T extends (...args: any[]) => any>(\n serverFn: T,\n config: TraceServerFnConfig = {},\n): T {\n const fnName = config.name || serverFn.name || 'serverFn';\n const captureArgs = config.captureArgs ?? true;\n const captureResults = config.captureResults ?? false;\n\n return new Proxy(serverFn, {\n apply(target, thisArg, argArray) {\n // If we're in the browser, just call the function without tracing\n // Server functions should never run in the browser, but this prevents\n // autotel (which uses Node.js APIs) from being executed if it somehow does\n if (!isServerSide()) {\n return target.apply(thisArg, argArray);\n }\n\n return trace(`tanstack.serverFn.${fnName}`, async (ctx: TraceContext) => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.RPC_SYSTEM]: 'tanstack-start',\n [SPAN_ATTRIBUTES.RPC_METHOD]: fnName,\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'serverFn',\n [SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_NAME]: fnName,\n });\n\n // Capture arguments if configured\n if (captureArgs && argArray.length > 0) {\n const args = argArray[0];\n if (args !== undefined) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS,\n JSON.stringify(args),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS,\n '[non-serializable]',\n );\n }\n }\n }\n\n try {\n const result = await Reflect.apply(target, thisArg, argArray);\n\n // Capture result if configured\n if (captureResults && result !== undefined) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n JSON.stringify(result),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n '[non-serializable]',\n );\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n throw error;\n }\n });\n },\n\n get(target, prop, receiver) {\n return Reflect.get(target, prop, receiver);\n },\n }) as T;\n}\n\n/**\n * Create a traced version of createServerFn\n *\n * This higher-order function wraps TanStack's createServerFn to automatically\n * add tracing to all created server functions.\n *\n * @param createServerFnOriginal - The original createServerFn from TanStack\n * @param defaultConfig - Default configuration for all server functions\n * @returns Wrapped createServerFn that produces traced server functions\n *\n * @example\n * ```typescript\n * import { createServerFn as originalCreateServerFn } from '@tanstack/react-start';\n * import { createTracedServerFnFactory } from 'autotel-tanstack/server-functions';\n *\n * export const createServerFn = createTracedServerFnFactory(originalCreateServerFn);\n *\n * // Now all server functions created with createServerFn are automatically traced\n * export const getUser = createServerFn({ method: 'GET' })\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n * ```\n */\nexport function createTracedServerFnFactory<\n TCreateServerFn extends (...args: any[]) => any,\n>(\n createServerFnOriginal: TCreateServerFn,\n defaultConfig: Omit<TraceServerFnConfig, 'name'> = {},\n): TCreateServerFn {\n return new Proxy(createServerFnOriginal, {\n apply(target, thisArg, argArray) {\n const result = Reflect.apply(target, thisArg, argArray);\n\n // If the result has a .handler method, wrap it\n if (\n result &&\n typeof result === 'object' &&\n 'handler' in result &&\n typeof result.handler === 'function'\n ) {\n const originalHandler = result.handler.bind(result);\n\n result.handler = function tracedHandler(handlerFn: unknown) {\n const wrappedHandler = originalHandler(handlerFn as never);\n\n // Try to infer function name from the handler\n const fnName = (handlerFn as { name?: string })?.name || 'serverFn';\n\n return traceServerFn(wrappedHandler, {\n ...defaultConfig,\n name: fnName,\n });\n };\n }\n\n return result;\n },\n }) as TCreateServerFn;\n}\n"]}
|
|
@@ -6,12 +6,45 @@ import { trace } from 'autotel';
|
|
|
6
6
|
function traceLoader(loaderFn, config = {}) {
|
|
7
7
|
const captureParams = config.captureParams ?? true;
|
|
8
8
|
const captureResult = config.captureResult ?? false;
|
|
9
|
-
|
|
9
|
+
const wrapped = (context) => {
|
|
10
10
|
if (!isServerSide()) {
|
|
11
11
|
return loaderFn(context);
|
|
12
12
|
}
|
|
13
13
|
const routeId = context?.route?.id || "unknown";
|
|
14
14
|
const spanName = config.name || `tanstack.loader.${routeId}`;
|
|
15
|
+
const result = loaderFn(context);
|
|
16
|
+
const isPromise = result instanceof Promise;
|
|
17
|
+
if (!isPromise) {
|
|
18
|
+
return trace(spanName, (ctx) => {
|
|
19
|
+
ctx.setAttributes({
|
|
20
|
+
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: "loader",
|
|
21
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,
|
|
22
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: "loader"
|
|
23
|
+
});
|
|
24
|
+
if (captureParams && context?.params) {
|
|
25
|
+
try {
|
|
26
|
+
ctx.setAttribute(
|
|
27
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
28
|
+
JSON.stringify(context.params)
|
|
29
|
+
);
|
|
30
|
+
} catch {
|
|
31
|
+
ctx.setAttribute(
|
|
32
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
33
|
+
"[non-serializable]"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (captureResult && result !== void 0) {
|
|
38
|
+
try {
|
|
39
|
+
ctx.setAttribute("tanstack.loader.result", JSON.stringify(result));
|
|
40
|
+
} catch {
|
|
41
|
+
ctx.setAttribute("tanstack.loader.result", "[non-serializable]");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
45
|
+
return result;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
15
48
|
return trace(spanName, async (ctx) => {
|
|
16
49
|
ctx.setAttributes({
|
|
17
50
|
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: "loader",
|
|
@@ -32,16 +65,19 @@ function traceLoader(loaderFn, config = {}) {
|
|
|
32
65
|
}
|
|
33
66
|
}
|
|
34
67
|
try {
|
|
35
|
-
const
|
|
36
|
-
if (captureResult &&
|
|
68
|
+
const asyncResult = await result;
|
|
69
|
+
if (captureResult && asyncResult !== void 0) {
|
|
37
70
|
try {
|
|
38
|
-
ctx.setAttribute(
|
|
71
|
+
ctx.setAttribute(
|
|
72
|
+
"tanstack.loader.result",
|
|
73
|
+
JSON.stringify(asyncResult)
|
|
74
|
+
);
|
|
39
75
|
} catch {
|
|
40
76
|
ctx.setAttribute("tanstack.loader.result", "[non-serializable]");
|
|
41
77
|
}
|
|
42
78
|
}
|
|
43
79
|
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
44
|
-
return
|
|
80
|
+
return asyncResult;
|
|
45
81
|
} catch (error) {
|
|
46
82
|
ctx.recordException(error);
|
|
47
83
|
ctx.setStatus({
|
|
@@ -51,26 +87,31 @@ function traceLoader(loaderFn, config = {}) {
|
|
|
51
87
|
throw error;
|
|
52
88
|
}
|
|
53
89
|
});
|
|
54
|
-
}
|
|
90
|
+
};
|
|
91
|
+
return wrapped;
|
|
55
92
|
}
|
|
56
93
|
function traceBeforeLoad(beforeLoadFn, config = {}) {
|
|
57
94
|
const captureParams = config.captureParams ?? true;
|
|
58
|
-
|
|
59
|
-
|
|
95
|
+
const wrapped = (input) => {
|
|
96
|
+
if (!isServerSide()) {
|
|
97
|
+
return beforeLoadFn(input);
|
|
98
|
+
}
|
|
99
|
+
const routeId = input?.route?.id || "unknown";
|
|
60
100
|
const spanName = config.name || `tanstack.beforeLoad.${routeId}`;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
101
|
+
const result = beforeLoadFn(input);
|
|
102
|
+
const isPromise = result instanceof Promise;
|
|
103
|
+
if (!isPromise) {
|
|
104
|
+
return trace(spanName, (ctx) => {
|
|
64
105
|
ctx.setAttributes({
|
|
65
106
|
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: "beforeLoad",
|
|
66
107
|
[SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,
|
|
67
108
|
[SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: "beforeLoad"
|
|
68
109
|
});
|
|
69
|
-
if (captureParams &&
|
|
110
|
+
if (captureParams && input?.params) {
|
|
70
111
|
try {
|
|
71
112
|
ctx.setAttribute(
|
|
72
113
|
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
73
|
-
JSON.stringify(
|
|
114
|
+
JSON.stringify(input.params)
|
|
74
115
|
);
|
|
75
116
|
} catch {
|
|
76
117
|
ctx.setAttribute(
|
|
@@ -79,27 +120,50 @@ function traceBeforeLoad(beforeLoadFn, config = {}) {
|
|
|
79
120
|
);
|
|
80
121
|
}
|
|
81
122
|
}
|
|
123
|
+
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
124
|
+
return result;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return trace(spanName, async (ctx) => {
|
|
128
|
+
ctx.setAttributes({
|
|
129
|
+
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: "beforeLoad",
|
|
130
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,
|
|
131
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: "beforeLoad"
|
|
132
|
+
});
|
|
133
|
+
if (captureParams && input?.params) {
|
|
82
134
|
try {
|
|
83
|
-
|
|
135
|
+
ctx.setAttribute(
|
|
136
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
137
|
+
JSON.stringify(input.params)
|
|
138
|
+
);
|
|
139
|
+
} catch {
|
|
140
|
+
ctx.setAttribute(
|
|
141
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
142
|
+
"[non-serializable]"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const asyncResult = await result;
|
|
148
|
+
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
149
|
+
return asyncResult;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const errorName = error.name;
|
|
152
|
+
if (errorName === "RedirectError" || errorName === "NotFoundError") {
|
|
153
|
+
ctx.setAttribute("tanstack.beforeLoad.redirect", true);
|
|
84
154
|
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} else {
|
|
92
|
-
ctx.recordException(error);
|
|
93
|
-
ctx.setStatus({
|
|
94
|
-
code: SpanStatusCode.ERROR,
|
|
95
|
-
message: error.message
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
throw error;
|
|
155
|
+
} else {
|
|
156
|
+
ctx.recordException(error);
|
|
157
|
+
ctx.setStatus({
|
|
158
|
+
code: SpanStatusCode.ERROR,
|
|
159
|
+
message: error.message
|
|
160
|
+
});
|
|
99
161
|
}
|
|
162
|
+
throw error;
|
|
100
163
|
}
|
|
101
|
-
);
|
|
102
|
-
}
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
return wrapped;
|
|
103
167
|
}
|
|
104
168
|
function createTracedRoute(routeId, config = {}) {
|
|
105
169
|
return {
|
|
@@ -125,5 +189,5 @@ function createTracedRoute(routeId, config = {}) {
|
|
|
125
189
|
}
|
|
126
190
|
|
|
127
191
|
export { createTracedRoute, traceBeforeLoad, traceLoader };
|
|
128
|
-
//# sourceMappingURL=chunk-
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
192
|
+
//# sourceMappingURL=chunk-5RYSHDCO.js.map
|
|
193
|
+
//# sourceMappingURL=chunk-5RYSHDCO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/loaders.ts"],"names":[],"mappings":"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","file":"chunk-5RYSHDCO.js","sourcesContent":["import { SpanStatusCode } from '@opentelemetry/api';\nimport { trace, type TraceContext } from 'autotel';\nimport { isServerSide } from './env';\nimport { type TraceLoaderConfig, SPAN_ATTRIBUTES } from './types';\n\n// Re-export types from @tanstack/react-router for consumers who need them\nexport type { LoaderFnContext } from '@tanstack/react-router';\n\n/**\n * Internal type for extracting route info from TanStack context.\n * This is a minimal interface used only for instrumentation - the actual\n * TanStack types flow through the generic parameter.\n */\ninterface TanStackContextInternal {\n route?: { id?: string };\n params?: Record<string, string>;\n}\n\n/**\n * Wrap a TanStack route loader with OpenTelemetry tracing\n *\n * This function wraps a loader function to automatically create spans\n * for each invocation. It captures route ID, params (optionally),\n * and errors.\n *\n * The generic type TLoaderFn preserves the full TanStack Router type inference,\n * including typed params, context, and return types.\n *\n * @param loaderFn - The loader function to wrap\n * @param config - Configuration options\n * @returns Wrapped loader function with tracing (preserves original types)\n *\n * @example\n * ```typescript\n * import { createFileRoute } from '@tanstack/react-router';\n * import { traceLoader } from 'autotel-tanstack/loaders';\n *\n * export const Route = createFileRoute('/users/$userId')({\n * // Types are fully preserved - params.userId is typed as string\n * loader: traceLoader(async ({ params }) => {\n * return await db.users.findUnique({ where: { id: params.userId } });\n * }),\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Sync loaders are also supported\n * export const Route = createFileRoute('/static')({\n * loader: traceLoader(({ context }) => ({\n * message: `Welcome, ${context.userId}!`,\n * })),\n * });\n * ```\n */\nexport function traceLoader<TLoaderFn extends (ctx: any) => any>(\n loaderFn: TLoaderFn,\n config: TraceLoaderConfig = {},\n): TLoaderFn {\n const captureParams = config.captureParams ?? true;\n const captureResult = config.captureResult ?? false;\n\n const wrapped = (context: TanStackContextInternal) => {\n // If we're in the browser, just call the loader without tracing\n // This prevents autotel (which uses Node.js APIs) from being executed in the browser\n if (!isServerSide()) {\n return loaderFn(context);\n }\n\n const routeId = context?.route?.id || 'unknown';\n const spanName = config.name || `tanstack.loader.${routeId}`;\n\n // Handle both sync and async loaders\n const result = loaderFn(context);\n const isPromise = result instanceof Promise;\n\n if (!isPromise) {\n // Sync loader - wrap in trace synchronously\n return trace(spanName, (ctx: TraceContext) => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'loader',\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'loader',\n });\n\n if (captureParams && context?.params) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n JSON.stringify(context.params),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n '[non-serializable]',\n );\n }\n }\n\n if (captureResult && result !== undefined) {\n try {\n ctx.setAttribute('tanstack.loader.result', JSON.stringify(result));\n } catch {\n ctx.setAttribute('tanstack.loader.result', '[non-serializable]');\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n });\n }\n\n // Async loader\n return trace(spanName, async (ctx: TraceContext) => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'loader',\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'loader',\n });\n\n if (captureParams && context?.params) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n JSON.stringify(context.params),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n '[non-serializable]',\n );\n }\n }\n\n try {\n const asyncResult = await result;\n\n if (captureResult && asyncResult !== undefined) {\n try {\n ctx.setAttribute(\n 'tanstack.loader.result',\n JSON.stringify(asyncResult),\n );\n } catch {\n ctx.setAttribute('tanstack.loader.result', '[non-serializable]');\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return asyncResult;\n } catch (error) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n throw error;\n }\n });\n };\n\n return wrapped as TLoaderFn;\n}\n\n/**\n * Wrap a TanStack route beforeLoad function with OpenTelemetry tracing\n *\n * This function wraps a beforeLoad function to automatically create spans.\n * beforeLoad runs before the route component renders and is typically\n * used for auth checks, redirects, or data prefetching.\n *\n * The generic type TBeforeLoadFn preserves the full TanStack Router type inference,\n * including typed params, context, search, and return types.\n *\n * @param beforeLoadFn - The beforeLoad function to wrap\n * @param config - Configuration options\n * @returns Wrapped beforeLoad function with tracing (preserves original types)\n *\n * @example\n * ```typescript\n * import { createFileRoute, redirect } from '@tanstack/react-router';\n * import { traceBeforeLoad } from 'autotel-tanstack/loaders';\n *\n * export const Route = createFileRoute('/dashboard')({\n * // Types are fully preserved - context, params, search are all typed\n * beforeLoad: traceBeforeLoad(async ({ context, params }) => {\n * if (!context.auth.isAuthenticated) {\n * throw redirect({ to: '/login' });\n * }\n * return { userId: params.userId }; // Return type flows to loader context\n * }),\n * loader: ({ context }) => {\n * // context.userId is typed from beforeLoad return\n * return { user: context.userId };\n * },\n * });\n * ```\n */\nexport function traceBeforeLoad<TBeforeLoadFn extends (opts: any) => any>(\n beforeLoadFn: TBeforeLoadFn,\n config: TraceLoaderConfig = {},\n): TBeforeLoadFn {\n const captureParams = config.captureParams ?? true;\n\n const wrapped = (input: TanStackContextInternal) => {\n // Skip tracing in browser\n if (!isServerSide()) {\n return beforeLoadFn(input);\n }\n\n const routeId = input?.route?.id || 'unknown';\n const spanName = config.name || `tanstack.beforeLoad.${routeId}`;\n\n // Handle both sync and async beforeLoad\n const result = beforeLoadFn(input);\n const isPromise = result instanceof Promise;\n\n if (!isPromise) {\n // Sync beforeLoad\n return trace(spanName, (ctx: TraceContext) => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'beforeLoad',\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'beforeLoad',\n });\n\n if (captureParams && input?.params) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n JSON.stringify(input.params),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n '[non-serializable]',\n );\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n });\n }\n\n // Async beforeLoad\n return trace(spanName, async (ctx: TraceContext) => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'beforeLoad',\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'beforeLoad',\n });\n\n if (captureParams && input?.params) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n JSON.stringify(input.params),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n '[non-serializable]',\n );\n }\n }\n\n try {\n const asyncResult = await result;\n ctx.setStatus({ code: SpanStatusCode.OK });\n return asyncResult;\n } catch (error) {\n // Check if this is a redirect or notFound (expected control flow)\n const errorName = (error as Error).name;\n if (errorName === 'RedirectError' || errorName === 'NotFoundError') {\n // Mark as OK since these are expected control flow\n ctx.setAttribute('tanstack.beforeLoad.redirect', true);\n ctx.setStatus({ code: SpanStatusCode.OK });\n } else {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n }\n throw error;\n }\n });\n };\n\n return wrapped as TBeforeLoadFn;\n}\n\n/**\n * Create a traced route configuration helper\n *\n * This higher-order function helps create route configurations\n * with automatic tracing for both loader and beforeLoad.\n *\n * @param routeId - The route identifier\n * @param config - Tracing configuration\n * @returns Object with traced loader and beforeLoad wrappers\n *\n * @example\n * ```typescript\n * import { createFileRoute } from '@tanstack/react-router';\n * import { createTracedRoute } from 'autotel-tanstack/loaders';\n *\n * const traced = createTracedRoute('/users/$userId');\n *\n * export const Route = createFileRoute('/users/$userId')({\n * beforeLoad: traced.beforeLoad(async ({ context }) => {\n * // Auth check\n * }),\n * loader: traced.loader(async ({ params }) => {\n * return await getUser(params.userId);\n * }),\n * });\n * ```\n */\nexport function createTracedRoute(\n routeId: string,\n config: Omit<TraceLoaderConfig, 'name'> = {},\n) {\n return {\n /**\n * Wrap a loader function with tracing\n */\n loader<TLoaderFn extends (ctx: any) => any>(\n loaderFn: TLoaderFn,\n ): TLoaderFn {\n return traceLoader(loaderFn, {\n ...config,\n name: `tanstack.loader.${routeId}`,\n });\n },\n\n /**\n * Wrap a beforeLoad function with tracing\n */\n beforeLoad<TBeforeLoadFn extends (opts: any) => any>(\n beforeLoadFn: TBeforeLoadFn,\n ): TBeforeLoadFn {\n return traceBeforeLoad(beforeLoadFn, {\n ...config,\n name: `tanstack.beforeLoad.${routeId}`,\n });\n },\n };\n}\n"]}
|
|
@@ -16,5 +16,5 @@ function withErrorReporting(fn, context) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export { clearErrors, createErrorReportingHandler, getRecentErrors, reportError, withErrorReporting };
|
|
19
|
-
//# sourceMappingURL=chunk-
|
|
20
|
-
//# sourceMappingURL=chunk-
|
|
19
|
+
//# sourceMappingURL=chunk-EFSKEYDJ.js.map
|
|
20
|
+
//# sourceMappingURL=chunk-EFSKEYDJ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser/error-reporting.ts"],"names":[],"mappings":";AAoBO,SAAS,WAAA,CACd,OACA,OAAA,EACM;AAIR;AAKO,SAAS,gBAAgB,KAAA,EAA8B;AAE5D,EAAA,OAAO,EAAC;AACV;AAKO,SAAS,WAAA,GAAoB;AAEpC;AAKO,SAAS,2BAAA,GAA8C;AAC5D,EAAA,OAAO,MAAM;AACX,IAAA,OAAO,QAAA,CAAS,KAAK,EAAE,MAAA,EAAQ,EAAC,EAAG,KAAA,EAAO,GAAG,CAAA;AAAA,EAC/C,CAAA;AACF;
|
|
1
|
+
{"version":3,"sources":["../src/browser/error-reporting.ts"],"names":[],"mappings":";AAoBO,SAAS,WAAA,CACd,OACA,OAAA,EACM;AAIR;AAKO,SAAS,gBAAgB,KAAA,EAA8B;AAE5D,EAAA,OAAO,EAAC;AACV;AAKO,SAAS,WAAA,GAAoB;AAEpC;AAKO,SAAS,2BAAA,GAA8C;AAC5D,EAAA,OAAO,MAAM;AACX,IAAA,OAAO,QAAA,CAAS,KAAK,EAAE,MAAA,EAAQ,EAAC,EAAG,KAAA,EAAO,GAAG,CAAA;AAAA,EAC/C,CAAA;AACF;AAMO,SAAS,kBAAA,CACd,IACA,OAAA,EACG;AAEH,EAAA,OAAO,EAAA;AACT","file":"chunk-EFSKEYDJ.js","sourcesContent":["/**\n * Browser stub for error-reporting module\n *\n * Error reporting/collection only happens on the server.\n * In browser, these are no-op functions.\n */\n\n/**\n * Error entry structure (stub)\n */\nexport interface ErrorEntry {\n timestamp: string;\n message: string;\n stack?: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * Browser stub: No-op\n */\nexport function reportError(\n error: Error,\n context?: Record<string, unknown>,\n): void {\n void error;\n void context;\n // No-op in browser\n}\n\n/**\n * Browser stub: Returns empty array\n */\nexport function getRecentErrors(limit?: number): ErrorEntry[] {\n void limit;\n return [];\n}\n\n/**\n * Browser stub: No-op\n */\nexport function clearErrors(): void {\n // No-op in browser\n}\n\n/**\n * Browser stub: Returns JSON Response with empty errors\n */\nexport function createErrorReportingHandler(): () => Response {\n return () => {\n return Response.json({ errors: [], count: 0 });\n };\n}\n\n/**\n * Browser stub: Returns function unchanged\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Intentional for type passthrough\nexport function withErrorReporting<T extends (...args: any[]) => any>(\n fn: T,\n context?: Record<string, unknown>,\n): T {\n void context;\n return fn;\n}\n"]}
|
|
@@ -92,5 +92,5 @@ function recordTiming(metricName, fn) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export { createMetricsHandler, metricsCollector, recordTiming };
|
|
95
|
-
//# sourceMappingURL=chunk-
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
95
|
+
//# sourceMappingURL=chunk-G526TOMY.js.map
|
|
96
|
+
//# sourceMappingURL=chunk-G526TOMY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/metrics.ts"],"names":[],"mappings":"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","file":"chunk-G526TOMY.js","sourcesContent":["/**\n * Performance metrics collection for TanStack Start\n *\n * Provides utilities to collect and expose performance metrics\n * following the patterns from TanStack Start observability guide.\n */\n\n/**\n * Performance timing data\n */\nexport interface TimingStats {\n count: number;\n avg: number;\n p50: number;\n p95: number;\n min: number;\n max: number;\n}\n\n/**\n * Metrics collector for performance data\n *\n * Collects timing metrics and provides statistical analysis.\n * Thread-safe for concurrent access.\n *\n * @example\n * ```typescript\n * import { metricsCollector } from 'autotel-tanstack/metrics';\n *\n * // Record a timing\n * metricsCollector.recordTiming('serverFn.getUser', 150);\n *\n * // Get stats\n * const stats = metricsCollector.getStats('serverFn.getUser');\n * console.log(`Average: ${stats.avg}ms, P95: ${stats.p95}ms`);\n * ```\n */\nclass MetricsCollector {\n private metrics = new Map<string, number[]>();\n private readonly maxSamples = 1000; // Limit memory usage\n\n /**\n * Record a timing measurement\n */\n recordTiming(name: string, duration: number): void {\n if (!this.metrics.has(name)) {\n this.metrics.set(name, []);\n }\n\n const timings = this.metrics.get(name)!;\n timings.push(duration);\n\n // Limit samples to prevent memory issues\n if (timings.length > this.maxSamples) {\n timings.shift(); // Remove oldest\n }\n }\n\n /**\n * Get statistics for a metric\n */\n getStats(name: string): TimingStats | null {\n const timings = this.metrics.get(name);\n if (!timings || timings.length === 0) {\n return null;\n }\n\n const sorted = [...timings].toSorted((a, b) => a - b);\n const sum = timings.reduce((a, b) => a + b, 0);\n\n return {\n count: timings.length,\n avg: sum / timings.length,\n p50: sorted.at(Math.floor(sorted.length * 0.5)) ?? 0,\n p95: sorted.at(Math.floor(sorted.length * 0.95)) ?? 0,\n min: sorted[0] ?? 0,\n max: sorted.at(-1) ?? 0,\n };\n }\n\n /**\n * Get all collected metrics\n */\n getAllStats(): Record<string, TimingStats> {\n const stats: Record<string, TimingStats> = {};\n for (const [name] of this.metrics) {\n const stat = this.getStats(name);\n if (stat) {\n stats[name] = stat;\n }\n }\n return stats;\n }\n\n /**\n * Reset all metrics\n */\n reset(): void {\n this.metrics.clear();\n }\n\n /**\n * Reset a specific metric\n */\n resetMetric(name: string): void {\n this.metrics.delete(name);\n }\n}\n\n/**\n * Global metrics collector instance\n */\nexport const metricsCollector = new MetricsCollector();\n\n/**\n * Helper to create a metrics endpoint handler\n *\n * Returns a handler that exposes metrics in JSON format.\n * Use this to create a `/metrics` endpoint.\n *\n * @example\n * ```typescript\n * // routes/metrics.ts\n * import { createFileRoute } from '@tanstack/react-router';\n * import { json } from '@tanstack/react-start';\n * import { createMetricsHandler } from 'autotel-tanstack/metrics';\n *\n * export const Route = createFileRoute('/metrics')({\n * server: {\n * handlers: {\n * GET: createMetricsHandler(),\n * },\n * },\n * });\n * ```\n */\nexport function createMetricsHandler() {\n return async () => {\n const { json } = await import('@tanstack/react-start');\n\n return json({\n system: {\n uptime: process.uptime(),\n memory: process.memoryUsage(),\n timestamp: new Date().toISOString(),\n },\n application: metricsCollector.getAllStats(),\n });\n };\n}\n\n/**\n * Auto-record timing from a function execution\n *\n * Wraps a function to automatically record its execution time.\n *\n * @example\n * ```typescript\n * import { recordTiming } from 'autotel-tanstack/metrics';\n *\n * const getUser = createServerFn({ method: 'GET' })\n * .handler(recordTiming('serverFn.getUser', async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * }));\n * ```\n */\nexport function recordTiming<T extends (...args: any[]) => any>(\n metricName: string,\n fn: T,\n): T {\n return (async (...args: Parameters<T>) => {\n const startTime = Date.now();\n try {\n const result = await fn(...args);\n const duration = Date.now() - startTime;\n metricsCollector.recordTiming(metricName, duration);\n return result as ReturnType<T>;\n } catch (error) {\n const duration = Date.now() - startTime;\n metricsCollector.recordTiming(`${metricName}.error`, duration);\n throw error;\n }\n }) as T;\n}\n"]}
|
|
@@ -222,5 +222,5 @@ function createTracingServerHandler(config) {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
export { createTracingMiddleware, createTracingServerHandler, functionTracingMiddleware, tracingMiddleware };
|
|
225
|
-
//# sourceMappingURL=chunk-
|
|
226
|
-
//# sourceMappingURL=chunk-
|
|
225
|
+
//# sourceMappingURL=chunk-NP4OJO7T.js.map
|
|
226
|
+
//# sourceMappingURL=chunk-NP4OJO7T.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"names":["tracingMiddleware"],"mappings":"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","file":"chunk-ETD6SGPH.js","sourcesContent":["import { context, SpanStatusCode, type Attributes } from '@opentelemetry/api';\nimport { trace, type TraceContext } from 'autotel';\nimport { extractContextFromRequest } from './context';\nimport { isServerSide } from './env';\nimport {\n type TracingMiddlewareConfig,\n DEFAULT_CONFIG,\n SPAN_ATTRIBUTES,\n} from './types';\n\n/**\n * Check if a path should be excluded from tracing\n */\nfunction shouldExcludePath(\n pathname: string,\n excludePaths: (string | RegExp)[],\n): boolean {\n for (const pattern of excludePaths) {\n if (typeof pattern === 'string') {\n // Simple glob matching\n if (pattern.includes('*')) {\n const regex = new RegExp(\n '^' + pattern.replaceAll('*', '.*').replaceAll('?', '.') + '$',\n );\n if (regex.test(pathname)) return true;\n } else {\n if (pathname === pattern || pathname.startsWith(pattern)) return true;\n }\n } else {\n if (pattern.test(pathname)) return true;\n }\n }\n return false;\n}\n\n/**\n * Build span attributes for HTTP requests\n */\nfunction buildRequestAttributes(\n request: Request,\n config: Required<\n Omit<TracingMiddlewareConfig, 'customAttributes' | 'service' | 'type'>\n >,\n): Attributes {\n const url = new URL(request.url);\n const attrs: Attributes = {\n [SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,\n [SPAN_ATTRIBUTES.URL_PATH]: url.pathname,\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'request',\n };\n\n if (url.search) {\n attrs[SPAN_ATTRIBUTES.URL_QUERY] = url.search;\n }\n\n // Capture configured headers\n if (config.captureHeaders) {\n for (const header of config.captureHeaders) {\n const value = request.headers.get(header);\n if (value) {\n attrs[`http.request.header.${header.toLowerCase()}`] = value;\n }\n }\n }\n\n return attrs;\n}\n\n/**\n * Build span attributes for server functions\n */\nfunction buildServerFnAttributes(\n functionName: string,\n method: string,\n args: unknown,\n config: Required<\n Omit<TracingMiddlewareConfig, 'customAttributes' | 'service' | 'type'>\n >,\n): Attributes {\n const attrs: Attributes = {\n [SPAN_ATTRIBUTES.RPC_SYSTEM]: 'tanstack-start',\n [SPAN_ATTRIBUTES.RPC_METHOD]: functionName,\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'serverFn',\n [SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_NAME]: functionName,\n [SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_METHOD]: method,\n };\n\n if (config.captureArgs && args !== undefined) {\n try {\n attrs[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS] = JSON.stringify(args);\n } catch {\n attrs[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS] = '[non-serializable]';\n }\n }\n\n return attrs;\n}\n\n/**\n * Generic middleware handler type (compatible with TanStack's middleware pattern)\n *\n * This type represents the shape of TanStack middleware handlers.\n * We use a generic type to avoid direct dependency on TanStack packages.\n */\nexport interface MiddlewareHandler<TContext = unknown> {\n (opts: {\n next: (ctx?: Partial<TContext>) => Promise<TContext>;\n context: TContext;\n request?: Request;\n pathname?: string;\n data?: unknown;\n method?: string;\n filename?: string;\n functionId?: string;\n signal?: AbortSignal;\n }): Promise<TContext>;\n}\n\n/**\n * Create a TanStack-compatible tracing middleware\n *\n * This creates middleware that automatically traces all requests/server functions\n * with OpenTelemetry spans. Use with TanStack Start's middleware system.\n *\n * @param config - Configuration options\n * @returns Middleware handler compatible with TanStack Start\n *\n * @example\n * ```typescript\n * // Global request middleware in app/start.ts\n * import { createStart } from '@tanstack/react-start';\n * import { createTracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const startInstance = createStart(() => ({\n * requestMiddleware: [\n * createTracingMiddleware({\n * captureHeaders: ['x-request-id', 'user-agent'],\n * excludePaths: ['/health', '/metrics'],\n * }),\n * ],\n * }));\n * ```\n *\n * @example\n * ```typescript\n * // Server function middleware\n * import { createServerFn } from '@tanstack/react-start';\n * import { createTracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const getUser = createServerFn({ method: 'GET' })\n * .middleware([createTracingMiddleware({ type: 'function' })])\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n * ```\n */\nexport function createTracingMiddleware<TContext = unknown>(\n config?: TracingMiddlewareConfig,\n): MiddlewareHandler<TContext> {\n const mergedConfig = {\n ...DEFAULT_CONFIG,\n ...config,\n type: config?.type ?? 'request',\n };\n\n return async function tracingMiddleware(opts) {\n // If we're in the browser, return a no-op middleware\n // This prevents autotel (which uses Node.js APIs) from being bundled/executed in the browser\n if (!isServerSide()) {\n return opts.next();\n }\n const { next, request, pathname, data, functionId } = opts;\n\n // For function middleware\n if (mergedConfig.type === 'function') {\n const fnName = functionId || 'unknown';\n const method = (opts as { method?: string }).method || 'POST';\n\n return trace(`tanstack.serverFn.${fnName}`, async (ctx: TraceContext) => {\n const attrs = buildServerFnAttributes(\n fnName,\n method,\n data,\n mergedConfig,\n );\n ctx.setAttributes(attrs as Record<string, string | number | boolean>);\n\n // Add custom attributes if provided\n if (config?.customAttributes) {\n const customAttrs = config.customAttributes({\n type: 'serverFn',\n name: fnName,\n args: data,\n });\n ctx.setAttributes(\n customAttrs as Record<string, string | number | boolean>,\n );\n }\n\n try {\n const result = await next();\n\n // Capture result if configured\n if (mergedConfig.captureResults && result !== undefined) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n JSON.stringify(result),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n '[non-serializable]',\n );\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n if (mergedConfig.captureErrors) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n\n // Report error to error store\n try {\n const { reportError } = await import('./error-reporting');\n reportError(error as Error, {\n type: 'serverFn',\n name: fnName,\n method,\n });\n } catch {\n // Error reporting not available, skip\n }\n }\n throw error;\n }\n }) as Promise<TContext>;\n }\n\n // For request middleware\n if (!request) {\n // No request available, just pass through\n return next();\n }\n\n const url = new URL(request.url);\n\n // Check if path should be excluded\n if (shouldExcludePath(url.pathname, mergedConfig.excludePaths)) {\n return next();\n }\n\n // Extract parent context from request headers\n const parentContext = extractContextFromRequest(request);\n\n // Run within parent context for distributed tracing\n return context.with(parentContext, async () => {\n const spanName = `${request.method} ${pathname || url.pathname}`;\n\n return trace(spanName, async (ctx: TraceContext) => {\n const attrs = buildRequestAttributes(request, mergedConfig);\n ctx.setAttributes(attrs as Record<string, string | number | boolean>);\n\n // Add custom attributes if provided\n if (config?.customAttributes) {\n const customAttrs = config.customAttributes({\n type: 'request',\n name: spanName,\n request,\n });\n ctx.setAttributes(\n customAttrs as Record<string, string | number | boolean>,\n );\n }\n\n const startTime = Date.now();\n\n try {\n const result = await next();\n\n const duration = Date.now() - startTime;\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,\n duration,\n );\n\n // Record timing in metrics collector\n try {\n const { metricsCollector } = await import('./metrics');\n metricsCollector.recordTiming(spanName, duration);\n } catch {\n // Metrics not available, skip\n }\n\n // Try to get response status from result if it's a Response\n if (result && typeof result === 'object' && 'status' in result) {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE,\n (result as { status: number }).status,\n );\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n const duration = Date.now() - startTime;\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,\n duration,\n );\n\n if (mergedConfig.captureErrors) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n\n // Report error to error store\n try {\n const { reportError } = await import('./error-reporting');\n reportError(error as Error, {\n type: 'request',\n method: request.method,\n pathname: url.pathname,\n });\n } catch {\n // Error reporting not available, skip\n }\n }\n throw error;\n }\n }) as Promise<TContext>;\n });\n };\n}\n\n/**\n * Pre-configured tracing middleware with sensible defaults\n *\n * Convenience export for quick setup. Uses adaptive sampling,\n * captures x-request-id header, and excludes common health check paths.\n *\n * @param config - Optional configuration overrides\n * @returns Middleware handler\n *\n * @example\n * ```typescript\n * import { createStart } from '@tanstack/react-start';\n * import { tracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const startInstance = createStart(() => ({\n * requestMiddleware: [tracingMiddleware()],\n * }));\n * ```\n */\nexport function tracingMiddleware<TContext = unknown>(\n config?: TracingMiddlewareConfig,\n): MiddlewareHandler<TContext> {\n return createTracingMiddleware({\n sampling: 'adaptive',\n captureHeaders: ['x-request-id', 'user-agent'],\n excludePaths: ['/health', '/healthz', '/ready', '/metrics', '/_ping'],\n ...config,\n });\n}\n\n/**\n * Create function-specific tracing middleware\n *\n * Convenience wrapper for server function middleware.\n *\n * @param config - Optional configuration\n * @returns Middleware handler for server functions\n *\n * @example\n * ```typescript\n * import { createServerFn } from '@tanstack/react-start';\n * import { functionTracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const getUser = createServerFn({ method: 'GET' })\n * .middleware([functionTracingMiddleware()])\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n * ```\n */\nexport function functionTracingMiddleware<TContext = unknown>(\n config?: Omit<TracingMiddlewareConfig, 'type'>,\n): MiddlewareHandler<TContext> {\n return createTracingMiddleware({\n ...config,\n type: 'function',\n });\n}\n\n/**\n * Create a tracing handler for use with TanStack's native createMiddleware()\n *\n * This provides the raw tracing logic that you can pass to createMiddleware().server().\n * Use this when you want full control over the middleware builder pattern.\n *\n * The handler accepts TanStack's middleware signature `{ next, context, request }`\n * and internally adapts it to our more flexible MiddlewareHandler interface.\n *\n * @param config - Configuration options\n * @returns Server handler function compatible with createMiddleware().server()\n *\n * @example\n * ```typescript\n * import { createStart, createMiddleware } from '@tanstack/react-start';\n * import { createTracingServerHandler } from 'autotel-tanstack/middleware';\n *\n * // TanStack-native middleware creation\n * const requestTracingMiddleware = createMiddleware().server(\n * createTracingServerHandler({ captureHeaders: ['x-request-id'] })\n * );\n *\n * export const start = createStart(() => ({\n * requestMiddleware: [requestTracingMiddleware],\n * }));\n * ```\n *\n * @example\n * ```typescript\n * // For server functions - use createMiddleware({ type: 'function' })\n * import { createStart, createMiddleware } from '@tanstack/react-start';\n * import { createTracingServerHandler } from 'autotel-tanstack/middleware';\n *\n * const functionTracingMiddleware = createMiddleware({ type: 'function' }).server(\n * createTracingServerHandler({ type: 'function', captureArgs: true })\n * );\n *\n * export const start = createStart(() => ({\n * functionMiddleware: [functionTracingMiddleware],\n * }));\n * ```\n */\nexport function createTracingServerHandler<TContext = unknown>(\n config?: TracingMiddlewareConfig,\n): (opts: {\n next: (ctx?: Partial<TContext>) => Promise<TContext>;\n context: TContext;\n request?: Request;\n}) => Promise<TContext> {\n const handler = createTracingMiddleware<TContext>(config);\n\n // Adapt TanStack's signature to our handler\n return async (opts) => {\n return handler(opts);\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts"],"names":["tracingMiddleware"],"mappings":"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","file":"chunk-NP4OJO7T.js","sourcesContent":["import { context, SpanStatusCode, type Attributes } from '@opentelemetry/api';\nimport { trace, type TraceContext } from 'autotel';\nimport { extractContextFromRequest } from './context';\nimport { isServerSide } from './env';\nimport {\n type TracingMiddlewareConfig,\n DEFAULT_CONFIG,\n SPAN_ATTRIBUTES,\n} from './types';\n\n/**\n * Check if a path should be excluded from tracing\n */\nfunction shouldExcludePath(\n pathname: string,\n excludePaths: (string | RegExp)[],\n): boolean {\n for (const pattern of excludePaths) {\n if (typeof pattern === 'string') {\n // Simple glob matching\n if (pattern.includes('*')) {\n const regex = new RegExp(\n '^' + pattern.replaceAll('*', '.*').replaceAll('?', '.') + '$',\n );\n if (regex.test(pathname)) return true;\n } else {\n if (pathname === pattern || pathname.startsWith(pattern)) return true;\n }\n } else {\n if (pattern.test(pathname)) return true;\n }\n }\n return false;\n}\n\n/**\n * Build span attributes for HTTP requests\n */\nfunction buildRequestAttributes(\n request: Request,\n config: Required<\n Omit<TracingMiddlewareConfig, 'customAttributes' | 'service' | 'type'>\n >,\n): Attributes {\n const url = new URL(request.url);\n const attrs: Attributes = {\n [SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,\n [SPAN_ATTRIBUTES.URL_PATH]: url.pathname,\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'request',\n };\n\n if (url.search) {\n attrs[SPAN_ATTRIBUTES.URL_QUERY] = url.search;\n }\n\n // Capture configured headers\n if (config.captureHeaders) {\n for (const header of config.captureHeaders) {\n const value = request.headers.get(header);\n if (value) {\n attrs[`http.request.header.${header.toLowerCase()}`] = value;\n }\n }\n }\n\n return attrs;\n}\n\n/**\n * Build span attributes for server functions\n */\nfunction buildServerFnAttributes(\n functionName: string,\n method: string,\n args: unknown,\n config: Required<\n Omit<TracingMiddlewareConfig, 'customAttributes' | 'service' | 'type'>\n >,\n): Attributes {\n const attrs: Attributes = {\n [SPAN_ATTRIBUTES.RPC_SYSTEM]: 'tanstack-start',\n [SPAN_ATTRIBUTES.RPC_METHOD]: functionName,\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'serverFn',\n [SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_NAME]: functionName,\n [SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_METHOD]: method,\n };\n\n if (config.captureArgs && args !== undefined) {\n try {\n attrs[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS] = JSON.stringify(args);\n } catch {\n attrs[SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS] = '[non-serializable]';\n }\n }\n\n return attrs;\n}\n\n/**\n * Generic middleware handler type (compatible with TanStack's middleware pattern)\n *\n * This type represents the shape of TanStack middleware handlers.\n * We use a generic type to avoid direct dependency on TanStack packages.\n */\nexport interface MiddlewareHandler<TContext = unknown> {\n (opts: {\n next: (ctx?: Partial<TContext>) => Promise<TContext>;\n context: TContext;\n request?: Request;\n pathname?: string;\n data?: unknown;\n method?: string;\n filename?: string;\n functionId?: string;\n signal?: AbortSignal;\n }): Promise<TContext>;\n}\n\n/**\n * Create a TanStack-compatible tracing middleware\n *\n * This creates middleware that automatically traces all requests/server functions\n * with OpenTelemetry spans. Use with TanStack Start's middleware system.\n *\n * @param config - Configuration options\n * @returns Middleware handler compatible with TanStack Start\n *\n * @example\n * ```typescript\n * // Global request middleware in app/start.ts\n * import { createStart } from '@tanstack/react-start';\n * import { createTracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const startInstance = createStart(() => ({\n * requestMiddleware: [\n * createTracingMiddleware({\n * captureHeaders: ['x-request-id', 'user-agent'],\n * excludePaths: ['/health', '/metrics'],\n * }),\n * ],\n * }));\n * ```\n *\n * @example\n * ```typescript\n * // Server function middleware\n * import { createServerFn } from '@tanstack/react-start';\n * import { createTracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const getUser = createServerFn({ method: 'GET' })\n * .middleware([createTracingMiddleware({ type: 'function' })])\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n * ```\n */\nexport function createTracingMiddleware<TContext = unknown>(\n config?: TracingMiddlewareConfig,\n): MiddlewareHandler<TContext> {\n const mergedConfig = {\n ...DEFAULT_CONFIG,\n ...config,\n type: config?.type ?? 'request',\n };\n\n return async function tracingMiddleware(opts) {\n // If we're in the browser, return a no-op middleware\n // This prevents autotel (which uses Node.js APIs) from being bundled/executed in the browser\n if (!isServerSide()) {\n return opts.next();\n }\n const { next, request, pathname, data, functionId } = opts;\n\n // For function middleware\n if (mergedConfig.type === 'function') {\n const fnName = functionId || 'unknown';\n const method = (opts as { method?: string }).method || 'POST';\n\n return trace(`tanstack.serverFn.${fnName}`, async (ctx: TraceContext) => {\n const attrs = buildServerFnAttributes(\n fnName,\n method,\n data,\n mergedConfig,\n );\n ctx.setAttributes(attrs as Record<string, string | number | boolean>);\n\n // Add custom attributes if provided\n if (config?.customAttributes) {\n const customAttrs = config.customAttributes({\n type: 'serverFn',\n name: fnName,\n args: data,\n });\n ctx.setAttributes(\n customAttrs as Record<string, string | number | boolean>,\n );\n }\n\n try {\n const result = await next();\n\n // Capture result if configured\n if (mergedConfig.captureResults && result !== undefined) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n JSON.stringify(result),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n '[non-serializable]',\n );\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n if (mergedConfig.captureErrors) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n\n // Report error to error store\n try {\n const { reportError } = await import('./error-reporting');\n reportError(error as Error, {\n type: 'serverFn',\n name: fnName,\n method,\n });\n } catch {\n // Error reporting not available, skip\n }\n }\n throw error;\n }\n }) as Promise<TContext>;\n }\n\n // For request middleware\n if (!request) {\n // No request available, just pass through\n return next();\n }\n\n const url = new URL(request.url);\n\n // Check if path should be excluded\n if (shouldExcludePath(url.pathname, mergedConfig.excludePaths)) {\n return next();\n }\n\n // Extract parent context from request headers\n const parentContext = extractContextFromRequest(request);\n\n // Run within parent context for distributed tracing\n return context.with(parentContext, async () => {\n const spanName = `${request.method} ${pathname || url.pathname}`;\n\n return trace(spanName, async (ctx: TraceContext) => {\n const attrs = buildRequestAttributes(request, mergedConfig);\n ctx.setAttributes(attrs as Record<string, string | number | boolean>);\n\n // Add custom attributes if provided\n if (config?.customAttributes) {\n const customAttrs = config.customAttributes({\n type: 'request',\n name: spanName,\n request,\n });\n ctx.setAttributes(\n customAttrs as Record<string, string | number | boolean>,\n );\n }\n\n const startTime = Date.now();\n\n try {\n const result = await next();\n\n const duration = Date.now() - startTime;\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,\n duration,\n );\n\n // Record timing in metrics collector\n try {\n const { metricsCollector } = await import('./metrics');\n metricsCollector.recordTiming(spanName, duration);\n } catch {\n // Metrics not available, skip\n }\n\n // Try to get response status from result if it's a Response\n if (result && typeof result === 'object' && 'status' in result) {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE,\n (result as { status: number }).status,\n );\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n const duration = Date.now() - startTime;\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,\n duration,\n );\n\n if (mergedConfig.captureErrors) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n\n // Report error to error store\n try {\n const { reportError } = await import('./error-reporting');\n reportError(error as Error, {\n type: 'request',\n method: request.method,\n pathname: url.pathname,\n });\n } catch {\n // Error reporting not available, skip\n }\n }\n throw error;\n }\n }) as Promise<TContext>;\n });\n };\n}\n\n/**\n * Pre-configured tracing middleware with sensible defaults\n *\n * Convenience export for quick setup. Uses adaptive sampling,\n * captures x-request-id header, and excludes common health check paths.\n *\n * @param config - Optional configuration overrides\n * @returns Middleware handler\n *\n * @example\n * ```typescript\n * import { createStart } from '@tanstack/react-start';\n * import { tracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const startInstance = createStart(() => ({\n * requestMiddleware: [tracingMiddleware()],\n * }));\n * ```\n */\nexport function tracingMiddleware<TContext = unknown>(\n config?: TracingMiddlewareConfig,\n): MiddlewareHandler<TContext> {\n return createTracingMiddleware({\n sampling: 'adaptive',\n captureHeaders: ['x-request-id', 'user-agent'],\n excludePaths: ['/health', '/healthz', '/ready', '/metrics', '/_ping'],\n ...config,\n });\n}\n\n/**\n * Create function-specific tracing middleware\n *\n * Convenience wrapper for server function middleware.\n *\n * @param config - Optional configuration\n * @returns Middleware handler for server functions\n *\n * @example\n * ```typescript\n * import { createServerFn } from '@tanstack/react-start';\n * import { functionTracingMiddleware } from 'autotel-tanstack/middleware';\n *\n * export const getUser = createServerFn({ method: 'GET' })\n * .middleware([functionTracingMiddleware()])\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n * ```\n */\nexport function functionTracingMiddleware<TContext = unknown>(\n config?: Omit<TracingMiddlewareConfig, 'type'>,\n): MiddlewareHandler<TContext> {\n return createTracingMiddleware({\n ...config,\n type: 'function',\n });\n}\n\n/**\n * Create a tracing handler for use with TanStack's native createMiddleware()\n *\n * This provides the raw tracing logic that you can pass to createMiddleware().server().\n * Use this when you want full control over the middleware builder pattern.\n *\n * The handler accepts TanStack's middleware signature `{ next, context, request }`\n * and internally adapts it to our more flexible MiddlewareHandler interface.\n *\n * @param config - Configuration options\n * @returns Server handler function compatible with createMiddleware().server()\n *\n * @example\n * ```typescript\n * import { createStart, createMiddleware } from '@tanstack/react-start';\n * import { createTracingServerHandler } from 'autotel-tanstack/middleware';\n *\n * // TanStack-native middleware creation\n * const requestTracingMiddleware = createMiddleware().server(\n * createTracingServerHandler({ captureHeaders: ['x-request-id'] })\n * );\n *\n * export const start = createStart(() => ({\n * requestMiddleware: [requestTracingMiddleware],\n * }));\n * ```\n *\n * @example\n * ```typescript\n * // For server functions - use createMiddleware({ type: 'function' })\n * import { createStart, createMiddleware } from '@tanstack/react-start';\n * import { createTracingServerHandler } from 'autotel-tanstack/middleware';\n *\n * const functionTracingMiddleware = createMiddleware({ type: 'function' }).server(\n * createTracingServerHandler({ type: 'function', captureArgs: true })\n * );\n *\n * export const start = createStart(() => ({\n * functionMiddleware: [functionTracingMiddleware],\n * }));\n * ```\n */\nexport function createTracingServerHandler<TContext = unknown>(\n config?: TracingMiddlewareConfig,\n): (opts: any) => any {\n const handler = createTracingMiddleware<TContext>(config);\n\n // Adapt TanStack's signature to our handler\n return async (opts: any) => {\n return handler(opts);\n };\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { TimingStats, createMetricsHandler, metricsCollector, recordTiming } fro
|
|
|
9
9
|
export { ErrorReport, createErrorReportingHandler, errorStore, reportError, withErrorReporting } from './error-reporting.js';
|
|
10
10
|
export { flush, init, shutdown, span, trace } from 'autotel';
|
|
11
11
|
import '@opentelemetry/api';
|
|
12
|
+
import '@tanstack/react-router';
|
|
12
13
|
import '@tanstack/react-start';
|
|
13
14
|
|
|
14
15
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { debugHeadersMiddleware } from './chunk-UTPW3QRT.js';
|
|
2
|
-
export { createMetricsHandler, metricsCollector, recordTiming } from './chunk-
|
|
2
|
+
export { createMetricsHandler, metricsCollector, recordTiming } from './chunk-G526TOMY.js';
|
|
3
3
|
export { createErrorReportingHandler, errorStore, reportError, withErrorReporting } from './chunk-XXBHZR3M.js';
|
|
4
|
-
export { createTracingMiddleware, createTracingServerHandler, functionTracingMiddleware, tracingMiddleware } from './chunk-
|
|
5
|
-
export { createTracedServerFnFactory, traceServerFn } from './chunk-
|
|
6
|
-
export { createTracedRoute, traceBeforeLoad, traceLoader } from './chunk-
|
|
4
|
+
export { createTracingMiddleware, createTracingServerHandler, functionTracingMiddleware, tracingMiddleware } from './chunk-NP4OJO7T.js';
|
|
5
|
+
export { createTracedServerFnFactory, traceServerFn } from './chunk-5JJXFTG3.js';
|
|
6
|
+
export { createTracedRoute, traceBeforeLoad, traceLoader } from './chunk-5RYSHDCO.js';
|
|
7
7
|
export { isBrowser, isNode, isServerSide } from './chunk-EUYFVNYE.js';
|
|
8
8
|
export { createTracedHandler, wrapStartHandler } from './chunk-Z5D2V4DU.js';
|
|
9
9
|
export { DEFAULT_CONFIG, SPAN_ATTRIBUTES } from './chunk-I4LX3LOG.js';
|
package/dist/loaders.d.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { c as TraceLoaderConfig } from './types-C37KSxMN.js';
|
|
2
|
+
export { LoaderFnContext } from '@tanstack/react-router';
|
|
2
3
|
import '@opentelemetry/api';
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* Loader context type (compatible with TanStack router loader context)
|
|
6
|
-
*/
|
|
7
|
-
interface LoaderContext {
|
|
8
|
-
params?: Record<string, string>;
|
|
9
|
-
route?: {
|
|
10
|
-
id?: string;
|
|
11
|
-
};
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
}
|
|
14
5
|
/**
|
|
15
6
|
* Wrap a TanStack route loader with OpenTelemetry tracing
|
|
16
7
|
*
|
|
@@ -18,9 +9,12 @@ interface LoaderContext {
|
|
|
18
9
|
* for each invocation. It captures route ID, params (optionally),
|
|
19
10
|
* and errors.
|
|
20
11
|
*
|
|
12
|
+
* The generic type TLoaderFn preserves the full TanStack Router type inference,
|
|
13
|
+
* including typed params, context, and return types.
|
|
14
|
+
*
|
|
21
15
|
* @param loaderFn - The loader function to wrap
|
|
22
16
|
* @param config - Configuration options
|
|
23
|
-
* @returns Wrapped loader function with tracing
|
|
17
|
+
* @returns Wrapped loader function with tracing (preserves original types)
|
|
24
18
|
*
|
|
25
19
|
* @example
|
|
26
20
|
* ```typescript
|
|
@@ -28,6 +22,7 @@ interface LoaderContext {
|
|
|
28
22
|
* import { traceLoader } from 'autotel-tanstack/loaders';
|
|
29
23
|
*
|
|
30
24
|
* export const Route = createFileRoute('/users/$userId')({
|
|
25
|
+
* // Types are fully preserved - params.userId is typed as string
|
|
31
26
|
* loader: traceLoader(async ({ params }) => {
|
|
32
27
|
* return await db.users.findUnique({ where: { id: params.userId } });
|
|
33
28
|
* }),
|
|
@@ -36,24 +31,15 @@ interface LoaderContext {
|
|
|
36
31
|
*
|
|
37
32
|
* @example
|
|
38
33
|
* ```typescript
|
|
39
|
-
* //
|
|
40
|
-
* export const Route = createFileRoute('/
|
|
41
|
-
* loader: traceLoader(
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* where: { id: params.productId, category: params.category },
|
|
45
|
-
* });
|
|
46
|
-
* },
|
|
47
|
-
* {
|
|
48
|
-
* name: 'loadProduct',
|
|
49
|
-
* captureParams: true,
|
|
50
|
-
* captureResult: false,
|
|
51
|
-
* }
|
|
52
|
-
* ),
|
|
34
|
+
* // Sync loaders are also supported
|
|
35
|
+
* export const Route = createFileRoute('/static')({
|
|
36
|
+
* loader: traceLoader(({ context }) => ({
|
|
37
|
+
* message: `Welcome, ${context.userId}!`,
|
|
38
|
+
* })),
|
|
53
39
|
* });
|
|
54
40
|
* ```
|
|
55
41
|
*/
|
|
56
|
-
declare function traceLoader<
|
|
42
|
+
declare function traceLoader<TLoaderFn extends (ctx: any) => any>(loaderFn: TLoaderFn, config?: TraceLoaderConfig): TLoaderFn;
|
|
57
43
|
/**
|
|
58
44
|
* Wrap a TanStack route beforeLoad function with OpenTelemetry tracing
|
|
59
45
|
*
|
|
@@ -61,28 +47,34 @@ declare function traceLoader<T extends (context: LoaderContext) => Promise<unkno
|
|
|
61
47
|
* beforeLoad runs before the route component renders and is typically
|
|
62
48
|
* used for auth checks, redirects, or data prefetching.
|
|
63
49
|
*
|
|
50
|
+
* The generic type TBeforeLoadFn preserves the full TanStack Router type inference,
|
|
51
|
+
* including typed params, context, search, and return types.
|
|
52
|
+
*
|
|
64
53
|
* @param beforeLoadFn - The beforeLoad function to wrap
|
|
65
54
|
* @param config - Configuration options
|
|
66
|
-
* @returns Wrapped beforeLoad function with tracing
|
|
55
|
+
* @returns Wrapped beforeLoad function with tracing (preserves original types)
|
|
67
56
|
*
|
|
68
57
|
* @example
|
|
69
58
|
* ```typescript
|
|
70
|
-
* import { createFileRoute } from '@tanstack/react-router';
|
|
59
|
+
* import { createFileRoute, redirect } from '@tanstack/react-router';
|
|
71
60
|
* import { traceBeforeLoad } from 'autotel-tanstack/loaders';
|
|
72
61
|
*
|
|
73
62
|
* export const Route = createFileRoute('/dashboard')({
|
|
74
|
-
*
|
|
63
|
+
* // Types are fully preserved - context, params, search are all typed
|
|
64
|
+
* beforeLoad: traceBeforeLoad(async ({ context, params }) => {
|
|
75
65
|
* if (!context.auth.isAuthenticated) {
|
|
76
66
|
* throw redirect({ to: '/login' });
|
|
77
67
|
* }
|
|
68
|
+
* return { userId: params.userId }; // Return type flows to loader context
|
|
78
69
|
* }),
|
|
79
|
-
* loader:
|
|
80
|
-
*
|
|
70
|
+
* loader: ({ context }) => {
|
|
71
|
+
* // context.userId is typed from beforeLoad return
|
|
72
|
+
* return { user: context.userId };
|
|
81
73
|
* },
|
|
82
74
|
* });
|
|
83
75
|
* ```
|
|
84
76
|
*/
|
|
85
|
-
declare function traceBeforeLoad<
|
|
77
|
+
declare function traceBeforeLoad<TBeforeLoadFn extends (opts: any) => any>(beforeLoadFn: TBeforeLoadFn, config?: TraceLoaderConfig): TBeforeLoadFn;
|
|
86
78
|
/**
|
|
87
79
|
* Create a traced route configuration helper
|
|
88
80
|
*
|
|
@@ -114,11 +106,11 @@ declare function createTracedRoute(routeId: string, config?: Omit<TraceLoaderCon
|
|
|
114
106
|
/**
|
|
115
107
|
* Wrap a loader function with tracing
|
|
116
108
|
*/
|
|
117
|
-
loader<
|
|
109
|
+
loader<TLoaderFn extends (ctx: any) => any>(loaderFn: TLoaderFn): TLoaderFn;
|
|
118
110
|
/**
|
|
119
111
|
* Wrap a beforeLoad function with tracing
|
|
120
112
|
*/
|
|
121
|
-
beforeLoad<
|
|
113
|
+
beforeLoad<TBeforeLoadFn extends (opts: any) => any>(beforeLoadFn: TBeforeLoadFn): TBeforeLoadFn;
|
|
122
114
|
};
|
|
123
115
|
|
|
124
116
|
export { createTracedRoute, traceBeforeLoad, traceLoader };
|
package/dist/loaders.js
CHANGED
package/dist/metrics.d.ts
CHANGED
|
@@ -108,6 +108,6 @@ declare function createMetricsHandler(): () => Promise<_tanstack_react_start.Jso
|
|
|
108
108
|
* }));
|
|
109
109
|
* ```
|
|
110
110
|
*/
|
|
111
|
-
declare function recordTiming<T extends (...args:
|
|
111
|
+
declare function recordTiming<T extends (...args: any[]) => any>(metricName: string, fn: T): T;
|
|
112
112
|
|
|
113
113
|
export { type TimingStats, createMetricsHandler, metricsCollector, recordTiming };
|
package/dist/metrics.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createMetricsHandler, metricsCollector, recordTiming } from './chunk-
|
|
1
|
+
export { createMetricsHandler, metricsCollector, recordTiming } from './chunk-G526TOMY.js';
|
|
2
2
|
import './chunk-EGRHWZRV.js';
|
|
3
3
|
//# sourceMappingURL=metrics.js.map
|
|
4
4
|
//# sourceMappingURL=metrics.js.map
|
package/dist/middleware.d.ts
CHANGED
|
@@ -142,10 +142,6 @@ declare function functionTracingMiddleware<TContext = unknown>(config?: Omit<Tra
|
|
|
142
142
|
* }));
|
|
143
143
|
* ```
|
|
144
144
|
*/
|
|
145
|
-
declare function createTracingServerHandler<TContext = unknown>(config?: TracingMiddlewareConfig): (opts:
|
|
146
|
-
next: (ctx?: Partial<TContext>) => Promise<TContext>;
|
|
147
|
-
context: TContext;
|
|
148
|
-
request?: Request;
|
|
149
|
-
}) => Promise<TContext>;
|
|
145
|
+
declare function createTracingServerHandler<TContext = unknown>(config?: TracingMiddlewareConfig): (opts: any) => any;
|
|
150
146
|
|
|
151
147
|
export { type MiddlewareHandler, createTracingMiddleware, createTracingServerHandler, functionTracingMiddleware, tracingMiddleware };
|
package/dist/middleware.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createTracingMiddleware, createTracingServerHandler, functionTracingMiddleware, tracingMiddleware } from './chunk-
|
|
1
|
+
export { createTracingMiddleware, createTracingServerHandler, functionTracingMiddleware, tracingMiddleware } from './chunk-NP4OJO7T.js';
|
|
2
2
|
import './chunk-EUYFVNYE.js';
|
|
3
3
|
import './chunk-I4LX3LOG.js';
|
|
4
4
|
import './chunk-NTY64BKS.js';
|
|
@@ -41,7 +41,7 @@ import '@opentelemetry/api';
|
|
|
41
41
|
* );
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
|
-
declare function traceServerFn<T extends (...args:
|
|
44
|
+
declare function traceServerFn<T extends (...args: any[]) => any>(serverFn: T, config?: TraceServerFnConfig): T;
|
|
45
45
|
/**
|
|
46
46
|
* Create a traced version of createServerFn
|
|
47
47
|
*
|
|
@@ -66,6 +66,6 @@ declare function traceServerFn<T extends (...args: unknown[]) => Promise<unknown
|
|
|
66
66
|
* });
|
|
67
67
|
* ```
|
|
68
68
|
*/
|
|
69
|
-
declare function createTracedServerFnFactory<TCreateServerFn extends (...args:
|
|
69
|
+
declare function createTracedServerFnFactory<TCreateServerFn extends (...args: any[]) => any>(createServerFnOriginal: TCreateServerFn, defaultConfig?: Omit<TraceServerFnConfig, 'name'>): TCreateServerFn;
|
|
70
70
|
|
|
71
71
|
export { createTracedServerFnFactory, traceServerFn };
|
package/dist/server-functions.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-tanstack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "OpenTelemetry instrumentation for TanStack Start - automatic tracing for server functions, middleware, and route loaders",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -117,11 +117,11 @@
|
|
|
117
117
|
"license": "MIT",
|
|
118
118
|
"dependencies": {
|
|
119
119
|
"@opentelemetry/api": "^1.9.0",
|
|
120
|
-
"autotel": "2.
|
|
120
|
+
"autotel": "2.14.0"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
|
123
|
-
"@tanstack/react-start": "^1.
|
|
124
|
-
"@tanstack/solid-start": "^1.
|
|
123
|
+
"@tanstack/react-start": "^1.157.15",
|
|
124
|
+
"@tanstack/solid-start": "^1.157.15"
|
|
125
125
|
},
|
|
126
126
|
"peerDependenciesMeta": {
|
|
127
127
|
"@tanstack/react-start": {
|
|
@@ -132,12 +132,13 @@
|
|
|
132
132
|
}
|
|
133
133
|
},
|
|
134
134
|
"devDependencies": {
|
|
135
|
-
"@opentelemetry/sdk-trace-base": "^2.
|
|
136
|
-
"@
|
|
135
|
+
"@opentelemetry/sdk-trace-base": "^2.5.0",
|
|
136
|
+
"@tanstack/react-router": "^1.157.15",
|
|
137
|
+
"@types/node": "^25.0.10",
|
|
137
138
|
"rimraf": "^6.1.2",
|
|
138
139
|
"tsup": "^8.5.1",
|
|
139
140
|
"typescript": "^5.9.3",
|
|
140
|
-
"vitest": "^4.0.
|
|
141
|
+
"vitest": "^4.0.18",
|
|
141
142
|
"vitest-mock-extended": "^3.1.0"
|
|
142
143
|
},
|
|
143
144
|
"repository": {
|
|
@@ -54,7 +54,8 @@ export function createErrorReportingHandler(): () => Response {
|
|
|
54
54
|
/**
|
|
55
55
|
* Browser stub: Returns function unchanged
|
|
56
56
|
*/
|
|
57
|
-
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Intentional for type passthrough
|
|
58
|
+
export function withErrorReporting<T extends (...args: any[]) => any>(
|
|
58
59
|
fn: T,
|
|
59
60
|
context?: Record<string, unknown>,
|
|
60
61
|
): T {
|
package/src/loaders.ts
CHANGED
|
@@ -3,15 +3,17 @@ import { trace, type TraceContext } from 'autotel';
|
|
|
3
3
|
import { isServerSide } from './env';
|
|
4
4
|
import { type TraceLoaderConfig, SPAN_ATTRIBUTES } from './types';
|
|
5
5
|
|
|
6
|
+
// Re-export types from @tanstack/react-router for consumers who need them
|
|
7
|
+
export type { LoaderFnContext } from '@tanstack/react-router';
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
|
-
*
|
|
10
|
+
* Internal type for extracting route info from TanStack context.
|
|
11
|
+
* This is a minimal interface used only for instrumentation - the actual
|
|
12
|
+
* TanStack types flow through the generic parameter.
|
|
8
13
|
*/
|
|
9
|
-
interface
|
|
14
|
+
interface TanStackContextInternal {
|
|
15
|
+
route?: { id?: string };
|
|
10
16
|
params?: Record<string, string>;
|
|
11
|
-
route?: {
|
|
12
|
-
id?: string;
|
|
13
|
-
};
|
|
14
|
-
[key: string]: unknown;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -21,9 +23,12 @@ interface LoaderContext {
|
|
|
21
23
|
* for each invocation. It captures route ID, params (optionally),
|
|
22
24
|
* and errors.
|
|
23
25
|
*
|
|
26
|
+
* The generic type TLoaderFn preserves the full TanStack Router type inference,
|
|
27
|
+
* including typed params, context, and return types.
|
|
28
|
+
*
|
|
24
29
|
* @param loaderFn - The loader function to wrap
|
|
25
30
|
* @param config - Configuration options
|
|
26
|
-
* @returns Wrapped loader function with tracing
|
|
31
|
+
* @returns Wrapped loader function with tracing (preserves original types)
|
|
27
32
|
*
|
|
28
33
|
* @example
|
|
29
34
|
* ```typescript
|
|
@@ -31,6 +36,7 @@ interface LoaderContext {
|
|
|
31
36
|
* import { traceLoader } from 'autotel-tanstack/loaders';
|
|
32
37
|
*
|
|
33
38
|
* export const Route = createFileRoute('/users/$userId')({
|
|
39
|
+
* // Types are fully preserved - params.userId is typed as string
|
|
34
40
|
* loader: traceLoader(async ({ params }) => {
|
|
35
41
|
* return await db.users.findUnique({ where: { id: params.userId } });
|
|
36
42
|
* }),
|
|
@@ -39,30 +45,22 @@ interface LoaderContext {
|
|
|
39
45
|
*
|
|
40
46
|
* @example
|
|
41
47
|
* ```typescript
|
|
42
|
-
* //
|
|
43
|
-
* export const Route = createFileRoute('/
|
|
44
|
-
* loader: traceLoader(
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* where: { id: params.productId, category: params.category },
|
|
48
|
-
* });
|
|
49
|
-
* },
|
|
50
|
-
* {
|
|
51
|
-
* name: 'loadProduct',
|
|
52
|
-
* captureParams: true,
|
|
53
|
-
* captureResult: false,
|
|
54
|
-
* }
|
|
55
|
-
* ),
|
|
48
|
+
* // Sync loaders are also supported
|
|
49
|
+
* export const Route = createFileRoute('/static')({
|
|
50
|
+
* loader: traceLoader(({ context }) => ({
|
|
51
|
+
* message: `Welcome, ${context.userId}!`,
|
|
52
|
+
* })),
|
|
56
53
|
* });
|
|
57
54
|
* ```
|
|
58
55
|
*/
|
|
59
|
-
export function traceLoader<
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
export function traceLoader<TLoaderFn extends (ctx: any) => any>(
|
|
57
|
+
loaderFn: TLoaderFn,
|
|
58
|
+
config: TraceLoaderConfig = {},
|
|
59
|
+
): TLoaderFn {
|
|
62
60
|
const captureParams = config.captureParams ?? true;
|
|
63
61
|
const captureResult = config.captureResult ?? false;
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
const wrapped = (context: TanStackContextInternal) => {
|
|
66
64
|
// If we're in the browser, just call the loader without tracing
|
|
67
65
|
// This prevents autotel (which uses Node.js APIs) from being executed in the browser
|
|
68
66
|
if (!isServerSide()) {
|
|
@@ -72,6 +70,47 @@ export function traceLoader<
|
|
|
72
70
|
const routeId = context?.route?.id || 'unknown';
|
|
73
71
|
const spanName = config.name || `tanstack.loader.${routeId}`;
|
|
74
72
|
|
|
73
|
+
// Handle both sync and async loaders
|
|
74
|
+
const result = loaderFn(context);
|
|
75
|
+
const isPromise = result instanceof Promise;
|
|
76
|
+
|
|
77
|
+
if (!isPromise) {
|
|
78
|
+
// Sync loader - wrap in trace synchronously
|
|
79
|
+
return trace(spanName, (ctx: TraceContext) => {
|
|
80
|
+
ctx.setAttributes({
|
|
81
|
+
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'loader',
|
|
82
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,
|
|
83
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'loader',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (captureParams && context?.params) {
|
|
87
|
+
try {
|
|
88
|
+
ctx.setAttribute(
|
|
89
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
90
|
+
JSON.stringify(context.params),
|
|
91
|
+
);
|
|
92
|
+
} catch {
|
|
93
|
+
ctx.setAttribute(
|
|
94
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
95
|
+
'[non-serializable]',
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (captureResult && result !== undefined) {
|
|
101
|
+
try {
|
|
102
|
+
ctx.setAttribute('tanstack.loader.result', JSON.stringify(result));
|
|
103
|
+
} catch {
|
|
104
|
+
ctx.setAttribute('tanstack.loader.result', '[non-serializable]');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
109
|
+
return result;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Async loader
|
|
75
114
|
return trace(spanName, async (ctx: TraceContext) => {
|
|
76
115
|
ctx.setAttributes({
|
|
77
116
|
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'loader',
|
|
@@ -79,7 +118,6 @@ export function traceLoader<
|
|
|
79
118
|
[SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'loader',
|
|
80
119
|
});
|
|
81
120
|
|
|
82
|
-
// Capture params if configured
|
|
83
121
|
if (captureParams && context?.params) {
|
|
84
122
|
try {
|
|
85
123
|
ctx.setAttribute(
|
|
@@ -95,19 +133,21 @@ export function traceLoader<
|
|
|
95
133
|
}
|
|
96
134
|
|
|
97
135
|
try {
|
|
98
|
-
const
|
|
136
|
+
const asyncResult = await result;
|
|
99
137
|
|
|
100
|
-
|
|
101
|
-
if (captureResult && result !== undefined) {
|
|
138
|
+
if (captureResult && asyncResult !== undefined) {
|
|
102
139
|
try {
|
|
103
|
-
ctx.setAttribute(
|
|
140
|
+
ctx.setAttribute(
|
|
141
|
+
'tanstack.loader.result',
|
|
142
|
+
JSON.stringify(asyncResult),
|
|
143
|
+
);
|
|
104
144
|
} catch {
|
|
105
145
|
ctx.setAttribute('tanstack.loader.result', '[non-serializable]');
|
|
106
146
|
}
|
|
107
147
|
}
|
|
108
148
|
|
|
109
149
|
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
110
|
-
return
|
|
150
|
+
return asyncResult;
|
|
111
151
|
} catch (error) {
|
|
112
152
|
ctx.recordException(error as Error);
|
|
113
153
|
ctx.setStatus({
|
|
@@ -117,7 +157,9 @@ export function traceLoader<
|
|
|
117
157
|
throw error;
|
|
118
158
|
}
|
|
119
159
|
});
|
|
120
|
-
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return wrapped as TLoaderFn;
|
|
121
163
|
}
|
|
122
164
|
|
|
123
165
|
/**
|
|
@@ -127,51 +169,66 @@ export function traceLoader<
|
|
|
127
169
|
* beforeLoad runs before the route component renders and is typically
|
|
128
170
|
* used for auth checks, redirects, or data prefetching.
|
|
129
171
|
*
|
|
172
|
+
* The generic type TBeforeLoadFn preserves the full TanStack Router type inference,
|
|
173
|
+
* including typed params, context, search, and return types.
|
|
174
|
+
*
|
|
130
175
|
* @param beforeLoadFn - The beforeLoad function to wrap
|
|
131
176
|
* @param config - Configuration options
|
|
132
|
-
* @returns Wrapped beforeLoad function with tracing
|
|
177
|
+
* @returns Wrapped beforeLoad function with tracing (preserves original types)
|
|
133
178
|
*
|
|
134
179
|
* @example
|
|
135
180
|
* ```typescript
|
|
136
|
-
* import { createFileRoute } from '@tanstack/react-router';
|
|
181
|
+
* import { createFileRoute, redirect } from '@tanstack/react-router';
|
|
137
182
|
* import { traceBeforeLoad } from 'autotel-tanstack/loaders';
|
|
138
183
|
*
|
|
139
184
|
* export const Route = createFileRoute('/dashboard')({
|
|
140
|
-
*
|
|
185
|
+
* // Types are fully preserved - context, params, search are all typed
|
|
186
|
+
* beforeLoad: traceBeforeLoad(async ({ context, params }) => {
|
|
141
187
|
* if (!context.auth.isAuthenticated) {
|
|
142
188
|
* throw redirect({ to: '/login' });
|
|
143
189
|
* }
|
|
190
|
+
* return { userId: params.userId }; // Return type flows to loader context
|
|
144
191
|
* }),
|
|
145
|
-
* loader:
|
|
146
|
-
*
|
|
192
|
+
* loader: ({ context }) => {
|
|
193
|
+
* // context.userId is typed from beforeLoad return
|
|
194
|
+
* return { user: context.userId };
|
|
147
195
|
* },
|
|
148
196
|
* });
|
|
149
197
|
* ```
|
|
150
198
|
*/
|
|
151
|
-
export function traceBeforeLoad<
|
|
152
|
-
|
|
153
|
-
|
|
199
|
+
export function traceBeforeLoad<TBeforeLoadFn extends (opts: any) => any>(
|
|
200
|
+
beforeLoadFn: TBeforeLoadFn,
|
|
201
|
+
config: TraceLoaderConfig = {},
|
|
202
|
+
): TBeforeLoadFn {
|
|
154
203
|
const captureParams = config.captureParams ?? true;
|
|
155
204
|
|
|
156
|
-
|
|
157
|
-
|
|
205
|
+
const wrapped = (input: TanStackContextInternal) => {
|
|
206
|
+
// Skip tracing in browser
|
|
207
|
+
if (!isServerSide()) {
|
|
208
|
+
return beforeLoadFn(input);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const routeId = input?.route?.id || 'unknown';
|
|
158
212
|
const spanName = config.name || `tanstack.beforeLoad.${routeId}`;
|
|
159
213
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
214
|
+
// Handle both sync and async beforeLoad
|
|
215
|
+
const result = beforeLoadFn(input);
|
|
216
|
+
const isPromise = result instanceof Promise;
|
|
217
|
+
|
|
218
|
+
if (!isPromise) {
|
|
219
|
+
// Sync beforeLoad
|
|
220
|
+
return trace(spanName, (ctx: TraceContext) => {
|
|
163
221
|
ctx.setAttributes({
|
|
164
222
|
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'beforeLoad',
|
|
165
223
|
[SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,
|
|
166
224
|
[SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'beforeLoad',
|
|
167
225
|
});
|
|
168
226
|
|
|
169
|
-
|
|
170
|
-
if (captureParams && context?.params) {
|
|
227
|
+
if (captureParams && input?.params) {
|
|
171
228
|
try {
|
|
172
229
|
ctx.setAttribute(
|
|
173
230
|
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
174
|
-
JSON.stringify(
|
|
231
|
+
JSON.stringify(input.params),
|
|
175
232
|
);
|
|
176
233
|
} catch {
|
|
177
234
|
ctx.setAttribute(
|
|
@@ -181,31 +238,57 @@ export function traceBeforeLoad<
|
|
|
181
238
|
}
|
|
182
239
|
}
|
|
183
240
|
|
|
241
|
+
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
242
|
+
return result;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Async beforeLoad
|
|
247
|
+
return trace(spanName, async (ctx: TraceContext) => {
|
|
248
|
+
ctx.setAttributes({
|
|
249
|
+
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'beforeLoad',
|
|
250
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,
|
|
251
|
+
[SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'beforeLoad',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (captureParams && input?.params) {
|
|
184
255
|
try {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
256
|
+
ctx.setAttribute(
|
|
257
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
258
|
+
JSON.stringify(input.params),
|
|
259
|
+
);
|
|
260
|
+
} catch {
|
|
261
|
+
ctx.setAttribute(
|
|
262
|
+
SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,
|
|
263
|
+
'[non-serializable]',
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const asyncResult = await result;
|
|
270
|
+
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
271
|
+
return asyncResult;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
// Check if this is a redirect or notFound (expected control flow)
|
|
274
|
+
const errorName = (error as Error).name;
|
|
275
|
+
if (errorName === 'RedirectError' || errorName === 'NotFoundError') {
|
|
276
|
+
// Mark as OK since these are expected control flow
|
|
277
|
+
ctx.setAttribute('tanstack.beforeLoad.redirect', true);
|
|
188
278
|
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
ctx.setAttribute('tanstack.beforeLoad.redirect', true);
|
|
196
|
-
ctx.setStatus({ code: SpanStatusCode.OK });
|
|
197
|
-
} else {
|
|
198
|
-
ctx.recordException(error as Error);
|
|
199
|
-
ctx.setStatus({
|
|
200
|
-
code: SpanStatusCode.ERROR,
|
|
201
|
-
message: (error as Error).message,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
throw error;
|
|
279
|
+
} else {
|
|
280
|
+
ctx.recordException(error as Error);
|
|
281
|
+
ctx.setStatus({
|
|
282
|
+
code: SpanStatusCode.ERROR,
|
|
283
|
+
message: (error as Error).message,
|
|
284
|
+
});
|
|
205
285
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return wrapped as TBeforeLoadFn;
|
|
209
292
|
}
|
|
210
293
|
|
|
211
294
|
/**
|
|
@@ -243,9 +326,9 @@ export function createTracedRoute(
|
|
|
243
326
|
/**
|
|
244
327
|
* Wrap a loader function with tracing
|
|
245
328
|
*/
|
|
246
|
-
loader<
|
|
247
|
-
loaderFn:
|
|
248
|
-
):
|
|
329
|
+
loader<TLoaderFn extends (ctx: any) => any>(
|
|
330
|
+
loaderFn: TLoaderFn,
|
|
331
|
+
): TLoaderFn {
|
|
249
332
|
return traceLoader(loaderFn, {
|
|
250
333
|
...config,
|
|
251
334
|
name: `tanstack.loader.${routeId}`,
|
|
@@ -255,9 +338,9 @@ export function createTracedRoute(
|
|
|
255
338
|
/**
|
|
256
339
|
* Wrap a beforeLoad function with tracing
|
|
257
340
|
*/
|
|
258
|
-
beforeLoad<
|
|
259
|
-
beforeLoadFn:
|
|
260
|
-
):
|
|
341
|
+
beforeLoad<TBeforeLoadFn extends (opts: any) => any>(
|
|
342
|
+
beforeLoadFn: TBeforeLoadFn,
|
|
343
|
+
): TBeforeLoadFn {
|
|
261
344
|
return traceBeforeLoad(beforeLoadFn, {
|
|
262
345
|
...config,
|
|
263
346
|
name: `tanstack.beforeLoad.${routeId}`,
|
package/src/metrics.ts
CHANGED
|
@@ -164,9 +164,10 @@ export function createMetricsHandler() {
|
|
|
164
164
|
* }));
|
|
165
165
|
* ```
|
|
166
166
|
*/
|
|
167
|
-
export function recordTiming<
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
export function recordTiming<T extends (...args: any[]) => any>(
|
|
168
|
+
metricName: string,
|
|
169
|
+
fn: T,
|
|
170
|
+
): T {
|
|
170
171
|
return (async (...args: Parameters<T>) => {
|
|
171
172
|
const startTime = Date.now();
|
|
172
173
|
try {
|
package/src/middleware.ts
CHANGED
|
@@ -443,15 +443,11 @@ export function functionTracingMiddleware<TContext = unknown>(
|
|
|
443
443
|
*/
|
|
444
444
|
export function createTracingServerHandler<TContext = unknown>(
|
|
445
445
|
config?: TracingMiddlewareConfig,
|
|
446
|
-
): (opts: {
|
|
447
|
-
next: (ctx?: Partial<TContext>) => Promise<TContext>;
|
|
448
|
-
context: TContext;
|
|
449
|
-
request?: Request;
|
|
450
|
-
}) => Promise<TContext> {
|
|
446
|
+
): (opts: any) => any {
|
|
451
447
|
const handler = createTracingMiddleware<TContext>(config);
|
|
452
448
|
|
|
453
449
|
// Adapt TanStack's signature to our handler
|
|
454
|
-
return async (opts) => {
|
|
450
|
+
return async (opts: any) => {
|
|
455
451
|
return handler(opts);
|
|
456
452
|
};
|
|
457
453
|
}
|
package/src/server-functions.ts
CHANGED
|
@@ -43,9 +43,10 @@ import { type TraceServerFnConfig, SPAN_ATTRIBUTES } from './types';
|
|
|
43
43
|
* );
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
|
-
export function traceServerFn<
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
export function traceServerFn<T extends (...args: any[]) => any>(
|
|
47
|
+
serverFn: T,
|
|
48
|
+
config: TraceServerFnConfig = {},
|
|
49
|
+
): T {
|
|
49
50
|
const fnName = config.name || serverFn.name || 'serverFn';
|
|
50
51
|
const captureArgs = config.captureArgs ?? true;
|
|
51
52
|
const captureResults = config.captureResults ?? false;
|
|
@@ -147,7 +148,7 @@ export function traceServerFn<
|
|
|
147
148
|
* ```
|
|
148
149
|
*/
|
|
149
150
|
export function createTracedServerFnFactory<
|
|
150
|
-
TCreateServerFn extends (...args:
|
|
151
|
+
TCreateServerFn extends (...args: any[]) => any,
|
|
151
152
|
>(
|
|
152
153
|
createServerFnOriginal: TCreateServerFn,
|
|
153
154
|
defaultConfig: Omit<TraceServerFnConfig, 'name'> = {},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loaders.ts"],"names":[],"mappings":"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","file":"chunk-HKM7LMO6.js","sourcesContent":["import { SpanStatusCode } from '@opentelemetry/api';\nimport { trace, type TraceContext } from 'autotel';\nimport { isServerSide } from './env';\nimport { type TraceLoaderConfig, SPAN_ATTRIBUTES } from './types';\n\n/**\n * Loader context type (compatible with TanStack router loader context)\n */\ninterface LoaderContext {\n params?: Record<string, string>;\n route?: {\n id?: string;\n };\n [key: string]: unknown;\n}\n\n/**\n * Wrap a TanStack route loader with OpenTelemetry tracing\n *\n * This function wraps a loader function to automatically create spans\n * for each invocation. It captures route ID, params (optionally),\n * and errors.\n *\n * @param loaderFn - The loader function to wrap\n * @param config - Configuration options\n * @returns Wrapped loader function with tracing\n *\n * @example\n * ```typescript\n * import { createFileRoute } from '@tanstack/react-router';\n * import { traceLoader } from 'autotel-tanstack/loaders';\n *\n * export const Route = createFileRoute('/users/$userId')({\n * loader: traceLoader(async ({ params }) => {\n * return await db.users.findUnique({ where: { id: params.userId } });\n * }),\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With custom name and param capture\n * export const Route = createFileRoute('/products/$category/$productId')({\n * loader: traceLoader(\n * async ({ params }) => {\n * return await db.products.findUnique({\n * where: { id: params.productId, category: params.category },\n * });\n * },\n * {\n * name: 'loadProduct',\n * captureParams: true,\n * captureResult: false,\n * }\n * ),\n * });\n * ```\n */\nexport function traceLoader<\n T extends (context: LoaderContext) => Promise<unknown>,\n>(loaderFn: T, config: TraceLoaderConfig = {}): T {\n const captureParams = config.captureParams ?? true;\n const captureResult = config.captureResult ?? false;\n\n return (async (context: LoaderContext) => {\n // If we're in the browser, just call the loader without tracing\n // This prevents autotel (which uses Node.js APIs) from being executed in the browser\n if (!isServerSide()) {\n return loaderFn(context);\n }\n\n const routeId = context?.route?.id || 'unknown';\n const spanName = config.name || `tanstack.loader.${routeId}`;\n\n return trace(spanName, async (ctx: TraceContext) => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'loader',\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'loader',\n });\n\n // Capture params if configured\n if (captureParams && context?.params) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n JSON.stringify(context.params),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n '[non-serializable]',\n );\n }\n }\n\n try {\n const result = await loaderFn(context);\n\n // Capture result if configured\n if (captureResult && result !== undefined) {\n try {\n ctx.setAttribute('tanstack.loader.result', JSON.stringify(result));\n } catch {\n ctx.setAttribute('tanstack.loader.result', '[non-serializable]');\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result as ReturnType<T>;\n } catch (error) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n throw error;\n }\n });\n }) as T;\n}\n\n/**\n * Wrap a TanStack route beforeLoad function with OpenTelemetry tracing\n *\n * This function wraps a beforeLoad function to automatically create spans.\n * beforeLoad runs before the route component renders and is typically\n * used for auth checks, redirects, or data prefetching.\n *\n * @param beforeLoadFn - The beforeLoad function to wrap\n * @param config - Configuration options\n * @returns Wrapped beforeLoad function with tracing\n *\n * @example\n * ```typescript\n * import { createFileRoute } from '@tanstack/react-router';\n * import { traceBeforeLoad } from 'autotel-tanstack/loaders';\n *\n * export const Route = createFileRoute('/dashboard')({\n * beforeLoad: traceBeforeLoad(async ({ context }) => {\n * if (!context.auth.isAuthenticated) {\n * throw redirect({ to: '/login' });\n * }\n * }),\n * loader: async () => {\n * return await fetchDashboardData();\n * },\n * });\n * ```\n */\nexport function traceBeforeLoad<\n T extends (context: LoaderContext) => Promise<unknown>,\n>(beforeLoadFn: T, config: TraceLoaderConfig = {}): T {\n const captureParams = config.captureParams ?? true;\n\n return (async (context: LoaderContext): Promise<Awaited<ReturnType<T>>> => {\n const routeId = context?.route?.id || 'unknown';\n const spanName = config.name || `tanstack.beforeLoad.${routeId}`;\n\n return trace(\n spanName,\n async (ctx: TraceContext): Promise<Awaited<ReturnType<T>>> => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'beforeLoad',\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID]: routeId,\n [SPAN_ATTRIBUTES.TANSTACK_LOADER_TYPE]: 'beforeLoad',\n });\n\n // Capture params if configured\n if (captureParams && context?.params) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n JSON.stringify(context.params),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_LOADER_PARAMS,\n '[non-serializable]',\n );\n }\n }\n\n try {\n const result = (await beforeLoadFn(context)) as Awaited<\n ReturnType<T>\n >;\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n // Check if this is a redirect or notFound (expected control flow)\n const errorName = (error as Error).name;\n if (errorName === 'RedirectError' || errorName === 'NotFoundError') {\n // Mark as OK since these are expected control flow\n ctx.setAttribute('tanstack.beforeLoad.redirect', true);\n ctx.setStatus({ code: SpanStatusCode.OK });\n } else {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n }\n throw error;\n }\n },\n );\n }) as T;\n}\n\n/**\n * Create a traced route configuration helper\n *\n * This higher-order function helps create route configurations\n * with automatic tracing for both loader and beforeLoad.\n *\n * @param routeId - The route identifier\n * @param config - Tracing configuration\n * @returns Object with traced loader and beforeLoad wrappers\n *\n * @example\n * ```typescript\n * import { createFileRoute } from '@tanstack/react-router';\n * import { createTracedRoute } from 'autotel-tanstack/loaders';\n *\n * const traced = createTracedRoute('/users/$userId');\n *\n * export const Route = createFileRoute('/users/$userId')({\n * beforeLoad: traced.beforeLoad(async ({ context }) => {\n * // Auth check\n * }),\n * loader: traced.loader(async ({ params }) => {\n * return await getUser(params.userId);\n * }),\n * });\n * ```\n */\nexport function createTracedRoute(\n routeId: string,\n config: Omit<TraceLoaderConfig, 'name'> = {},\n) {\n return {\n /**\n * Wrap a loader function with tracing\n */\n loader<T extends (context: LoaderContext) => Promise<unknown>>(\n loaderFn: T,\n ): T {\n return traceLoader(loaderFn, {\n ...config,\n name: `tanstack.loader.${routeId}`,\n });\n },\n\n /**\n * Wrap a beforeLoad function with tracing\n */\n beforeLoad<T extends (context: LoaderContext) => Promise<unknown>>(\n beforeLoadFn: T,\n ): T {\n return traceBeforeLoad(beforeLoadFn, {\n ...config,\n name: `tanstack.beforeLoad.${routeId}`,\n });\n },\n };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/metrics.ts"],"names":[],"mappings":"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","file":"chunk-JSI6QG7M.js","sourcesContent":["/**\n * Performance metrics collection for TanStack Start\n *\n * Provides utilities to collect and expose performance metrics\n * following the patterns from TanStack Start observability guide.\n */\n\n/**\n * Performance timing data\n */\nexport interface TimingStats {\n count: number;\n avg: number;\n p50: number;\n p95: number;\n min: number;\n max: number;\n}\n\n/**\n * Metrics collector for performance data\n *\n * Collects timing metrics and provides statistical analysis.\n * Thread-safe for concurrent access.\n *\n * @example\n * ```typescript\n * import { metricsCollector } from 'autotel-tanstack/metrics';\n *\n * // Record a timing\n * metricsCollector.recordTiming('serverFn.getUser', 150);\n *\n * // Get stats\n * const stats = metricsCollector.getStats('serverFn.getUser');\n * console.log(`Average: ${stats.avg}ms, P95: ${stats.p95}ms`);\n * ```\n */\nclass MetricsCollector {\n private metrics = new Map<string, number[]>();\n private readonly maxSamples = 1000; // Limit memory usage\n\n /**\n * Record a timing measurement\n */\n recordTiming(name: string, duration: number): void {\n if (!this.metrics.has(name)) {\n this.metrics.set(name, []);\n }\n\n const timings = this.metrics.get(name)!;\n timings.push(duration);\n\n // Limit samples to prevent memory issues\n if (timings.length > this.maxSamples) {\n timings.shift(); // Remove oldest\n }\n }\n\n /**\n * Get statistics for a metric\n */\n getStats(name: string): TimingStats | null {\n const timings = this.metrics.get(name);\n if (!timings || timings.length === 0) {\n return null;\n }\n\n const sorted = [...timings].toSorted((a, b) => a - b);\n const sum = timings.reduce((a, b) => a + b, 0);\n\n return {\n count: timings.length,\n avg: sum / timings.length,\n p50: sorted.at(Math.floor(sorted.length * 0.5)) ?? 0,\n p95: sorted.at(Math.floor(sorted.length * 0.95)) ?? 0,\n min: sorted[0] ?? 0,\n max: sorted.at(-1) ?? 0,\n };\n }\n\n /**\n * Get all collected metrics\n */\n getAllStats(): Record<string, TimingStats> {\n const stats: Record<string, TimingStats> = {};\n for (const [name] of this.metrics) {\n const stat = this.getStats(name);\n if (stat) {\n stats[name] = stat;\n }\n }\n return stats;\n }\n\n /**\n * Reset all metrics\n */\n reset(): void {\n this.metrics.clear();\n }\n\n /**\n * Reset a specific metric\n */\n resetMetric(name: string): void {\n this.metrics.delete(name);\n }\n}\n\n/**\n * Global metrics collector instance\n */\nexport const metricsCollector = new MetricsCollector();\n\n/**\n * Helper to create a metrics endpoint handler\n *\n * Returns a handler that exposes metrics in JSON format.\n * Use this to create a `/metrics` endpoint.\n *\n * @example\n * ```typescript\n * // routes/metrics.ts\n * import { createFileRoute } from '@tanstack/react-router';\n * import { json } from '@tanstack/react-start';\n * import { createMetricsHandler } from 'autotel-tanstack/metrics';\n *\n * export const Route = createFileRoute('/metrics')({\n * server: {\n * handlers: {\n * GET: createMetricsHandler(),\n * },\n * },\n * });\n * ```\n */\nexport function createMetricsHandler() {\n return async () => {\n const { json } = await import('@tanstack/react-start');\n\n return json({\n system: {\n uptime: process.uptime(),\n memory: process.memoryUsage(),\n timestamp: new Date().toISOString(),\n },\n application: metricsCollector.getAllStats(),\n });\n };\n}\n\n/**\n * Auto-record timing from a function execution\n *\n * Wraps a function to automatically record its execution time.\n *\n * @example\n * ```typescript\n * import { recordTiming } from 'autotel-tanstack/metrics';\n *\n * const getUser = createServerFn({ method: 'GET' })\n * .handler(recordTiming('serverFn.getUser', async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * }));\n * ```\n */\nexport function recordTiming<\n T extends (...args: unknown[]) => Promise<unknown>,\n>(metricName: string, fn: T): T {\n return (async (...args: Parameters<T>) => {\n const startTime = Date.now();\n try {\n const result = await fn(...args);\n const duration = Date.now() - startTime;\n metricsCollector.recordTiming(metricName, duration);\n return result as ReturnType<T>;\n } catch (error) {\n const duration = Date.now() - startTime;\n metricsCollector.recordTiming(`${metricName}.error`, duration);\n throw error;\n }\n }) as T;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server-functions.ts"],"names":[],"mappings":"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","file":"chunk-TNOQTZ3N.js","sourcesContent":["import { SpanStatusCode } from '@opentelemetry/api';\nimport { trace, type TraceContext } from 'autotel';\nimport { isServerSide } from './env';\nimport { type TraceServerFnConfig, SPAN_ATTRIBUTES } from './types';\n\n/**\n * Wrap a TanStack server function with OpenTelemetry tracing\n *\n * This function wraps a server function to automatically create spans\n * for each invocation. It captures function name, arguments (optionally),\n * results (optionally), and errors.\n *\n * @param serverFn - The server function to wrap\n * @param config - Configuration options\n * @returns Wrapped server function with tracing\n *\n * @example\n * ```typescript\n * import { createServerFn } from '@tanstack/react-start';\n * import { traceServerFn } from 'autotel-tanstack/server-functions';\n *\n * const getUserBase = createServerFn({ method: 'GET' })\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n *\n * export const getUser = traceServerFn(getUserBase, { name: 'getUser' });\n * ```\n *\n * @example\n * ```typescript\n * // With argument and result capture (careful with PII!)\n * export const createUser = traceServerFn(\n * createServerFn({ method: 'POST' })\n * .handler(async ({ data }) => {\n * return await db.users.create({ data });\n * }),\n * {\n * name: 'createUser',\n * captureArgs: true,\n * captureResults: false, // Don't capture for PII reasons\n * }\n * );\n * ```\n */\nexport function traceServerFn<\n T extends (...args: unknown[]) => Promise<unknown>,\n>(serverFn: T, config: TraceServerFnConfig = {}): T {\n const fnName = config.name || serverFn.name || 'serverFn';\n const captureArgs = config.captureArgs ?? true;\n const captureResults = config.captureResults ?? false;\n\n return new Proxy(serverFn, {\n apply(target, thisArg, argArray) {\n // If we're in the browser, just call the function without tracing\n // Server functions should never run in the browser, but this prevents\n // autotel (which uses Node.js APIs) from being executed if it somehow does\n if (!isServerSide()) {\n return target.apply(thisArg, argArray);\n }\n\n return trace(`tanstack.serverFn.${fnName}`, async (ctx: TraceContext) => {\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.RPC_SYSTEM]: 'tanstack-start',\n [SPAN_ATTRIBUTES.RPC_METHOD]: fnName,\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'serverFn',\n [SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_NAME]: fnName,\n });\n\n // Capture arguments if configured\n if (captureArgs && argArray.length > 0) {\n const args = argArray[0];\n if (args !== undefined) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS,\n JSON.stringify(args),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_ARGS,\n '[non-serializable]',\n );\n }\n }\n }\n\n try {\n const result = await Reflect.apply(target, thisArg, argArray);\n\n // Capture result if configured\n if (captureResults && result !== undefined) {\n try {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n JSON.stringify(result),\n );\n } catch {\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_RESULT,\n '[non-serializable]',\n );\n }\n }\n\n ctx.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n ctx.recordException(error as Error);\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: (error as Error).message,\n });\n throw error;\n }\n });\n },\n\n get(target, prop, receiver) {\n return Reflect.get(target, prop, receiver);\n },\n }) as T;\n}\n\n/**\n * Create a traced version of createServerFn\n *\n * This higher-order function wraps TanStack's createServerFn to automatically\n * add tracing to all created server functions.\n *\n * @param createServerFnOriginal - The original createServerFn from TanStack\n * @param defaultConfig - Default configuration for all server functions\n * @returns Wrapped createServerFn that produces traced server functions\n *\n * @example\n * ```typescript\n * import { createServerFn as originalCreateServerFn } from '@tanstack/react-start';\n * import { createTracedServerFnFactory } from 'autotel-tanstack/server-functions';\n *\n * export const createServerFn = createTracedServerFnFactory(originalCreateServerFn);\n *\n * // Now all server functions created with createServerFn are automatically traced\n * export const getUser = createServerFn({ method: 'GET' })\n * .handler(async ({ data: id }) => {\n * return await db.users.findUnique({ where: { id } });\n * });\n * ```\n */\nexport function createTracedServerFnFactory<\n TCreateServerFn extends (...args: unknown[]) => unknown,\n>(\n createServerFnOriginal: TCreateServerFn,\n defaultConfig: Omit<TraceServerFnConfig, 'name'> = {},\n): TCreateServerFn {\n return new Proxy(createServerFnOriginal, {\n apply(target, thisArg, argArray) {\n const result = Reflect.apply(target, thisArg, argArray);\n\n // If the result has a .handler method, wrap it\n if (\n result &&\n typeof result === 'object' &&\n 'handler' in result &&\n typeof result.handler === 'function'\n ) {\n const originalHandler = result.handler.bind(result);\n\n result.handler = function tracedHandler(handlerFn: unknown) {\n const wrappedHandler = originalHandler(handlerFn as never);\n\n // Try to infer function name from the handler\n const fnName = (handlerFn as { name?: string })?.name || 'serverFn';\n\n return traceServerFn(wrappedHandler, {\n ...defaultConfig,\n name: fnName,\n });\n };\n }\n\n return result;\n },\n }) as TCreateServerFn;\n}\n"]}
|