stitchkit 0.46.0 → 0.48.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/README.md +13 -3
- package/dist/browser/cancellation.d.ts +14 -0
- package/dist/browser/cancellation.d.ts.map +1 -0
- package/dist/browser/client-multipart.d.ts +3 -1
- package/dist/browser/client-multipart.d.ts.map +1 -1
- package/dist/browser/client.d.ts +1 -0
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +1 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/contract/define.d.ts +67 -15
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/{index-zwqty9zf.js → index-44xysy8r.js} +465 -49
- package/dist/{index-pwyedf7b.js → index-45dz4m51.js} +46 -0
- package/dist/{index-c40tkxcd.js → index-8ekq6res.js} +1 -1
- package/dist/{index-5s8b7z6q.js → index-ee621cmy.js} +9 -9
- package/dist/{index-62pqb23z.js → index-kp8xamqp.js} +1 -1
- package/dist/{index-w1s873ng.js → index-nrytvb30.js} +4 -3
- package/dist/index.js +195 -94
- package/dist/node.js +2 -2
- package/dist/observability/audit.d.ts +22 -3
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +82 -15
- package/dist/server/context.d.ts +8 -5
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/implement.d.ts +33 -2
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +28 -6
- package/dist/server/middleware/auth.d.ts +7 -4
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/multipart.d.ts +13 -18
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/types.d.ts +48 -15
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools.js +171 -74
- package/llms-full.txt +411 -67
- 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,
|
|
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 };
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
isUnsafeKey,
|
|
7
7
|
safeJsonParse,
|
|
8
8
|
unauthorized
|
|
9
|
-
} from "./index-
|
|
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
|
-
|
|
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-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
409
|
-
|
|
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));
|
|
@@ -488,11 +578,11 @@ function contractEndpointMatchers(contract, endpointNames, contractConfig) {
|
|
|
488
578
|
}
|
|
489
579
|
function createClient(contract, configOrClient, contractConfig) {
|
|
490
580
|
const client = {};
|
|
491
|
-
const
|
|
581
|
+
const makeExecutor = isHttpAdapter(configOrClient) ? (endpoint) => createHttpExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig);
|
|
492
582
|
for (const [key, endpoint] of typedEntries(contract.endpoints)) {
|
|
493
583
|
if (endpoint.expose && !endpoint.expose.includes("HTTP"))
|
|
494
584
|
continue;
|
|
495
|
-
setClientMethod(client, key,
|
|
585
|
+
setClientMethod(client, key, createEndpointMethod(endpoint, makeExecutor(endpoint), contractConfig));
|
|
496
586
|
}
|
|
497
587
|
return client;
|
|
498
588
|
}
|
|
@@ -533,98 +623,109 @@ function isHttpAdapter(value) {
|
|
|
533
623
|
function setClientMethod(target, key, method) {
|
|
534
624
|
target[key] = method;
|
|
535
625
|
}
|
|
536
|
-
function
|
|
626
|
+
function createEndpointMethod(endpoint, execute, contractConfig) {
|
|
627
|
+
const hasScopedArguments = (contractConfig?.stripPrefixKeys?.length ?? 0) > 0;
|
|
628
|
+
if (endpointHasArguments(endpoint) || hasScopedArguments) {
|
|
629
|
+
const method2 = (requestArgs) => execute(readClientRequestArgs(requestArgs), undefined);
|
|
630
|
+
return Object.assign(method2, {
|
|
631
|
+
withOptions: (requestArgs, options) => execute(readClientRequestArgs(requestArgs), readClientRequestOptions(options))
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
const method = () => execute({}, undefined);
|
|
635
|
+
return Object.assign(method, {
|
|
636
|
+
withOptions: (options) => execute({}, readClientRequestOptions(options))
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
function createHttpExecutor(endpoint, prefix, client, config) {
|
|
537
640
|
const httpMethod = endpoint.method.toLowerCase();
|
|
538
|
-
return (
|
|
539
|
-
const
|
|
540
|
-
const plan = planClientRequest(endpoint, prefix, firstArg, config);
|
|
641
|
+
return (requestArgs, options) => {
|
|
642
|
+
const plan = planClientRequest(endpoint, prefix, requestArgs, config);
|
|
541
643
|
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
644
|
if (httpMethod === "get" || httpMethod === "head" || httpMethod === "delete") {
|
|
547
645
|
throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
|
|
548
646
|
}
|
|
549
|
-
const formData =
|
|
550
|
-
|
|
551
|
-
appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
|
|
552
|
-
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(undefined, endpoint)));
|
|
647
|
+
const formData = buildMultipartForm(endpoint.multipart, plan.remainingArgs);
|
|
648
|
+
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(options, endpoint)));
|
|
553
649
|
}
|
|
554
650
|
if (httpMethod === "get" || httpMethod === "head") {
|
|
555
|
-
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(
|
|
651
|
+
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(options, endpoint)));
|
|
556
652
|
}
|
|
557
653
|
if (httpMethod === "delete") {
|
|
558
|
-
return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(
|
|
654
|
+
return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(options, endpoint)));
|
|
559
655
|
}
|
|
560
|
-
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(
|
|
656
|
+
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(options, endpoint)));
|
|
561
657
|
};
|
|
562
658
|
}
|
|
563
|
-
function
|
|
564
|
-
return async (
|
|
565
|
-
const plan = planClientRequest(endpoint, prefix,
|
|
659
|
+
function createFetchExecutor(endpoint, prefix, config, contractConfig) {
|
|
660
|
+
return async (requestArgs, options) => {
|
|
661
|
+
const plan = planClientRequest(endpoint, prefix, requestArgs, contractConfig);
|
|
566
662
|
const url = joinClientBaseUrl(config.baseUrl, plan.relativeUrl);
|
|
567
663
|
const headers = {
|
|
568
664
|
Accept: "application/json",
|
|
569
665
|
...typeof config.headers === "function" ? config.headers() : config.headers
|
|
570
666
|
};
|
|
571
|
-
const
|
|
572
|
-
const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input &&
|
|
667
|
+
const cancellation = createRequestCancellation(options?.signal, endpoint.timeout ?? config.timeout ?? 30000);
|
|
668
|
+
const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && Object.keys(plan.remainingArgs).length > 0;
|
|
573
669
|
if (hasBody)
|
|
574
670
|
headers["Content-Type"] = "application/json";
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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");
|
|
671
|
+
try {
|
|
672
|
+
return await cancellation.run(async (signal) => {
|
|
673
|
+
const body = endpoint.multipart ? buildMultipartForm(endpoint.multipart, plan.remainingArgs) : hasBody ? JSON.stringify(plan.remainingArgs) : undefined;
|
|
674
|
+
const res = await fetch(url, {
|
|
675
|
+
method: endpoint.method,
|
|
676
|
+
headers,
|
|
677
|
+
credentials: config.credentials,
|
|
678
|
+
signal,
|
|
679
|
+
...body !== undefined && { body }
|
|
680
|
+
});
|
|
681
|
+
if (!res.ok) {
|
|
682
|
+
await throwForErrorResponse(res, config, { error: res.statusText });
|
|
599
683
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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");
|
|
684
|
+
if (endpoint.rawResponse)
|
|
685
|
+
return res;
|
|
686
|
+
if (!endpoint.output) {
|
|
687
|
+
const text = await res.text();
|
|
688
|
+
if (text.length > 0) {
|
|
689
|
+
throw new Error("Server returned data for an endpoint with no output contract");
|
|
690
|
+
}
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
return endpoint.output.parse(await res.json());
|
|
694
|
+
});
|
|
695
|
+
} catch (error) {
|
|
696
|
+
if (error instanceof RequestCancellationError) {
|
|
697
|
+
throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
|
|
622
698
|
}
|
|
623
|
-
|
|
699
|
+
if (ApiError.is(error))
|
|
700
|
+
throw error;
|
|
701
|
+
const message = error instanceof Error ? error.message : undefined;
|
|
702
|
+
throw new ApiError("UNKNOWN_ERROR", 0, message ? { message } : undefined, message);
|
|
624
703
|
}
|
|
625
|
-
return endpoint.output.parse(await res.json());
|
|
626
704
|
};
|
|
627
705
|
}
|
|
706
|
+
function endpointHasArguments(endpoint) {
|
|
707
|
+
return Boolean(endpoint.params || endpoint.input || endpoint.multipart);
|
|
708
|
+
}
|
|
709
|
+
function readClientRequestArgs(requestArgs) {
|
|
710
|
+
if (requestArgs !== undefined && !isRecord(requestArgs)) {
|
|
711
|
+
throw new TypeError("Endpoint arguments must be an object");
|
|
712
|
+
}
|
|
713
|
+
return requestArgs ?? {};
|
|
714
|
+
}
|
|
715
|
+
function readClientRequestOptions(value) {
|
|
716
|
+
if (!isRecord(value))
|
|
717
|
+
throw new TypeError("Client request options must be an object");
|
|
718
|
+
const signal = value.signal;
|
|
719
|
+
if (signal === undefined)
|
|
720
|
+
return {};
|
|
721
|
+
if (!isAbortSignal(signal)) {
|
|
722
|
+
throw new TypeError("Client request signal must be an AbortSignal");
|
|
723
|
+
}
|
|
724
|
+
return { signal };
|
|
725
|
+
}
|
|
726
|
+
function isAbortSignal(value) {
|
|
727
|
+
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";
|
|
728
|
+
}
|
|
628
729
|
async function throwForErrorResponse(res, config, fallbackBody) {
|
|
629
730
|
const body = await res.json().catch(() => fallbackBody);
|
|
630
731
|
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-
|
|
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-
|
|
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
|
|
9
|
-
*
|
|
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
|
|
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,
|
|
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"}
|