stitchkit 0.56.0 → 0.56.1

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.
@@ -466,12 +466,20 @@ async function parseMultipart(req, descriptor, fieldsSchema, receivers) {
466
466
  }
467
467
 
468
468
  // src/server/request-body.ts
469
+ function requestAbortReason(req) {
470
+ return req.signal.reason ?? new DOMException("The connection was closed", "AbortError");
471
+ }
472
+ function throwIfRequestAborted(req) {
473
+ if (req.signal.aborted)
474
+ throw requestAbortReason(req);
475
+ }
469
476
  function assertJsonBodyLimit(maxBytes, owner) {
470
477
  if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {
471
478
  throw new Error(`${owner} must be a positive safe integer, received ${maxBytes}`);
472
479
  }
473
480
  }
474
481
  async function readRequestText(req, maxBytes) {
482
+ throwIfRequestAborted(req);
475
483
  if (maxBytes === undefined)
476
484
  return req.text();
477
485
  assertJsonBodyLimit(maxBytes, "maxJsonBodyBytes");
@@ -480,9 +488,25 @@ async function readRequestText(req, maxBytes) {
480
488
  return "";
481
489
  const chunks = [];
482
490
  let total = 0;
491
+ let rejectAbortedRead = (_reason) => {
492
+ return;
493
+ };
494
+ const abortedRead = new Promise((_resolve, reject) => {
495
+ rejectAbortedRead = reject;
496
+ });
497
+ const onAbort = () => {
498
+ const reason = requestAbortReason(req);
499
+ rejectAbortedRead(reason);
500
+ reader.cancel(reason).catch(() => {
501
+ return;
502
+ });
503
+ };
504
+ req.signal.addEventListener("abort", onAbort, { once: true });
505
+ if (req.signal.aborted)
506
+ onAbort();
483
507
  try {
484
508
  while (true) {
485
- const { done, value } = await reader.read();
509
+ const { done, value } = await Promise.race([reader.read(), abortedRead]);
486
510
  if (done)
487
511
  break;
488
512
  total += value.byteLength;
@@ -493,7 +517,9 @@ async function readRequestText(req, maxBytes) {
493
517
  chunks.push(value);
494
518
  }
495
519
  } finally {
496
- reader.releaseLock();
520
+ req.signal.removeEventListener("abort", onAbort);
521
+ if (!req.signal.aborted)
522
+ reader.releaseLock();
497
523
  }
498
524
  const bytes = new Uint8Array(total);
499
525
  let offset = 0;
@@ -630,6 +656,8 @@ function safePath(pathname) {
630
656
  return out;
631
657
  }
632
658
  function levelForStatus(status) {
659
+ if (status === 499)
660
+ return "info";
633
661
  if (status >= 500)
634
662
  return "error";
635
663
  if (status >= 400)
@@ -1037,6 +1065,29 @@ function validateRawRoutes(rawRoutes) {
1037
1065
 
1038
1066
  // src/server/create.ts
1039
1067
  var BODY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
1068
+ var MAX_REQUEST_ABORT_CAUSE_DEPTH = 8;
1069
+ function containsRequestAbortReason(error, reason) {
1070
+ let current = error;
1071
+ const visited = new Set;
1072
+ for (let depth = 0;depth <= MAX_REQUEST_ABORT_CAUSE_DEPTH; depth += 1) {
1073
+ if (current === reason)
1074
+ return true;
1075
+ if (depth === MAX_REQUEST_ABORT_CAUSE_DEPTH || !isRecord(current))
1076
+ return false;
1077
+ if (visited.has(current))
1078
+ return false;
1079
+ visited.add(current);
1080
+ current = current.cause;
1081
+ }
1082
+ return false;
1083
+ }
1084
+ function isClientClosedRequest(req, error) {
1085
+ if (!req.signal.aborted)
1086
+ return false;
1087
+ if (isRecord(error) && error.name === "AbortError")
1088
+ return true;
1089
+ return req.signal.reason !== undefined && containsRequestAbortReason(error, req.signal.reason);
1090
+ }
1040
1091
  function createHandler(config) {
1041
1092
  const { cors, hooks, logging = false, observability, trustProxy = false } = config;
1042
1093
  if (cors)
@@ -1100,7 +1151,7 @@ function createHandler(config) {
1100
1151
  } catch {}
1101
1152
  }
1102
1153
  let completed = false;
1103
- const complete = (status, errorCode2) => {
1154
+ const complete = (status, errorCode2, outcome) => {
1104
1155
  if (completed)
1105
1156
  return;
1106
1157
  completed = true;
@@ -1148,11 +1199,22 @@ function createHandler(config) {
1148
1199
  const context = getRequestContext();
1149
1200
  if (observability && context) {
1150
1201
  try {
1151
- observability.complete({ context, statusCode: status, durationMs, payload });
1202
+ observability.complete({
1203
+ context,
1204
+ statusCode: status,
1205
+ durationMs,
1206
+ payload,
1207
+ ...outcome !== undefined && { outcome }
1208
+ });
1152
1209
  } catch {}
1153
1210
  }
1154
1211
  };
1155
1212
  const respondError = async (err, errCtx, endpoint) => {
1213
+ if (isClientClosedRequest(req, err)) {
1214
+ const response = applyCors(new Response(null, { status: 499, statusText: "Client Closed Request" }), cors, req);
1215
+ complete(response.status, undefined, "cancelled");
1216
+ return response;
1217
+ }
1156
1218
  const recordFailure = (normalized) => {
1157
1219
  if (getRequestContext()?.error !== undefined)
1158
1220
  return;
package/dist/node.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  createHandler,
9
9
  createServerLifecycle,
10
10
  createSocketIOServer
11
- } from "./index-n4nfa7gh.js";
11
+ } from "./index-75njxz7p.js";
12
12
  import {
13
13
  createImplement,
14
14
  createImplementRegistry,
@@ -34,6 +34,8 @@ export interface SinkDrop {
34
34
  export interface RequestObservabilityConfig extends RequestEventSinkConfig {
35
35
  /** Clone and record JSON request bodies. Default false. */
36
36
  includePayload?: boolean;
37
+ /** Emit client-closed HTTP requests as `outcome: 'cancelled'`. Default false. */
38
+ includeCancelled?: boolean;
37
39
  }
38
40
  export interface ObservabilityConfig {
39
41
  request?: RequestObservabilityConfig;
@@ -45,6 +47,8 @@ export interface HttpRequestCompletion {
45
47
  statusCode: number;
46
48
  durationMs: number;
47
49
  payload?: Promise<unknown>;
50
+ /** Framework-owned neutral cancellation; currently paired with HTTP 499. */
51
+ outcome?: 'cancelled';
48
52
  }
49
53
  /** Server-facing request projection returned by `createObservability`. */
50
54
  export interface HttpRequestObserver {
@@ -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;AAClE,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAChF,OAAO,EAEL,KAAK,wBAAwB,EAI7B,KAAK,mBAAmB,EAEzB,MAAM,UAAU,CAAC;AAGlB,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;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;IAC3B,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,8DAA8D;IAC9D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,0BAA2B,SAAQ,sBAAsB;IACxE,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED,gEAAgE;AAChE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5B;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACnD;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,aAAa,CAAC;IACxB,iDAAiD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,2EAA2E;IAC3E,SAAS,IAAI,mBAAmB,CAAC;IACjC,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAC5C;AAkOD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAyJ9E"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/observability/audit.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAChF,OAAO,EAEL,KAAK,wBAAwB,EAI7B,KAAK,mBAAmB,EAEzB,MAAM,UAAU,CAAC;AAGlB,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;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;IAC3B,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,8DAA8D;IAC9D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,0BAA2B,SAAQ,sBAAsB;IACxE,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED,gEAAgE;AAChE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACnD;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,aAAa,CAAC;IACxB,iDAAiD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,2EAA2E;IAC3E,SAAS,IAAI,mBAAmB,CAAC;IACjC,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAC5C;AAkOD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CA8J9E"}
@@ -48,6 +48,11 @@ export interface RequestEvent {
48
48
  parentSpanId?: string;
49
49
  /** Whether the call succeeded. */
50
50
  ok: boolean;
51
+ /**
52
+ * Explicit non-failure outcome. Present only for event classes a sink opted
53
+ * into; ordinary success/failure rows retain their released shape.
54
+ */
55
+ outcome?: 'cancelled';
51
56
  /** HTTP status — the real status for HTTP, `200` / `400` for a tool call. */
52
57
  statusCode: number;
53
58
  /** Wall-clock duration. */
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/observability/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/E,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;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,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,2EAA2E;IAC3E,SAAS,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC;IACxC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,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;;;;;OAKG;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;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,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,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/E,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;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,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,2EAA2E;IAC3E,SAAS,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC;IACxC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,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;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,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;;;;;OAKG;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;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,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"}
@@ -250,13 +250,16 @@ function createObservability(config) {
250
250
  let closePromise;
251
251
  const request = config.request ? {
252
252
  includePayload: config.request.includePayload ?? false,
253
- complete: ({ context, statusCode, durationMs, payload }) => {
253
+ complete: ({ context, statusCode, durationMs, payload, outcome }) => {
254
254
  const requestConfig = config.request;
255
255
  if (!requestConfig)
256
256
  return;
257
+ const cancelled = outcome === "cancelled";
258
+ if (cancelled && !requestConfig.includeCancelled)
259
+ return;
257
260
  requestManager?.submit(async () => {
258
261
  let body;
259
- if (payload) {
262
+ if (!cancelled && payload) {
260
263
  try {
261
264
  body = await payload;
262
265
  } catch {
@@ -277,15 +280,18 @@ function createObservability(config) {
277
280
  traceId: context.trace.traceId,
278
281
  spanId: context.trace.spanId,
279
282
  parentSpanId: context.trace.parentSpanId,
280
- ok: statusCode < 400,
283
+ ok: !cancelled && statusCode < 400,
284
+ ...cancelled && { outcome: "cancelled" },
281
285
  statusCode,
282
286
  durationMs,
283
- errorCode: context.error?.code,
284
- errorMessage: context.error?.message,
285
- ...context.error?.details !== undefined && {
286
- errorDetail: sanitizePayload(context.error.details, requestConfig.sanitize)
287
+ ...!cancelled && {
288
+ errorCode: context.error?.code,
289
+ errorMessage: context.error?.message,
290
+ ...context.error?.details !== undefined && {
291
+ errorDetail: sanitizePayload(context.error.details, requestConfig.sanitize)
292
+ }
287
293
  },
288
- payload: sanitizePayload(body, requestConfig.sanitize),
294
+ payload: cancelled ? null : sanitizePayload(body, requestConfig.sanitize),
289
295
  resultSize: null,
290
296
  responseBytes: 0,
291
297
  userId: context.userId,
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAA2B,MAAM,SAAS,CAAC;AAIpF,wBAAgB,aAAa,CAAC,OAAO,GAAG,OAAO,EAC7C,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAC7B,YAAY,CAAC,OAAO,CAAC,CA8fvB"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"AAqDA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAA2B,MAAM,SAAS,CAAC;AA2BpF,wBAAgB,aAAa,CAAC,OAAO,GAAG,OAAO,EAC7C,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAC7B,YAAY,CAAC,OAAO,CAAC,CA8gBvB"}
@@ -55,7 +55,7 @@ export interface ErrorHookConfig<TWireCode extends string = string> {
55
55
  * `satisfies Record<StitchErrorCode, …>` makes a new framework code a compile
56
56
  * error here. Codes you threw yourself (not stitchkit's) pass through as-is.
57
57
  */
58
- codeMap?: Record<StitchErrorCode, TWireCode>;
58
+ codeMap?: Partial<Record<StitchErrorCode, TWireCode>>;
59
59
  /** Build the response body from the resolved error. */
60
60
  /**
61
61
  * Build the response body from the resolved error. `ctx` is the request's
@@ -1 +1 @@
1
- {"version":3,"file":"error-hook.d.ts","sourceRoot":"","sources":["../../src/server/error-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzD,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,oCAAoC;AACpC,MAAM,WAAW,eAAe,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM;IAChE;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAC7C,uDAAuD;IACvD;;;;;OAKG;IACH,MAAM,EAAE,CACN,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,cAAc,EACnB,QAAQ,CAAC,EAAE,SAAS,KACjB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,cAAc,EACnB,QAAQ,CAAC,EAAE,SAAS,KACjB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAC/D,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,WAAW,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CA0BxC"}
1
+ {"version":3,"file":"error-hook.d.ts","sourceRoot":"","sources":["../../src/server/error-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzD,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,oCAAoC;AACpC,MAAM,WAAW,eAAe,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM;IAChE;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC;IACtD,uDAAuD;IACvD;;;;;OAKG;IACH,MAAM,EAAE,CACN,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,cAAc,EACnB,QAAQ,CAAC,EAAE,SAAS,KACjB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,cAAc,EACnB,QAAQ,CAAC,EAAE,SAAS,KACjB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAC/D,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,WAAW,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAgCxC"}
@@ -12,7 +12,7 @@ import {
12
12
  parseMultipart,
13
13
  socketIoLane,
14
14
  webSocketLane
15
- } from "../index-n4nfa7gh.js";
15
+ } from "../index-75njxz7p.js";
16
16
  import {
17
17
  composeAuthHooks,
18
18
  createAuthHook,
@@ -349,7 +349,8 @@ function cacheHeaders(maxAge, scope = "public") {
349
349
  function createErrorHook(config) {
350
350
  return async (ctx, error, endpoint) => {
351
351
  const appErr = normalizeError(error);
352
- const code = config.codeMap && isStitchErrorCode(appErr.code) ? config.codeMap[appErr.code] : appErr.code;
352
+ const mapped = config.codeMap && isStitchErrorCode(appErr.code) ? config.codeMap[appErr.code] : undefined;
353
+ const code = mapped ?? appErr.code;
353
354
  const info = {
354
355
  code,
355
356
  status: appErr.status,
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"AAqCA,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAGlE;AAaD,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;;;;;GAKG;AACH,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,CAIA;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGnE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,UAAU,CASZ;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAMR;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,UAAU,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,MAAM,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAyBzD"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"AAqCA,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAGlE;AAaD,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,CAKxE;AAED;;;;;GAKG;AACH,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,CAIA;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGnE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,UAAU,CASZ;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAMR;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,UAAU,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,MAAM,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAyBzD"}
@@ -1 +1 @@
1
- {"version":3,"file":"request-body.d.ts","sourceRoot":"","sources":["../../src/server/request-body.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIrF;AAED,6FAA6F;AAC7F,wBAAsB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BtF"}
1
+ {"version":3,"file":"request-body.d.ts","sourceRoot":"","sources":["../../src/server/request-body.ts"],"names":[],"mappings":"AAUA,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIrF;AAED,6FAA6F;AAC7F,wBAAsB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDtF"}
package/llms-full.txt CHANGED
@@ -989,6 +989,18 @@ createServer({
989
989
  | `'pretty'` | two coloured lines per request — `→` on arrival, `←` on completion | no — a line sized for a terminal is not a record |
990
990
  | `'json'` | one structured line per completed request | yes |
991
991
 
992
+ Status `499` has one framework-wide meaning: the client closed the request. It
993
+ is logged at `info`, not under the ordinary `4xx → warn` rule. A confirmed
994
+ disconnect is not sent through project `onError`, `normalizeError` or the
995
+ request-error recorder; an `AbortError` while the request signal is still active
996
+ remains an internal failure. A runtime abort reason may be preserved by identity
997
+ through at most eight cycle-safe standard `cause` links; error messages and codes
998
+ are never classifiers. The same rule applies when the disconnect happens while
999
+ Stitchkit is reading a JSON upload body: bounded reads race every pending stream
1000
+ read against the request signal and never parse a cancelled partial body. `499`
1001
+ is transport telemetry, not a response declared in the contract or generated
1002
+ OpenAPI document.
1003
+
992
1004
  Unset, `format` follows `NODE_ENV`: `json` under `production`, `pretty`
993
1005
  otherwise. That default is read **per request** — not at import, not when this
994
1006
  package was built — so it reflects the environment your app actually runs in.
@@ -1159,7 +1171,9 @@ createServer({
1159
1171
  - **`afterHandle`** — receives the handler result; return a replacement to
1160
1172
  transform it.
1161
1173
  - **`onError`** — receives any thrown error; return a `Response` to customise
1162
- the error body. Without it, errors render through the standard envelope.
1174
+ the error body. Without it, errors render through the standard envelope. A
1175
+ confirmed client disconnect is a transport cancellation rather than an
1176
+ application error and deliberately bypasses this hook.
1163
1177
 
1164
1178
  Hooks see `RuntimeContext` (loose types); handlers see `HandlerContext` (typed).
1165
1179
  That split is deliberate — see [ADR 0003](../decisions/0003-two-context-types.md).
@@ -4878,9 +4892,11 @@ a request context, and event projections — and you usually touch only the last
4878
4892
  ### createObservability
4879
4893
 
4880
4894
  `createObservability` configures request and tool projections independently.
4881
- Every completed call is normalised into one `RequestEvent`; HTTP completion is
4882
- owned directly by `createHandler`, while MCP/Agent completion uses the canonical
4883
- `ToolCallHooks` runner. There is no nested HTTP audit wrapper.
4895
+ Every completed application call is normalised into one `RequestEvent`; HTTP
4896
+ completion is owned directly by `createHandler`, while MCP/Agent completion uses
4897
+ the canonical `ToolCallHooks` runner. Confirmed HTTP client cancellation stays
4898
+ in the access log by default and becomes a structured event only when the
4899
+ request sink opts in. There is no nested HTTP audit wrapper.
4884
4900
 
4885
4901
  ```ts
4886
4902
  import { createObservability } from 'stitchkit/observability'
@@ -4901,6 +4917,7 @@ export const observability = createObservability({
4901
4917
  request: {
4902
4918
  write,
4903
4919
  includePayload: false, // default: no Request.clone(), payload is null
4920
+ includeCancelled: false, // default: keep client closes in access logs only
4904
4921
  filter: (event) => event.method !== 'GET',
4905
4922
  },
4906
4923
  tools: {
@@ -4923,6 +4940,40 @@ Each sink runs fire-and-forget and fails independently: a slow or broken request
4923
4940
  sink cannot block the response, suppress operational logging or break the tool
4924
4941
  sink.
4925
4942
 
4943
+ #### Client cancellation
4944
+
4945
+ A physical client close is classified only when the request's own signal is
4946
+ aborted and the thrown value is either an `AbortError` or contains the exact
4947
+ `request.signal.reason` by identity at the top level or within at most eight
4948
+ standard `cause` links. Cause traversal is cycle-safe. No message or error-code
4949
+ matching is used; active requests and unrelated/deeper causes remain application
4950
+ failures. The access completion is always `499/info`, without application error
4951
+ fields or project `onError`.
4952
+
4953
+ Structured request sinks are default-preserving: existing sinks receive no row
4954
+ for this outcome. Opt in when cancellation frequency belongs in the durable
4955
+ stream:
4956
+
4957
+ ```ts
4958
+ const observability = createObservability({
4959
+ request: {
4960
+ includeCancelled: true,
4961
+ write: (event) => {
4962
+ if (event.outcome === 'cancelled') return recordClientClose(event)
4963
+ return recordApplicationRequest(event)
4964
+ },
4965
+ },
4966
+ })
4967
+ ```
4968
+
4969
+ An opted-in row has `outcome: 'cancelled'`, `ok: false`, `statusCode: 499`, the
4970
+ ordinary identity/trace/timing fields and no `errorCode`, `errorMessage` or
4971
+ `errorDetail`. `ok` remains the legacy success bit: branch on `outcome` first.
4972
+ Cancellation rows use the same filter, capacity, diagnostics, ordering,
4973
+ `flush()` and `close()` machinery as every request event. MCP protocol
4974
+ cancellation remains represented by `event.mcp.outcome`; Agent and CLI have no
4975
+ generic client-disconnect signal and are not inferred from error text.
4976
+
4926
4977
  The fire-and-forget work has an explicit bounded lifecycle:
4927
4978
 
4928
4979
  ```ts
@@ -5003,7 +5054,8 @@ queryable across all three:
5003
5054
  | `httpMethod` | the contract verb on **tool** events (their `method` is `TOOL`) — filter reads vs writes across both surfaces with `(event.httpMethod ?? event.method) !== 'GET'` |
5004
5055
  | `dimensions` | app-defined domain dimensions (tenant / project / entity id) — see [request context](#request-context) |
5005
5056
  | `traceId` / `spanId` / `parentSpanId` | [W3C trace context](#trace-context) |
5006
- | `ok` / `statusCode` | outcome real HTTP status, or `200`/`400` for a tool |
5057
+ | `outcome` | optional `'cancelled'` on explicitly enabled HTTP client-close rows; ordinary rows omit it |
5058
+ | `ok` / `statusCode` | legacy success bit plus real HTTP status, or `200`/`400` for a tool; an opted-in cancellation is `false` / `499` |
5007
5059
  | `durationMs` / `startedAt` | timing |
5008
5060
  | `errorCode` / `errorMessage` / `errorDetail` | failures only — `errorDetail` carries the structure the message flattens (e.g. Zod issues) |
5009
5061
  | `payload` | sanitised tool arguments; HTTP is `null` unless request `includePayload` is enabled |
@@ -5253,6 +5305,10 @@ success and error alike — carrying the tool name, the arguments, the result, t
5253
5305
  duration, the call context, the endpoint identity, and (only when the call failed
5254
5306
  by throwing) the raw thrown value.
5255
5307
 
5308
+ A confirmed HTTP client disconnect is the exception to the raw-hook table: it
5309
+ is transport cancellation, so neither `afterHandle` nor project `onError` runs.
5310
+ Use the `499/info` access completion or opt-in request cancellation event above.
5311
+
5256
5312
  ```ts
5257
5313
  createMcpHandler({
5258
5314
  serverInfo, auth, services,
@@ -7537,7 +7593,7 @@ audit event. See the [Observability guide](../guide/observability.md).
7537
7593
  | Export | Kind | Summary |
7538
7594
  |--------|------|---------|
7539
7595
  | `createObservability` | function | configure framework-owned request completion and canonical tool event sinks — [guide](../guide/observability.md#createobservability) |
7540
- | `RequestEvent` | _type_ | the normalised audit event handed to the sink |
7596
+ | `RequestEvent` | _type_ | the normalised audit event handed to the sink; opt-in HTTP cancellation rows carry `outcome: 'cancelled'` |
7541
7597
  | `ObservabilityConfig` | _type_ | independent request and tool sink configuration |
7542
7598
  | `Observability` | _type_ | `{ request?, toolCall, getStatus(), flush(), close() }` with bounded sink lifecycle |
7543
7599
  | `ObservabilitySinkStatus` | _type_ | immutable counters for one bounded request/tool sink |
@@ -7545,11 +7601,11 @@ audit event. See the [Observability guide](../guide/observability.md).
7545
7601
  | `ObservabilityDrainReport` | _type_ | final closed/drained snapshot plus duration |
7546
7602
  | `ObservabilitySinkStatusSchema` / `ObservabilityStatusSchema` / `ObservabilityDrainReportSchema` | schema | runtime schemas for status/report integration boundaries |
7547
7603
  | `RequestEventSinkConfig` | _type_ | `write`, filter/sanitisation, `maxPending`, `onSinkError` and `onDrop` |
7548
- | `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture |
7604
+ | `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture and default-off `includeCancelled` rows |
7549
7605
  | `SinkDropReason` | _type_ | `'capacity' \| 'closed'` |
7550
7606
  | `SinkError` | _type_ | isolated sink/projection failure and optional event |
7551
7607
  | `SinkDrop` | _type_ | rejected event, reason and current pending count |
7552
- | `HttpRequestCompletion` | _type_ | the single framework-owned HTTP outcome projected to logging and request events |
7608
+ | `HttpRequestCompletion` | _type_ | the single framework-owned HTTP outcome, including optional neutral cancellation, projected to logging and request events |
7553
7609
  | `HttpRequestObserver` | _type_ | server-facing projection consumed by `HandlerConfig.observability` |
7554
7610
 
7555
7611
  ### Request context
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.56.0",
3
+ "version": "0.56.1",
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",