autotel-tanstack 1.13.30 → 1.13.31
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 +8 -35
- package/dist/auto.d.ts.map +1 -0
- package/dist/auto.js +41 -22
- package/dist/auto.js.map +1 -1
- package/dist/browser/context.d.ts +50 -0
- package/dist/browser/context.d.ts.map +1 -0
- package/dist/browser/context.js +54 -2
- package/dist/browser/context.js.map +1 -1
- package/dist/browser/debug-headers.d.ts +10 -0
- package/dist/browser/debug-headers.d.ts.map +1 -0
- package/dist/browser/debug-headers.js +12 -2
- package/dist/browser/debug-headers.js.map +1 -1
- package/dist/browser/error-reporting.d.ts +39 -0
- package/dist/browser/error-reporting.d.ts.map +1 -0
- package/dist/browser/error-reporting.js +35 -2
- package/dist/browser/error-reporting.js.map +1 -1
- package/dist/browser/handlers.d.ts +14 -0
- package/dist/browser/handlers.d.ts.map +1 -0
- package/dist/browser/handlers.js +10 -2
- package/dist/browser/handlers.js.map +1 -1
- package/dist/browser/index.d.ts +11 -0
- package/dist/browser/index.js +12 -12
- package/dist/browser/loaders.d.ts +31 -0
- package/dist/browser/loaders.d.ts.map +1 -0
- package/dist/browser/loaders.js +29 -2
- package/dist/browser/loaders.js.map +1 -1
- package/dist/browser/metrics.d.ts +56 -0
- package/dist/browser/metrics.d.ts.map +1 -0
- package/dist/browser/metrics.js +48 -2
- package/dist/browser/metrics.js.map +1 -1
- package/dist/browser/middleware.d.ts +42 -0
- package/dist/browser/middleware.d.ts.map +1 -0
- package/dist/browser/middleware.js +36 -2
- package/dist/browser/middleware.js.map +1 -1
- package/dist/browser/server-functions.d.ts +14 -0
- package/dist/browser/server-functions.d.ts.map +1 -0
- package/dist/browser/server-functions.js +16 -2
- package/dist/browser/server-functions.js.map +1 -1
- package/dist/browser/testing.d.ts +85 -0
- package/dist/browser/testing.d.ts.map +1 -0
- package/dist/browser/testing.js +43 -2
- package/dist/browser/testing.js.map +1 -1
- package/dist/browser/types.d.ts +2 -0
- package/dist/browser/types.js +37 -2
- package/dist/browser/types.js.map +1 -1
- package/dist/context.d.ts +5 -3
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +112 -3
- package/dist/context.js.map +1 -1
- package/dist/debug-headers.d.ts +14 -14
- package/dist/debug-headers.d.ts.map +1 -0
- package/dist/debug-headers.js +62 -4
- package/dist/debug-headers.js.map +1 -1
- package/dist/env-BpFWNnpL.js +30 -0
- package/dist/env-BpFWNnpL.js.map +1 -0
- package/dist/error-reporting.d.ts +37 -37
- package/dist/error-reporting.d.ts.map +1 -0
- package/dist/error-reporting.js +154 -3
- package/dist/error-reporting.js.map +1 -1
- package/dist/handlers.d.ts +5 -4
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +192 -6
- package/dist/handlers.js.map +1 -1
- package/dist/index.d.ts +15 -16
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -16
- package/dist/instrument-DS7YCE1R.d.ts +10 -0
- package/dist/instrument-DS7YCE1R.d.ts.map +1 -0
- package/dist/instrument-DdLlMfRi.js +80 -0
- package/dist/instrument-DdLlMfRi.js.map +1 -0
- package/dist/loaders-DrVVY25K.d.ts +2402 -0
- package/dist/loaders-DrVVY25K.d.ts.map +1 -0
- package/dist/loaders.d.ts +2 -116
- package/dist/loaders.js +234 -5
- package/dist/loaders.js.map +1 -1
- package/dist/metrics.d.ts +39 -39
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +144 -3
- package/dist/metrics.js.map +1 -1
- package/dist/middleware.d.ts +16 -15
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +290 -7
- package/dist/middleware.js.map +1 -1
- package/dist/route-filter-dLg-j3jR.js +33 -0
- package/dist/route-filter-dLg-j3jR.js.map +1 -0
- package/dist/server-functions.d.ts +4 -3
- package/dist/server-functions.d.ts.map +1 -0
- package/dist/server-functions.js +133 -5
- package/dist/server-functions.js.map +1 -1
- package/dist/testing.d.ts +164 -65
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +212 -147
- package/dist/testing.js.map +1 -1
- package/dist/types-BJ7FyVoX.d.ts +87 -0
- package/dist/types-BJ7FyVoX.d.ts.map +1 -0
- package/dist/types-BrccP0yX.js +38 -0
- package/dist/types-BrccP0yX.js.map +1 -0
- package/dist/types-pQgmQa4j.d.ts +154 -0
- package/dist/types-pQgmQa4j.d.ts.map +1 -0
- package/package.json +7 -7
- package/dist/browser/index.js.map +0 -1
- package/dist/chunk-7OXOAS64.js +0 -41
- package/dist/chunk-7OXOAS64.js.map +0 -1
- package/dist/chunk-A7WMQ2BC.js +0 -25
- package/dist/chunk-A7WMQ2BC.js.map +0 -1
- package/dist/chunk-CCME55EK.js +0 -28
- package/dist/chunk-CCME55EK.js.map +0 -1
- package/dist/chunk-CSFIPJC2.js +0 -11
- package/dist/chunk-CSFIPJC2.js.map +0 -1
- package/dist/chunk-DTZCOB4W.js +0 -32
- package/dist/chunk-DTZCOB4W.js.map +0 -1
- package/dist/chunk-EFSKEYDJ.js +0 -20
- package/dist/chunk-EFSKEYDJ.js.map +0 -1
- package/dist/chunk-EGRHWZRV.js +0 -3
- package/dist/chunk-EGRHWZRV.js.map +0 -1
- package/dist/chunk-ESU66L3L.js +0 -92
- package/dist/chunk-ESU66L3L.js.map +0 -1
- package/dist/chunk-EUYFVNYE.js +0 -16
- package/dist/chunk-EUYFVNYE.js.map +0 -1
- package/dist/chunk-FFQ4FJKE.js +0 -185
- package/dist/chunk-FFQ4FJKE.js.map +0 -1
- package/dist/chunk-G526TOMY.js +0 -96
- package/dist/chunk-G526TOMY.js.map +0 -1
- package/dist/chunk-I4LX3LOG.js +0 -35
- package/dist/chunk-I4LX3LOG.js.map +0 -1
- package/dist/chunk-JXO7H6KO.js +0 -10
- package/dist/chunk-JXO7H6KO.js.map +0 -1
- package/dist/chunk-KPXGFKPU.js +0 -193
- package/dist/chunk-KPXGFKPU.js.map +0 -1
- package/dist/chunk-LRA2UVVS.js +0 -210
- package/dist/chunk-LRA2UVVS.js.map +0 -1
- package/dist/chunk-MFYOV2SF.js +0 -32
- package/dist/chunk-MFYOV2SF.js.map +0 -1
- package/dist/chunk-MNP65ZX7.js +0 -21
- package/dist/chunk-MNP65ZX7.js.map +0 -1
- package/dist/chunk-NTY64BKS.js +0 -38
- package/dist/chunk-NTY64BKS.js.map +0 -1
- package/dist/chunk-UMEJU65Q.js +0 -34
- package/dist/chunk-UMEJU65Q.js.map +0 -1
- package/dist/chunk-UTPW3QRT.js +0 -52
- package/dist/chunk-UTPW3QRT.js.map +0 -1
- package/dist/chunk-V3RO5N2M.js +0 -8
- package/dist/chunk-V3RO5N2M.js.map +0 -1
- package/dist/chunk-XXBHZR3M.js +0 -99
- package/dist/chunk-XXBHZR3M.js.map +0 -1
- package/dist/chunk-YQYYPJCK.js +0 -37
- package/dist/chunk-YQYYPJCK.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instrument-DRR7VL63.d.ts +0 -46
- package/dist/types-m5OjZJ-4.d.ts +0 -152
package/dist/debug-headers.js
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { r as isServerSide } from "./env-BpFWNnpL.js";
|
|
2
|
+
|
|
3
|
+
//#region src/debug-headers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create middleware that adds debug headers to responses in development
|
|
6
|
+
*
|
|
7
|
+
* Adds helpful debug information to response headers:
|
|
8
|
+
* - X-Debug-Timestamp: Request timestamp
|
|
9
|
+
* - X-Debug-Node-Version: Node.js version
|
|
10
|
+
* - X-Debug-Uptime: Process uptime in seconds
|
|
11
|
+
* - X-Debug-Trace-Id: Current trace ID (if available)
|
|
12
|
+
*
|
|
13
|
+
* @param config - Configuration options
|
|
14
|
+
* @returns Middleware handler
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createStart } from '@tanstack/react-start';
|
|
19
|
+
* import { debugHeadersMiddleware } from 'autotel-tanstack/debug-headers';
|
|
20
|
+
*
|
|
21
|
+
* export const startInstance = createStart(() => ({
|
|
22
|
+
* requestMiddleware: [debugHeadersMiddleware()],
|
|
23
|
+
* }));
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function debugHeadersMiddleware(config = {}) {
|
|
27
|
+
if (!isServerSide()) return async function debugHeadersHandler(opts) {
|
|
28
|
+
return opts.next();
|
|
29
|
+
};
|
|
30
|
+
const enabled = config.enabled ?? (typeof process !== "undefined" && process.env.NODE_ENV === "development");
|
|
31
|
+
return async function debugHeadersHandler(opts) {
|
|
32
|
+
const { next, request } = opts;
|
|
33
|
+
if (!enabled || !request) return next();
|
|
34
|
+
const result = await next();
|
|
35
|
+
if (!(result instanceof Response)) return result;
|
|
36
|
+
const response = result;
|
|
37
|
+
const newHeaders = new Headers(response.headers);
|
|
38
|
+
newHeaders.set("X-Debug-Timestamp", (/* @__PURE__ */ new Date()).toISOString());
|
|
39
|
+
newHeaders.set("X-Debug-Node-Version", process.version);
|
|
40
|
+
newHeaders.set("X-Debug-Uptime", Math.floor(process.uptime()).toString());
|
|
41
|
+
try {
|
|
42
|
+
const { trace } = await import("@opentelemetry/api");
|
|
43
|
+
const activeSpan = trace.getActiveSpan();
|
|
44
|
+
if (activeSpan) {
|
|
45
|
+
const spanContext = activeSpan.spanContext();
|
|
46
|
+
if (spanContext.traceId) newHeaders.set("X-Debug-Trace-Id", spanContext.traceId);
|
|
47
|
+
}
|
|
48
|
+
} catch {}
|
|
49
|
+
if (config.customHeaders) for (const [key, value] of Object.entries(config.customHeaders)) {
|
|
50
|
+
const headerValue = typeof value === "function" ? value() : value;
|
|
51
|
+
newHeaders.set(`X-Debug-${key}`, headerValue);
|
|
52
|
+
}
|
|
53
|
+
return new Response(response.body, {
|
|
54
|
+
status: response.status,
|
|
55
|
+
statusText: response.statusText,
|
|
56
|
+
headers: newHeaders
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { debugHeadersMiddleware };
|
|
5
63
|
//# sourceMappingURL=debug-headers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"debug-headers.js","names":[],"sources":["../src/debug-headers.ts"],"sourcesContent":["import { isServerSide } from './env';\nimport type { MiddlewareHandler } from './middleware';\n\n/**\n * Configuration for debug headers middleware\n */\nexport interface DebugHeadersConfig {\n /**\n * Whether to enable debug headers\n * @default process.env.NODE_ENV === 'development'\n */\n enabled?: boolean;\n\n /**\n * Custom headers to add\n */\n customHeaders?: Record<string, string | (() => string)>;\n}\n\n/**\n * Create middleware that adds debug headers to responses in development\n *\n * Adds helpful debug information to response headers:\n * - X-Debug-Timestamp: Request timestamp\n * - X-Debug-Node-Version: Node.js version\n * - X-Debug-Uptime: Process uptime in seconds\n * - X-Debug-Trace-Id: Current trace ID (if available)\n *\n * @param config - Configuration options\n * @returns Middleware handler\n *\n * @example\n * ```typescript\n * import { createStart } from '@tanstack/react-start';\n * import { debugHeadersMiddleware } from 'autotel-tanstack/debug-headers';\n *\n * export const startInstance = createStart(() => ({\n * requestMiddleware: [debugHeadersMiddleware()],\n * }));\n * ```\n */\nexport function debugHeadersMiddleware(\n config: DebugHeadersConfig = {},\n): MiddlewareHandler {\n // If we're in the browser, return a no-op middleware\n if (!isServerSide()) {\n return async function debugHeadersHandler(opts) {\n return opts.next();\n };\n }\n\n const enabled =\n config.enabled ??\n (typeof process !== 'undefined' && process.env.NODE_ENV === 'development');\n\n return async function debugHeadersHandler(opts) {\n const { next, request } = opts;\n\n if (!enabled || !request) {\n return next();\n }\n\n const result = await next();\n\n // Check if result is a Response\n if (!(result instanceof Response)) {\n return result;\n }\n\n const response = result;\n\n // Clone response to add headers (responses are immutable)\n const newHeaders = new Headers(response.headers);\n\n // Add standard debug headers\n newHeaders.set('X-Debug-Timestamp', new Date().toISOString());\n newHeaders.set('X-Debug-Node-Version', process.version);\n newHeaders.set('X-Debug-Uptime', Math.floor(process.uptime()).toString());\n\n // Add trace ID if available\n try {\n const { trace } = await import('@opentelemetry/api');\n const activeSpan = trace.getActiveSpan();\n if (activeSpan) {\n const spanContext = activeSpan.spanContext();\n if (spanContext.traceId) {\n newHeaders.set('X-Debug-Trace-Id', spanContext.traceId);\n }\n }\n } catch {\n // OpenTelemetry not available, skip trace ID\n }\n\n // Add custom headers\n if (config.customHeaders) {\n for (const [key, value] of Object.entries(config.customHeaders)) {\n const headerValue = typeof value === 'function' ? value() : value;\n newHeaders.set(`X-Debug-${key}`, headerValue);\n }\n }\n\n // Return new response with debug headers\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,uBACd,SAA6B,CAAC,GACX;CAEnB,IAAI,CAAC,aAAa,GAChB,OAAO,eAAe,oBAAoB,MAAM;EAC9C,OAAO,KAAK,KAAK;CACnB;CAGF,MAAM,UACJ,OAAO,YACN,OAAO,YAAY,eAAe,QAAQ,IAAI,aAAa;CAE9D,OAAO,eAAe,oBAAoB,MAAM;EAC9C,MAAM,EAAE,MAAM,YAAY;EAE1B,IAAI,CAAC,WAAW,CAAC,SACf,OAAO,KAAK;EAGd,MAAM,SAAS,MAAM,KAAK;EAG1B,IAAI,EAAE,kBAAkB,WACtB,OAAO;EAGT,MAAM,WAAW;EAGjB,MAAM,aAAa,IAAI,QAAQ,SAAS,OAAO;EAG/C,WAAW,IAAI,sCAAqB,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC;EAC5D,WAAW,IAAI,wBAAwB,QAAQ,OAAO;EACtD,WAAW,IAAI,kBAAkB,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;EAGxE,IAAI;GACF,MAAM,EAAE,UAAU,MAAM,OAAO;GAC/B,MAAM,aAAa,MAAM,cAAc;GACvC,IAAI,YAAY;IACd,MAAM,cAAc,WAAW,YAAY;IAC3C,IAAI,YAAY,SACd,WAAW,IAAI,oBAAoB,YAAY,OAAO;GAE1D;EACF,QAAQ,CAER;EAGA,IAAI,OAAO,eACT,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,aAAa,GAAG;GAC/D,MAAM,cAAc,OAAO,UAAU,aAAa,MAAM,IAAI;GAC5D,WAAW,IAAI,WAAW,OAAO,WAAW;EAC9C;EAIF,OAAO,IAAI,SAAS,SAAS,MAAM;GACjC,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB,SAAS;EACX,CAAC;CACH;AACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/env.ts
|
|
2
|
+
/**
|
|
3
|
+
* Environment detection utilities
|
|
4
|
+
* Prevents server-only code from running in the browser
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Check if we're running in a browser environment
|
|
8
|
+
* Uses typeof checks to avoid TypeScript DOM type requirements
|
|
9
|
+
*/
|
|
10
|
+
function isBrowser() {
|
|
11
|
+
return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Check if we're running in a Node.js environment
|
|
15
|
+
*/
|
|
16
|
+
function isNode() {
|
|
17
|
+
return typeof process !== "undefined" && typeof process.versions !== "undefined" && typeof process.versions.node !== "undefined";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if we're in a server-side context
|
|
21
|
+
* In TanStack Start, middleware runs on the server, but router config
|
|
22
|
+
* might be evaluated on both sides during SSR
|
|
23
|
+
*/
|
|
24
|
+
function isServerSide() {
|
|
25
|
+
return isNode() && !isBrowser();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { isNode as n, isServerSide as r, isBrowser as t };
|
|
30
|
+
//# sourceMappingURL=env-BpFWNnpL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-BpFWNnpL.js","names":[],"sources":["../src/env.ts"],"sourcesContent":["/**\n * Environment detection utilities\n * Prevents server-only code from running in the browser\n */\n\n/**\n * Check if we're running in a browser environment\n * Uses typeof checks to avoid TypeScript DOM type requirements\n */\nexport function isBrowser(): boolean {\n return (\n typeof globalThis !== 'undefined' &&\n // @ts-expect-error - window may not exist in Node.js, that's the point\n typeof globalThis.window !== 'undefined' &&\n // @ts-expect-error - document may not exist in Node.js, that's the point\n typeof globalThis.document !== 'undefined'\n );\n}\n\n/**\n * Check if we're running in a Node.js environment\n */\nexport function isNode(): boolean {\n return (\n typeof process !== 'undefined' &&\n typeof process.versions !== 'undefined' &&\n typeof process.versions.node !== 'undefined'\n );\n}\n\n/**\n * Check if we're in a server-side context\n * In TanStack Start, middleware runs on the server, but router config\n * might be evaluated on both sides during SSR\n */\nexport function isServerSide(): boolean {\n // In TanStack Start, if we're in a request handler context, we're on the server\n // Check for Node.js environment (server) and not browser\n return isNode() && !isBrowser();\n}\n\n/**\n * Safely check if a module is available (for optional dependencies)\n */\nexport function isModuleAvailable(moduleName: string): boolean {\n try {\n // This will only work in Node.js, not browser\n if (typeof require !== 'undefined') {\n require.resolve(moduleName);\n return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,YAAqB;CACnC,OACE,OAAO,eAAe,eAEtB,OAAO,WAAW,WAAW,eAE7B,OAAO,WAAW,aAAa;AAEnC;;;;AAKA,SAAgB,SAAkB;CAChC,OACE,OAAO,YAAY,eACnB,OAAO,QAAQ,aAAa,eAC5B,OAAO,QAAQ,SAAS,SAAS;AAErC;;;;;;AAOA,SAAgB,eAAwB;CAGtC,OAAO,OAAO,KAAK,CAAC,UAAU;AAChC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/error-reporting.d.ts
|
|
3
2
|
/**
|
|
4
3
|
* Error reporting utilities for TanStack Start
|
|
5
4
|
*
|
|
@@ -10,15 +9,15 @@ import * as _tanstack_react_start from '@tanstack/react-start';
|
|
|
10
9
|
* Error report data structure
|
|
11
10
|
*/
|
|
12
11
|
interface ErrorReport {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
id: string;
|
|
13
|
+
count: number;
|
|
14
|
+
lastSeen: Date;
|
|
15
|
+
error: {
|
|
16
|
+
name: string;
|
|
17
|
+
message: string;
|
|
18
|
+
stack?: string;
|
|
19
|
+
context?: unknown;
|
|
20
|
+
};
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
23
|
* Error store for in-memory error tracking
|
|
@@ -27,28 +26,28 @@ interface ErrorReport {
|
|
|
27
26
|
* Thread-safe for concurrent access.
|
|
28
27
|
*/
|
|
29
28
|
declare class ErrorStore {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
29
|
+
private errors;
|
|
30
|
+
private readonly maxErrors;
|
|
31
|
+
/**
|
|
32
|
+
* Report an error
|
|
33
|
+
*/
|
|
34
|
+
reportError(error: Error, context?: unknown): string;
|
|
35
|
+
/**
|
|
36
|
+
* Get all error reports
|
|
37
|
+
*/
|
|
38
|
+
getAllErrors(): ErrorReport[];
|
|
39
|
+
/**
|
|
40
|
+
* Get a specific error by ID
|
|
41
|
+
*/
|
|
42
|
+
getError(id: string): ErrorReport | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Clear all errors
|
|
45
|
+
*/
|
|
46
|
+
clear(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Clear a specific error
|
|
49
|
+
*/
|
|
50
|
+
clearError(id: string): void;
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
53
|
* Global error store instance
|
|
@@ -95,8 +94,8 @@ declare function reportError(error: Error, context?: unknown): string;
|
|
|
95
94
|
* });
|
|
96
95
|
* ```
|
|
97
96
|
*/
|
|
98
|
-
declare function createErrorReportingHandler(): () => Promise<
|
|
99
|
-
|
|
97
|
+
declare function createErrorReportingHandler(): () => Promise<import("@tanstack/react-start").JsonResponse<{
|
|
98
|
+
errors: ErrorReport[];
|
|
100
99
|
}>>;
|
|
101
100
|
/**
|
|
102
101
|
* Wrap a function with automatic error reporting
|
|
@@ -114,5 +113,6 @@ declare function createErrorReportingHandler(): () => Promise<_tanstack_react_st
|
|
|
114
113
|
* ```
|
|
115
114
|
*/
|
|
116
115
|
declare function withErrorReporting<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => Promise<TReturn>, context?: Record<string, unknown>): (...args: TArgs) => Promise<TReturn>;
|
|
117
|
-
|
|
118
|
-
export {
|
|
116
|
+
//#endregion
|
|
117
|
+
export { ErrorReport, createErrorReportingHandler, errorStore, reportError, withErrorReporting };
|
|
118
|
+
//# sourceMappingURL=error-reporting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-reporting.d.ts","names":[],"sources":["../src/error-reporting.ts"],"mappings":";;AAUA;;;;;;;;UAAiB,WAAA;EACf,EAAA;EACA,KAAA;EACA,QAAA,EAAU,IAAI;EACd,KAAA;IACE,IAAA;IACA,OAAA;IACA,KAAA;IACA,OAAA;EAAA;AAAA;;;;;;;cAUE,UAAA;EAAA,QACI,MAAA;EAAA,iBACS,SAAA;EAKS;;;EAA1B,WAAA,CAAY,KAAA,EAAO,KAAA,EAAO,OAAA;EA+DjB;;;EAPT,YAAA,IAAgB,WAAA;EAqBL;;AAAU;EAdrB,QAAA,CAAS,EAAA,WAAa,WAAA;EAsBkB;;;EAfxC,KAAA;EAmCc;;;EA5Bd,UAAA,CAAW,EAAA;AAAA;;;;cAQA,UAAA,EAAU,UAAmB;AA8C1C;;;;;;;;;;AAyBA;;;;;;;;AAzBA,iBA1BgB,WAAA,CAAY,KAAA,EAAO,KAAK,EAAE,OAAA;;;;;;;;;;;;;;;;;;AAsDJ;;;;;iBA5BtB,2BAAA,UAA2B,OAAA,iCAAA,YAAA;UAAA,WAAA;AAAA;;;;;;;;;;;;;;;;iBAyB3B,kBAAA,mCACd,EAAA,MAAQ,IAAA,EAAM,KAAA,KAAU,OAAA,CAAQ,OAAA,GAChC,OAAA,GAAU,MAAA,wBACL,IAAA,EAAM,KAAA,KAAU,OAAA,CAAQ,OAAA"}
|
package/dist/error-reporting.js
CHANGED
|
@@ -1,4 +1,155 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
//#region src/error-reporting.ts
|
|
2
|
+
/**
|
|
3
|
+
* Error store for in-memory error tracking
|
|
4
|
+
*
|
|
5
|
+
* Stores error reports with deduplication by error name + message.
|
|
6
|
+
* Thread-safe for concurrent access.
|
|
7
|
+
*/
|
|
8
|
+
var ErrorStore = class {
|
|
9
|
+
errors = /* @__PURE__ */ new Map();
|
|
10
|
+
maxErrors = 100;
|
|
11
|
+
/**
|
|
12
|
+
* Report an error
|
|
13
|
+
*/
|
|
14
|
+
reportError(error, context) {
|
|
15
|
+
const key = `${error.name}:${error.message}`;
|
|
16
|
+
const existing = this.errors.get(key);
|
|
17
|
+
if (existing) {
|
|
18
|
+
existing.count++;
|
|
19
|
+
existing.lastSeen = /* @__PURE__ */ new Date();
|
|
20
|
+
if (context) existing.error.context = {
|
|
21
|
+
...existing.error.context,
|
|
22
|
+
...context
|
|
23
|
+
};
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
const report = {
|
|
27
|
+
id: key,
|
|
28
|
+
count: 1,
|
|
29
|
+
lastSeen: /* @__PURE__ */ new Date(),
|
|
30
|
+
error: {
|
|
31
|
+
name: error.name,
|
|
32
|
+
message: error.message,
|
|
33
|
+
stack: error.stack,
|
|
34
|
+
context
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
this.errors.set(key, report);
|
|
38
|
+
if (this.errors.size > this.maxErrors) {
|
|
39
|
+
const oldest = [...this.errors.entries()].toSorted((a, b) => a[1].lastSeen.getTime() - b[1].lastSeen.getTime())[0];
|
|
40
|
+
this.errors.delete(oldest[0]);
|
|
41
|
+
}
|
|
42
|
+
console.error("[ERROR REPORTED]:", {
|
|
43
|
+
error: error.message,
|
|
44
|
+
count: 1,
|
|
45
|
+
context
|
|
46
|
+
});
|
|
47
|
+
return key;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get all error reports
|
|
51
|
+
*/
|
|
52
|
+
getAllErrors() {
|
|
53
|
+
return [...this.errors.values()];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get a specific error by ID
|
|
57
|
+
*/
|
|
58
|
+
getError(id) {
|
|
59
|
+
return this.errors.get(id);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Clear all errors
|
|
63
|
+
*/
|
|
64
|
+
clear() {
|
|
65
|
+
this.errors.clear();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clear a specific error
|
|
69
|
+
*/
|
|
70
|
+
clearError(id) {
|
|
71
|
+
this.errors.delete(id);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Global error store instance
|
|
76
|
+
*/
|
|
77
|
+
const errorStore = new ErrorStore();
|
|
78
|
+
/**
|
|
79
|
+
* Report an error to the error store
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { reportError } from 'autotel-tanstack/error-reporting';
|
|
84
|
+
*
|
|
85
|
+
* try {
|
|
86
|
+
* await riskyOperation();
|
|
87
|
+
* } catch (error) {
|
|
88
|
+
* reportError(error as Error, {
|
|
89
|
+
* userId: context.userId,
|
|
90
|
+
* operation: 'riskyOperation',
|
|
91
|
+
* });
|
|
92
|
+
* throw error;
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
function reportError(error, context) {
|
|
97
|
+
return errorStore.reportError(error, context);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create an error reporting endpoint handler
|
|
101
|
+
*
|
|
102
|
+
* Returns a handler that exposes error reports in JSON format.
|
|
103
|
+
* Use this to create an `/admin/errors` endpoint.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* // routes/admin/errors.ts
|
|
108
|
+
* import { createFileRoute } from '@tanstack/react-router';
|
|
109
|
+
* import { json } from '@tanstack/react-start';
|
|
110
|
+
* import { createErrorReportingHandler } from 'autotel-tanstack/error-reporting';
|
|
111
|
+
*
|
|
112
|
+
* export const Route = createFileRoute('/admin/errors')({
|
|
113
|
+
* server: {
|
|
114
|
+
* handlers: {
|
|
115
|
+
* GET: createErrorReportingHandler(),
|
|
116
|
+
* },
|
|
117
|
+
* },
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
function createErrorReportingHandler() {
|
|
122
|
+
return async () => {
|
|
123
|
+
const { json } = await import("@tanstack/react-start");
|
|
124
|
+
return json({ errors: errorStore.getAllErrors() });
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Wrap a function with automatic error reporting
|
|
129
|
+
*
|
|
130
|
+
* Automatically reports errors to the error store.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* import { withErrorReporting } from 'autotel-tanstack/error-reporting';
|
|
135
|
+
*
|
|
136
|
+
* const riskyOperation = createServerFn()
|
|
137
|
+
* .handler(withErrorReporting(async () => {
|
|
138
|
+
* return await performOperation();
|
|
139
|
+
* }, { operation: 'riskyOperation' }));
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
function withErrorReporting(fn, context) {
|
|
143
|
+
return async (...args) => {
|
|
144
|
+
try {
|
|
145
|
+
return await fn(...args);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
reportError(error, context);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
export { createErrorReportingHandler, errorStore, reportError, withErrorReporting };
|
|
4
155
|
//# sourceMappingURL=error-reporting.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"error-reporting.js","names":[],"sources":["../src/error-reporting.ts"],"sourcesContent":["/**\n * Error reporting utilities for TanStack Start\n *\n * Provides basic error reporting without external dependencies,\n * following the patterns from TanStack Start observability guide.\n */\n\n/**\n * Error report data structure\n */\nexport interface ErrorReport {\n id: string;\n count: number;\n lastSeen: Date;\n error: {\n name: string;\n message: string;\n stack?: string;\n context?: unknown;\n };\n}\n\n/**\n * Error store for in-memory error tracking\n *\n * Stores error reports with deduplication by error name + message.\n * Thread-safe for concurrent access.\n */\nclass ErrorStore {\n private errors = new Map<string, ErrorReport>();\n private readonly maxErrors = 100; // Limit memory usage\n\n /**\n * Report an error\n */\n reportError(error: Error, context?: unknown): string {\n const key = `${error.name}:${error.message}`;\n const existing = this.errors.get(key);\n\n if (existing) {\n existing.count++;\n existing.lastSeen = new Date();\n if (context) {\n // Merge context\n existing.error.context = {\n ...(existing.error.context as Record<string, unknown>),\n ...(context as Record<string, unknown>),\n };\n }\n return key;\n }\n\n // Add new error\n const report: ErrorReport = {\n id: key,\n count: 1,\n lastSeen: new Date(),\n error: {\n name: error.name,\n message: error.message,\n stack: error.stack,\n context,\n },\n };\n\n this.errors.set(key, report);\n\n // Limit stored errors\n if (this.errors.size > this.maxErrors) {\n // Remove oldest error\n const entries = [...this.errors.entries()];\n const oldest = entries.toSorted(\n (a: [string, ErrorReport], b: [string, ErrorReport]) =>\n a[1].lastSeen.getTime() - b[1].lastSeen.getTime(),\n )[0];\n this.errors.delete(oldest[0]);\n }\n\n // Log immediately\n console.error('[ERROR REPORTED]:', {\n error: error.message,\n count: 1,\n context,\n });\n\n return key;\n }\n\n /**\n * Get all error reports\n */\n getAllErrors(): ErrorReport[] {\n return [...this.errors.values()];\n }\n\n /**\n * Get a specific error by ID\n */\n getError(id: string): ErrorReport | undefined {\n return this.errors.get(id);\n }\n\n /**\n * Clear all errors\n */\n clear(): void {\n this.errors.clear();\n }\n\n /**\n * Clear a specific error\n */\n clearError(id: string): void {\n this.errors.delete(id);\n }\n}\n\n/**\n * Global error store instance\n */\nexport const errorStore = new ErrorStore();\n\n/**\n * Report an error to the error store\n *\n * @example\n * ```typescript\n * import { reportError } from 'autotel-tanstack/error-reporting';\n *\n * try {\n * await riskyOperation();\n * } catch (error) {\n * reportError(error as Error, {\n * userId: context.userId,\n * operation: 'riskyOperation',\n * });\n * throw error;\n * }\n * ```\n */\nexport function reportError(error: Error, context?: unknown): string {\n return errorStore.reportError(error, context);\n}\n\n/**\n * Create an error reporting endpoint handler\n *\n * Returns a handler that exposes error reports in JSON format.\n * Use this to create an `/admin/errors` endpoint.\n *\n * @example\n * ```typescript\n * // routes/admin/errors.ts\n * import { createFileRoute } from '@tanstack/react-router';\n * import { json } from '@tanstack/react-start';\n * import { createErrorReportingHandler } from 'autotel-tanstack/error-reporting';\n *\n * export const Route = createFileRoute('/admin/errors')({\n * server: {\n * handlers: {\n * GET: createErrorReportingHandler(),\n * },\n * },\n * });\n * ```\n */\nexport function createErrorReportingHandler() {\n return async () => {\n const { json } = await import('@tanstack/react-start');\n\n return json({\n errors: errorStore.getAllErrors(),\n });\n };\n}\n\n/**\n * Wrap a function with automatic error reporting\n *\n * Automatically reports errors to the error store.\n *\n * @example\n * ```typescript\n * import { withErrorReporting } from 'autotel-tanstack/error-reporting';\n *\n * const riskyOperation = createServerFn()\n * .handler(withErrorReporting(async () => {\n * return await performOperation();\n * }, { operation: 'riskyOperation' }));\n * ```\n */\nexport function withErrorReporting<TArgs extends unknown[], TReturn>(\n fn: (...args: TArgs) => Promise<TReturn>,\n context?: Record<string, unknown>,\n): (...args: TArgs) => Promise<TReturn> {\n return async (...args: TArgs): Promise<TReturn> => {\n try {\n return await fn(...args);\n } catch (error) {\n reportError(error as Error, context);\n throw error;\n }\n };\n}\n"],"mappings":";;;;;;;AA4BA,IAAM,aAAN,MAAiB;CACf,AAAQ,yBAAS,IAAI,IAAyB;CAC9C,AAAiB,YAAY;;;;CAK7B,YAAY,OAAc,SAA2B;EACnD,MAAM,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM;EACnC,MAAM,WAAW,KAAK,OAAO,IAAI,GAAG;EAEpC,IAAI,UAAU;GACZ,SAAS;GACT,SAAS,2BAAW,IAAI,KAAK;GAC7B,IAAI,SAEF,SAAS,MAAM,UAAU;IACvB,GAAI,SAAS,MAAM;IACnB,GAAI;GACN;GAEF,OAAO;EACT;EAGA,MAAM,SAAsB;GAC1B,IAAI;GACJ,OAAO;GACP,0BAAU,IAAI,KAAK;GACnB,OAAO;IACL,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,OAAO,MAAM;IACb;GACF;EACF;EAEA,KAAK,OAAO,IAAI,KAAK,MAAM;EAG3B,IAAI,KAAK,OAAO,OAAO,KAAK,WAAW;GAGrC,MAAM,SAAS,CADE,GAAG,KAAK,OAAO,QAAQ,CACnB,CAAC,CAAC,UACpB,GAA0B,MACzB,EAAE,EAAE,CAAC,SAAS,QAAQ,IAAI,EAAE,EAAE,CAAC,SAAS,QAAQ,CACpD,CAAC,CAAC;GACF,KAAK,OAAO,OAAO,OAAO,EAAE;EAC9B;EAGA,QAAQ,MAAM,qBAAqB;GACjC,OAAO,MAAM;GACb,OAAO;GACP;EACF,CAAC;EAED,OAAO;CACT;;;;CAKA,eAA8B;EAC5B,OAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC;CACjC;;;;CAKA,SAAS,IAAqC;EAC5C,OAAO,KAAK,OAAO,IAAI,EAAE;CAC3B;;;;CAKA,QAAc;EACZ,KAAK,OAAO,MAAM;CACpB;;;;CAKA,WAAW,IAAkB;EAC3B,KAAK,OAAO,OAAO,EAAE;CACvB;AACF;;;;AAKA,MAAa,aAAa,IAAI,WAAW;;;;;;;;;;;;;;;;;;;AAoBzC,SAAgB,YAAY,OAAc,SAA2B;CACnE,OAAO,WAAW,YAAY,OAAO,OAAO;AAC9C;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,8BAA8B;CAC5C,OAAO,YAAY;EACjB,MAAM,EAAE,SAAS,MAAM,OAAO;EAE9B,OAAO,KAAK,EACV,QAAQ,WAAW,aAAa,EAClC,CAAC;CACH;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,mBACd,IACA,SACsC;CACtC,OAAO,OAAO,GAAG,SAAkC;EACjD,IAAI;GACF,OAAO,MAAM,GAAG,GAAG,IAAI;EACzB,SAAS,OAAO;GACd,YAAY,OAAgB,OAAO;GACnC,MAAM;EACR;CACF;AACF"}
|
package/dist/handlers.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import '@opentelemetry/api';
|
|
1
|
+
import { s as WrapStartHandlerConfig } from "./types-pQgmQa4j.js";
|
|
3
2
|
|
|
3
|
+
//#region src/handlers.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Request handler type (compatible with TanStack Start handlers)
|
|
6
6
|
*/
|
|
7
7
|
type RequestHandler = (request: Request, opts?: {
|
|
8
|
-
|
|
8
|
+
context?: Record<string, unknown>;
|
|
9
9
|
}) => Promise<Response> | Response;
|
|
10
10
|
/**
|
|
11
11
|
* Wrap a TanStack Start handler with OpenTelemetry tracing
|
|
@@ -66,5 +66,6 @@ declare function wrapStartHandler(config?: WrapStartHandlerConfig): (handler: Re
|
|
|
66
66
|
* ```
|
|
67
67
|
*/
|
|
68
68
|
declare function createTracedHandler(config?: Omit<WrapStartHandlerConfig, 'endpoint' | 'headers' | 'service'>): (handler: RequestHandler) => RequestHandler;
|
|
69
|
-
|
|
69
|
+
//#endregion
|
|
70
70
|
export { createTracedHandler, wrapStartHandler };
|
|
71
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","names":[],"sources":["../src/handlers.ts"],"mappings":";;;;;AAQiB;KAKZ,cAAA,IACH,OAAA,EAAS,OAAA,EACT,IAAA;EAAS,OAAA,GAAU,MAAA;AAAA,MAChB,OAAA,CAAQ,QAAA,IAAY,QAAA;;;;;;;;;;;;;;;;AAAQ;AAgCjC;;;;;;;;;;;;;;iBAAgB,gBAAA,CACd,MAAA,GAAQ,sBAAA,IACN,OAAA,EAAS,cAAA,KAAmB,cAAA;AA6JhC;;;;;;;;;;;;;;;;AAE8C;;;;;;;;;;;AAF9C,iBAAgB,mBAAA,CACd,MAAA,GAAQ,IAAA,CAAK,sBAAA,yCACX,OAAA,EAAS,cAAA,KAAmB,cAAA"}
|
package/dist/handlers.js
CHANGED
|
@@ -1,7 +1,193 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
1
|
+
import { n as SPAN_ATTRIBUTES, t as DEFAULT_CONFIG } from "./types-BrccP0yX.js";
|
|
2
|
+
import { extractContextFromRequest } from "./context.js";
|
|
3
|
+
import { t as isExcludedPath } from "./route-filter-dLg-j3jR.js";
|
|
4
|
+
import { SpanStatusCode, context } from "@opentelemetry/api";
|
|
5
|
+
import { init, trace } from "autotel";
|
|
6
|
+
|
|
7
|
+
//#region src/handlers.ts
|
|
8
|
+
/**
|
|
9
|
+
* Wrap a TanStack Start handler with OpenTelemetry tracing
|
|
10
|
+
*
|
|
11
|
+
* This function wraps the entire request handler to automatically create
|
|
12
|
+
* spans for all incoming requests. It initializes OpenTelemetry and
|
|
13
|
+
* provides comprehensive request tracing.
|
|
14
|
+
*
|
|
15
|
+
* @param config - Configuration options including OTLP endpoint and headers
|
|
16
|
+
* @returns Function that wraps a request handler
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // server.ts
|
|
21
|
+
* import { createStartHandler, defaultStreamHandler } from '@tanstack/react-start/server';
|
|
22
|
+
* import { wrapStartHandler } from 'autotel-tanstack/handlers';
|
|
23
|
+
*
|
|
24
|
+
* export default wrapStartHandler({
|
|
25
|
+
* service: 'my-app',
|
|
26
|
+
* endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
27
|
+
* headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
|
|
28
|
+
* })(createStartHandler(defaultStreamHandler));
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // With env var configuration (recommended for production)
|
|
34
|
+
* // Set OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS
|
|
35
|
+
* export default wrapStartHandler()(createStartHandler(defaultStreamHandler));
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function wrapStartHandler(config = {}) {
|
|
39
|
+
const mergedConfig = {
|
|
40
|
+
...DEFAULT_CONFIG,
|
|
41
|
+
...config
|
|
42
|
+
};
|
|
43
|
+
const service = config.service || process.env.OTEL_SERVICE_NAME || "tanstack-start";
|
|
44
|
+
const endpoint = config.endpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
45
|
+
let headers = config.headers;
|
|
46
|
+
if (!headers && process.env.OTEL_EXPORTER_OTLP_HEADERS) {
|
|
47
|
+
headers = {};
|
|
48
|
+
const pairs = process.env.OTEL_EXPORTER_OTLP_HEADERS.split(",");
|
|
49
|
+
for (const pair of pairs) {
|
|
50
|
+
const [key, value] = pair.split("=");
|
|
51
|
+
if (key && value) headers[key.trim()] = value.trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
init({
|
|
55
|
+
service,
|
|
56
|
+
endpoint,
|
|
57
|
+
headers
|
|
58
|
+
});
|
|
59
|
+
return function wrapHandler(handler) {
|
|
60
|
+
return async function tracedHandler(request, opts) {
|
|
61
|
+
const url = new URL(request.url);
|
|
62
|
+
if (isExcludedPath(url.pathname, mergedConfig.excludePaths)) return handler(request, opts);
|
|
63
|
+
const parentContext = extractContextFromRequest(request);
|
|
64
|
+
return context.with(parentContext, async () => {
|
|
65
|
+
const spanName = `${request.method} ${url.pathname}`;
|
|
66
|
+
return trace(spanName, async (ctx) => {
|
|
67
|
+
ctx.setAttributes({
|
|
68
|
+
[SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,
|
|
69
|
+
[SPAN_ATTRIBUTES.URL_PATH]: url.pathname,
|
|
70
|
+
[SPAN_ATTRIBUTES.URL_FULL]: request.url,
|
|
71
|
+
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: "request"
|
|
72
|
+
});
|
|
73
|
+
if (url.search) ctx.setAttribute(SPAN_ATTRIBUTES.URL_QUERY, url.search);
|
|
74
|
+
if (mergedConfig.captureHeaders) for (const header of mergedConfig.captureHeaders) {
|
|
75
|
+
const value = request.headers.get(header);
|
|
76
|
+
if (value) ctx.setAttribute(`http.request.header.${header.toLowerCase()}`, value);
|
|
77
|
+
}
|
|
78
|
+
if (config.customAttributes) {
|
|
79
|
+
const customAttrs = config.customAttributes({
|
|
80
|
+
type: "request",
|
|
81
|
+
name: spanName,
|
|
82
|
+
request
|
|
83
|
+
});
|
|
84
|
+
ctx.setAttributes(customAttrs);
|
|
85
|
+
}
|
|
86
|
+
const startTime = Date.now();
|
|
87
|
+
try {
|
|
88
|
+
const response = await handler(request, opts);
|
|
89
|
+
const duration = Date.now() - startTime;
|
|
90
|
+
ctx.setAttribute(SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS, duration);
|
|
91
|
+
ctx.setAttribute(SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE, response.status);
|
|
92
|
+
if (response.status >= 400) ctx.setStatus({
|
|
93
|
+
code: SpanStatusCode.ERROR,
|
|
94
|
+
message: `HTTP ${response.status}`
|
|
95
|
+
});
|
|
96
|
+
else ctx.setStatus({ code: SpanStatusCode.OK });
|
|
97
|
+
return response;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const duration = Date.now() - startTime;
|
|
100
|
+
ctx.setAttribute(SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS, duration);
|
|
101
|
+
if (mergedConfig.captureErrors) ctx.recordError(error);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create a traced handler without auto-initialization
|
|
111
|
+
*
|
|
112
|
+
* Use this when you want to initialize autotel separately
|
|
113
|
+
* (e.g., with more advanced configuration).
|
|
114
|
+
*
|
|
115
|
+
* @param config - Configuration options (excluding endpoint/headers)
|
|
116
|
+
* @returns Function that wraps a request handler
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* import { init } from 'autotel';
|
|
121
|
+
* import { createTracedHandler } from 'autotel-tanstack/handlers';
|
|
122
|
+
*
|
|
123
|
+
* // Initialize autotel with custom configuration
|
|
124
|
+
* init({
|
|
125
|
+
* service: 'my-app',
|
|
126
|
+
* endpoint: 'https://api.honeycomb.io',
|
|
127
|
+
* instrumentations: [/* custom instrumentations *\/],
|
|
128
|
+
* });
|
|
129
|
+
*
|
|
130
|
+
* // Wrap handler without re-initializing
|
|
131
|
+
* export default createTracedHandler({
|
|
132
|
+
* captureHeaders: ['x-request-id'],
|
|
133
|
+
* })(createStartHandler(defaultStreamHandler));
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
function createTracedHandler(config = {}) {
|
|
137
|
+
const mergedConfig = {
|
|
138
|
+
...DEFAULT_CONFIG,
|
|
139
|
+
...config
|
|
140
|
+
};
|
|
141
|
+
return function wrapHandler(handler) {
|
|
142
|
+
return async function tracedHandler(request, opts) {
|
|
143
|
+
const url = new URL(request.url);
|
|
144
|
+
if (isExcludedPath(url.pathname, mergedConfig.excludePaths)) return handler(request, opts);
|
|
145
|
+
const parentContext = extractContextFromRequest(request);
|
|
146
|
+
return context.with(parentContext, async () => {
|
|
147
|
+
const spanName = `${request.method} ${url.pathname}`;
|
|
148
|
+
return trace(spanName, async (ctx) => {
|
|
149
|
+
ctx.setAttributes({
|
|
150
|
+
[SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,
|
|
151
|
+
[SPAN_ATTRIBUTES.URL_PATH]: url.pathname,
|
|
152
|
+
[SPAN_ATTRIBUTES.TANSTACK_TYPE]: "request"
|
|
153
|
+
});
|
|
154
|
+
if (url.search) ctx.setAttribute(SPAN_ATTRIBUTES.URL_QUERY, url.search);
|
|
155
|
+
if (mergedConfig.captureHeaders) for (const header of mergedConfig.captureHeaders) {
|
|
156
|
+
const value = request.headers.get(header);
|
|
157
|
+
if (value) ctx.setAttribute(`http.request.header.${header.toLowerCase()}`, value);
|
|
158
|
+
}
|
|
159
|
+
if (config.customAttributes) {
|
|
160
|
+
const customAttrs = config.customAttributes({
|
|
161
|
+
type: "request",
|
|
162
|
+
name: spanName,
|
|
163
|
+
request
|
|
164
|
+
});
|
|
165
|
+
ctx.setAttributes(customAttrs);
|
|
166
|
+
}
|
|
167
|
+
const startTime = Date.now();
|
|
168
|
+
try {
|
|
169
|
+
const response = await handler(request, opts);
|
|
170
|
+
const duration = Date.now() - startTime;
|
|
171
|
+
ctx.setAttribute(SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS, duration);
|
|
172
|
+
ctx.setAttribute(SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE, response.status);
|
|
173
|
+
if (response.status >= 400) ctx.setStatus({
|
|
174
|
+
code: SpanStatusCode.ERROR,
|
|
175
|
+
message: `HTTP ${response.status}`
|
|
176
|
+
});
|
|
177
|
+
else ctx.setStatus({ code: SpanStatusCode.OK });
|
|
178
|
+
return response;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const duration = Date.now() - startTime;
|
|
181
|
+
ctx.setAttribute(SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS, duration);
|
|
182
|
+
if (mergedConfig.captureErrors) ctx.recordError(error);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
export { createTracedHandler, wrapStartHandler };
|
|
7
193
|
//# sourceMappingURL=handlers.js.map
|
package/dist/handlers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"handlers.js"}
|
|
1
|
+
{"version":3,"file":"handlers.js","names":[],"sources":["../src/handlers.ts"],"sourcesContent":["import { context, SpanStatusCode } from '@opentelemetry/api';\nimport { trace, init, type TraceContext } from 'autotel';\nimport { extractContextFromRequest } from './context';\nimport { isExcludedPath } from './route-filter';\nimport {\n type WrapStartHandlerConfig,\n DEFAULT_CONFIG,\n SPAN_ATTRIBUTES,\n} from './types';\n\n/**\n * Request handler type (compatible with TanStack Start handlers)\n */\ntype RequestHandler = (\n request: Request,\n opts?: { context?: Record<string, unknown> },\n) => Promise<Response> | Response;\n\n/**\n * Wrap a TanStack Start handler with OpenTelemetry tracing\n *\n * This function wraps the entire request handler to automatically create\n * spans for all incoming requests. It initializes OpenTelemetry and\n * provides comprehensive request tracing.\n *\n * @param config - Configuration options including OTLP endpoint and headers\n * @returns Function that wraps a request handler\n *\n * @example\n * ```typescript\n * // server.ts\n * import { createStartHandler, defaultStreamHandler } from '@tanstack/react-start/server';\n * import { wrapStartHandler } from 'autotel-tanstack/handlers';\n *\n * export default wrapStartHandler({\n * service: 'my-app',\n * endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n * headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },\n * })(createStartHandler(defaultStreamHandler));\n * ```\n *\n * @example\n * ```typescript\n * // With env var configuration (recommended for production)\n * // Set OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS\n * export default wrapStartHandler()(createStartHandler(defaultStreamHandler));\n * ```\n */\nexport function wrapStartHandler(\n config: WrapStartHandlerConfig = {},\n): (handler: RequestHandler) => RequestHandler {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n\n // Initialize autotel with provided configuration\n const service =\n config.service || process.env.OTEL_SERVICE_NAME || 'tanstack-start';\n const endpoint = config.endpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT;\n\n // Parse headers from env if not provided\n let headers = config.headers;\n if (!headers && process.env.OTEL_EXPORTER_OTLP_HEADERS) {\n headers = {};\n const pairs = process.env.OTEL_EXPORTER_OTLP_HEADERS.split(',');\n for (const pair of pairs) {\n const [key, value] = pair.split('=');\n if (key && value) {\n headers[key.trim()] = value.trim();\n }\n }\n }\n\n // Initialize OpenTelemetry\n init({\n service,\n endpoint,\n headers,\n });\n\n return function wrapHandler(handler: RequestHandler): RequestHandler {\n return async function tracedHandler(\n request: Request,\n opts?: { context?: Record<string, unknown> },\n ): Promise<Response> {\n const url = new URL(request.url);\n\n // Check if path should be excluded\n if (isExcludedPath(url.pathname, mergedConfig.excludePaths)) {\n return handler(request, opts);\n }\n\n // Extract parent context from request headers\n const parentContext = extractContextFromRequest(request);\n\n // Run within parent context\n return context.with(parentContext, async () => {\n const spanName = `${request.method} ${url.pathname}`;\n\n return trace(spanName, async (ctx: TraceContext) => {\n // Set HTTP semantic attributes\n ctx.setAttributes({\n [SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,\n [SPAN_ATTRIBUTES.URL_PATH]: url.pathname,\n [SPAN_ATTRIBUTES.URL_FULL]: request.url,\n [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'request',\n });\n\n if (url.search) {\n ctx.setAttribute(SPAN_ATTRIBUTES.URL_QUERY, url.search);\n }\n\n // Capture configured headers\n if (mergedConfig.captureHeaders) {\n for (const header of mergedConfig.captureHeaders) {\n const value = request.headers.get(header);\n if (value) {\n ctx.setAttribute(\n `http.request.header.${header.toLowerCase()}`,\n value,\n );\n }\n }\n }\n\n // Add custom attributes\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 response = await handler(request, opts);\n const duration = Date.now() - startTime;\n\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,\n duration,\n );\n ctx.setAttribute(\n SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE,\n response.status,\n );\n\n // Set status based on HTTP status code\n if (response.status >= 400) {\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: `HTTP ${response.status}`,\n });\n } else {\n ctx.setStatus({ code: SpanStatusCode.OK });\n }\n\n return response;\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.recordError(error);\n }\n\n throw error;\n }\n });\n });\n };\n };\n}\n\n/**\n * Create a traced handler without auto-initialization\n *\n * Use this when you want to initialize autotel separately\n * (e.g., with more advanced configuration).\n *\n * @param config - Configuration options (excluding endpoint/headers)\n * @returns Function that wraps a request handler\n *\n * @example\n * ```typescript\n * import { init } from 'autotel';\n * import { createTracedHandler } from 'autotel-tanstack/handlers';\n *\n * // Initialize autotel with custom configuration\n * init({\n * service: 'my-app',\n * endpoint: 'https://api.honeycomb.io',\n * instrumentations: [/* custom instrumentations *\\/],\n * });\n *\n * // Wrap handler without re-initializing\n * export default createTracedHandler({\n * captureHeaders: ['x-request-id'],\n * })(createStartHandler(defaultStreamHandler));\n * ```\n */\nexport function createTracedHandler(\n config: Omit<WrapStartHandlerConfig, 'endpoint' | 'headers' | 'service'> = {},\n): (handler: RequestHandler) => RequestHandler {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n\n return function wrapHandler(handler: RequestHandler): RequestHandler {\n return async function tracedHandler(\n request: Request,\n opts?: { context?: Record<string, unknown> },\n ): Promise<Response> {\n const url = new URL(request.url);\n\n // Check if path should be excluded\n if (isExcludedPath(url.pathname, mergedConfig.excludePaths)) {\n return handler(request, opts);\n }\n\n const parentContext = extractContextFromRequest(request);\n\n return context.with(parentContext, async () => {\n const spanName = `${request.method} ${url.pathname}`;\n\n return trace(spanName, async (ctx: TraceContext) => {\n ctx.setAttributes({\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 ctx.setAttribute(SPAN_ATTRIBUTES.URL_QUERY, url.search);\n }\n\n if (mergedConfig.captureHeaders) {\n for (const header of mergedConfig.captureHeaders) {\n const value = request.headers.get(header);\n if (value) {\n ctx.setAttribute(\n `http.request.header.${header.toLowerCase()}`,\n value,\n );\n }\n }\n }\n\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 response = await handler(request, opts);\n const duration = Date.now() - startTime;\n\n ctx.setAttribute(\n SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,\n duration,\n );\n ctx.setAttribute(\n SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE,\n response.status,\n );\n\n if (response.status >= 400) {\n ctx.setStatus({\n code: SpanStatusCode.ERROR,\n message: `HTTP ${response.status}`,\n });\n } else {\n ctx.setStatus({ code: SpanStatusCode.OK });\n }\n\n return response;\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.recordError(error);\n }\n\n throw error;\n }\n });\n });\n };\n };\n}\n"],"mappings":"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"}
|