stitchkit 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/client-multipart.d.ts +22 -0
- package/dist/browser/client-multipart.d.ts.map +1 -0
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +7 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/contract/define.d.ts +79 -2
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/factory.d.ts +1 -0
- package/dist/contract/factory.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/{index-h9d1fm0p.js → index-4whcb3c3.js} +64 -22
- package/dist/{index-a9n8m4ec.js → index-jvescqgr.js} +1 -1
- package/dist/{index-x62gnfsk.js → index-pmftwk2a.js} +19 -1
- package/dist/{index-g8kyab85.js → index-y4132gqh.js} +81 -12
- package/dist/{index-tje0q6gp.js → index-zfscdskj.js} +6 -1
- package/dist/{index-4gawbm74.js → index-zza375qp.js} +41 -3
- package/dist/index.js +39 -25
- package/dist/internal/errors.d.ts +6 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/write-download.d.ts +17 -0
- package/dist/internal/write-download.d.ts.map +1 -0
- package/dist/node.js +3 -3
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +14 -3
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +21 -8
- package/dist/server/middleware/cors.d.ts +19 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/router.d.ts +27 -0
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/types.d.ts +44 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +2 -0
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +1 -1
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +2 -1
- package/dist/tools/list-names.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +10 -3
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/mount-upload.d.ts.map +1 -1
- package/dist/tools/mount-wait.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +13 -0
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/names.d.ts +34 -0
- package/dist/tools/names.d.ts.map +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/transports.d.ts.map +1 -1
- package/dist/tools.js +69 -49
- package/llms-full.txt +223 -12
- package/llms.txt +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/server/middleware/cors.ts
|
|
2
2
|
var DEFAULT_CORS_ALLOW_HEADERS = "Content-Type, Authorization, X-Trace-Id, traceparent, tracestate";
|
|
3
|
+
var DEFAULT_CORS_EXPOSE_HEADERS = "Content-Disposition, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified, X-Trace-Id";
|
|
3
4
|
function assertCorsConfig(config) {
|
|
4
5
|
if (config.credentials && (config.origin === undefined || config.origin === "*")) {
|
|
5
6
|
throw new Error("[stitchkit] cors: `credentials: true` cannot be combined with a wildcard origin. " + "Set `origin` to an explicit string or list.");
|
|
@@ -23,6 +24,10 @@ function corsHeaders(config, requestOrigin) {
|
|
|
23
24
|
"Access-Control-Allow-Methods": config.methods ?? "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
24
25
|
"Access-Control-Allow-Headers": config.headers ?? DEFAULT_CORS_ALLOW_HEADERS
|
|
25
26
|
};
|
|
27
|
+
const expose = Array.isArray(config.exposeHeaders) ? config.exposeHeaders.join(", ") : config.exposeHeaders ?? DEFAULT_CORS_EXPOSE_HEADERS;
|
|
28
|
+
if (expose !== "") {
|
|
29
|
+
headers["Access-Control-Expose-Headers"] = expose;
|
|
30
|
+
}
|
|
26
31
|
if (allowOrigin !== undefined) {
|
|
27
32
|
headers["Access-Control-Allow-Origin"] = allowOrigin;
|
|
28
33
|
if (config.credentials) {
|
|
@@ -41,4 +46,4 @@ function corsPreflightResponse(config, req) {
|
|
|
41
46
|
});
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
export { DEFAULT_CORS_ALLOW_HEADERS, assertCorsConfig, corsHeaders, corsPreflightResponse };
|
|
49
|
+
export { DEFAULT_CORS_ALLOW_HEADERS, DEFAULT_CORS_EXPOSE_HEADERS, assertCorsConfig, corsHeaders, corsPreflightResponse };
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRecord
|
|
3
|
+
} from "./index-c7nyw0yt.js";
|
|
4
|
+
|
|
1
5
|
// src/contract/errors.ts
|
|
2
6
|
var APP_ERROR_BRAND = Symbol.for("stitchkit.AppError");
|
|
3
7
|
|
|
@@ -64,6 +68,15 @@ function isStitchErrorCode(code) {
|
|
|
64
68
|
function appError(code, message, details) {
|
|
65
69
|
throw new AppError(code, message, isStitchErrorCode(code) ? STITCH_ERROR_STATUS[code] : 500, details);
|
|
66
70
|
}
|
|
71
|
+
|
|
72
|
+
// src/contract/define.ts
|
|
73
|
+
function mergeMeta(contractMeta, endpointMeta) {
|
|
74
|
+
if (!contractMeta)
|
|
75
|
+
return endpointMeta;
|
|
76
|
+
if (!endpointMeta)
|
|
77
|
+
return { ...contractMeta };
|
|
78
|
+
return { ...contractMeta, ...endpointMeta };
|
|
79
|
+
}
|
|
67
80
|
// src/contract/pagination.ts
|
|
68
81
|
import { z } from "zod";
|
|
69
82
|
|
|
@@ -121,10 +134,35 @@ function normalizeError(err) {
|
|
|
121
134
|
console.error("[stitchkit] unhandled error:", err);
|
|
122
135
|
return new AppError("INTERNAL_SERVER_ERROR", "Internal server error", 500);
|
|
123
136
|
}
|
|
124
|
-
function
|
|
137
|
+
function strippedPaths(before, after, prefix) {
|
|
138
|
+
if (Array.isArray(before)) {
|
|
139
|
+
if (!Array.isArray(after))
|
|
140
|
+
return [];
|
|
141
|
+
return before.flatMap((item, i) => strippedPaths(item, after[i], `${prefix}[${i}]`));
|
|
142
|
+
}
|
|
143
|
+
if (!isRecord(before) || !isRecord(after))
|
|
144
|
+
return [];
|
|
145
|
+
const paths = [];
|
|
146
|
+
for (const [key, value] of Object.entries(before)) {
|
|
147
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
148
|
+
if (!(key in after)) {
|
|
149
|
+
paths.push(path);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
paths.push(...strippedPaths(value, after[key], path));
|
|
153
|
+
}
|
|
154
|
+
return paths;
|
|
155
|
+
}
|
|
156
|
+
function validateHandlerOutput(schema, data, onStripped) {
|
|
125
157
|
const parsed = schema.safeParse(data);
|
|
126
|
-
if (parsed.success)
|
|
158
|
+
if (parsed.success) {
|
|
159
|
+
if (onStripped) {
|
|
160
|
+
const paths = strippedPaths(data, parsed.data, "");
|
|
161
|
+
if (paths.length > 0)
|
|
162
|
+
onStripped(paths);
|
|
163
|
+
}
|
|
127
164
|
return { ok: true, data: parsed.data };
|
|
165
|
+
}
|
|
128
166
|
return {
|
|
129
167
|
ok: false,
|
|
130
168
|
message: `Handler output does not match the contract: ${formatZodError(parsed.error)}`
|
|
@@ -138,4 +176,4 @@ function isWithinDir(root, target) {
|
|
|
138
176
|
return target === root || target === base || target.startsWith(base + sep);
|
|
139
177
|
}
|
|
140
178
|
|
|
141
|
-
export { AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, normalizeError, validateHandlerOutput, isWithinDir };
|
|
179
|
+
export { mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, normalizeError, validateHandlerOutput, isWithinDir };
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
paginatedSchema,
|
|
17
17
|
rateLimited,
|
|
18
18
|
unauthorized
|
|
19
|
-
} from "./index-
|
|
19
|
+
} from "./index-pmftwk2a.js";
|
|
20
20
|
import {
|
|
21
21
|
isRecord,
|
|
22
22
|
mapObject,
|
|
@@ -28,6 +28,25 @@ function inputIsQuery(method) {
|
|
|
28
28
|
return method === "GET" || method === "DELETE";
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// src/browser/client-multipart.ts
|
|
32
|
+
function isFileDescriptor(value) {
|
|
33
|
+
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";
|
|
34
|
+
}
|
|
35
|
+
function isMultipartFile(value) {
|
|
36
|
+
return value instanceof Blob || isFileDescriptor(value);
|
|
37
|
+
}
|
|
38
|
+
function appendMultipartFile(form, field, file) {
|
|
39
|
+
const sink = form;
|
|
40
|
+
sink.append(field, file);
|
|
41
|
+
}
|
|
42
|
+
function appendFormFields(formData, values, skipKeys) {
|
|
43
|
+
for (const [key, value] of Object.entries(values)) {
|
|
44
|
+
if (skipKeys.has(key) || value === undefined || value === null)
|
|
45
|
+
continue;
|
|
46
|
+
formData.append(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
// src/browser/http.ts
|
|
32
51
|
import ky, { isHTTPError } from "ky";
|
|
33
52
|
|
|
@@ -188,6 +207,9 @@ function createHttpClient(config) {
|
|
|
188
207
|
if (options.responseType === "blob") {
|
|
189
208
|
return client[method](url, kyOptions).blob();
|
|
190
209
|
}
|
|
210
|
+
if (options.responseType === "response") {
|
|
211
|
+
return await client[method](url, kyOptions);
|
|
212
|
+
}
|
|
191
213
|
const response = await client[method](url, kyOptions);
|
|
192
214
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
193
215
|
return;
|
|
@@ -229,10 +251,15 @@ function createHttpClient(config) {
|
|
|
229
251
|
}
|
|
230
252
|
|
|
231
253
|
// src/browser/client.ts
|
|
232
|
-
function withTimeout(options,
|
|
233
|
-
|
|
254
|
+
function withTimeout(options, endpoint) {
|
|
255
|
+
const responseType = endpoint.rawResponse ? "response" : undefined;
|
|
256
|
+
if (endpoint.timeout === undefined && responseType === undefined)
|
|
234
257
|
return options;
|
|
235
|
-
return {
|
|
258
|
+
return {
|
|
259
|
+
...options,
|
|
260
|
+
...endpoint.timeout !== undefined && { timeout: endpoint.timeout },
|
|
261
|
+
...responseType && { responseType }
|
|
262
|
+
};
|
|
236
263
|
}
|
|
237
264
|
function withOutput(endpoint, result) {
|
|
238
265
|
const schema = endpoint.output;
|
|
@@ -312,15 +339,15 @@ function createHttpMethod(endpoint, prefix, client, config) {
|
|
|
312
339
|
const formData = new FormData;
|
|
313
340
|
appendMultipartFile(formData, endpoint.multipart, file);
|
|
314
341
|
appendFormFields(formData, firstArg, new Set([...prefixKeys, endpoint.multipart]));
|
|
315
|
-
return withOutput(endpoint, client[httpMethod](url, formData, withTimeout(undefined, endpoint
|
|
342
|
+
return withOutput(endpoint, client[httpMethod](url, formData, withTimeout(undefined, endpoint)));
|
|
316
343
|
}
|
|
317
344
|
if (isGet) {
|
|
318
345
|
const params = collectQueryParams(firstArg, prefixKeys, endpoint);
|
|
319
|
-
return withOutput(endpoint, client.get(url, withTimeout(params ? { params } : undefined, endpoint
|
|
346
|
+
return withOutput(endpoint, client.get(url, withTimeout(params ? { params } : undefined, endpoint)));
|
|
320
347
|
}
|
|
321
348
|
if (httpMethod === "delete") {
|
|
322
349
|
const params = collectQueryParams(firstArg, prefixKeys, endpoint);
|
|
323
|
-
return withOutput(endpoint, client.delete(url, withTimeout(params ? { params } : undefined, endpoint
|
|
350
|
+
return withOutput(endpoint, client.delete(url, withTimeout(params ? { params } : undefined, endpoint)));
|
|
324
351
|
}
|
|
325
352
|
const payload = {};
|
|
326
353
|
for (const [key, value] of Object.entries(firstArg)) {
|
|
@@ -328,7 +355,7 @@ function createHttpMethod(endpoint, prefix, client, config) {
|
|
|
328
355
|
payload[key] = value;
|
|
329
356
|
}
|
|
330
357
|
}
|
|
331
|
-
return withOutput(endpoint, client[httpMethod](url, Object.keys(payload).length > 0 ? payload : undefined, withTimeout(undefined, endpoint
|
|
358
|
+
return withOutput(endpoint, client[httpMethod](url, Object.keys(payload).length > 0 ? payload : undefined, withTimeout(undefined, endpoint)));
|
|
332
359
|
};
|
|
333
360
|
}
|
|
334
361
|
function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
@@ -384,6 +411,8 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
384
411
|
if (!res2.ok) {
|
|
385
412
|
await throwForErrorResponse(res2, config, null);
|
|
386
413
|
}
|
|
414
|
+
if (endpoint.rawResponse)
|
|
415
|
+
return res2;
|
|
387
416
|
if (res2.status === 204)
|
|
388
417
|
return;
|
|
389
418
|
const json2 = await res2.json();
|
|
@@ -401,29 +430,14 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
401
430
|
if (!res.ok) {
|
|
402
431
|
await throwForErrorResponse(res, config, { error: res.statusText });
|
|
403
432
|
}
|
|
433
|
+
if (endpoint.rawResponse)
|
|
434
|
+
return res;
|
|
404
435
|
if (res.status === 204)
|
|
405
436
|
return;
|
|
406
437
|
const json = await res.json();
|
|
407
438
|
return endpoint.output ? endpoint.output.parse(json) : json;
|
|
408
439
|
};
|
|
409
440
|
}
|
|
410
|
-
function isFileDescriptor(value) {
|
|
411
|
-
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";
|
|
412
|
-
}
|
|
413
|
-
function isMultipartFile(value) {
|
|
414
|
-
return value instanceof Blob || isFileDescriptor(value);
|
|
415
|
-
}
|
|
416
|
-
function appendMultipartFile(form, field, file) {
|
|
417
|
-
const sink = form;
|
|
418
|
-
sink.append(field, file);
|
|
419
|
-
}
|
|
420
|
-
function appendFormFields(formData, values, skipKeys) {
|
|
421
|
-
for (const [key, value] of Object.entries(values)) {
|
|
422
|
-
if (skipKeys.has(key) || value === undefined || value === null)
|
|
423
|
-
continue;
|
|
424
|
-
formData.append(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
441
|
async function throwForErrorResponse(res, config, fallbackBody) {
|
|
428
442
|
const body = await res.json().catch(() => fallbackBody);
|
|
429
443
|
config.onError?.(res.status, body);
|
|
@@ -29,8 +29,13 @@ export declare function normalizeError(err: unknown): AppError;
|
|
|
29
29
|
* Validate a handler's return value against the contract `output` schema. A
|
|
30
30
|
* mismatch is a **server** fault (the handler broke its own contract) — shared
|
|
31
31
|
* by the HTTP and tool transports so both report it identically.
|
|
32
|
+
*
|
|
33
|
+
* `onStripped` is the migration diagnostic: a handler returning more than its
|
|
34
|
+
* contract declares has the extra fields **deleted**, correctly but invisibly —
|
|
35
|
+
* types cannot catch it (structural typing does not reject excess properties) and
|
|
36
|
+
* nothing logs it. Pass a reporter to find out; omit it and nothing is computed.
|
|
32
37
|
*/
|
|
33
|
-
export declare function validateHandlerOutput(schema: ZodType, data: unknown): {
|
|
38
|
+
export declare function validateHandlerOutput(schema: ZodType, data: unknown, onStripped?: (paths: string[]) => void): {
|
|
34
39
|
ok: true;
|
|
35
40
|
data: unknown;
|
|
36
41
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQvC,wBAAgB,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CAMxD;AAED,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,eAAe,EAAE,CAM9D;AAKD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI1D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAgBrD;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,EACb,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAe9D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write a downloaded body to disk, with the containment check both download
|
|
3
|
+
* paths need.
|
|
4
|
+
*
|
|
5
|
+
* Shared by the CLI's `--output-dir` and the `mountDownload` native tool. It
|
|
6
|
+
* deliberately takes an **already-resolved** target rather than building one:
|
|
7
|
+
* the two callers derive the filename differently (an untrusted `file.name` vs a
|
|
8
|
+
* name derived from the URL) and, more importantly, report the path back to the
|
|
9
|
+
* user — so composing it here would silently change one of those outputs from
|
|
10
|
+
* relative to absolute.
|
|
11
|
+
*
|
|
12
|
+
* The containment re-check is cheap and belongs on this side: both callers reduce
|
|
13
|
+
* the name to a basename first, but that is an invariant of *their* code, and
|
|
14
|
+
* this is where the write actually happens.
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeDownload(root: string, target: string, data: Uint8Array): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=write-download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-download.d.ts","sourceRoot":"","sources":["../../src/internal/write-download.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
package/dist/node.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
createImplement,
|
|
4
4
|
createSocketIOServer,
|
|
5
5
|
implement
|
|
6
|
-
} from "./index-
|
|
7
|
-
import"./index-
|
|
6
|
+
} from "./index-y4132gqh.js";
|
|
7
|
+
import"./index-zfscdskj.js";
|
|
8
8
|
import {
|
|
9
9
|
AppError,
|
|
10
10
|
appError,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
notFound,
|
|
15
15
|
rateLimited,
|
|
16
16
|
unauthorized
|
|
17
|
-
} from "./index-
|
|
17
|
+
} from "./index-zza375qp.js";
|
|
18
18
|
import"./index-dzx781tm.js";
|
|
19
19
|
import"./index-khwedj16.js";
|
|
20
20
|
import"./index-c7nyw0yt.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAEV,eAAe,EACf,aAAa,EAGd,MAAM,SAAS,CAAC;AAEjB,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAyRxF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,uBAsBnD"}
|
|
@@ -22,11 +22,16 @@
|
|
|
22
22
|
* CONFLICT: 'conflict', RATE_LIMITED: 'rate_limited',
|
|
23
23
|
* INTERNAL_SERVER_ERROR: 'internal',
|
|
24
24
|
* } satisfies Record<StitchErrorCode, string>,
|
|
25
|
-
* render: (info) => ({
|
|
25
|
+
* render: (info, ctx) => ({
|
|
26
|
+
* ok: false,
|
|
27
|
+
* error: { code: info.code, message: info.message },
|
|
28
|
+
* traceId: ctx.traceId,
|
|
29
|
+
* }),
|
|
26
30
|
* });
|
|
27
31
|
* createServer({ services, hooks: { onError } });
|
|
28
32
|
* ```
|
|
29
33
|
*/
|
|
34
|
+
import type { RuntimeContext } from '../contract';
|
|
30
35
|
import { type StitchErrorCode } from '../contract';
|
|
31
36
|
import type { LifecycleHooks } from './types';
|
|
32
37
|
/** The normalised error handed to `render` — code already remapped. */
|
|
@@ -51,9 +56,15 @@ export interface ErrorHookConfig<TWireCode extends string = string> {
|
|
|
51
56
|
*/
|
|
52
57
|
codeMap?: Record<StitchErrorCode, TWireCode>;
|
|
53
58
|
/** Build the response body from the resolved error. */
|
|
54
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Build the response body from the resolved error. `ctx` is the request's
|
|
61
|
+
* `RuntimeContext` — read `ctx.traceId` / `ctx.spanId` to put a correlation id
|
|
62
|
+
* in the envelope, which is the ordinary reason to have one. Declaring the
|
|
63
|
+
* parameter is optional: a one-argument `render` stays assignable.
|
|
64
|
+
*/
|
|
65
|
+
render: (info: ResolvedError, ctx: RuntimeContext) => unknown;
|
|
55
66
|
/** Observe the raw thrown value before rendering — logging / metrics. */
|
|
56
|
-
onError?: (error: unknown, info: ResolvedError) => void;
|
|
67
|
+
onError?: (error: unknown, info: ResolvedError, ctx: RuntimeContext) => void;
|
|
57
68
|
}
|
|
58
69
|
/** Build an `onError` hook from a code map + envelope renderer. */
|
|
59
70
|
export declare function createErrorHook<TWireCode extends string = string>(config: ErrorHookConfig<TWireCode>): NonNullable<LifecycleHooks['onError']>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-hook.d.ts","sourceRoot":"","sources":["../../src/server/error-hook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"error-hook.d.ts","sourceRoot":"","sources":["../../src/server/error-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,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,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC;IAC9D,yEAAyE;IACzE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;CAC9E;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,CAyBxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"implement.d.ts","sourceRoot":"","sources":["../../src/server/implement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"implement.d.ts","sourceRoot":"","sources":["../../src/server/implement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG5E,OAAO,KAAK,EAAE,QAAQ,EAAa,UAAU,EAAE,MAAM,SAAS,CAAC;AAK/D;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrC,IAAI,SAAS,cAAc,GAAG,cAAc,EAC5C,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,UAAU,CAyD3E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAS,cAAc,MACjD,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAC3C,UAAU,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,EAChC,UAAU,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,KAC1B,UAAU,CACd"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { AppError, appError, badRequest, conflict, forbidden, isStitchErrorCode, notFound, rateLimited, STITCH_ERROR_STATUS, type StitchErrorCode, unauthorized, } from '../contract';
|
|
2
2
|
export { errorCode, formatZodError, normalizeError, type ZodIssueSummary, zodIssues, } from '../internal/errors';
|
|
3
|
+
export { isWithinDir } from '../internal/within-dir';
|
|
3
4
|
export { cacheHeaders, createCache } from './cache';
|
|
4
5
|
export { createHandler, createServer } from './create';
|
|
5
6
|
export { createErrorHook, type ErrorHookConfig, type ResolvedError, } from './error-hook';
|
|
@@ -8,7 +9,7 @@ export { type ByteRange, parseByteRange, type ServeFileOptions, serveFile, weakE
|
|
|
8
9
|
export { createImplement, implement } from './implement';
|
|
9
10
|
export { type AuthHook, type AuthHookConfig, type AuthRule, type BearerResolverConfig, createAuthHook, createBearerResolver, extractToken, type JwtPayload, type SignJwtOptions, signJwt, verifyJwt, } from './middleware/auth';
|
|
10
11
|
export { type CookieDef, type CookieOptions, defineCookie, parseCookies, serializeCookie, } from './middleware/cookies';
|
|
11
|
-
export { type CorsConfig, corsHeaders, corsPreflightResponse, DEFAULT_CORS_ALLOW_HEADERS, } from './middleware/cors';
|
|
12
|
+
export { type CorsConfig, corsHeaders, corsPreflightResponse, DEFAULT_CORS_ALLOW_HEADERS, DEFAULT_CORS_EXPOSE_HEADERS, } from './middleware/cors';
|
|
12
13
|
export { deriveCodeChallenge, type PkceMethod, verifyPkce } from './middleware/pkce';
|
|
13
14
|
export { parseMultipart } from './multipart';
|
|
14
15
|
export { generateOpenApiDocument, type OpenApiConfig, type OpenApiDocument, type OpenApiInfo, type OpenApiServer, openApiRoute, } from './openapi';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,KAAK,eAAe,EACpB,YAAY,GACb,MAAM,aAAa,CAAC;AAIrB,OAAO,EACL,SAAS,EACT,cAAc,EACd,cAAc,EACd,KAAK,eAAe,EACpB,SAAS,GACV,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,KAAK,eAAe,EACpB,YAAY,GACb,MAAM,aAAa,CAAC;AAIrB,OAAO,EACL,SAAS,EACT,cAAc,EACd,cAAc,EACd,KAAK,eAAe,EACpB,SAAS,GACV,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,KAAK,SAAS,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,SAAS,EACT,QAAQ,GACT,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,oBAAoB,EACzB,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,OAAO,EACP,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,YAAY,EACZ,YAAY,EACZ,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,UAAU,EACf,WAAW,EACX,qBAAqB,EACrB,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,KAAK,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,uBAAuB,EACvB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,EACL,KAAK,eAAe,EACpB,SAAS,EACT,eAAe,EACf,aAAa,EACb,eAAe,EACf,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrE,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,QAAQ,EACR,cAAc,EACd,SAAS,EACT,QAAQ,EACR,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,YAAY,EACjB,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,aAAa,GACd,MAAM,aAAa,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
socketIoLane,
|
|
11
11
|
staticRoute,
|
|
12
12
|
webSocketLane
|
|
13
|
-
} from "../index-
|
|
13
|
+
} from "../index-y4132gqh.js";
|
|
14
14
|
import {
|
|
15
15
|
createAuthHook,
|
|
16
16
|
createBearerResolver,
|
|
@@ -23,12 +23,13 @@ import {
|
|
|
23
23
|
signJwt,
|
|
24
24
|
verifyJwt,
|
|
25
25
|
verifyPkce
|
|
26
|
-
} from "../index-
|
|
26
|
+
} from "../index-jvescqgr.js";
|
|
27
27
|
import {
|
|
28
28
|
DEFAULT_CORS_ALLOW_HEADERS,
|
|
29
|
+
DEFAULT_CORS_EXPOSE_HEADERS,
|
|
29
30
|
corsHeaders,
|
|
30
31
|
corsPreflightResponse
|
|
31
|
-
} from "../index-
|
|
32
|
+
} from "../index-zfscdskj.js";
|
|
32
33
|
import {
|
|
33
34
|
jsonSchemaFields,
|
|
34
35
|
toJsonSchema
|
|
@@ -43,12 +44,13 @@ import {
|
|
|
43
44
|
forbidden,
|
|
44
45
|
formatZodError,
|
|
45
46
|
isStitchErrorCode,
|
|
47
|
+
isWithinDir,
|
|
46
48
|
normalizeError,
|
|
47
49
|
notFound,
|
|
48
50
|
rateLimited,
|
|
49
51
|
unauthorized,
|
|
50
52
|
zodIssues
|
|
51
|
-
} from "../index-
|
|
53
|
+
} from "../index-zza375qp.js";
|
|
52
54
|
import {
|
|
53
55
|
extractIp,
|
|
54
56
|
generateTraceId,
|
|
@@ -120,7 +122,7 @@ function cacheHeaders(maxAge, scope = "public") {
|
|
|
120
122
|
}
|
|
121
123
|
// src/server/error-hook.ts
|
|
122
124
|
function createErrorHook(config) {
|
|
123
|
-
return (
|
|
125
|
+
return (ctx, error) => {
|
|
124
126
|
const appErr = normalizeError(error);
|
|
125
127
|
const code = config.codeMap && isStitchErrorCode(appErr.code) ? config.codeMap[appErr.code] : appErr.code;
|
|
126
128
|
const info = {
|
|
@@ -130,8 +132,8 @@ function createErrorHook(config) {
|
|
|
130
132
|
details: appErr.details,
|
|
131
133
|
hint: appErr.hint
|
|
132
134
|
};
|
|
133
|
-
config.onError?.(error, info);
|
|
134
|
-
return new Response(JSON.stringify(config.render(info)), {
|
|
135
|
+
config.onError?.(error, info, ctx);
|
|
136
|
+
return new Response(JSON.stringify(config.render(info, ctx)), {
|
|
135
137
|
status: info.status,
|
|
136
138
|
headers: { "content-type": "application/json" }
|
|
137
139
|
});
|
|
@@ -425,7 +427,16 @@ function generateOpenApiDocument(config) {
|
|
|
425
427
|
}
|
|
426
428
|
}
|
|
427
429
|
const responses = { ...errorResponses(method.scope) };
|
|
428
|
-
if (method.
|
|
430
|
+
if (method.rawResponse) {
|
|
431
|
+
responses["200"] = {
|
|
432
|
+
description: "Success",
|
|
433
|
+
content: {
|
|
434
|
+
[method.contentType ?? "application/octet-stream"]: {
|
|
435
|
+
schema: { type: "string", format: "binary" }
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
} else if (method.outputSchema) {
|
|
429
440
|
responses["200"] = {
|
|
430
441
|
description: "Success",
|
|
431
442
|
content: { "application/json": { schema: safeJson(method.outputSchema, "output") } }
|
|
@@ -645,6 +656,7 @@ export {
|
|
|
645
656
|
openApiRoute,
|
|
646
657
|
notFound,
|
|
647
658
|
normalizeError,
|
|
659
|
+
isWithinDir,
|
|
648
660
|
isStitchErrorCode,
|
|
649
661
|
implement,
|
|
650
662
|
getClientInfo,
|
|
@@ -676,6 +688,7 @@ export {
|
|
|
676
688
|
badRequest,
|
|
677
689
|
appError,
|
|
678
690
|
STITCH_ERROR_STATUS,
|
|
691
|
+
DEFAULT_CORS_EXPOSE_HEADERS,
|
|
679
692
|
DEFAULT_CORS_ALLOW_HEADERS,
|
|
680
693
|
AppError
|
|
681
694
|
};
|
|
@@ -3,6 +3,12 @@ export interface CorsConfig {
|
|
|
3
3
|
credentials?: boolean;
|
|
4
4
|
methods?: string;
|
|
5
5
|
headers?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Response headers a cross-origin caller is allowed to *read*
|
|
8
|
+
* (`Access-Control-Expose-Headers`). Defaults to
|
|
9
|
+
* {@link DEFAULT_CORS_EXPOSE_HEADERS}; pass `[]` to emit none.
|
|
10
|
+
*/
|
|
11
|
+
exposeHeaders?: string | string[];
|
|
6
12
|
}
|
|
7
13
|
/**
|
|
8
14
|
* The default `Access-Control-Allow-Headers` — the request headers stitchkit's
|
|
@@ -17,6 +23,19 @@ export interface CorsConfig {
|
|
|
17
23
|
* feature dies on the cross-origin preflight.
|
|
18
24
|
*/
|
|
19
25
|
export declare const DEFAULT_CORS_ALLOW_HEADERS = "Content-Type, Authorization, X-Trace-Id, traceparent, tracestate";
|
|
26
|
+
/**
|
|
27
|
+
* The default `Access-Control-Expose-Headers`. Without it a cross-origin
|
|
28
|
+
* `fetch` can read only the CORS-safelisted response headers, so a browser
|
|
29
|
+
* downloading a file cannot recover its name (`Content-Disposition`), and a
|
|
30
|
+
* client cannot revalidate (`ETag`) or resume (`Content-Range`) — the very
|
|
31
|
+
* headers `serveFile` exists to emit.
|
|
32
|
+
*
|
|
33
|
+
* An explicit list rather than `*`, because the wildcard is ignored whenever
|
|
34
|
+
* the request carries credentials — exactly the authenticated download case.
|
|
35
|
+
* These are headers the server already chose to send; exposing them reveals
|
|
36
|
+
* nothing new. Opt out with `exposeHeaders: []`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_CORS_EXPOSE_HEADERS = "Content-Disposition, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified, X-Trace-Id";
|
|
20
39
|
/**
|
|
21
40
|
* Reject an unsafe CORS config at construction. `credentials: true` with a
|
|
22
41
|
* wildcard origin would reflect *any* caller's `Origin` with
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/cors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/cors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,qEAC6B,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,uGAC8D,CAAC;AAEvG;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAOzD;AAoBD,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA2BxB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,GAAG,QAAQ,CAKhF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../src/server/openapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE/D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IACxB,iFAAiF;IACjF,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC,CAAC;IAChE,oCAAoC;IACpC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAChD;AAsED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../src/server/openapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE/D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IACxB,iFAAiF;IACjF,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC,CAAC;IAChE,oCAAoC;IACpC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAChD;AAsED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAmI9E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,QAAQ,CAM9E"}
|
package/dist/server/router.d.ts
CHANGED
|
@@ -27,6 +27,33 @@ export declare function matchRoute(routeMap: RouteMap, httpMethod: string, pathn
|
|
|
27
27
|
export declare function allowedMethods(routeMap: RouteMap, pathname: string): string[];
|
|
28
28
|
/** Startup guard — throws when two routes collapse to the same shape. */
|
|
29
29
|
export declare function validateRoutes(routeMap: RouteMap): void;
|
|
30
|
+
/** One contract route that a raw route matches first, so the contract route is dead. */
|
|
31
|
+
export interface ShadowedRoute {
|
|
32
|
+
/** The contract route pattern, e.g. `GET /documents/:id/pdf`. */
|
|
33
|
+
pattern: string;
|
|
34
|
+
/** `serviceName.key` of the endpoint that will never run. */
|
|
35
|
+
endpoint: string;
|
|
36
|
+
/** The raw route that wins, e.g. `GET /documents/:id/pdf` or `ALL /files/*`. */
|
|
37
|
+
rawRoute: string;
|
|
38
|
+
/** The shadowed endpoint's scope — the auth gate the raw route bypasses. */
|
|
39
|
+
scope?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Startup diagnostic — raw routes are matched **before** contract routes, so a
|
|
43
|
+
* raw route covering a contract path silently wins and the endpoint never runs.
|
|
44
|
+
*
|
|
45
|
+
* This matters most for the migration raw-response endpoints exist to enable
|
|
46
|
+
* (→ ADR 0038): move a download out of `rawRoutes` into the contract to gain the
|
|
47
|
+
* auth gate, forget to delete the old raw route, and the bytes keep being served
|
|
48
|
+
* ungated — with no error anywhere. Reported, not thrown: an overlapping
|
|
49
|
+
* wildcard (a SPA fallback) can be deliberate, and refusing to boot a working
|
|
50
|
+
* app would be the worse failure.
|
|
51
|
+
*
|
|
52
|
+
* Detection runs the **real** `matchRawRoute` against a concrete probe path
|
|
53
|
+
* built from each contract route, so it can never disagree with what the
|
|
54
|
+
* dispatcher actually does.
|
|
55
|
+
*/
|
|
56
|
+
export declare function findShadowedRoutes(routeMap: RouteMap, rawRoutes: RawRoute[] | undefined): ShadowedRoute[];
|
|
30
57
|
export declare function matchRawRoute(rawRoutes: RawRoute[], httpMethod: string, pathname: string): {
|
|
31
58
|
route: RawRoute;
|
|
32
59
|
params: Record<string, string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE/E,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAqCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,QAAQ,CAmCjE;AAED,wBAAgB,UAAU,CACxB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,UAAU,GAAG,IAAI,CAkBnB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAc7E;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAiBvD;AAID,wBAAgB,aAAa,CAC3B,SAAS,EAAE,QAAQ,EAAE,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CAkC5D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAkCjE"}
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE/E,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAqCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,QAAQ,CAmCjE;AAED,wBAAgB,UAAU,CACxB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,UAAU,GAAG,IAAI,CAkBnB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAc7E;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAiBvD;AAED,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,QAAQ,EAAE,GAAG,SAAS,GAChC,aAAa,EAAE,CAmBjB;AAID,wBAAgB,aAAa,CAC3B,SAAS,EAAE,QAAQ,EAAE,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CAkC5D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAkCjE"}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -5,11 +5,33 @@ type Prop<T, K extends string> = K extends keyof T ? T[K] : undefined;
|
|
|
5
5
|
type InferParams<E> = Prop<E, 'params'> extends ZodType<infer P> ? P : undefined;
|
|
6
6
|
type InferInput<E> = Prop<E, 'input'> extends ZodType<infer I> ? I : undefined;
|
|
7
7
|
type InferOutput<E> = Prop<E, 'output'> extends ZodType<infer O> ? O : never;
|
|
8
|
+
/**
|
|
9
|
+
* What an endpoint's handler must return: the `Response` itself for a `raw`
|
|
10
|
+
* endpoint, the output type when there is an output schema, nothing otherwise.
|
|
11
|
+
* Enforced in both directions — a raw handler returning data and a normal
|
|
12
|
+
* handler returning a `Response` are both compile errors (the latter used to
|
|
13
|
+
* be accepted and silently serialized to `{}`).
|
|
14
|
+
*/
|
|
15
|
+
type HandlerReturn<E> = E extends {
|
|
16
|
+
rawResponse: true;
|
|
17
|
+
} ? Response | Promise<Response> : Prop<E, 'output'> extends ZodType ? Promise<InferOutput<E>> | InferOutput<E> : void | Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* `ctx.req` is optional in general — a tool call has no `Request`. A raw
|
|
20
|
+
* endpoint is HTTP-only by declaration, and its handler needs the request
|
|
21
|
+
* (`serveFile(ctx.req, …)` reads `Range` / `If-None-Match`), so it is narrowed
|
|
22
|
+
* to a guaranteed `Request` there rather than making every raw handler write
|
|
23
|
+
* a non-null assertion. → ADR 0038.
|
|
24
|
+
*/
|
|
25
|
+
type RequiredRequest<E> = E extends {
|
|
26
|
+
rawResponse: true;
|
|
27
|
+
} ? {
|
|
28
|
+
req: Request;
|
|
29
|
+
} : unknown;
|
|
8
30
|
export type Handlers<C extends Record<string, EndpointDef>, TCtx extends RuntimeContext = HandlerContext> = {
|
|
9
31
|
[K in keyof C]: (ctx: TCtx & {
|
|
10
32
|
params: InferParams<C[K]>;
|
|
11
33
|
input: InferInput<C[K]>;
|
|
12
|
-
}
|
|
34
|
+
} & RequiredRequest<C[K]>) => HandlerReturn<C[K]>;
|
|
13
35
|
};
|
|
14
36
|
export interface MethodDef<TParams = unknown, TInput = unknown, TOutput = unknown> {
|
|
15
37
|
method: HttpMethod;
|
|
@@ -54,6 +76,14 @@ export interface MethodDef<TParams = unknown, TInput = unknown, TOutput = unknow
|
|
|
54
76
|
* the consumer narrows the type. Never serialized to OpenAPI. → ADR 0021.
|
|
55
77
|
*/
|
|
56
78
|
meta?: Record<string, unknown>;
|
|
79
|
+
/**
|
|
80
|
+
* The handler returns the `Response` itself — from `EndpointDef.rawResponse`. Routed
|
|
81
|
+
* and gated like any endpoint, but never serialized, never validated against
|
|
82
|
+
* an output schema and never mounted as a tool. → ADR 0038.
|
|
83
|
+
*/
|
|
84
|
+
rawResponse?: true;
|
|
85
|
+
/** Documented response media type of a raw-response endpoint — OpenAPI only. */
|
|
86
|
+
contentType?: string;
|
|
57
87
|
handler: (ctx: RuntimeContext) => Promise<TOutput> | TOutput;
|
|
58
88
|
}
|
|
59
89
|
export interface ServiceDef {
|
|
@@ -152,6 +182,19 @@ export interface HandlerConfig {
|
|
|
152
182
|
cors?: CorsConfig;
|
|
153
183
|
hooks?: LifecycleHooks;
|
|
154
184
|
logging?: boolean | StitchLogger;
|
|
185
|
+
/**
|
|
186
|
+
* Report handler-output keys the contract schema removed, as dot-paths, through
|
|
187
|
+
* the configured logger. **Off by default** — the strip itself is correct (the
|
|
188
|
+
* contract is the published shape of the response), it is only *invisible*, and
|
|
189
|
+
* a permanent key-diff on every response is the wrong price for that.
|
|
190
|
+
*
|
|
191
|
+
* Turn it on while migrating a live API: your handlers may be returning more
|
|
192
|
+
* than the contract declares, and nothing else will tell you — types cannot
|
|
193
|
+
* (structural typing does not reject excess properties) and the client just
|
|
194
|
+
* receives fewer fields. Read the list, fix or widen the contracts, turn it off.
|
|
195
|
+
* → ADR 0037.
|
|
196
|
+
*/
|
|
197
|
+
warnOnOutputStrip?: boolean;
|
|
155
198
|
traceId?: (req: Request) => string;
|
|
156
199
|
/**
|
|
157
200
|
* Trust the `x-forwarded-for` / `x-real-ip` headers for the client IP.
|