stitchkit 0.9.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/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  emitResult,
5
5
  parseCliArgs,
6
6
  pollUntilDone
7
- } from "./index-13psnhhe.js";
7
+ } from "./index-f39j6twc.js";
8
8
  import"./index-0ed3bx43.js";
9
9
  import"./index-jgpsd7dy.js";
10
10
  import"./index-tm7dqzxc.js";
@@ -5,9 +5,9 @@ export type Transport = (typeof ALL_TRANSPORTS)[number];
5
5
  /**
6
6
  * The transport tag on `ctx.source`. The four built-ins keep autocomplete, but
7
7
  * the union is **open** (`string & {}`) so a bring-your-own transport — e.g. a
8
- * raw-WebSocket lane driving a contract through `createContractDispatcher` can
9
- * tag its own calls (`source: 'local-ws'`). `source` is transport-only and
10
- * carries no framework behaviour (→ ADR 0002 / ADR 0027).
8
+ * raw-WebSocket lane that runs a contract through the app's own dispatch loop
9
+ * can tag its own calls (`source: 'local-ws'`). `source` is transport-only and
10
+ * carries no framework behaviour (→ ADR 0002).
11
11
  */
12
12
  export type TransportSource = 'http' | 'mcp' | 'agent' | 'cli' | (string & {});
13
13
  interface EndpointDefBase {
@@ -40,10 +40,10 @@ interface EndpointDefBase {
40
40
  * The core attaches **no** behaviour to it (it stays generic — ADR 0002): it
41
41
  * rides through to `MethodDef.idempotent`, where a transport that can retry
42
42
  * reads it. A reliable bring-your-own-transport lane (e.g. a raw-WebSocket
43
- * client over `createContractDispatcher`) replays an `idempotent` call after a
44
- * reconnect — that is the durability guarantee — while a non-idempotent one is
45
- * rejected rather than re-sent (a duplicate would be a second side effect).
46
- * Unset means "unknown" — a careful transport treats it as non-idempotent.
43
+ * client) replays an `idempotent` call after a reconnect — that is the
44
+ * durability guarantee — while a non-idempotent one is rejected rather than
45
+ * re-sent (a duplicate would be a second side effect). Unset means "unknown" —
46
+ * a careful transport treats it as non-idempotent.
47
47
  */
48
48
  idempotent?: boolean;
49
49
  /**
@@ -129,6 +129,17 @@ export interface RuntimeContext {
129
129
  input: unknown;
130
130
  file?: File;
131
131
  source: TransportSource;
132
+ /**
133
+ * The raw Web `Request`, its parsed `URL` and `Headers` — set on the HTTP
134
+ * transport (and reachable in every lifecycle hook, including `onError` on a
135
+ * validation failure). Absent on the non-HTTP transports (MCP / agent / CLI /
136
+ * a bring-your-own lane), which carry no `Request`, so they are optional and a
137
+ * reader narrows them. Web Fetch types only — the core stays Fetch-clean
138
+ * (→ ADR 0013).
139
+ */
140
+ req?: Request;
141
+ url?: URL;
142
+ headers?: Headers;
132
143
  traceId?: string;
133
144
  spanId?: string;
134
145
  ipAddress?: string;
@@ -140,6 +151,11 @@ export interface HandlerContext<TParams = undefined, TInput = undefined> {
140
151
  input: TInput;
141
152
  file?: File;
142
153
  source: TransportSource;
154
+ /** Raw Web `Request` / `URL` / `Headers` — set on the HTTP transport, absent
155
+ * on the tool transports (see {@link RuntimeContext}). */
156
+ req?: Request;
157
+ url?: URL;
158
+ headers?: Headers;
143
159
  traceId?: string;
144
160
  spanId?: string;
145
161
  ipAddress?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/contract/define.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/contract/define.ts"],"names":[],"mappings":"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"}
@@ -1000,4 +1000,4 @@ function safeInputSchema(tool) {
1000
1000
  }
1001
1001
  }
1002
1002
 
1003
- export { coerceJsonArgs, toolResultFromError, executeToolMethod, flattenDiscriminatedUnion, collectTools, createToolRunner, formatToolError, fetchGuarded, readCapped, parseCliArgs, DEFAULT_EXIT_CODES, emitResult, pollUntil, pollUntilDone, createCli };
1003
+ export { coerceJsonArgs, toolResultFromError, flattenDiscriminatedUnion, collectTools, createToolRunner, formatToolError, fetchGuarded, readCapped, parseCliArgs, DEFAULT_EXIT_CODES, emitResult, pollUntil, pollUntilDone, createCli };
@@ -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
- } from "./index-p9m9c0jw.js";
13
+ resolveTraceId,
14
+ setRequestEndpoint
15
+ } from "./index-fwqnkc90.js";
15
16
  import {
16
17
  __require,
17
18
  isUnsafeKey,
@@ -343,38 +344,15 @@ async function readJsonBody(req) {
343
344
  badRequest("Invalid JSON body");
344
345
  }
345
346
  }
346
- async function buildContext(req, url, method, pathParams, traceId, clientIp, maxUploadBytes) {
347
- const parsedParams = method.paramsSchema ? method.paramsSchema.parse(pathParams) : undefined;
348
- let parsedInput;
349
- let file;
350
- if (method.multipart) {
351
- const cap = method.maxUploadBytes ?? maxUploadBytes;
352
- const multipart = await parseMultipart(req, method.multipart, method.inputSchema, cap);
353
- parsedInput = multipart.fields;
354
- file = multipart.file;
355
- } else if (method.inputSchema) {
356
- if (req.method === "GET") {
357
- parsedInput = method.inputSchema.parse(parseQueryParams(url));
358
- } else if (req.method === "DELETE") {
359
- const ct = req.headers.get("content-type");
360
- if (ct?.includes("application/json")) {
361
- parsedInput = method.inputSchema.parse(await readJsonBody(req));
362
- } else {
363
- parsedInput = method.inputSchema.parse(parseQueryParams(url));
364
- }
365
- } else {
366
- parsedInput = method.inputSchema.parse(await readJsonBody(req));
367
- }
368
- }
347
+ function buildBaseContext(req, url, pathParams, traceId, clientIp) {
369
348
  const safePathParams = {};
370
349
  for (const [k, v] of Object.entries(pathParams)) {
371
350
  if (!RESERVED_KEYS.has(k) && !isUnsafeKey(k))
372
351
  safePathParams[k] = v;
373
352
  }
374
353
  return {
375
- params: parsedParams,
376
- input: parsedInput,
377
- ...file && { file },
354
+ params: pathParams,
355
+ input: undefined,
378
356
  source: "http",
379
357
  req,
380
358
  url,
@@ -384,6 +362,27 @@ async function buildContext(req, url, method, pathParams, traceId, clientIp, max
384
362
  ...getClientInfo(req, clientIp)
385
363
  };
386
364
  }
365
+ async function parseRequestInto(ctx, req, url, method, maxUploadBytes) {
366
+ if (method.paramsSchema) {
367
+ ctx.params = method.paramsSchema.parse(ctx.params);
368
+ }
369
+ if (method.multipart) {
370
+ const cap = method.maxUploadBytes ?? maxUploadBytes;
371
+ const multipart = await parseMultipart(req, method.multipart, method.inputSchema, cap);
372
+ ctx.input = multipart.fields;
373
+ if (multipart.file)
374
+ ctx.file = multipart.file;
375
+ } else if (method.inputSchema) {
376
+ if (req.method === "GET") {
377
+ ctx.input = method.inputSchema.parse(parseQueryParams(url));
378
+ } else if (req.method === "DELETE") {
379
+ const ct = req.headers.get("content-type");
380
+ ctx.input = ct?.includes("application/json") ? method.inputSchema.parse(await readJsonBody(req)) : method.inputSchema.parse(parseQueryParams(url));
381
+ } else {
382
+ ctx.input = method.inputSchema.parse(await readJsonBody(req));
383
+ }
384
+ }
385
+ }
387
386
  function buildErrorContext(req, url, traceId, clientIp) {
388
387
  return {
389
388
  params: undefined,
@@ -447,8 +446,8 @@ function levelForStatus(status) {
447
446
  return "warn";
448
447
  return "info";
449
448
  }
450
- function buildLogFields(method, path, status, durationMs, traceId) {
451
- return { traceId, method, path, status, durationMs };
449
+ function buildLogFields(method, path, status, durationMs, traceId, errorCode) {
450
+ return { traceId, method, path, status, durationMs, ...errorCode && { errorCode } };
452
451
  }
453
452
  function formatMs(ms) {
454
453
  if (ms >= 1000)
@@ -492,20 +491,21 @@ function logIncoming(req, pathname, traceId, ipAddress) {
492
491
  }
493
492
  return log;
494
493
  }
495
- function logOutgoing(req, pathname, status, log, ipAddress) {
494
+ function logOutgoing(req, pathname, status, log, ipAddress, errorCode) {
496
495
  const ms = elapsedMs(log.startTime);
497
496
  if (isProd) {
498
497
  console.log(JSON.stringify({
499
498
  ts: new Date().toISOString(),
500
499
  level: levelForStatus(status),
501
500
  msg: `${req.method} ${pathname} ${status}`,
502
- ...buildLogFields(req.method, pathname, status, Math.round(ms), log.traceId),
501
+ ...buildLogFields(req.method, pathname, status, Math.round(ms), log.traceId, errorCode),
503
502
  ip: ipAddress
504
503
  }));
505
504
  return;
506
505
  }
507
506
  const mc = METHOD_COLOR[req.method] ?? c.dim;
508
- console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${log.traceId}${c.reset} ${c.cyan}←${c.reset} ${safePath(pathname)} ${statusColor(status)}${status}${c.reset} ${durationColor(ms)}${formatMs(ms)}${c.reset} ${ipLabel(ipAddress ?? "")}`);
507
+ const code = errorCode ? ` ${c.red}${errorCode}${c.reset}` : "";
508
+ console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${log.traceId}${c.reset} ${c.cyan}←${c.reset} ${safePath(pathname)} ${statusColor(status)}${status}${c.reset}${code} ${durationColor(ms)}${formatMs(ms)}${c.reset} ${ipLabel(ipAddress ?? "")}`);
509
509
  }
510
510
 
511
511
  // src/server/create.ts
@@ -534,15 +534,15 @@ function createHandler(config) {
534
534
  });
535
535
  reqLog = { traceId, startTime: performance.now() };
536
536
  }
537
- const logDone = (status) => {
537
+ const logDone = (status, errorCode) => {
538
538
  if (!reqLog)
539
539
  return;
540
540
  if (useDefaultLog)
541
- logOutgoing(req, url.pathname, status, reqLog, ipAddress);
541
+ logOutgoing(req, url.pathname, status, reqLog, ipAddress, errorCode);
542
542
  if (customLogger) {
543
543
  const durationMs = Math.round(elapsedMs(reqLog.startTime));
544
544
  const level = levelForStatus(status);
545
- customLogger[level](`${req.method} ${url.pathname} ${status} ${durationMs}ms`, buildLogFields(req.method, url.pathname, status, durationMs, reqLog.traceId));
545
+ customLogger[level](`${req.method} ${url.pathname} ${status}${errorCode ? ` ${errorCode}` : ""} ${durationMs}ms`, buildLogFields(req.method, url.pathname, status, durationMs, reqLog.traceId, errorCode));
546
546
  }
547
547
  };
548
548
  const respondError = async (err, errCtx, endpoint) => {
@@ -557,7 +557,7 @@ function createHandler(config) {
557
557
  } catch {}
558
558
  }
559
559
  const appErr = normalizeError(err);
560
- logDone(appErr.status);
560
+ logDone(appErr.status, appErr.code);
561
561
  return json(appErr.toJSON(), appErr.status, cors, req);
562
562
  };
563
563
  if (cors && req.method === "OPTIONS") {
@@ -600,9 +600,10 @@ function createHandler(config) {
600
600
  return respondError(new AppError("NOT_FOUND", "Not found", 404));
601
601
  }
602
602
  const { method, pathParams, groupHooks } = match;
603
- let ctx;
603
+ const ctx = buildBaseContext(req, url, pathParams, traceId, clientIp);
604
+ setRequestEndpoint(method.serviceName, method.key);
604
605
  try {
605
- ctx = await buildContext(req, url, method, pathParams, traceId, clientIp, config.maxUploadBytes);
606
+ await parseRequestInto(ctx, req, url, method, config.maxUploadBytes);
606
607
  if (hooks?.beforeHandle) {
607
608
  await hooks.beforeHandle(ctx, method);
608
609
  }
package/dist/node.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createImplement,
4
4
  createSocketIOServer,
5
5
  implement
6
- } from "./index-zshrc6kx.js";
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-p9m9c0jw.js";
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,CA+F9D"}
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"}
@@ -1,4 +1,5 @@
1
1
  import type { TransportSource } from '../contract';
2
+ import type { JsonValue } from './sanitize';
2
3
  import { type TraceContext } from './trace';
3
4
  /** Everything known about the request in flight. */
4
5
  export interface RequestContext {
@@ -18,10 +19,25 @@ export interface RequestContext {
18
19
  userAgent?: string;
19
20
  /** Resolved user id — set late, once auth has run. */
20
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>;
21
36
  /** Error outcome — set late, by the error handler. */
22
37
  error?: {
23
38
  code?: string;
24
39
  message?: string;
40
+ details?: JsonValue;
25
41
  };
26
42
  }
27
43
  /** Run `fn` with `ctx` as the active request context. */
@@ -38,13 +54,33 @@ export declare function getUserId(): string | undefined;
38
54
  * from the auth hook.
39
55
  */
40
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;
41
74
  /**
42
75
  * Record the error outcome on the active context. Call this from the error
43
- * handler — the audit hook reads it when the request completes.
76
+ * handler — the audit hook reads it when the request completes. Optional
77
+ * `details` carries structure the message string flattens (e.g. the failing
78
+ * Zod issues) onto `RequestEvent.errorDetail`.
44
79
  */
45
80
  export declare function setRequestError(error: {
46
81
  code?: string;
47
82
  message?: string;
83
+ details?: JsonValue;
48
84
  }): void;
49
85
  /** Options for `wrapInRequestContext`. */
50
86
  export interface WrapRequestContextOptions {
@@ -1 +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,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"}
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. */
@@ -31,6 +45,12 @@ export interface RequestEvent {
31
45
  errorCode?: string;
32
46
  /** Error message — failures only. */
33
47
  errorMessage?: string;
48
+ /**
49
+ * Structured error detail — failures only, when the error handler recorded it
50
+ * via `setRequestError({ details })` (e.g. the failing validation issues the
51
+ * `errorMessage` string flattens). HTTP path only.
52
+ */
53
+ errorDetail?: JsonValue;
34
54
  /** Sanitised request payload — the HTTP body or the tool arguments. */
35
55
  payload: JsonValue | null;
36
56
  /** Item count of the result, when it is a list. */
@@ -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,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-031q8xmx.js";
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,
@@ -148,6 +152,7 @@ function createAuditHook(config) {
148
152
  durationMs,
149
153
  errorCode: ctx.error?.code,
150
154
  errorMessage: ctx.error?.message,
155
+ ...ctx.error?.details !== undefined && { errorDetail: ctx.error.details },
151
156
  payload: sanitizePayload(body, sanitize),
152
157
  resultSize: null,
153
158
  responseBytes: 0,
@@ -162,14 +167,17 @@ function createAuditHook(config) {
162
167
  };
163
168
  };
164
169
  const toolCall = {
165
- afterToolCall: (toolName, args, result, durationMs, context) => {
166
- const parent = getRequestContext()?.trace;
167
- const span = parent ? childSpan(parent) : createTraceContext();
170
+ afterToolCall: (toolName, args, result, durationMs, context, endpoint) => {
171
+ const requestCtx = getRequestContext();
172
+ const span = requestCtx ? childSpan(requestCtx.trace) : createTraceContext();
168
173
  const measure = result.ok ? measureSize(result.data) : { resultSize: null, responseBytes: 0 };
169
174
  emit({
170
175
  source: context.source,
171
176
  method: "TOOL",
172
177
  path: `/${context.source}/${toolName}`,
178
+ serviceName: endpoint.serviceName,
179
+ action: endpoint.key,
180
+ ...requestCtx?.dimensions !== undefined && { dimensions: requestCtx.dimensions },
173
181
  toolName,
174
182
  traceId: span.traceId,
175
183
  spanId: span.spanId,
@@ -198,6 +206,8 @@ export {
198
206
  truncatePreview,
199
207
  setRequestUser,
200
208
  setRequestError,
209
+ setRequestEndpoint,
210
+ setRequestDimensions,
201
211
  sanitizePayload,
202
212
  runWithRequestContext,
203
213
  resolveTraceContext,
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Transport-agnostic: wrap it around any pub/sub. `createSocketIOClient`'s
11
11
  * `retain` option uses it internally, and a bring-your-own-transport lane (a raw
12
- * WebSocket driving a contract through `createContractDispatcher`) can use it
12
+ * WebSocket driving a contract through the app's own dispatch loop) can use it
13
13
  * directly for its own event channel. Browser-safe — no Node built-ins.
14
14
  */
15
15
  export interface RetainedTopics<Events extends Record<string, unknown>> {
@@ -5,6 +5,23 @@
5
5
  import { type RuntimeContext } from '../contract';
6
6
  import { type ClientIpOptions } from './request';
7
7
  import type { MethodDef } from './types';
8
- export declare function buildContext(req: Request, url: URL, method: MethodDef, pathParams: Record<string, string>, traceId: string, clientIp: ClientIpOptions, maxUploadBytes?: number): Promise<RuntimeContext>;
8
+ /**
9
+ * Assemble everything knowable from the URL alone — path params, the request,
10
+ * trace id and client info — **before** any schema parsing. Bound first so a
11
+ * later validation failure still hands `onError` the path params and the
12
+ * request (an empty context loses both); `parseRequestInto` then enriches it
13
+ * with the schema-validated `params` / `input`.
14
+ *
15
+ * `params` starts as the raw matched path params (a property of the URL, known
16
+ * the moment the route matched) and is replaced by the validated value when the
17
+ * endpoint declares a `paramsSchema`.
18
+ */
19
+ export declare function buildBaseContext(req: Request, url: URL, pathParams: Record<string, string>, traceId: string, clientIp: ClientIpOptions): RuntimeContext;
20
+ /**
21
+ * Parse `params` / `input` against the endpoint schemas and write them onto an
22
+ * already-assembled base context. A validation failure throws a `ZodError`
23
+ * here — the base context (path params, request) is preserved for `onError`.
24
+ */
25
+ export declare function parseRequestInto(ctx: RuntimeContext, req: Request, url: URL, method: MethodDef, maxUploadBytes?: number): Promise<void>;
9
26
  export declare function buildErrorContext(req: Request, url: URL, traceId: string, clientIp: ClientIpOptions): RuntimeContext;
10
27
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAG9D,OAAO,EAAE,KAAK,eAAe,EAAmC,MAAM,WAAW,CAAC;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAmCzC,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,eAAe,EACzB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,CA8CzB;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,eAAe,GACxB,cAAc,CAWhB"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAG9D,OAAO,EAAE,KAAK,eAAe,EAAmC,MAAM,WAAW,CAAC;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAmCzC;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,eAAe,GACxB,cAAc,CAmBhB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,SAAS,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,eAAe,GACxB,cAAc,CAWhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"AA8BA,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,CAgNxF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,uBAsBnD"}
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"}
@@ -12,7 +12,7 @@ import {
12
12
  socketIoLane,
13
13
  staticRoute,
14
14
  webSocketLane
15
- } from "../index-zshrc6kx.js";
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-p9m9c0jw.js";
53
+ } from "../index-fwqnkc90.js";
56
54
  import {
57
55
  isRecord
58
56
  } from "../index-tm7dqzxc.js";
@@ -3,12 +3,13 @@ export declare function elapsedMs(startTime: number): number;
3
3
  /** Map an HTTP status to a log level. */
4
4
  export declare function levelForStatus(status: number): 'error' | 'warn' | 'info';
5
5
  /** The structured fields shared by every completed-request log line. */
6
- export declare function buildLogFields(method: string, path: string, status: number, durationMs: number, traceId: string): {
6
+ export declare function buildLogFields(method: string, path: string, status: number, durationMs: number, traceId: string, errorCode?: string): {
7
7
  traceId: string;
8
8
  method: string;
9
9
  path: string;
10
10
  status: number;
11
11
  durationMs: number;
12
+ errorCode?: string;
12
13
  };
13
14
  export interface RequestLog {
14
15
  traceId: string;
@@ -18,5 +19,5 @@ export declare function shouldLog(pathname: string, method: string): boolean;
18
19
  /** Open the timing window for a request. Development prints a `→` line. */
19
20
  export declare function logIncoming(req: Request, pathname: string, traceId: string, ipAddress?: string): RequestLog;
20
21
  /** Close a request. Development: `←` line. Production: one structured JSON line. */
21
- export declare function logOutgoing(req: Request, pathname: string, status: number, log: RequestLog, ipAddress?: string): void;
22
+ export declare function logOutgoing(req: Request, pathname: string, status: number, log: RequestLog, ipAddress?: string, errorCode?: string): void;
22
23
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"AAwCA,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAoBD,yCAAyC;AACzC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAIxE;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAEvF;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGnE;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,UAAU,CASZ;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,UAAU,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAoBN"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"AAwCA,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAoBD,yCAAyC;AACzC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAIxE;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAEA;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGnE;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,UAAU,CASZ;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,UAAU,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CA4BN"}
package/dist/tools.d.ts CHANGED
@@ -4,7 +4,6 @@ export { type CliConfig, createCli } from './tools/cli';
4
4
  export type { ExitCodeMap } from './tools/cli-format';
5
5
  export type { CliWaitConfig } from './tools/cli-wait';
6
6
  export { coerceJsonArgs } from './tools/coerce';
7
- export { type ContractDispatcher, type ContractDispatcherConfig, createContractDispatcher, } from './tools/dispatch';
8
7
  export type { ToolCallHooks, ToolLifecycle, ToolResult } from './tools/execute';
9
8
  export { flattenDiscriminatedUnion } from './tools/flatten';
10
9
  export { buildToolManifest, type ToolManifestEntry } from './tools/manifest';
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,KAAK,kBAAkB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,KAAK,kBAAkB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/tools.js CHANGED
@@ -8,14 +8,13 @@ import {
8
8
  collectTools,
9
9
  createCli,
10
10
  createToolRunner,
11
- executeToolMethod,
12
11
  fetchGuarded,
13
12
  flattenDiscriminatedUnion,
14
13
  formatToolError,
15
14
  pollUntil,
16
15
  readCapped,
17
16
  toolResultFromError
18
- } from "./index-13psnhhe.js";
17
+ } from "./index-f39j6twc.js";
19
18
  import {
20
19
  toJsonSchema
21
20
  } from "./index-0ed3bx43.js";
@@ -69,30 +68,6 @@ function mountAgent(services, config = {}) {
69
68
  }
70
69
  return tools;
71
70
  }
72
- // src/tools/dispatch.ts
73
- function createContractDispatcher(services, config) {
74
- const list = Array.isArray(services) ? services : [services];
75
- const index = new Map;
76
- for (const service of list) {
77
- for (const [name, method] of Object.entries(service.methods)) {
78
- if (index.has(name)) {
79
- throw new Error(`createContractDispatcher: duplicate method "${name}" across services`);
80
- }
81
- index.set(name, method);
82
- }
83
- }
84
- return {
85
- methods: [...index.keys()],
86
- dispatch(method, args, context) {
87
- const target = index.get(method);
88
- if (!target) {
89
- return Promise.resolve(toolResultFromError(new AppError("NOT_FOUND", `Unknown method: ${method}`, 404)));
90
- }
91
- const ctx = { ...config.context, ...context, source: config.source };
92
- return executeToolMethod(target, method, args, ctx, config.hooks, config.lifecycle, config.coerceJsonArgs ?? false);
93
- }
94
- };
95
- }
96
71
  // src/tools/manifest.ts
97
72
  function buildToolManifest(tools) {
98
73
  return tools.map((t) => {
@@ -1311,7 +1286,6 @@ export {
1311
1286
  createToolkit,
1312
1287
  createStdioMcpServer,
1313
1288
  createMcpHandler,
1314
- createContractDispatcher,
1315
1289
  createCli,
1316
1290
  collectTools,
1317
1291
  coerceJsonArgs,
package/llms-full.txt CHANGED
@@ -1876,37 +1876,20 @@ Notes:
1876
1876
 
1877
1877
  Sometimes the transport is neither HTTP nor Socket.IO — a desktop app whose UI
1878
1878
  webview talks to its own local Bun sidecar over a raw WebSocket, an IPC channel,
1879
- a queue worker. You still want one contract: a `defineContract` with typed
1880
- client/server, Zod validation and a typed error envelope, not a hand-rolled
1881
- method registry alongside it.
1879
+ a queue worker. You can still drive it from one `defineContract`: share the
1880
+ contract's Zod schemas to validate each inbound frame, run your handlers, and
1881
+ reuse the contract metadata below. You own **both** the wire (framing,
1882
+ handshake, reconnect) *and* the per-call validate-run loop — stitchkit ships the
1883
+ contract and its metadata, not a transport engine. A reliable-RPC-over-raw-
1884
+ WebSocket engine would be a competing WebSocket transport
1885
+ ([ADR 0008](../decisions/0008-thin-wrappers.md)); the wire stays yours.
1882
1886
 
1883
- stitchkit ships the **executor**, not the transport. `createContractDispatcher`
1884
- runs a contract method by its key through the *same* core as the MCP / agent
1885
- mounts — same validation, the same `{ ok, data } | { ok: false, code, … }`
1886
- envelope, the same hooks and `beforeHandle` scope gate. You own the wire (framing,
1887
- handshake, reconnect) and call `dispatch` per inbound frame.
1887
+ Two pieces of the contract carry straight over to a bring-your-own lane:
1888
1888
 
1889
- ```ts
1890
- import { createContractDispatcher } from 'stitchkit/tools'
1891
- import { implement } from 'stitchkit/server'
1892
-
1893
- const service = implement(runtimeContract, { 'tasks.setDone': (ctx) => doIt(ctx.input), … })
1894
-
1895
- const dispatcher = createContractDispatcher(service, { source: 'local-ws' })
1896
-
1897
- // In your raw-WebSocket server, per `{ id, method, params }` frame:
1898
- ws.onmessage = async (frame) => {
1899
- const { id, method, params } = JSON.parse(frame.data)
1900
- const result = await dispatcher.dispatch(method, params) // validates + runs
1901
- ws.send(JSON.stringify({ id, ...result })) // { ok, data } | { ok:false, code, … }
1902
- }
1903
- ```
1904
-
1905
- `dispatch` never throws for a normal call — a handler error becomes a failed
1906
- result, and an unknown method is a `NOT_FOUND` result. Pass `hooks`
1907
- (`beforeToolCall` / `afterToolCall`) and `lifecycle.beforeHandle` to audit and
1908
- scope-guard exactly like the other transports. Tag calls with your own
1909
- `source` — `TransportSource` is an open union.
1889
+ - **`source` is an open tag.** `TransportSource` is `'http' | 'mcp' | 'agent' |
1890
+ 'cli' | (string & {})`, so a handler or hook can tell your transport's calls
1891
+ apart tag them `source: 'local-ws'` and read `ctx.source`.
1892
+ - **`idempotent` drives replay-on-reconnect** — see below.
1910
1893
 
1911
1894
  ### Durability — `idempotent` + replay
1912
1895
 
@@ -1932,8 +1915,9 @@ also catches up on the latest pushed state.
1932
1915
 
1933
1916
  This is deliberately *not* a reliable-RPC engine — that would be a competing
1934
1917
  WebSocket transport ([ADR 0008](../decisions/0008-thin-wrappers.md)). stitchkit
1935
- gives you the executor and the metadata; the wire stays yours. See
1936
- [ADR 0027](../decisions/0027-transport-neutral-contract-execution.md).
1918
+ gives you the contract and the metadata (`idempotent`, the open `source` tag,
1919
+ `createRetainedTopics`); the wire and the per-call execution stay yours. See
1920
+ [ADR 0028](../decisions/0028-revert-contract-dispatcher.md).
1937
1921
 
1938
1922
 
1939
1923
  ==============================================================================
@@ -2272,11 +2256,13 @@ queryable across all three:
2272
2256
  |-------|-------|
2273
2257
  | `source` | `http` \| `mcp` \| `agent` |
2274
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 |
2275
2260
  | `toolName` | tool calls only |
2261
+ | `dimensions` | app-defined domain dimensions (tenant / project / entity id) — see [request context](#request-context) |
2276
2262
  | `traceId` / `spanId` / `parentSpanId` | [W3C trace context](#trace-context) |
2277
2263
  | `ok` / `statusCode` | outcome — real HTTP status, or `200`/`400` for a tool |
2278
2264
  | `durationMs` / `startedAt` | timing |
2279
- | `errorCode` / `errorMessage` | failures only |
2265
+ | `errorCode` / `errorMessage` / `errorDetail` | failures only — `errorDetail` carries the structure the message flattens (e.g. Zod issues) |
2280
2266
  | `payload` | the request body / tool arguments — sanitised |
2281
2267
  | `resultSize` / `responseBytes` | result item count + serialised size |
2282
2268
  | `userId` / `ipAddress` / `userAgent` | identity |
@@ -2295,15 +2281,36 @@ Bun.serve({
2295
2281
  })
2296
2282
  ```
2297
2283
 
2298
- Two fields are filled in late the resolved user, and the error outcome. Set
2299
- them from the hooks that know:
2284
+ Some fields are filled in late. Set them from the hooks that know:
2300
2285
 
2301
2286
  ```ts
2302
- import { setRequestError, setRequestUser } from 'stitchkit/observability'
2287
+ import {
2288
+ setRequestDimensions,
2289
+ setRequestError,
2290
+ setRequestUser,
2291
+ } from 'stitchkit/observability'
2303
2292
 
2304
2293
  createAuthHook({ /* … */ inject: (ctx, user) => user && setRequestUser(user.id) })
2305
2294
  // in your onError hook:
2306
- 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 })
2307
2314
  ```
2308
2315
 
2309
2316
  Make the framework router share this trace id — so request logs and your
@@ -2394,14 +2401,15 @@ outcome and the duration, neither of which exists before the handler runs.
2394
2401
 
2395
2402
  ### Keying a row on (service, action)
2396
2403
 
2397
- For a per-endpoint audit row keyed by **service** and **action**, read the
2398
- endpoint identity off the `MethodDef` the hook receives — `endpoint.serviceName`
2399
- (the contract prefix) and `endpoint.key` (the endpoint key, e.g. `updatePartial`).
2400
- They are stable and always present (→ ADR 0022); the action is not in the URL and
2401
- `toolName` is absent on HTTP-only endpoints, so this is the only reliable pair.
2402
- `afterHandle` also gives you the handler `result` so it, not `createAuditHook`'s
2403
- HTTP wrapper (which never sees the response body), is the home for a rich mutation
2404
- audit that records output:
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:
2405
2413
 
2406
2414
  ```ts
2407
2415
  hooks: {
@@ -3058,7 +3066,6 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3058
3066
  | `mountMcp` | function | add contract tools to an existing `McpServer` — [guide](../guide/mcp-and-agents.md#mountmcp) |
3059
3067
  | `implementRemote` | function | bind a contract to a remote HTTP API — [guide](../guide/mcp-and-agents.md#proxying-a-remote-api--implementremote) |
3060
3068
  | `mountAgent` | function | a Vercel AI SDK `ToolSet` from a service — [guide](../guide/mcp-and-agents.md#ai-agents--mountagent) |
3061
- | `createContractDispatcher` | function | run a contract over a bring-your-own transport — [guide](../guide/realtime.md#bring-your-own-transport) |
3062
3069
  | `createCli` | function | a command-line program from contracts — [guide](../guide/cli.md) (also on `stitchkit/cli`) |
3063
3070
  | `createToolkit` | function | context-typed tool mounts — [guide](../guide/cli.md#typed-context) |
3064
3071
  | `mountViewFile` | function | a native multimodal "view file" MCP tool |
@@ -3070,8 +3077,6 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3070
3077
  | `ImplementRemoteOptions` | _type_ | options for `implementRemote` |
3071
3078
  | `McpMountConfig` | _type_ | config for `mountMcp` |
3072
3079
  | `AgentMountConfig` | _type_ | config for `mountAgent` |
3073
- | `ContractDispatcher` | _type_ | the `createContractDispatcher` handle |
3074
- | `ContractDispatcherConfig` | _type_ | config for `createContractDispatcher` |
3075
3080
  | `AgentContext` | _type_ | the context merged into agent tool handlers |
3076
3081
  | `CliConfig` | _type_ | config for `createCli` |
3077
3082
  | `CliWaitConfig` | _type_ | `--wait` polling config |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",
@@ -155,8 +155,8 @@
155
155
  "@tanstack/react-query": "5.101.0",
156
156
  "@types/bun": "^1.3.14",
157
157
  "@types/json-schema": "^7.0.15",
158
- "@types/react": "^19.2.16",
159
- "ai": "^6.0.196",
158
+ "@types/react": "^19.2.17",
159
+ "ai": "^6.0.207",
160
160
  "react": "^19.2.7",
161
161
  "react-query-kit": "^3.3.4",
162
162
  "socket.io": "^4.8.3",
@@ -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 };
@@ -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 };
@@ -1,65 +0,0 @@
1
- /**
2
- * Drive a contract over a **bring-your-own transport**.
3
- *
4
- * The HTTP server, the MCP mount and the agent mount all run a contract method
5
- * the same way: slice the flat args, validate `params` / `input` against the Zod
6
- * schemas, run the handler, validate the output, and return a typed result
7
- * envelope. `createContractDispatcher` exposes exactly that core for a transport
8
- * stitchkit does not own — a raw WebSocket lane (webview ↔ a local sidecar), an
9
- * IPC channel, a queue worker. The transport owns the wire (framing, handshake,
10
- * reconnect); the dispatcher owns "given a method name and args, run it safely."
11
- *
12
- * This is the same execution path as `mountMcp` / `mountAgent` (it shares
13
- * `executeToolMethod`), so a bespoke transport gets identical validation and the
14
- * identical `{ ok, code, details, hint }` error envelope for free, instead of a
15
- * hand-rolled method registry. → ADR 0027. Upholds ADR 0008 (no competing
16
- * engine — the transport is the app's) and ADR 0002 (the core stays generic —
17
- * `source` is a free tag, `idempotent` carries no built-in behaviour).
18
- */
19
- import { type TransportSource } from '../contract';
20
- import type { ServiceDef } from '../server/types';
21
- import { type ToolCallHooks, type ToolLifecycle, type ToolResult } from './execute';
22
- export interface ContractDispatcherConfig {
23
- /** Transport tag written to every call's `ctx.source` — e.g. `'local-ws'`. */
24
- source: TransportSource;
25
- /** Static context merged into every call (overridden by per-call context). */
26
- context?: Record<string, unknown>;
27
- /**
28
- * Observability hooks — the same `beforeToolCall` / `afterToolCall` the MCP and
29
- * agent mounts fire, so a bespoke transport audits calls the same way.
30
- */
31
- hooks?: ToolCallHooks;
32
- /**
33
- * Auth / scope gate and result transform run for every call — pass a
34
- * `createAuthHook` result as `beforeHandle` to scope-guard the transport like
35
- * an HTTP route.
36
- */
37
- lifecycle?: ToolLifecycle;
38
- /**
39
- * Coerce JSON-stringified array / object arguments before validation. Off by
40
- * default — a structured transport (WebSocket JSON) sends real types; only an
41
- * LLM-style flat-string transport needs it.
42
- */
43
- coerceJsonArgs?: boolean;
44
- }
45
- export interface ContractDispatcher {
46
- /**
47
- * Run one contract method by its endpoint key (e.g. `'transcriptions.list'`)
48
- * with a flat argument object. Returns the `{ ok: true, data } | { ok: false,
49
- * code, details?, hint? }` envelope the MCP / agent transports use — it does
50
- * not throw for a normal call; a handler error becomes a failed result, and an
51
- * unknown method is a `NOT_FOUND` result. Per-call `context` is merged after
52
- * the static `config.context` (neither can shadow `source`).
53
- */
54
- dispatch(method: string, args: Record<string, unknown>, context?: Record<string, unknown>): Promise<ToolResult>;
55
- /** Every method key this dispatcher can run, across the given services. */
56
- readonly methods: readonly string[];
57
- }
58
- /**
59
- * Build a {@link ContractDispatcher} from one or more `implement()` services.
60
- * Every method of every service is dispatchable by its key — exposure is the
61
- * transport's choice (which services it dispatches), not the contract's `expose`
62
- * (that gates the built-in HTTP / MCP / agent / CLI transports).
63
- */
64
- export declare function createContractDispatcher(services: ServiceDef | ServiceDef[], config: ContractDispatcherConfig): ContractDispatcher;
65
- //# sourceMappingURL=dispatch.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/tools/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAY,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,UAAU,EAEhB,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,MAAM,EAAE,eAAe,CAAC;IACxB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,QAAQ,CACN,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,UAAU,GAAG,UAAU,EAAE,EACnC,MAAM,EAAE,wBAAwB,GAC/B,kBAAkB,CAqCpB"}