stitchkit 0.0.1 → 0.2.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/LICENSE +21 -0
- package/README.md +376 -4
- package/dist/browser/client.d.ts +30 -0
- package/dist/browser/client.d.ts.map +1 -0
- package/dist/browser/http.d.ts +77 -0
- package/dist/browser/http.d.ts.map +1 -0
- package/dist/browser/socket-io.d.ts +69 -0
- package/dist/browser/socket-io.d.ts.map +1 -0
- package/dist/browser/stream.d.ts +2 -0
- package/dist/browser/stream.d.ts.map +1 -0
- package/dist/contract/define.d.ts +94 -0
- package/dist/contract/define.d.ts.map +1 -0
- package/dist/contract/errors.d.ts +47 -0
- package/dist/contract/errors.d.ts.map +1 -0
- package/dist/contract/index.d.ts +4 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +26 -0
- package/dist/contract/pagination.d.ts +19 -0
- package/dist/contract/pagination.d.ts.map +1 -0
- package/dist/index-5sxnvwb1.js +368 -0
- package/dist/index-809wc1tt.js +18 -0
- package/dist/index-a35v22fh.js +29 -0
- package/dist/index-kckky6zw.js +96 -0
- package/dist/index-ke4mx4ea.js +38 -0
- package/dist/index-n7bmdwmz.js +68 -0
- package/dist/index-v2z2v3mq.js +587 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/internal/errors.d.ts +5 -0
- package/dist/internal/errors.d.ts.map +1 -0
- package/dist/internal/typed.d.ts +9 -0
- package/dist/internal/typed.d.ts.map +1 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +26 -0
- package/dist/observability/audit.d.ts +32 -0
- package/dist/observability/audit.d.ts.map +1 -0
- package/dist/observability/context.d.ts +60 -0
- package/dist/observability/context.d.ts.map +1 -0
- package/dist/observability/event.d.ts +49 -0
- package/dist/observability/event.d.ts.map +1 -0
- package/dist/observability/index.d.ts +14 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +266 -0
- package/dist/observability/sanitize.d.ts +42 -0
- package/dist/observability/sanitize.d.ts.map +1 -0
- package/dist/observability/trace.d.ts +42 -0
- package/dist/observability/trace.d.ts.map +1 -0
- package/dist/react/cache-bridge.d.ts +53 -0
- package/dist/react/cache-bridge.d.ts.map +1 -0
- package/dist/react/cursor-query.d.ts +37 -0
- package/dist/react/cursor-query.d.ts.map +1 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +52 -0
- package/dist/server/cache.d.ts +15 -0
- package/dist/server/cache.d.ts.map +1 -0
- package/dist/server/context.d.ts +9 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/create.d.ts +4 -0
- package/dist/server/create.d.ts.map +1 -0
- package/dist/server/event-bus.d.ts +24 -0
- package/dist/server/event-bus.d.ts.map +1 -0
- package/dist/server/implement.d.ts +16 -0
- package/dist/server/implement.d.ts.map +1 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +399 -0
- package/dist/server/logger.d.ts +21 -0
- package/dist/server/logger.d.ts.map +1 -0
- package/dist/server/middleware/auth.d.ts +50 -0
- package/dist/server/middleware/auth.d.ts.map +1 -0
- package/dist/server/middleware/cookies.d.ts +34 -0
- package/dist/server/middleware/cookies.d.ts.map +1 -0
- package/dist/server/middleware/cors.d.ts +9 -0
- package/dist/server/middleware/cors.d.ts.map +1 -0
- package/dist/server/multipart.d.ts +14 -0
- package/dist/server/multipart.d.ts.map +1 -0
- package/dist/server/node.d.ts +12 -0
- package/dist/server/node.d.ts.map +1 -0
- package/dist/server/rate-limit.d.ts +17 -0
- package/dist/server/rate-limit.d.ts.map +1 -0
- package/dist/server/request.d.ts +22 -0
- package/dist/server/request.d.ts.map +1 -0
- package/dist/server/router.d.ts +42 -0
- package/dist/server/router.d.ts.map +1 -0
- package/dist/server/socket-io.d.ts +42 -0
- package/dist/server/socket-io.d.ts.map +1 -0
- package/dist/server/stream.d.ts +18 -0
- package/dist/server/stream.d.ts.map +1 -0
- package/dist/server/swept-map.d.ts +22 -0
- package/dist/server/swept-map.d.ts.map +1 -0
- package/dist/server/types.d.ts +127 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/tools/agent.d.ts +27 -0
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/coerce.d.ts +8 -0
- package/dist/tools/coerce.d.ts.map +1 -0
- package/dist/tools/execute.d.ts +45 -0
- package/dist/tools/execute.d.ts.map +1 -0
- package/dist/tools/flatten.d.ts +15 -0
- package/dist/tools/flatten.d.ts.map +1 -0
- package/dist/tools/json-schema.d.ts +18 -0
- package/dist/tools/json-schema.d.ts.map +1 -0
- package/dist/tools/manifest.d.ts +13 -0
- package/dist/tools/manifest.d.ts.map +1 -0
- package/dist/tools/mcp-handler.d.ts +21 -0
- package/dist/tools/mcp-handler.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.d.ts +25 -0
- package/dist/tools/mcp-stdio.d.ts.map +1 -0
- package/dist/tools/mcp.d.ts +92 -0
- package/dist/tools/mcp.d.ts.map +1 -0
- package/dist/tools/mount.d.ts +73 -0
- package/dist/tools/mount.d.ts.map +1 -0
- package/dist/tools/names.d.ts +2 -0
- package/dist/tools/names.d.ts.map +1 -0
- package/dist/tools/remote.d.ts +22 -0
- package/dist/tools/remote.d.ts.map +1 -0
- package/dist/tools/schema.d.ts +21 -0
- package/dist/tools/schema.d.ts.map +1 -0
- package/dist/tools/view-file.d.ts +57 -0
- package/dist/tools/view-file.d.ts.map +1 -0
- package/dist/tools.d.ts +12 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +837 -0
- package/package.json +147 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,wBAAgB,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CASxD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAWrD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function typedEntries<T extends object>(value: T): Array<{
|
|
2
|
+
[K in keyof T]: [K, T[K]];
|
|
3
|
+
}[keyof T]>;
|
|
4
|
+
/** Narrow an unknown value to a plain object — not `null`, not an array. */
|
|
5
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
6
|
+
export declare function mapObject<TSource extends object, TResult extends {
|
|
7
|
+
[K in keyof TSource]?: unknown;
|
|
8
|
+
}>(source: TSource, mapper: <K extends keyof TSource>(key: K, value: TSource[K]) => TResult[K] | undefined): TResult;
|
|
9
|
+
//# sourceMappingURL=typed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typed.d.ts","sourceRoot":"","sources":["../../src/internal/typed.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAC3C,KAAK,EAAE,CAAC,GACP,KAAK,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAE/C;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,SAAS,CACvB,OAAO,SAAS,MAAM,EACtB,OAAO,SAAS;KAAG,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,EAAE,OAAO;CAAE,EAElD,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,GACrF,OAAO,CAOT"}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACxF,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHandler
|
|
3
|
+
} from "./index-v2z2v3mq.js";
|
|
4
|
+
import"./index-a35v22fh.js";
|
|
5
|
+
import"./index-kckky6zw.js";
|
|
6
|
+
import"./index-ke4mx4ea.js";
|
|
7
|
+
// src/server/node.ts
|
|
8
|
+
import { serve } from "srvx";
|
|
9
|
+
async function serveNode(config) {
|
|
10
|
+
const { port = 3000, hostname, ...handlerConfig } = config;
|
|
11
|
+
const handler = createHandler(handlerConfig);
|
|
12
|
+
const server = serve({ port, hostname, fetch: handler });
|
|
13
|
+
await server.ready();
|
|
14
|
+
const listenUrl = server.url ?? `http://${hostname ?? "localhost"}:${port}`;
|
|
15
|
+
const resolvedPort = Number(new URL(listenUrl).port) || port;
|
|
16
|
+
const resolvedHost = hostname ?? "localhost";
|
|
17
|
+
return {
|
|
18
|
+
url: `http://${resolvedHost}:${resolvedPort}`,
|
|
19
|
+
port: resolvedPort,
|
|
20
|
+
close: (closeActive) => server.close(closeActive)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
serveNode,
|
|
25
|
+
createHandler
|
|
26
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ToolCallHooks } from '../tools/execute';
|
|
2
|
+
import type { RequestEvent } from './event';
|
|
3
|
+
import { type SanitizeOptions } from './sanitize';
|
|
4
|
+
/** Config for `createAuditHook`. */
|
|
5
|
+
export interface AuditConfig {
|
|
6
|
+
/**
|
|
7
|
+
* The sink — persists one audit event. It runs fire-and-forget and its own
|
|
8
|
+
* errors are swallowed, so a slow or failing write never blocks or breaks the
|
|
9
|
+
* request it observes. Keep it asynchronous and self-contained.
|
|
10
|
+
*/
|
|
11
|
+
write: (event: RequestEvent) => void | Promise<void>;
|
|
12
|
+
/** Keep only events for which this returns `true`. Default: keep every event. */
|
|
13
|
+
filter?: (event: RequestEvent) => boolean;
|
|
14
|
+
/** Payload sanitisation tuning — passed through to `sanitizePayload`. */
|
|
15
|
+
sanitize?: SanitizeOptions;
|
|
16
|
+
}
|
|
17
|
+
/** What `createAuditHook` returns — one wiring point per surface. */
|
|
18
|
+
export interface AuditHook {
|
|
19
|
+
/**
|
|
20
|
+
* Wrap the HTTP fetch handler — audits every request from its final
|
|
21
|
+
* response (success and error alike). Compose it INSIDE `wrapInRequestContext`
|
|
22
|
+
* (it reads that context for trace ids, timing and identity).
|
|
23
|
+
*/
|
|
24
|
+
http: <S>(handler: (req: Request, server: S) => Promise<Response>) => (req: Request, server: S) => Promise<Response>;
|
|
25
|
+
/**
|
|
26
|
+
* Tool-call hooks — audits every MCP / agent tool call. Pass as `hooks` to
|
|
27
|
+
* `createMcpHandler`, `createStdioMcpServer` or `mountAgent`.
|
|
28
|
+
*/
|
|
29
|
+
toolCall: ToolCallHooks;
|
|
30
|
+
}
|
|
31
|
+
export declare function createAuditHook(config: AuditConfig): AuditHook;
|
|
32
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +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,CA6F9D"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { TransportSource } from '../contract';
|
|
2
|
+
import { type TraceContext } from './trace';
|
|
3
|
+
/** Everything known about the request in flight. */
|
|
4
|
+
export interface RequestContext {
|
|
5
|
+
/** W3C trace ids for this request. */
|
|
6
|
+
trace: TraceContext;
|
|
7
|
+
/** Which surface the request arrived on. */
|
|
8
|
+
source: TransportSource;
|
|
9
|
+
/** HTTP verb. */
|
|
10
|
+
method: string;
|
|
11
|
+
/** Request path. */
|
|
12
|
+
path: string;
|
|
13
|
+
/** Monotonic start, for duration — `process.hrtime.bigint()`. */
|
|
14
|
+
startedAt: bigint;
|
|
15
|
+
/** Client IP, when resolvable. */
|
|
16
|
+
ipAddress?: string;
|
|
17
|
+
/** Client user-agent, when present. */
|
|
18
|
+
userAgent?: string;
|
|
19
|
+
/** Resolved user id — set late, once auth has run. */
|
|
20
|
+
userId?: string;
|
|
21
|
+
/** Error outcome — set late, by the error handler. */
|
|
22
|
+
error?: {
|
|
23
|
+
code?: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Run `fn` with `ctx` as the active request context. */
|
|
28
|
+
export declare function runWithRequestContext<T>(ctx: RequestContext, fn: () => T): T;
|
|
29
|
+
/** The active request context, or `undefined` outside any request. */
|
|
30
|
+
export declare function getRequestContext(): RequestContext | undefined;
|
|
31
|
+
/** The active trace id — the one id to stamp on every log line. */
|
|
32
|
+
export declare function getTraceId(): string | undefined;
|
|
33
|
+
/** The active user id, once auth has resolved it. */
|
|
34
|
+
export declare function getUserId(): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Attach the resolved user to the active context. The ALS record is mutable —
|
|
37
|
+
* the update is visible to everything already holding the context. Call this
|
|
38
|
+
* from the auth hook.
|
|
39
|
+
*/
|
|
40
|
+
export declare function setRequestUser(userId: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Record the error outcome on the active context. Call this from the error
|
|
43
|
+
* handler — the audit hook reads it when the request completes.
|
|
44
|
+
*/
|
|
45
|
+
export declare function setRequestError(error: {
|
|
46
|
+
code?: string;
|
|
47
|
+
message?: string;
|
|
48
|
+
}): void;
|
|
49
|
+
/**
|
|
50
|
+
* Wrap a fetch handler so it runs inside a fresh `RequestContext` — trace ids
|
|
51
|
+
* (the inbound `traceparent` continued, or freshly minted), timing and client
|
|
52
|
+
* info.
|
|
53
|
+
*
|
|
54
|
+
* Compose it as the OUTERMOST wrapper of the server's fetch handler. To make
|
|
55
|
+
* the framework router reuse this trace id (so request logs and application
|
|
56
|
+
* logs share one id), pass `traceId: getTraceId` to `createServer` /
|
|
57
|
+
* `createHandler`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function wrapInRequestContext<S>(handler: (req: Request, server: S) => Promise<Response>): (req: Request, server: S) => Promise<Response>;
|
|
60
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/observability/context.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,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,sDAAsD;IACtD,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;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;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGhF;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,GACtD,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAYhD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { TransportSource } from '../contract';
|
|
2
|
+
import type { JsonValue } from './sanitize';
|
|
3
|
+
/**
|
|
4
|
+
* A normalised audit event — one shape for a completed call on any surface
|
|
5
|
+
* (an HTTP request, an MCP tool call, an agent tool call). `createAuditHook`
|
|
6
|
+
* produces it and hands it to the project's `write` sink. The project maps it
|
|
7
|
+
* onto its own audit table; stitchkit owns the normalisation.
|
|
8
|
+
*/
|
|
9
|
+
export interface RequestEvent {
|
|
10
|
+
/** Surface the call arrived on. */
|
|
11
|
+
source: TransportSource;
|
|
12
|
+
/** HTTP verb, or `TOOL` for a tool call. */
|
|
13
|
+
method: string;
|
|
14
|
+
/** Request path — `/api/...` for HTTP, `/{source}/{tool}` for a tool call. */
|
|
15
|
+
path: string;
|
|
16
|
+
/** Tool name — tool calls only. */
|
|
17
|
+
toolName?: string;
|
|
18
|
+
/** W3C trace id — correlates every span of one logical request. */
|
|
19
|
+
traceId: string;
|
|
20
|
+
/** W3C span id — unique to this call. */
|
|
21
|
+
spanId: string;
|
|
22
|
+
/** Parent span id, when this call is nested under another. */
|
|
23
|
+
parentSpanId?: string;
|
|
24
|
+
/** Whether the call succeeded. */
|
|
25
|
+
ok: boolean;
|
|
26
|
+
/** HTTP status — the real status for HTTP, `200` / `400` for a tool call. */
|
|
27
|
+
statusCode: number;
|
|
28
|
+
/** Wall-clock duration. */
|
|
29
|
+
durationMs: number;
|
|
30
|
+
/** Error code — failures only. */
|
|
31
|
+
errorCode?: string;
|
|
32
|
+
/** Error message — failures only. */
|
|
33
|
+
errorMessage?: string;
|
|
34
|
+
/** Sanitised request payload — the HTTP body or the tool arguments. */
|
|
35
|
+
payload: JsonValue | null;
|
|
36
|
+
/** Item count of the result, when it is a list. */
|
|
37
|
+
resultSize: number | null;
|
|
38
|
+
/** Serialised byte length of the result. */
|
|
39
|
+
responseBytes: number;
|
|
40
|
+
/** Resolved user id, when authenticated. */
|
|
41
|
+
userId?: string;
|
|
42
|
+
/** Client IP. */
|
|
43
|
+
ipAddress?: string;
|
|
44
|
+
/** Client user-agent. */
|
|
45
|
+
userAgent?: string;
|
|
46
|
+
/** When the call started. */
|
|
47
|
+
startedAt: Date;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=event.d.ts.map
|
|
@@ -0,0 +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,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,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `stitchkit/observability` — the audit layer one level above the raw hooks.
|
|
3
|
+
*
|
|
4
|
+
* W3C trace context, an `AsyncLocalStorage` request context, payload
|
|
5
|
+
* sanitisation, a normalised `RequestEvent`, and `createAuditHook` to wire it
|
|
6
|
+
* all into one sink. A project's logging becomes a table plus a `write`
|
|
7
|
+
* function — nothing else.
|
|
8
|
+
*/
|
|
9
|
+
export { type AuditConfig, type AuditHook, createAuditHook } from './audit';
|
|
10
|
+
export { getRequestContext, getTraceId, getUserId, type RequestContext, runWithRequestContext, setRequestError, setRequestUser, wrapInRequestContext, } from './context';
|
|
11
|
+
export type { RequestEvent } from './event';
|
|
12
|
+
export { type JsonValue, measureSize, redact, type SanitizeOptions, type SizeMeasure, sanitizePayload, truncatePreview, } from './sanitize';
|
|
13
|
+
export { childSpan, createTraceContext, formatTraceparent, parseTraceparent, resolveTraceContext, type TraceContext, } from './trace';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRecord
|
|
3
|
+
} from "../index-809wc1tt.js";
|
|
4
|
+
import {
|
|
5
|
+
getClientInfo
|
|
6
|
+
} from "../index-ke4mx4ea.js";
|
|
7
|
+
|
|
8
|
+
// src/observability/context.ts
|
|
9
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
|
|
11
|
+
// src/observability/trace.ts
|
|
12
|
+
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
|
|
13
|
+
function randomHex(bytes) {
|
|
14
|
+
const arr = new Uint8Array(bytes);
|
|
15
|
+
crypto.getRandomValues(arr);
|
|
16
|
+
let hex = "";
|
|
17
|
+
for (const byte of arr)
|
|
18
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
19
|
+
return hex;
|
|
20
|
+
}
|
|
21
|
+
function createTraceContext() {
|
|
22
|
+
return { traceId: randomHex(16), spanId: randomHex(8) };
|
|
23
|
+
}
|
|
24
|
+
function parseTraceparent(header) {
|
|
25
|
+
if (!header)
|
|
26
|
+
return null;
|
|
27
|
+
const match = TRACEPARENT_RE.exec(header.trim());
|
|
28
|
+
if (!match?.[1] || !match[2])
|
|
29
|
+
return null;
|
|
30
|
+
return {
|
|
31
|
+
traceId: match[1].toLowerCase(),
|
|
32
|
+
spanId: randomHex(8),
|
|
33
|
+
parentSpanId: match[2].toLowerCase()
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function formatTraceparent(ctx) {
|
|
37
|
+
return `00-${ctx.traceId}-${ctx.spanId}-01`;
|
|
38
|
+
}
|
|
39
|
+
function resolveTraceContext(req) {
|
|
40
|
+
return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
|
|
41
|
+
}
|
|
42
|
+
function childSpan(parent) {
|
|
43
|
+
return {
|
|
44
|
+
traceId: parent.traceId,
|
|
45
|
+
spanId: randomHex(8),
|
|
46
|
+
parentSpanId: parent.spanId
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/observability/context.ts
|
|
51
|
+
var storage = new AsyncLocalStorage;
|
|
52
|
+
function runWithRequestContext(ctx, fn) {
|
|
53
|
+
return storage.run(ctx, fn);
|
|
54
|
+
}
|
|
55
|
+
function getRequestContext() {
|
|
56
|
+
return storage.getStore();
|
|
57
|
+
}
|
|
58
|
+
function getTraceId() {
|
|
59
|
+
return storage.getStore()?.trace.traceId;
|
|
60
|
+
}
|
|
61
|
+
function getUserId() {
|
|
62
|
+
return storage.getStore()?.userId;
|
|
63
|
+
}
|
|
64
|
+
function setRequestUser(userId) {
|
|
65
|
+
const ctx = storage.getStore();
|
|
66
|
+
if (ctx)
|
|
67
|
+
ctx.userId = userId;
|
|
68
|
+
}
|
|
69
|
+
function setRequestError(error) {
|
|
70
|
+
const ctx = storage.getStore();
|
|
71
|
+
if (ctx)
|
|
72
|
+
ctx.error = error;
|
|
73
|
+
}
|
|
74
|
+
function wrapInRequestContext(handler) {
|
|
75
|
+
return (req, server) => {
|
|
76
|
+
const ctx = {
|
|
77
|
+
trace: resolveTraceContext(req),
|
|
78
|
+
source: "http",
|
|
79
|
+
method: req.method,
|
|
80
|
+
path: new URL(req.url).pathname,
|
|
81
|
+
startedAt: process.hrtime.bigint(),
|
|
82
|
+
...getClientInfo(req)
|
|
83
|
+
};
|
|
84
|
+
return runWithRequestContext(ctx, () => handler(req, server));
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/observability/sanitize.ts
|
|
89
|
+
var DEFAULT_SENSITIVE_KEYS = /(password|passwd|pwd|secret|token|apikey|api[-_ ]?key|auth|authorization|bearer|session|cookie|init[-_ ]?data|credential|private[-_ ]?key)/i;
|
|
90
|
+
var DEFAULT_MAX_BYTES = 16000;
|
|
91
|
+
var DEFAULT_MAX_DEPTH = 20;
|
|
92
|
+
var MASK = "[redacted]";
|
|
93
|
+
function redact(value, options = {}) {
|
|
94
|
+
const sensitive = options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS;
|
|
95
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
96
|
+
const walk = (input, depth) => {
|
|
97
|
+
if (depth > maxDepth)
|
|
98
|
+
return "[max depth]";
|
|
99
|
+
if (input === null || input === undefined)
|
|
100
|
+
return null;
|
|
101
|
+
if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
|
|
102
|
+
return input;
|
|
103
|
+
}
|
|
104
|
+
if (input instanceof Uint8Array || input instanceof ArrayBuffer) {
|
|
105
|
+
return { _type: "binary", size: input.byteLength };
|
|
106
|
+
}
|
|
107
|
+
if (input instanceof Blob) {
|
|
108
|
+
return { _type: "blob", size: input.size, mime: input.type };
|
|
109
|
+
}
|
|
110
|
+
if (typeof FormData !== "undefined" && input instanceof FormData) {
|
|
111
|
+
return { _type: "formdata", keys: [...input.keys()] };
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(input)) {
|
|
114
|
+
return input.map((item) => walk(item, depth + 1));
|
|
115
|
+
}
|
|
116
|
+
if (isRecord(input)) {
|
|
117
|
+
const out = {};
|
|
118
|
+
for (const [key, val] of Object.entries(input)) {
|
|
119
|
+
out[key] = sensitive.test(key) ? MASK : walk(val, depth + 1);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
return String(input);
|
|
124
|
+
};
|
|
125
|
+
return walk(value, 0);
|
|
126
|
+
}
|
|
127
|
+
function truncatePreview(value, maxBytes = DEFAULT_MAX_BYTES) {
|
|
128
|
+
const json = JSON.stringify(value);
|
|
129
|
+
if (json.length <= maxBytes)
|
|
130
|
+
return value;
|
|
131
|
+
return {
|
|
132
|
+
_truncated: true,
|
|
133
|
+
_originalBytes: json.length,
|
|
134
|
+
preview: `${json.slice(0, maxBytes)}…`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function sanitizePayload(value, options = {}) {
|
|
138
|
+
if (value === undefined || value === null)
|
|
139
|
+
return null;
|
|
140
|
+
return truncatePreview(redact(value, options), options.maxBytes ?? DEFAULT_MAX_BYTES);
|
|
141
|
+
}
|
|
142
|
+
function measureSize(value) {
|
|
143
|
+
if (value === undefined || value === null) {
|
|
144
|
+
return { resultSize: null, responseBytes: 0 };
|
|
145
|
+
}
|
|
146
|
+
const responseBytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
147
|
+
if (Array.isArray(value)) {
|
|
148
|
+
return { resultSize: value.length, responseBytes };
|
|
149
|
+
}
|
|
150
|
+
if (typeof value === "object" && "items" in value && Array.isArray(value.items)) {
|
|
151
|
+
return { resultSize: value.items.length, responseBytes };
|
|
152
|
+
}
|
|
153
|
+
return { resultSize: null, responseBytes };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/observability/audit.ts
|
|
157
|
+
var BODY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
158
|
+
function toolErrorMessage(result) {
|
|
159
|
+
const { details, hint } = result;
|
|
160
|
+
if (isRecord(details) && typeof details.message === "string") {
|
|
161
|
+
return details.message;
|
|
162
|
+
}
|
|
163
|
+
return hint;
|
|
164
|
+
}
|
|
165
|
+
function readString(value) {
|
|
166
|
+
return typeof value === "string" ? value : undefined;
|
|
167
|
+
}
|
|
168
|
+
function createAuditHook(config) {
|
|
169
|
+
const { write, filter, sanitize } = config;
|
|
170
|
+
const emit = async (event) => {
|
|
171
|
+
try {
|
|
172
|
+
if (filter && !filter(event))
|
|
173
|
+
return;
|
|
174
|
+
await write(event);
|
|
175
|
+
} catch {}
|
|
176
|
+
};
|
|
177
|
+
const http = (handler) => {
|
|
178
|
+
return async (req, server) => {
|
|
179
|
+
const bodyClone = BODY_METHODS.has(req.method) ? req.clone() : null;
|
|
180
|
+
const res = await handler(req, server);
|
|
181
|
+
const ctx = getRequestContext();
|
|
182
|
+
if (ctx) {
|
|
183
|
+
const durationMs = Math.round(Number(process.hrtime.bigint() - ctx.startedAt) / 1e6);
|
|
184
|
+
(async () => {
|
|
185
|
+
let body;
|
|
186
|
+
if (bodyClone) {
|
|
187
|
+
try {
|
|
188
|
+
body = await bodyClone.json();
|
|
189
|
+
} catch {
|
|
190
|
+
body = undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
await emit({
|
|
194
|
+
source: ctx.source,
|
|
195
|
+
method: ctx.method,
|
|
196
|
+
path: ctx.path,
|
|
197
|
+
traceId: ctx.trace.traceId,
|
|
198
|
+
spanId: ctx.trace.spanId,
|
|
199
|
+
parentSpanId: ctx.trace.parentSpanId,
|
|
200
|
+
ok: res.status < 400,
|
|
201
|
+
statusCode: res.status,
|
|
202
|
+
durationMs,
|
|
203
|
+
errorCode: ctx.error?.code,
|
|
204
|
+
errorMessage: ctx.error?.message,
|
|
205
|
+
payload: sanitizePayload(body, sanitize),
|
|
206
|
+
resultSize: null,
|
|
207
|
+
responseBytes: 0,
|
|
208
|
+
userId: ctx.userId,
|
|
209
|
+
ipAddress: ctx.ipAddress,
|
|
210
|
+
userAgent: ctx.userAgent,
|
|
211
|
+
startedAt: new Date(Date.now() - durationMs)
|
|
212
|
+
});
|
|
213
|
+
})();
|
|
214
|
+
}
|
|
215
|
+
return res;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
const toolCall = {
|
|
219
|
+
afterToolCall: (toolName, args, result, durationMs, context) => {
|
|
220
|
+
const parent = getRequestContext()?.trace;
|
|
221
|
+
const span = parent ? childSpan(parent) : createTraceContext();
|
|
222
|
+
const measure = result.ok ? measureSize(result.data) : { resultSize: null, responseBytes: 0 };
|
|
223
|
+
emit({
|
|
224
|
+
source: context.source,
|
|
225
|
+
method: "TOOL",
|
|
226
|
+
path: `/${context.source}/${toolName}`,
|
|
227
|
+
toolName,
|
|
228
|
+
traceId: span.traceId,
|
|
229
|
+
spanId: span.spanId,
|
|
230
|
+
parentSpanId: span.parentSpanId,
|
|
231
|
+
ok: result.ok,
|
|
232
|
+
statusCode: result.ok ? 200 : 400,
|
|
233
|
+
durationMs,
|
|
234
|
+
errorCode: result.ok ? undefined : result.code,
|
|
235
|
+
errorMessage: result.ok ? undefined : toolErrorMessage(result),
|
|
236
|
+
payload: sanitizePayload(args, sanitize),
|
|
237
|
+
resultSize: measure.resultSize,
|
|
238
|
+
responseBytes: measure.responseBytes,
|
|
239
|
+
userId: readString(context.userId),
|
|
240
|
+
ipAddress: readString(context.ipAddress),
|
|
241
|
+
userAgent: readString(context.userAgent),
|
|
242
|
+
startedAt: new Date(Date.now() - durationMs)
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
return { http, toolCall };
|
|
247
|
+
}
|
|
248
|
+
export {
|
|
249
|
+
wrapInRequestContext,
|
|
250
|
+
truncatePreview,
|
|
251
|
+
setRequestUser,
|
|
252
|
+
setRequestError,
|
|
253
|
+
sanitizePayload,
|
|
254
|
+
runWithRequestContext,
|
|
255
|
+
resolveTraceContext,
|
|
256
|
+
redact,
|
|
257
|
+
parseTraceparent,
|
|
258
|
+
measureSize,
|
|
259
|
+
getUserId,
|
|
260
|
+
getTraceId,
|
|
261
|
+
getRequestContext,
|
|
262
|
+
formatTraceparent,
|
|
263
|
+
createTraceContext,
|
|
264
|
+
createAuditHook,
|
|
265
|
+
childSpan
|
|
266
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** A JSON-serialisable value — what a sanitised payload always reduces to. */
|
|
2
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
/** Tuning for `redact` / `sanitizePayload`. */
|
|
6
|
+
export interface SanitizeOptions {
|
|
7
|
+
/** Keys whose VALUE is masked. Default covers passwords, tokens, secrets… */
|
|
8
|
+
sensitiveKeys?: RegExp;
|
|
9
|
+
/** Cap on the serialised payload — anything larger collapses to a preview. Default 16 KB. */
|
|
10
|
+
maxBytes?: number;
|
|
11
|
+
/** Recursion limit — deeper nesting collapses to a marker. Default 20. */
|
|
12
|
+
maxDepth?: number;
|
|
13
|
+
}
|
|
14
|
+
/** The result of `measureSize`. */
|
|
15
|
+
export interface SizeMeasure {
|
|
16
|
+
/** Item count, when the value is a list (or a `{ items: [] }` page). */
|
|
17
|
+
resultSize: number | null;
|
|
18
|
+
/** Serialised byte length. */
|
|
19
|
+
responseBytes: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Deep-copy `value` into a JSON-safe shape: secret-named keys masked, binary
|
|
23
|
+
* blobs reduced to `{ _type, size }` metadata, branches past `maxDepth`
|
|
24
|
+
* truncated. Always returns a `JsonValue` — safe to store directly.
|
|
25
|
+
*/
|
|
26
|
+
export declare function redact(value: unknown, options?: SanitizeOptions): JsonValue;
|
|
27
|
+
/**
|
|
28
|
+
* Cap a `JsonValue` by serialised size — anything over `maxBytes` collapses to
|
|
29
|
+
* a `{ _truncated, _originalBytes, preview }` marker.
|
|
30
|
+
*/
|
|
31
|
+
export declare function truncatePreview(value: JsonValue, maxBytes?: number): JsonValue;
|
|
32
|
+
/**
|
|
33
|
+
* Sanitise an arbitrary payload for an audit row — `redact` then
|
|
34
|
+
* `truncatePreview`. `null` / `undefined` reduce to `null`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sanitizePayload(value: unknown, options?: SanitizeOptions): JsonValue | null;
|
|
37
|
+
/**
|
|
38
|
+
* Measure a result for the audit row — item count when it is a list (a bare
|
|
39
|
+
* array or a `{ items: [] }` page), and the serialised byte length.
|
|
40
|
+
*/
|
|
41
|
+
export declare function measureSize(value: unknown): SizeMeasure;
|
|
42
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/observability/sanitize.ts"],"names":[],"mappings":"AAMA,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,8BAA8B;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB;AASD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,SAAS,CAsC/E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,SAAoB,GAAG,SAAS,CAQzF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,eAAoB,GAC5B,SAAS,GAAG,IAAI,CAGlB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAYvD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C Trace Context — `traceparent` parsing, formatting and span chaining.
|
|
3
|
+
*
|
|
4
|
+
* `traceparent` format: `00-{traceId:32hex}-{spanId:16hex}-{flags:2hex}`.
|
|
5
|
+
* Spec: https://www.w3.org/TR/trace-context/
|
|
6
|
+
*
|
|
7
|
+
* A trace id is stable across every span of one logical request — it ties the
|
|
8
|
+
* front-end call, the HTTP handler and each nested tool call into one timeline.
|
|
9
|
+
* A span id is unique to a single operation within that trace.
|
|
10
|
+
*/
|
|
11
|
+
/** A point in a distributed trace — one trace, one span, an optional parent. */
|
|
12
|
+
export interface TraceContext {
|
|
13
|
+
/** 32-hex trace id — stable across every span of one logical request. */
|
|
14
|
+
traceId: string;
|
|
15
|
+
/** 16-hex span id — unique to this operation. */
|
|
16
|
+
spanId: string;
|
|
17
|
+
/** 16-hex id of the span that caused this one, when there is one. */
|
|
18
|
+
parentSpanId?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A fresh root trace — a new trace id, a new span id, no parent. */
|
|
21
|
+
export declare function createTraceContext(): TraceContext;
|
|
22
|
+
/**
|
|
23
|
+
* Parse a `traceparent` header into a `TraceContext`. The inbound span id
|
|
24
|
+
* becomes THIS context's `parentSpanId` and a fresh span id is minted — the
|
|
25
|
+
* receiver opens a new span under the caller's trace. Returns `null` for a
|
|
26
|
+
* missing or malformed header.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseTraceparent(header: string | null | undefined): TraceContext | null;
|
|
29
|
+
/** Render a `TraceContext` as a `traceparent` header value (sampled flag `01`). */
|
|
30
|
+
export declare function formatTraceparent(ctx: TraceContext): string;
|
|
31
|
+
/**
|
|
32
|
+
* The trace for an incoming request — the caller's `traceparent` continued
|
|
33
|
+
* when present and valid, else a fresh root trace.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveTraceContext(req: Request): TraceContext;
|
|
36
|
+
/**
|
|
37
|
+
* A child span of `parent` — the same trace id, a fresh span id, `parentSpanId`
|
|
38
|
+
* set to the parent's span. Open one per nested operation (e.g. each tool call
|
|
39
|
+
* inside an HTTP request).
|
|
40
|
+
*/
|
|
41
|
+
export declare function childSpan(parent: TraceContext): TraceContext;
|
|
42
|
+
//# sourceMappingURL=trace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/observability/trace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAaD,qEAAqE;AACrE,wBAAgB,kBAAkB,IAAI,YAAY,CAEjD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CASvF;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAM5D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache bridge — wire socket events into the TanStack Query cache.
|
|
3
|
+
*
|
|
4
|
+
* Transport-agnostic: works with ANY emitter exposing `on(event, handler) =>
|
|
5
|
+
* unsubscribe` — socket.io, stitchkit's `SocketClient`, an `EventTarget`
|
|
6
|
+
* wrapper. `TEvents` is a socket.io-style event map (`{ event: (payload) =>
|
|
7
|
+
* void }`, i.e. `ServerToClientEvents`) — so the whole family (all on
|
|
8
|
+
* socket.io) shares one cache-sync helper.
|
|
9
|
+
*
|
|
10
|
+
* Each handler updates `queryClient` in response to a typed server event.
|
|
11
|
+
* `markFresh(key)` (called from a mutation's onSuccess) records a key as
|
|
12
|
+
* just-mutated; a handler calls `ctx.isFresh(key)` to skip a stale socket echo
|
|
13
|
+
* and avoid a double update (mutation result + socket event).
|
|
14
|
+
*/
|
|
15
|
+
import type { QueryClient, QueryKey } from '@tanstack/react-query';
|
|
16
|
+
/** Minimal emitter — subscribe to an event, get an unsubscribe back. */
|
|
17
|
+
export interface CacheBridgeSocket<TEvents> {
|
|
18
|
+
on<K extends keyof TEvents & string>(event: K, handler: TEvents[K]): () => void;
|
|
19
|
+
}
|
|
20
|
+
export interface CacheBridgeContext {
|
|
21
|
+
queryClient: QueryClient;
|
|
22
|
+
/** True if `markFresh(key)` was called for this key within `freshWindow` ms. */
|
|
23
|
+
isFresh(key: QueryKey): boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Payload of a socket.io-style event entry `(payload) => void`. */
|
|
26
|
+
type EventPayload<T> = T extends (payload: infer P) => unknown ? P : never;
|
|
27
|
+
export type CacheBridgeHandler<TPayload> = (data: TPayload, ctx: CacheBridgeContext) => void;
|
|
28
|
+
export type CacheBridgeHandlers<TEvents> = {
|
|
29
|
+
[K in keyof TEvents & string]?: CacheBridgeHandler<EventPayload<TEvents[K]>>;
|
|
30
|
+
};
|
|
31
|
+
export interface CacheBridgeConfig<TEvents> {
|
|
32
|
+
socket: CacheBridgeSocket<TEvents>;
|
|
33
|
+
/**
|
|
34
|
+
* The TanStack Query client, or a thunk returning it. A thunk is resolved
|
|
35
|
+
* lazily at `connect()` time — keeps the bridge SSR-safe when it lives in a
|
|
36
|
+
* module-level `const` (client components are still evaluated on the server,
|
|
37
|
+
* where a browser-only `QueryClient` does not exist yet).
|
|
38
|
+
*/
|
|
39
|
+
queryClient: QueryClient | (() => QueryClient);
|
|
40
|
+
handlers: CacheBridgeHandlers<TEvents>;
|
|
41
|
+
/** Window (ms) after `markFresh()` during which `isFresh()` returns true. Default 500. */
|
|
42
|
+
freshWindow?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface CacheBridge {
|
|
45
|
+
connect(): void;
|
|
46
|
+
disconnect(): void;
|
|
47
|
+
/** Mark a query key as just-mutated. Call from a mutation's onSuccess so
|
|
48
|
+
* handlers can skip a stale socket echo via `ctx.isFresh()`. */
|
|
49
|
+
markFresh(key: QueryKey): void;
|
|
50
|
+
}
|
|
51
|
+
export declare function createCacheBridge<TEvents>(config: CacheBridgeConfig<TEvents>): CacheBridge;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=cache-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-bridge.d.ts","sourceRoot":"","sources":["../../src/react/cache-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE,wEAAwE;AACxE,MAAM,WAAW,iBAAiB,CAAC,OAAO;IACxC,EAAE,CAAC,CAAC,SAAS,MAAM,OAAO,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;CACjF;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,gFAAgF;IAChF,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC;CACjC;AAED,oEAAoE;AACpE,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC;AAE3E,MAAM,MAAM,kBAAkB,CAAC,QAAQ,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAE7F,MAAM,MAAM,mBAAmB,CAAC,OAAO,IAAI;KACxC,CAAC,IAAI,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7E,CAAC;AAEF,MAAM,WAAW,iBAAiB,CAAC,OAAO;IACxC,MAAM,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACnC;;;;;OAKG;IACH,WAAW,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,CAAC;IAC/C,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACvC,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,IAAI,IAAI,CAAC;IAChB,UAAU,IAAI,IAAI,CAAC;IACnB;qEACiE;IACjE,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;CAChC;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,WAAW,CA8C1F"}
|