stitchkit 0.10.0 → 0.11.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/index-fwqnkc90.js +151 -0
- package/dist/{index-s8gtzt8w.js → index-renxz7c2.js} +4 -2
- package/dist/node.js +2 -2
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/context.d.ts +31 -0
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/event.d.ts +14 -0
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +14 -5
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/index.js +3 -5
- package/llms-full.txt +37 -13
- package/package.json +1 -1
- package/dist/index-031q8xmx.js +0 -87
- package/dist/index-p9m9c0jw.js +0 -58
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRecord,
|
|
3
|
+
isUnsafeKey
|
|
4
|
+
} from "./index-tm7dqzxc.js";
|
|
5
|
+
|
|
6
|
+
// src/server/request.ts
|
|
7
|
+
function generateTraceId() {
|
|
8
|
+
return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
|
|
9
|
+
}
|
|
10
|
+
function resolveTraceId(req) {
|
|
11
|
+
const header = req.headers.get("x-request-id") ?? req.headers.get("x-trace-id");
|
|
12
|
+
const trimmed = header?.trim();
|
|
13
|
+
if (trimmed && trimmed.length <= 128 && /^[\w.-]+$/.test(trimmed)) {
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
return generateTraceId();
|
|
17
|
+
}
|
|
18
|
+
function resolveSocketIp(req, server) {
|
|
19
|
+
if (typeof server === "object" && server !== null && "requestIP" in server && typeof server.requestIP === "function") {
|
|
20
|
+
const addr = server.requestIP(req);
|
|
21
|
+
if (isRecord(addr) && typeof addr.address === "string" && addr.address) {
|
|
22
|
+
return addr.address;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if ("ip" in req && typeof req.ip === "string" && req.ip)
|
|
26
|
+
return req.ip;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
function extractIp(req, options = {}) {
|
|
30
|
+
if (options.trustProxy) {
|
|
31
|
+
const forwarded = req.headers.get("x-forwarded-for");
|
|
32
|
+
if (forwarded)
|
|
33
|
+
return (forwarded.split(",")[0] ?? "").trim().replace(/^::ffff:/, "");
|
|
34
|
+
const realIp = req.headers.get("x-real-ip");
|
|
35
|
+
if (realIp)
|
|
36
|
+
return realIp.trim().replace(/^::ffff:/, "");
|
|
37
|
+
}
|
|
38
|
+
return (options.socketIp ?? "").replace(/^::ffff:/, "");
|
|
39
|
+
}
|
|
40
|
+
function getClientInfo(req, options = {}) {
|
|
41
|
+
return {
|
|
42
|
+
ipAddress: extractIp(req, options) || undefined,
|
|
43
|
+
userAgent: req.headers.get("user-agent") ?? undefined
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function parseQueryParams(url) {
|
|
47
|
+
const query = {};
|
|
48
|
+
for (const key of new Set(url.searchParams.keys())) {
|
|
49
|
+
if (isUnsafeKey(key))
|
|
50
|
+
continue;
|
|
51
|
+
const values = url.searchParams.getAll(key);
|
|
52
|
+
const [first] = values;
|
|
53
|
+
query[key] = values.length === 1 && first !== undefined ? first : values;
|
|
54
|
+
}
|
|
55
|
+
return query;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/observability/trace.ts
|
|
59
|
+
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
|
|
60
|
+
function randomHex(bytes) {
|
|
61
|
+
const arr = new Uint8Array(bytes);
|
|
62
|
+
crypto.getRandomValues(arr);
|
|
63
|
+
let hex = "";
|
|
64
|
+
for (const byte of arr)
|
|
65
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
66
|
+
return hex;
|
|
67
|
+
}
|
|
68
|
+
function createTraceContext() {
|
|
69
|
+
return { traceId: randomHex(16), spanId: randomHex(8) };
|
|
70
|
+
}
|
|
71
|
+
function parseTraceparent(header) {
|
|
72
|
+
if (!header)
|
|
73
|
+
return null;
|
|
74
|
+
const match = TRACEPARENT_RE.exec(header.trim());
|
|
75
|
+
if (!match?.[1] || !match[2])
|
|
76
|
+
return null;
|
|
77
|
+
const traceId = match[1].toLowerCase();
|
|
78
|
+
const parentSpanId = match[2].toLowerCase();
|
|
79
|
+
if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
|
|
80
|
+
return null;
|
|
81
|
+
return { traceId, spanId: randomHex(8), parentSpanId };
|
|
82
|
+
}
|
|
83
|
+
function formatTraceparent(ctx) {
|
|
84
|
+
return `00-${ctx.traceId}-${ctx.spanId}-01`;
|
|
85
|
+
}
|
|
86
|
+
function resolveTraceContext(req) {
|
|
87
|
+
return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
|
|
88
|
+
}
|
|
89
|
+
function childSpan(parent) {
|
|
90
|
+
return {
|
|
91
|
+
traceId: parent.traceId,
|
|
92
|
+
spanId: randomHex(8),
|
|
93
|
+
parentSpanId: parent.spanId
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/observability/context.ts
|
|
98
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
99
|
+
var storage = new AsyncLocalStorage;
|
|
100
|
+
function runWithRequestContext(ctx, fn) {
|
|
101
|
+
return storage.run(ctx, fn);
|
|
102
|
+
}
|
|
103
|
+
function getRequestContext() {
|
|
104
|
+
return storage.getStore();
|
|
105
|
+
}
|
|
106
|
+
function getTraceId() {
|
|
107
|
+
return storage.getStore()?.trace.traceId;
|
|
108
|
+
}
|
|
109
|
+
function getUserId() {
|
|
110
|
+
return storage.getStore()?.userId;
|
|
111
|
+
}
|
|
112
|
+
function setRequestUser(userId) {
|
|
113
|
+
const ctx = storage.getStore();
|
|
114
|
+
if (ctx)
|
|
115
|
+
ctx.userId = userId;
|
|
116
|
+
}
|
|
117
|
+
function setRequestEndpoint(serviceName, action) {
|
|
118
|
+
const ctx = storage.getStore();
|
|
119
|
+
if (ctx) {
|
|
120
|
+
ctx.serviceName = serviceName;
|
|
121
|
+
ctx.action = action;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function setRequestDimensions(dimensions) {
|
|
125
|
+
const ctx = storage.getStore();
|
|
126
|
+
if (ctx)
|
|
127
|
+
ctx.dimensions = { ...ctx.dimensions, ...dimensions };
|
|
128
|
+
}
|
|
129
|
+
function setRequestError(error) {
|
|
130
|
+
const ctx = storage.getStore();
|
|
131
|
+
if (ctx)
|
|
132
|
+
ctx.error = error;
|
|
133
|
+
}
|
|
134
|
+
function wrapInRequestContext(handler, options = {}) {
|
|
135
|
+
return (req, server) => {
|
|
136
|
+
const ctx = {
|
|
137
|
+
trace: resolveTraceContext(req),
|
|
138
|
+
source: "http",
|
|
139
|
+
method: req.method,
|
|
140
|
+
path: new URL(req.url, "http://localhost").pathname,
|
|
141
|
+
startedAt: process.hrtime.bigint(),
|
|
142
|
+
...getClientInfo(req, {
|
|
143
|
+
trustProxy: options.trustProxy,
|
|
144
|
+
socketIp: resolveSocketIp(req, server)
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
return runWithRequestContext(ctx, () => handler(req, server));
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
|
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
getClientInfo,
|
|
11
11
|
parseQueryParams,
|
|
12
12
|
resolveSocketIp,
|
|
13
|
-
resolveTraceId
|
|
14
|
-
|
|
13
|
+
resolveTraceId,
|
|
14
|
+
setRequestEndpoint
|
|
15
|
+
} from "./index-fwqnkc90.js";
|
|
15
16
|
import {
|
|
16
17
|
__require,
|
|
17
18
|
isUnsafeKey,
|
|
@@ -600,6 +601,7 @@ function createHandler(config) {
|
|
|
600
601
|
}
|
|
601
602
|
const { method, pathParams, groupHooks } = match;
|
|
602
603
|
const ctx = buildBaseContext(req, url, pathParams, traceId, clientIp);
|
|
604
|
+
setRequestEndpoint(method.serviceName, method.key);
|
|
603
605
|
try {
|
|
604
606
|
await parseRequestInto(ctx, req, url, method, config.maxUploadBytes);
|
|
605
607
|
if (hooks?.beforeHandle) {
|
package/dist/node.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createImplement,
|
|
4
4
|
createSocketIOServer,
|
|
5
5
|
implement
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-renxz7c2.js";
|
|
7
7
|
import {
|
|
8
8
|
AppError,
|
|
9
9
|
appError,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
rateLimited,
|
|
15
15
|
unauthorized
|
|
16
16
|
} from "./index-jgpsd7dy.js";
|
|
17
|
-
import"./index-
|
|
17
|
+
import"./index-fwqnkc90.js";
|
|
18
18
|
import"./index-tm7dqzxc.js";
|
|
19
19
|
// src/server/node.ts
|
|
20
20
|
import { serve } from "srvx";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/observability/audit.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAGhF,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAC1C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,IAAI,EAAE,CAAC,CAAC,EACN,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,KACpD,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpD;;;OAGG;IACH,QAAQ,EAAE,aAAa,CAAC;CACzB;AAmBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/observability/audit.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAGhF,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAC1C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,IAAI,EAAE,CAAC,CAAC,EACN,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,KACpD,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpD;;;OAGG;IACH,QAAQ,EAAE,aAAa,CAAC;CACzB;AAmBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAsG9D"}
|
|
@@ -19,6 +19,20 @@ export interface RequestContext {
|
|
|
19
19
|
userAgent?: string;
|
|
20
20
|
/** Resolved user id — set late, once auth has run. */
|
|
21
21
|
userId?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Stable endpoint identity — `(serviceName, action)` of the matched contract
|
|
24
|
+
* route. Written by the HTTP pipeline when the route matches, *before*
|
|
25
|
+
* validation, so even a failed request is attributed to the operation it
|
|
26
|
+
* targeted. → ADR 0022.
|
|
27
|
+
*/
|
|
28
|
+
serviceName?: string;
|
|
29
|
+
action?: string;
|
|
30
|
+
/**
|
|
31
|
+
* App-defined domain dimensions (a tenant / project / entity id, …) — an
|
|
32
|
+
* opaque bag the core attaches no meaning to (→ ADR 0021), surfaced on
|
|
33
|
+
* `RequestEvent.dimensions`. Set via `setRequestDimensions`.
|
|
34
|
+
*/
|
|
35
|
+
dimensions?: Record<string, string>;
|
|
22
36
|
/** Error outcome — set late, by the error handler. */
|
|
23
37
|
error?: {
|
|
24
38
|
code?: string;
|
|
@@ -40,6 +54,23 @@ export declare function getUserId(): string | undefined;
|
|
|
40
54
|
* from the auth hook.
|
|
41
55
|
*/
|
|
42
56
|
export declare function setRequestUser(userId: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Attach the matched endpoint's stable `(serviceName, action)` identity to the
|
|
59
|
+
* active context. The framework's HTTP pipeline calls this when a contract route
|
|
60
|
+
* matches — *before* validation — so the audit event for a request carries the
|
|
61
|
+
* operation it targeted even when the request fails pre-handler. No-op outside a
|
|
62
|
+
* request context. → ADR 0022.
|
|
63
|
+
*/
|
|
64
|
+
export declare function setRequestEndpoint(serviceName: string, action: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Merge app-defined domain dimensions (a tenant / project / entity id, …) onto
|
|
67
|
+
* the active context — an opaque bag the core gives no meaning to (→ ADR 0021),
|
|
68
|
+
* surfaced on `RequestEvent.dimensions`. Resolve them cheaply from `ctx.params` /
|
|
69
|
+
* headers in `beforeHandle` (success) or `onError` (a pre-handler failure) and
|
|
70
|
+
* they land on the audit event for the request, success or failure alike. Merges
|
|
71
|
+
* across calls; no-op outside a request context.
|
|
72
|
+
*/
|
|
73
|
+
export declare function setRequestDimensions(dimensions: Record<string, string>): void;
|
|
43
74
|
/**
|
|
44
75
|
* Record the error outcome on the active context. Call this from the error
|
|
45
76
|
* handler — the audit hook reads it when the request completes. Optional
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/observability/context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/observability/context.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEjE,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,KAAK,EAAE,YAAY,CAAC;IACpB,4CAA4C;IAC5C,MAAM,EAAE,eAAe,CAAC;IACxB,iBAAiB;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,sDAAsD;IACtD,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;CAClE;AAID,yDAAyD;AACzD,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE5E;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAE9D;AAED,mEAAmE;AACnE,wBAAgB,UAAU,IAAI,MAAM,GAAG,SAAS,CAE/C;AAED,qDAAqD;AACrD,wBAAgB,SAAS,IAAI,MAAM,GAAG,SAAS,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGnD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAM5E;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,GAAG,IAAI,CAGP;AAED,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,EACvD,OAAO,GAAE,yBAA8B,GACtC,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiBhD"}
|
|
@@ -13,6 +13,20 @@ export interface RequestEvent {
|
|
|
13
13
|
method: string;
|
|
14
14
|
/** Request path — `/api/...` for HTTP, `/{source}/{tool}` for a tool call. */
|
|
15
15
|
path: string;
|
|
16
|
+
/**
|
|
17
|
+
* Stable owning-contract identity of the matched operation — the "service"
|
|
18
|
+
* (contract prefix) and "action" (endpoint key) halves. Set on every surface
|
|
19
|
+
* (HTTP, MCP, agent) from the contract, not parsed from `path`. → ADR 0022.
|
|
20
|
+
*/
|
|
21
|
+
serviceName?: string;
|
|
22
|
+
action?: string;
|
|
23
|
+
/**
|
|
24
|
+
* App-defined domain dimensions for the call — e.g. a tenant / project /
|
|
25
|
+
* entity id. An opaque bag the core attaches no meaning to (→ ADR 0021);
|
|
26
|
+
* populated by `setRequestDimensions`. The sink maps it onto its own columns
|
|
27
|
+
* instead of re-deriving identity from the path.
|
|
28
|
+
*/
|
|
29
|
+
dimensions?: Record<string, string>;
|
|
16
30
|
/** Tool name — tool calls only. */
|
|
17
31
|
toolName?: string;
|
|
18
32
|
/** W3C trace id — correlates every span of one logical request. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/observability/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,MAAM,EAAE,eAAe,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,uEAAuE;IACvE,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,mDAAmD;IACnD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/observability/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,MAAM,EAAE,eAAe,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,uEAAuE;IACvE,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,mDAAmD;IACnD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* function — nothing else.
|
|
8
8
|
*/
|
|
9
9
|
export { type AuditConfig, type AuditHook, createAuditHook } from './audit';
|
|
10
|
-
export { getRequestContext, getTraceId, getUserId, type RequestContext, runWithRequestContext, setRequestError, setRequestUser, wrapInRequestContext, } from './context';
|
|
10
|
+
export { getRequestContext, getTraceId, getUserId, type RequestContext, runWithRequestContext, setRequestDimensions, setRequestEndpoint, setRequestError, setRequestUser, wrapInRequestContext, } from './context';
|
|
11
11
|
export type { RequestEvent } from './event';
|
|
12
12
|
export { type JsonValue, measureSize, redact, type SanitizeOptions, type SizeMeasure, sanitizePayload, truncatePreview, } from './sanitize';
|
|
13
13
|
export { childSpan, createTraceContext, formatTraceparent, parseTraceparent, resolveTraceContext, type TraceContext, } from './trace';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,KAAK,cAAc,EACnB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACL,KAAK,SAAS,EACd,WAAW,EACX,MAAM,EACN,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,KAAK,cAAc,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACL,KAAK,SAAS,EACd,WAAW,EACX,MAAM,EACN,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|
|
@@ -8,11 +8,12 @@ import {
|
|
|
8
8
|
parseTraceparent,
|
|
9
9
|
resolveTraceContext,
|
|
10
10
|
runWithRequestContext,
|
|
11
|
+
setRequestDimensions,
|
|
12
|
+
setRequestEndpoint,
|
|
11
13
|
setRequestError,
|
|
12
14
|
setRequestUser,
|
|
13
15
|
wrapInRequestContext
|
|
14
|
-
} from "../index-
|
|
15
|
-
import"../index-p9m9c0jw.js";
|
|
16
|
+
} from "../index-fwqnkc90.js";
|
|
16
17
|
import {
|
|
17
18
|
isRecord,
|
|
18
19
|
isUnsafeKey
|
|
@@ -140,6 +141,9 @@ function createAuditHook(config) {
|
|
|
140
141
|
source: ctx.source,
|
|
141
142
|
method: ctx.method,
|
|
142
143
|
path: ctx.path,
|
|
144
|
+
...ctx.serviceName !== undefined && { serviceName: ctx.serviceName },
|
|
145
|
+
...ctx.action !== undefined && { action: ctx.action },
|
|
146
|
+
...ctx.dimensions !== undefined && { dimensions: ctx.dimensions },
|
|
143
147
|
traceId: ctx.trace.traceId,
|
|
144
148
|
spanId: ctx.trace.spanId,
|
|
145
149
|
parentSpanId: ctx.trace.parentSpanId,
|
|
@@ -163,14 +167,17 @@ function createAuditHook(config) {
|
|
|
163
167
|
};
|
|
164
168
|
};
|
|
165
169
|
const toolCall = {
|
|
166
|
-
afterToolCall: (toolName, args, result, durationMs, context) => {
|
|
167
|
-
const
|
|
168
|
-
const span =
|
|
170
|
+
afterToolCall: (toolName, args, result, durationMs, context, endpoint) => {
|
|
171
|
+
const requestCtx = getRequestContext();
|
|
172
|
+
const span = requestCtx ? childSpan(requestCtx.trace) : createTraceContext();
|
|
169
173
|
const measure = result.ok ? measureSize(result.data) : { resultSize: null, responseBytes: 0 };
|
|
170
174
|
emit({
|
|
171
175
|
source: context.source,
|
|
172
176
|
method: "TOOL",
|
|
173
177
|
path: `/${context.source}/${toolName}`,
|
|
178
|
+
serviceName: endpoint.serviceName,
|
|
179
|
+
action: endpoint.key,
|
|
180
|
+
...requestCtx?.dimensions !== undefined && { dimensions: requestCtx.dimensions },
|
|
174
181
|
toolName,
|
|
175
182
|
traceId: span.traceId,
|
|
176
183
|
spanId: span.spanId,
|
|
@@ -199,6 +206,8 @@ export {
|
|
|
199
206
|
truncatePreview,
|
|
200
207
|
setRequestUser,
|
|
201
208
|
setRequestError,
|
|
209
|
+
setRequestEndpoint,
|
|
210
|
+
setRequestDimensions,
|
|
202
211
|
sanitizePayload,
|
|
203
212
|
runWithRequestContext,
|
|
204
213
|
resolveTraceContext,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAEV,eAAe,EACf,aAAa,EAGd,MAAM,SAAS,CAAC;AAEjB,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAsNxF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,uBAsBnD"}
|
package/dist/server/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
socketIoLane,
|
|
13
13
|
staticRoute,
|
|
14
14
|
webSocketLane
|
|
15
|
-
} from "../index-
|
|
15
|
+
} from "../index-renxz7c2.js";
|
|
16
16
|
import {
|
|
17
17
|
createAuthHook,
|
|
18
18
|
createBearerResolver,
|
|
@@ -43,16 +43,14 @@ import {
|
|
|
43
43
|
rateLimited,
|
|
44
44
|
unauthorized
|
|
45
45
|
} from "../index-jgpsd7dy.js";
|
|
46
|
-
import {
|
|
47
|
-
getTraceId
|
|
48
|
-
} from "../index-031q8xmx.js";
|
|
49
46
|
import {
|
|
50
47
|
extractIp,
|
|
51
48
|
generateTraceId,
|
|
52
49
|
getClientInfo,
|
|
50
|
+
getTraceId,
|
|
53
51
|
resolveSocketIp,
|
|
54
52
|
resolveTraceId
|
|
55
|
-
} from "../index-
|
|
53
|
+
} from "../index-fwqnkc90.js";
|
|
56
54
|
import {
|
|
57
55
|
isRecord
|
|
58
56
|
} from "../index-tm7dqzxc.js";
|
package/llms-full.txt
CHANGED
|
@@ -2256,11 +2256,13 @@ queryable across all three:
|
|
|
2256
2256
|
|-------|-------|
|
|
2257
2257
|
| `source` | `http` \| `mcp` \| `agent` |
|
|
2258
2258
|
| `method` / `path` | the verb + path, or `TOOL` + `/{source}/{tool}` |
|
|
2259
|
+
| `serviceName` / `action` | stable contract identity of the operation (→ ADR 0022) — from the contract, not parsed from `path`; set on every surface, present even on a pre-handler 400 |
|
|
2259
2260
|
| `toolName` | tool calls only |
|
|
2261
|
+
| `dimensions` | app-defined domain dimensions (tenant / project / entity id) — see [request context](#request-context) |
|
|
2260
2262
|
| `traceId` / `spanId` / `parentSpanId` | [W3C trace context](#trace-context) |
|
|
2261
2263
|
| `ok` / `statusCode` | outcome — real HTTP status, or `200`/`400` for a tool |
|
|
2262
2264
|
| `durationMs` / `startedAt` | timing |
|
|
2263
|
-
| `errorCode` / `errorMessage` | failures only |
|
|
2265
|
+
| `errorCode` / `errorMessage` / `errorDetail` | failures only — `errorDetail` carries the structure the message flattens (e.g. Zod issues) |
|
|
2264
2266
|
| `payload` | the request body / tool arguments — sanitised |
|
|
2265
2267
|
| `resultSize` / `responseBytes` | result item count + serialised size |
|
|
2266
2268
|
| `userId` / `ipAddress` / `userAgent` | identity |
|
|
@@ -2279,15 +2281,36 @@ Bun.serve({
|
|
|
2279
2281
|
})
|
|
2280
2282
|
```
|
|
2281
2283
|
|
|
2282
|
-
|
|
2283
|
-
them from the hooks that know:
|
|
2284
|
+
Some fields are filled in late. Set them from the hooks that know:
|
|
2284
2285
|
|
|
2285
2286
|
```ts
|
|
2286
|
-
import {
|
|
2287
|
+
import {
|
|
2288
|
+
setRequestDimensions,
|
|
2289
|
+
setRequestError,
|
|
2290
|
+
setRequestUser,
|
|
2291
|
+
} from 'stitchkit/observability'
|
|
2287
2292
|
|
|
2288
2293
|
createAuthHook({ /* … */ inject: (ctx, user) => user && setRequestUser(user.id) })
|
|
2289
2294
|
// in your onError hook:
|
|
2290
|
-
setRequestError({ code: err.code, message: err.message })
|
|
2295
|
+
setRequestError({ code: err.code, message: err.message, details: err.issues })
|
|
2296
|
+
```
|
|
2297
|
+
|
|
2298
|
+
**Endpoint identity is automatic.** The framework writes the matched operation's
|
|
2299
|
+
`(serviceName, action)` into the context at route-match, *before* validation — so
|
|
2300
|
+
`event.serviceName` / `event.action` are present on every event, including a
|
|
2301
|
+
pre-handler 400. Nothing to wire.
|
|
2302
|
+
|
|
2303
|
+
**Domain dimensions** — attach your own tenant / project / entity id with
|
|
2304
|
+
`setRequestDimensions`. It is an opaque `Record<string, string>` the core gives no
|
|
2305
|
+
meaning to (→ ADR 0021). Resolve it cheaply from `ctx.params` / headers in
|
|
2306
|
+
`beforeHandle` (success) or `onError` (a pre-handler failure — `ctx.params` /
|
|
2307
|
+
`ctx.req` are available there) and it lands on `event.dimensions` for the request
|
|
2308
|
+
either way, so your sink reads it as a column instead of re-parsing the path:
|
|
2309
|
+
|
|
2310
|
+
```ts
|
|
2311
|
+
// beforeHandle (success) and onError (failure) alike:
|
|
2312
|
+
const projectId = ctx.req?.headers.get('x-project') ?? String(ctx.params?.projectId ?? '')
|
|
2313
|
+
if (projectId) setRequestDimensions({ projectId })
|
|
2291
2314
|
```
|
|
2292
2315
|
|
|
2293
2316
|
Make the framework router share this trace id — so request logs and your
|
|
@@ -2378,14 +2401,15 @@ outcome and the duration, neither of which exists before the handler runs.
|
|
|
2378
2401
|
|
|
2379
2402
|
### Keying a row on (service, action)
|
|
2380
2403
|
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
`
|
|
2386
|
-
`
|
|
2387
|
-
|
|
2388
|
-
|
|
2404
|
+
`createAuditHook` already keys every event by **service** and **action**
|
|
2405
|
+
(`event.serviceName` / `event.action`, → ADR 0029) — reach for the raw hook only
|
|
2406
|
+
when you also need the handler **output**, which the audit wrapper never sees. For
|
|
2407
|
+
that, read the endpoint identity off the `MethodDef` the hook receives —
|
|
2408
|
+
`endpoint.serviceName` (the contract prefix) and `endpoint.key` (the endpoint key,
|
|
2409
|
+
e.g. `updatePartial`). They are stable and always present (→ ADR 0022); the action
|
|
2410
|
+
is not in the URL and `toolName` is absent on HTTP-only endpoints, so this is the
|
|
2411
|
+
only reliable pair. `afterHandle` also gives you the handler `result` — so it is
|
|
2412
|
+
the home for a rich mutation audit that records output:
|
|
2389
2413
|
|
|
2390
2414
|
```ts
|
|
2391
2415
|
hooks: {
|
package/package.json
CHANGED
package/dist/index-031q8xmx.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getClientInfo,
|
|
3
|
-
resolveSocketIp
|
|
4
|
-
} from "./index-p9m9c0jw.js";
|
|
5
|
-
|
|
6
|
-
// src/observability/trace.ts
|
|
7
|
-
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
|
|
8
|
-
function randomHex(bytes) {
|
|
9
|
-
const arr = new Uint8Array(bytes);
|
|
10
|
-
crypto.getRandomValues(arr);
|
|
11
|
-
let hex = "";
|
|
12
|
-
for (const byte of arr)
|
|
13
|
-
hex += byte.toString(16).padStart(2, "0");
|
|
14
|
-
return hex;
|
|
15
|
-
}
|
|
16
|
-
function createTraceContext() {
|
|
17
|
-
return { traceId: randomHex(16), spanId: randomHex(8) };
|
|
18
|
-
}
|
|
19
|
-
function parseTraceparent(header) {
|
|
20
|
-
if (!header)
|
|
21
|
-
return null;
|
|
22
|
-
const match = TRACEPARENT_RE.exec(header.trim());
|
|
23
|
-
if (!match?.[1] || !match[2])
|
|
24
|
-
return null;
|
|
25
|
-
const traceId = match[1].toLowerCase();
|
|
26
|
-
const parentSpanId = match[2].toLowerCase();
|
|
27
|
-
if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
|
|
28
|
-
return null;
|
|
29
|
-
return { traceId, spanId: randomHex(8), parentSpanId };
|
|
30
|
-
}
|
|
31
|
-
function formatTraceparent(ctx) {
|
|
32
|
-
return `00-${ctx.traceId}-${ctx.spanId}-01`;
|
|
33
|
-
}
|
|
34
|
-
function resolveTraceContext(req) {
|
|
35
|
-
return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
|
|
36
|
-
}
|
|
37
|
-
function childSpan(parent) {
|
|
38
|
-
return {
|
|
39
|
-
traceId: parent.traceId,
|
|
40
|
-
spanId: randomHex(8),
|
|
41
|
-
parentSpanId: parent.spanId
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/observability/context.ts
|
|
46
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
47
|
-
var storage = new AsyncLocalStorage;
|
|
48
|
-
function runWithRequestContext(ctx, fn) {
|
|
49
|
-
return storage.run(ctx, fn);
|
|
50
|
-
}
|
|
51
|
-
function getRequestContext() {
|
|
52
|
-
return storage.getStore();
|
|
53
|
-
}
|
|
54
|
-
function getTraceId() {
|
|
55
|
-
return storage.getStore()?.trace.traceId;
|
|
56
|
-
}
|
|
57
|
-
function getUserId() {
|
|
58
|
-
return storage.getStore()?.userId;
|
|
59
|
-
}
|
|
60
|
-
function setRequestUser(userId) {
|
|
61
|
-
const ctx = storage.getStore();
|
|
62
|
-
if (ctx)
|
|
63
|
-
ctx.userId = userId;
|
|
64
|
-
}
|
|
65
|
-
function setRequestError(error) {
|
|
66
|
-
const ctx = storage.getStore();
|
|
67
|
-
if (ctx)
|
|
68
|
-
ctx.error = error;
|
|
69
|
-
}
|
|
70
|
-
function wrapInRequestContext(handler, options = {}) {
|
|
71
|
-
return (req, server) => {
|
|
72
|
-
const ctx = {
|
|
73
|
-
trace: resolveTraceContext(req),
|
|
74
|
-
source: "http",
|
|
75
|
-
method: req.method,
|
|
76
|
-
path: new URL(req.url, "http://localhost").pathname,
|
|
77
|
-
startedAt: process.hrtime.bigint(),
|
|
78
|
-
...getClientInfo(req, {
|
|
79
|
-
trustProxy: options.trustProxy,
|
|
80
|
-
socketIp: resolveSocketIp(req, server)
|
|
81
|
-
})
|
|
82
|
-
};
|
|
83
|
-
return runWithRequestContext(ctx, () => handler(req, server));
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export { createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestError, wrapInRequestContext };
|
package/dist/index-p9m9c0jw.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isRecord,
|
|
3
|
-
isUnsafeKey
|
|
4
|
-
} from "./index-tm7dqzxc.js";
|
|
5
|
-
|
|
6
|
-
// src/server/request.ts
|
|
7
|
-
function generateTraceId() {
|
|
8
|
-
return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
|
|
9
|
-
}
|
|
10
|
-
function resolveTraceId(req) {
|
|
11
|
-
const header = req.headers.get("x-request-id") ?? req.headers.get("x-trace-id");
|
|
12
|
-
const trimmed = header?.trim();
|
|
13
|
-
if (trimmed && trimmed.length <= 128 && /^[\w.-]+$/.test(trimmed)) {
|
|
14
|
-
return trimmed;
|
|
15
|
-
}
|
|
16
|
-
return generateTraceId();
|
|
17
|
-
}
|
|
18
|
-
function resolveSocketIp(req, server) {
|
|
19
|
-
if (typeof server === "object" && server !== null && "requestIP" in server && typeof server.requestIP === "function") {
|
|
20
|
-
const addr = server.requestIP(req);
|
|
21
|
-
if (isRecord(addr) && typeof addr.address === "string" && addr.address) {
|
|
22
|
-
return addr.address;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if ("ip" in req && typeof req.ip === "string" && req.ip)
|
|
26
|
-
return req.ip;
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
function extractIp(req, options = {}) {
|
|
30
|
-
if (options.trustProxy) {
|
|
31
|
-
const forwarded = req.headers.get("x-forwarded-for");
|
|
32
|
-
if (forwarded)
|
|
33
|
-
return (forwarded.split(",")[0] ?? "").trim().replace(/^::ffff:/, "");
|
|
34
|
-
const realIp = req.headers.get("x-real-ip");
|
|
35
|
-
if (realIp)
|
|
36
|
-
return realIp.trim().replace(/^::ffff:/, "");
|
|
37
|
-
}
|
|
38
|
-
return (options.socketIp ?? "").replace(/^::ffff:/, "");
|
|
39
|
-
}
|
|
40
|
-
function getClientInfo(req, options = {}) {
|
|
41
|
-
return {
|
|
42
|
-
ipAddress: extractIp(req, options) || undefined,
|
|
43
|
-
userAgent: req.headers.get("user-agent") ?? undefined
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function parseQueryParams(url) {
|
|
47
|
-
const query = {};
|
|
48
|
-
for (const key of new Set(url.searchParams.keys())) {
|
|
49
|
-
if (isUnsafeKey(key))
|
|
50
|
-
continue;
|
|
51
|
-
const values = url.searchParams.getAll(key);
|
|
52
|
-
const [first] = values;
|
|
53
|
-
query[key] = values.length === 1 && first !== undefined ? first : values;
|
|
54
|
-
}
|
|
55
|
-
return query;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export { generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams };
|