stitchkit 0.46.0 → 0.47.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.
Files changed (46) hide show
  1. package/README.md +4 -3
  2. package/dist/browser/cancellation.d.ts +14 -0
  3. package/dist/browser/cancellation.d.ts.map +1 -0
  4. package/dist/browser/client-multipart.d.ts +3 -1
  5. package/dist/browser/client-multipart.d.ts.map +1 -1
  6. package/dist/browser/client.d.ts +1 -0
  7. package/dist/browser/client.d.ts.map +1 -1
  8. package/dist/browser/http.d.ts +1 -0
  9. package/dist/browser/http.d.ts.map +1 -1
  10. package/dist/cli.js +2 -2
  11. package/dist/contract/define.d.ts +59 -15
  12. package/dist/contract/define.d.ts.map +1 -1
  13. package/dist/contract/index.d.ts +1 -1
  14. package/dist/contract/index.d.ts.map +1 -1
  15. package/dist/contract/index.js +1 -1
  16. package/dist/{index-zwqty9zf.js → index-44xysy8r.js} +465 -49
  17. package/dist/{index-pwyedf7b.js → index-45dz4m51.js} +46 -0
  18. package/dist/{index-c40tkxcd.js → index-8ekq6res.js} +1 -1
  19. package/dist/{index-5s8b7z6q.js → index-ee621cmy.js} +9 -9
  20. package/dist/{index-62pqb23z.js → index-kp8xamqp.js} +1 -1
  21. package/dist/{index-w1s873ng.js → index-nrytvb30.js} +4 -3
  22. package/dist/index.js +191 -88
  23. package/dist/node.js +2 -2
  24. package/dist/observability/audit.d.ts +22 -3
  25. package/dist/observability/audit.d.ts.map +1 -1
  26. package/dist/observability/index.d.ts +1 -1
  27. package/dist/observability/index.d.ts.map +1 -1
  28. package/dist/observability/index.js +82 -15
  29. package/dist/server/context.d.ts +8 -5
  30. package/dist/server/context.d.ts.map +1 -1
  31. package/dist/server/create.d.ts.map +1 -1
  32. package/dist/server/implement.d.ts +33 -2
  33. package/dist/server/implement.d.ts.map +1 -1
  34. package/dist/server/index.d.ts +3 -3
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +28 -6
  37. package/dist/server/middleware/auth.d.ts +7 -4
  38. package/dist/server/middleware/auth.d.ts.map +1 -1
  39. package/dist/server/multipart.d.ts +13 -18
  40. package/dist/server/multipart.d.ts.map +1 -1
  41. package/dist/server/openapi.d.ts.map +1 -1
  42. package/dist/server/types.d.ts +48 -15
  43. package/dist/server/types.d.ts.map +1 -1
  44. package/dist/tools.js +167 -68
  45. package/llms-full.txt +367 -67
  46. package/package.json +1 -1
@@ -114,6 +114,14 @@ function parseTrailingWildcard(path) {
114
114
  return wildcard;
115
115
  }
116
116
 
117
+ // src/internal/safe-json.ts
118
+ function isUnsafeKey(key) {
119
+ return key === "__proto__";
120
+ }
121
+ function safeJsonParse(text) {
122
+ return JSON.parse(text, (key, value) => isUnsafeKey(key) ? undefined : value);
123
+ }
124
+
117
125
  // src/contract/define.ts
118
126
  function mergeMeta(contractMeta, endpointMeta) {
119
127
  if (!contractMeta)
@@ -260,14 +268,6 @@ function validateDeclaredOutput(schema, data, onStripped) {
260
268
  return validateHandlerOutput(schema, data, onStripped);
261
269
  }
262
270
 
263
- // src/internal/safe-json.ts
264
- function isUnsafeKey(key) {
265
- return key === "__proto__";
266
- }
267
- function safeJsonParse(text) {
268
- return JSON.parse(text, (key, value) => isUnsafeKey(key) ? undefined : value);
269
- }
270
-
271
271
  // src/server/request.ts
272
272
  function generateTraceId() {
273
273
  return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
@@ -454,4 +454,4 @@ function wrapInRequestContext(handler, options = {}) {
454
454
  };
455
455
  }
456
456
 
457
- export { __require, parseTrailingWildcard, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, typedEntries, isRecord, callRuntimeHandler, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateDeclaredOutput, isUnsafeKey, safeJsonParse, generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, resolvePropagationContext, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
457
+ export { __require, parseTrailingWildcard, isUnsafeKey, safeJsonParse, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, typedEntries, isRecord, callRuntimeHandler, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateDeclaredOutput, generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, resolvePropagationContext, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
@@ -15,7 +15,7 @@ import {
15
15
  runWithRequestContext,
16
16
  safeJsonParse,
17
17
  validateDeclaredOutput
18
- } from "./index-5s8b7z6q.js";
18
+ } from "./index-ee621cmy.js";
19
19
 
20
20
  // src/tools/coerce.ts
21
21
  import { z } from "zod";
@@ -6,7 +6,7 @@ import {
6
6
  isUnsafeKey,
7
7
  safeJsonParse,
8
8
  unauthorized
9
- } from "./index-5s8b7z6q.js";
9
+ } from "./index-ee621cmy.js";
10
10
 
11
11
  // src/server/middleware/cookies.ts
12
12
  function parseCookies(header) {
@@ -151,7 +151,7 @@ function extractToken(req, cookieName) {
151
151
  function createAuthHook(config) {
152
152
  const onAnonymous = config.onAnonymous ?? (() => unauthorized());
153
153
  const onForbidden = config.onForbidden ?? (() => forbidden());
154
- return async (ctx, endpoint) => {
154
+ async function auth(ctx, endpoint) {
155
155
  const identity = ctx.source === "http" ? await config.resolve(ctx) : await config.resolveFromContext?.(ctx) ?? null;
156
156
  config.inject?.(ctx, identity);
157
157
  const scope = endpoint.scope ?? config.defaultScope;
@@ -171,7 +171,8 @@ function createAuthHook(config) {
171
171
  return;
172
172
  if (!await rule(identity, ctx))
173
173
  onForbidden();
174
- };
174
+ }
175
+ return auth;
175
176
  }
176
177
  function createBearerResolver(config) {
177
178
  return async (req) => {
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  parseTrailingWildcard,
18
18
  rateLimited,
19
19
  unauthorized
20
- } from "./index-pwyedf7b.js";
20
+ } from "./index-45dz4m51.js";
21
21
  import {
22
22
  isRecord,
23
23
  mapObject,
@@ -25,6 +25,63 @@ import {
25
25
  typedEntries
26
26
  } from "./index-tss6bk5c.js";
27
27
 
28
+ // src/browser/cancellation.ts
29
+ class RequestCancellationError extends Error {
30
+ cause;
31
+ constructor(cause) {
32
+ super(cause === "caller" ? "Request was aborted" : "Request timed out");
33
+ this.cause = cause;
34
+ this.name = "RequestCancellationError";
35
+ }
36
+ }
37
+ function createRequestCancellation(caller, timeoutMs) {
38
+ if (!caller && timeoutMs === undefined) {
39
+ return { signal: undefined, run: (operation) => operation() };
40
+ }
41
+ const controller = new AbortController;
42
+ let cause;
43
+ let timer;
44
+ const abortFromCaller = () => {
45
+ if (cause)
46
+ return;
47
+ cause = "caller";
48
+ controller.abort(caller?.reason);
49
+ };
50
+ if (caller?.aborted)
51
+ abortFromCaller();
52
+ else
53
+ caller?.addEventListener("abort", abortFromCaller, { once: true });
54
+ if (timeoutMs !== undefined) {
55
+ timer = setTimeout(() => {
56
+ if (cause)
57
+ return;
58
+ cause = "timeout";
59
+ controller.abort(new DOMException("Request timed out", "TimeoutError"));
60
+ }, timeoutMs);
61
+ }
62
+ return {
63
+ signal: controller.signal,
64
+ async run(operation) {
65
+ try {
66
+ if (cause === "caller")
67
+ throw cancellationError(cause);
68
+ return await operation(controller.signal);
69
+ } catch (error) {
70
+ if (cause)
71
+ throw cancellationError(cause);
72
+ throw error;
73
+ } finally {
74
+ if (timer !== undefined)
75
+ clearTimeout(timer);
76
+ caller?.removeEventListener("abort", abortFromCaller);
77
+ }
78
+ }
79
+ };
80
+ }
81
+ function cancellationError(cause) {
82
+ return new RequestCancellationError(cause);
83
+ }
84
+
28
85
  // src/browser/client-multipart.ts
29
86
  function isFileDescriptor(value) {
30
87
  return typeof value === "object" && value !== null && !(value instanceof Blob) && "uri" in value && typeof value.uri === "string" && "name" in value && typeof value.name === "string" && "type" in value && typeof value.type === "string";
@@ -43,6 +100,35 @@ function appendFormFields(formData, values, skipKeys) {
43
100
  formData.append(key, typeof value === "string" ? value : JSON.stringify(value));
44
101
  }
45
102
  }
103
+ function buildMultipartForm(descriptor, values) {
104
+ const formData = new FormData;
105
+ const fileFields = new Set(Object.keys(descriptor.files));
106
+ for (const [field, policy] of Object.entries(descriptor.files)) {
107
+ const value = values[field];
108
+ if (value === undefined) {
109
+ if (policy.required !== false)
110
+ throw new Error(`Missing multipart file field: ${field}`);
111
+ continue;
112
+ }
113
+ if (policy.multiple === true) {
114
+ if (!Array.isArray(value) || value.length === 0) {
115
+ throw new Error(`Multipart file field "${field}" must be a non-empty array`);
116
+ }
117
+ for (const file of value) {
118
+ if (!isMultipartFile(file)) {
119
+ throw new Error(`Invalid multipart file field: ${field}`);
120
+ }
121
+ appendMultipartFile(formData, field, file);
122
+ }
123
+ continue;
124
+ }
125
+ if (!isMultipartFile(value))
126
+ throw new Error(`Invalid multipart file field: ${field}`);
127
+ appendMultipartFile(formData, field, value);
128
+ }
129
+ appendFormFields(formData, values, fileFields);
130
+ return formData;
131
+ }
46
132
 
47
133
  // src/internal/http-input.ts
48
134
  function inputIsQuery(method) {
@@ -314,7 +400,7 @@ function createHttpClient(config) {
314
400
  const client = ky.create({
315
401
  prefix: config.baseUrl,
316
402
  credentials: config.credentials ?? "include",
317
- timeout: config.timeout ?? 30000,
403
+ timeout: false,
318
404
  retry: {
319
405
  limit: config.retry?.limit ?? 2,
320
406
  methods: config.retry?.methods ?? ["get"],
@@ -360,8 +446,10 @@ function createHttpClient(config) {
360
446
  }
361
447
  });
362
448
  async function request(method, url, data, options = {}) {
449
+ const cancellation = createRequestCancellation(options.signal, options.timeout ?? config.timeout ?? 30000);
363
450
  const kyOptions = {
364
- timeout: options.timeout
451
+ timeout: false,
452
+ signal: cancellation.signal
365
453
  };
366
454
  if (options.params) {
367
455
  const searchParams = new URLSearchParams;
@@ -385,31 +473,33 @@ function createHttpClient(config) {
385
473
  kyOptions.json = data;
386
474
  }
387
475
  try {
388
- if (options.responseType === "blob") {
389
- return transportResult(await client[method](url, kyOptions).blob());
390
- }
391
- if (options.responseType === "response") {
392
- return transportResult(await client[method](url, kyOptions));
393
- }
394
- const response = await client[method](url, kyOptions);
395
- if (options.responseType === "void") {
396
- const text = await response.text();
397
- if (text.length > 0) {
398
- throw new Error("Server returned data for an endpoint with no output contract");
476
+ return await cancellation.run(async () => {
477
+ if (options.responseType === "blob") {
478
+ return transportResult(await client[method](url, kyOptions).blob());
399
479
  }
400
- }
401
- if (options.responseType === "void" || response.status === 204 || response.headers.get("content-length") === "0") {
402
- return transportResult(undefined);
403
- }
404
- return await response.json();
480
+ if (options.responseType === "response") {
481
+ return transportResult(await client[method](url, kyOptions));
482
+ }
483
+ const response = await client[method](url, kyOptions);
484
+ if (options.responseType === "void") {
485
+ const text = await response.text();
486
+ if (text.length > 0) {
487
+ throw new Error("Server returned data for an endpoint with no output contract");
488
+ }
489
+ }
490
+ if (options.responseType === "void" || response.status === 204 || response.headers.get("content-length") === "0") {
491
+ return transportResult(undefined);
492
+ }
493
+ return await response.json();
494
+ });
405
495
  } catch (error) {
496
+ if (error instanceof RequestCancellationError) {
497
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
498
+ }
406
499
  if (ApiError.is(error))
407
500
  throw error;
408
- const isAbort = error instanceof Error && error.name === "AbortError";
409
- if (!isAbort) {
410
- emit({ type: "network_error" });
411
- }
412
- const response = !isAbort && isHTTPError(error) ? error.response : undefined;
501
+ emit({ type: "network_error" });
502
+ const response = isHTTPError(error) ? error.response : undefined;
413
503
  const status = response?.status ?? 0;
414
504
  const msg = error instanceof Error ? error.message : undefined;
415
505
  throw new ApiError("UNKNOWN_ERROR", status, msg ? { message: msg } : undefined, undefined, undefined, responseTraceId(response));
@@ -536,95 +626,108 @@ function setClientMethod(target, key, method) {
536
626
  function createHttpMethod(endpoint, prefix, client, config) {
537
627
  const httpMethod = endpoint.method.toLowerCase();
538
628
  return (...args) => {
539
- const firstArg = args[0] ?? {};
629
+ const { requestArgs: firstArg, options } = splitClientCall(endpoint, args, config);
540
630
  const plan = planClientRequest(endpoint, prefix, firstArg, config);
541
631
  if (endpoint.multipart) {
542
- const file = firstArg[endpoint.multipart];
543
- if (!isMultipartFile(file)) {
544
- throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
545
- }
546
632
  if (httpMethod === "get" || httpMethod === "head" || httpMethod === "delete") {
547
633
  throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
548
634
  }
549
- const formData = new FormData;
550
- appendMultipartFile(formData, endpoint.multipart, file);
551
- appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
552
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(undefined, endpoint)));
635
+ const formData = buildMultipartForm(endpoint.multipart, plan.remainingArgs);
636
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(options, endpoint)));
553
637
  }
554
638
  if (httpMethod === "get" || httpMethod === "head") {
555
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(undefined, endpoint)));
639
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(options, endpoint)));
556
640
  }
557
641
  if (httpMethod === "delete") {
558
- return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(undefined, endpoint)));
642
+ return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(options, endpoint)));
559
643
  }
560
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(undefined, endpoint)));
644
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(options, endpoint)));
561
645
  };
562
646
  }
563
647
  function createFetchMethod(endpoint, prefix, config, contractConfig) {
564
- return async (args) => {
565
- const plan = planClientRequest(endpoint, prefix, args ?? {}, contractConfig);
648
+ return async (...callArgs) => {
649
+ const { requestArgs, options } = splitClientCall(endpoint, callArgs, contractConfig);
650
+ const plan = planClientRequest(endpoint, prefix, requestArgs, contractConfig);
566
651
  const url = joinClientBaseUrl(config.baseUrl, plan.relativeUrl);
567
652
  const headers = {
568
653
  Accept: "application/json",
569
654
  ...typeof config.headers === "function" ? config.headers() : config.headers
570
655
  };
571
- const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
572
- const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && args;
656
+ const cancellation = createRequestCancellation(options?.signal, endpoint.timeout ?? config.timeout ?? 30000);
657
+ const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && Object.keys(plan.remainingArgs).length > 0;
573
658
  if (hasBody)
574
659
  headers["Content-Type"] = "application/json";
575
- if (endpoint.multipart && args) {
576
- const file = args[endpoint.multipart];
577
- if (!isMultipartFile(file)) {
578
- throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
579
- }
580
- const formData = new FormData;
581
- appendMultipartFile(formData, endpoint.multipart, file);
582
- appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
583
- const res2 = await fetch(url, {
584
- method: endpoint.method,
585
- headers,
586
- credentials: config.credentials,
587
- body: formData,
588
- signal
589
- });
590
- if (!res2.ok) {
591
- await throwForErrorResponse(res2, config, null);
592
- }
593
- if (endpoint.rawResponse)
594
- return res2;
595
- if (!endpoint.output) {
596
- const text = await res2.text();
597
- if (text.length > 0) {
598
- throw new Error("Server returned data for an endpoint with no output contract");
660
+ try {
661
+ return await cancellation.run(async (signal) => {
662
+ const body = endpoint.multipart ? buildMultipartForm(endpoint.multipart, plan.remainingArgs) : hasBody ? JSON.stringify(plan.remainingArgs) : undefined;
663
+ const res = await fetch(url, {
664
+ method: endpoint.method,
665
+ headers,
666
+ credentials: config.credentials,
667
+ signal,
668
+ ...body !== undefined && { body }
669
+ });
670
+ if (!res.ok) {
671
+ await throwForErrorResponse(res, config, { error: res.statusText });
599
672
  }
600
- return;
601
- }
602
- return endpoint.output.parse(await res2.json());
603
- }
604
- const res = await fetch(url, {
605
- method: endpoint.method,
606
- headers,
607
- credentials: config.credentials,
608
- signal,
609
- ...hasBody && {
610
- body: JSON.stringify(plan.remainingArgs)
611
- }
612
- });
613
- if (!res.ok) {
614
- await throwForErrorResponse(res, config, { error: res.statusText });
615
- }
616
- if (endpoint.rawResponse)
617
- return res;
618
- if (!endpoint.output) {
619
- const text = await res.text();
620
- if (text.length > 0) {
621
- throw new Error("Server returned data for an endpoint with no output contract");
673
+ if (endpoint.rawResponse)
674
+ return res;
675
+ if (!endpoint.output) {
676
+ const text = await res.text();
677
+ if (text.length > 0) {
678
+ throw new Error("Server returned data for an endpoint with no output contract");
679
+ }
680
+ return;
681
+ }
682
+ return endpoint.output.parse(await res.json());
683
+ });
684
+ } catch (error) {
685
+ if (error instanceof RequestCancellationError) {
686
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
622
687
  }
623
- return;
688
+ if (ApiError.is(error))
689
+ throw error;
690
+ const message = error instanceof Error ? error.message : undefined;
691
+ throw new ApiError("UNKNOWN_ERROR", 0, message ? { message } : undefined, message);
624
692
  }
625
- return endpoint.output.parse(await res.json());
626
693
  };
627
694
  }
695
+ function endpointHasArguments(endpoint) {
696
+ return Boolean(endpoint.params || endpoint.input || endpoint.multipart);
697
+ }
698
+ function splitClientCall(endpoint, args, contractConfig) {
699
+ const hasScopedArguments = (contractConfig?.stripPrefixKeys?.length ?? 0) > 0;
700
+ if (!endpointHasArguments(endpoint) && !hasScopedArguments) {
701
+ return {
702
+ requestArgs: {},
703
+ options: readClientRequestOptions(args[0])
704
+ };
705
+ }
706
+ const requestArgs = args[0];
707
+ if (requestArgs !== undefined && !isRecord(requestArgs)) {
708
+ throw new TypeError("Endpoint arguments must be an object");
709
+ }
710
+ return {
711
+ requestArgs: requestArgs ?? {},
712
+ options: readClientRequestOptions(args[1])
713
+ };
714
+ }
715
+ function readClientRequestOptions(value) {
716
+ if (value === undefined)
717
+ return;
718
+ if (!isRecord(value))
719
+ throw new TypeError("Client request options must be an object");
720
+ const signal = value.signal;
721
+ if (signal === undefined)
722
+ return {};
723
+ if (!isAbortSignal(signal)) {
724
+ throw new TypeError("Client request signal must be an AbortSignal");
725
+ }
726
+ return { signal };
727
+ }
728
+ function isAbortSignal(value) {
729
+ return typeof value === "object" && value !== null && "aborted" in value && typeof value.aborted === "boolean" && "addEventListener" in value && typeof value.addEventListener === "function" && "removeEventListener" in value && typeof value.removeEventListener === "function";
730
+ }
628
731
  async function throwForErrorResponse(res, config, fallbackBody) {
629
732
  const body = await res.json().catch(() => fallbackBody);
630
733
  config.onError?.(res.status, body);
package/dist/node.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createImplement,
5
5
  createSocketIOServer,
6
6
  implement
7
- } from "./index-zwqty9zf.js";
7
+ } from "./index-44xysy8r.js";
8
8
  import"./index-r1qp4rve.js";
9
9
  import {
10
10
  AppError,
@@ -15,7 +15,7 @@ import {
15
15
  notFound,
16
16
  rateLimited,
17
17
  unauthorized
18
- } from "./index-5s8b7z6q.js";
18
+ } from "./index-ee621cmy.js";
19
19
  // src/server/node.ts
20
20
  import { serve } from "srvx";
21
21
  async function serveNode(config) {
@@ -5,15 +5,30 @@ import { type SanitizeOptions } from './sanitize';
5
5
  /** One isolated RequestEvent sink and its sanitisation policy. */
6
6
  export interface RequestEventSinkConfig {
7
7
  /**
8
- * The sink — persists one audit event. It runs fire-and-forget and its own
9
- * errors are swallowed, so a slow or failing write never blocks or breaks the
10
- * request it observes. Keep it asynchronous and self-contained.
8
+ * The sink — persists one audit event. It runs fire-and-forget; failures are
9
+ * reported through `onSinkError` without breaking the observed work.
11
10
  */
12
11
  write: (event: RequestEvent) => void | Promise<void>;
13
12
  /** Keep only events for which this returns `true`. Default: keep every event. */
14
13
  filter?: (event: RequestEvent) => boolean;
15
14
  /** Payload sanitisation tuning — passed through to `sanitizePayload`. */
16
15
  sanitize?: SanitizeOptions;
16
+ /** Maximum writes concurrently awaiting settlement. Default: 1000. */
17
+ maxPending?: number;
18
+ /** Observe failed filtering, projection or persistence. */
19
+ onSinkError?: (failure: SinkError) => void | Promise<void>;
20
+ /** Observe events rejected by bounded or closed admission. */
21
+ onDrop?: (drop: SinkDrop) => void | Promise<void>;
22
+ }
23
+ export type SinkDropReason = 'capacity' | 'closed';
24
+ export interface SinkError {
25
+ error: unknown;
26
+ event?: RequestEvent;
27
+ }
28
+ export interface SinkDrop {
29
+ reason: SinkDropReason;
30
+ event: RequestEvent;
31
+ pending: number;
17
32
  }
18
33
  export interface RequestObservabilityConfig extends RequestEventSinkConfig {
19
34
  /** Clone and record JSON request bodies. Default false. */
@@ -39,6 +54,10 @@ export interface HttpRequestObserver {
39
54
  export interface Observability {
40
55
  request?: HttpRequestObserver;
41
56
  toolCall: ToolCallHooks;
57
+ /** Wait for events admitted before this call. */
58
+ flush(): Promise<void>;
59
+ /** Stop admission and drain every previously accepted event. Idempotent. */
60
+ close(): Promise<void>;
42
61
  }
43
62
  export declare function createObservability(config: ObservabilityConfig): Observability;
44
63
  //# sourceMappingURL=audit.d.ts.map
@@ -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;AAGhF,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC;;;;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,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;CACzB;AAkFD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAwH9E"}
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;AAGhF,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,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAuKD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAkI9E"}
@@ -6,7 +6,7 @@
6
6
  * framework-owned HTTP completion plus canonical tool hooks into independent
7
7
  * sinks.
8
8
  */
9
- export { createObservability, type HttpRequestCompletion, type HttpRequestObserver, type Observability, type ObservabilityConfig, type RequestEventSinkConfig, type RequestObservabilityConfig, } from './audit';
9
+ export { createObservability, type HttpRequestCompletion, type HttpRequestObserver, type Observability, type ObservabilityConfig, type RequestEventSinkConfig, type RequestObservabilityConfig, type SinkDrop, type SinkDropReason, type SinkError, } from './audit';
10
10
  export { getRequestContext, getTraceId, getUserId, type RequestContext, runWithRequestContext, setRequestDimensions, setRequestEndpoint, setRequestError, setRequestUser, type WrapRequestContextOptions, 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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,GAChC,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,KAAK,cAAc,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,KAAK,yBAAyB,EAC9B,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,yBAAyB,EACzB,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,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,SAAS,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,KAAK,cAAc,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,KAAK,yBAAyB,EAC9B,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,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}