stitchkit 0.2.0 → 0.3.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/http.d.ts +5 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/{index-a35v22fh.js → index-0t460v7k.js} +12 -4
- package/dist/{index-n7bmdwmz.js → index-4b1j82gp.js} +8 -4
- package/dist/{index-5sxnvwb1.js → index-4kz3aqpx.js} +2 -2
- package/dist/{index-v2z2v3mq.js → index-dpj1jcys.js} +150 -60
- package/dist/index-kzfs85xp.js +9 -0
- package/dist/index-mwmpw6j1.js +60 -0
- package/dist/index-q6ja2qwq.js +7 -0
- package/dist/index.js +4 -3
- package/dist/internal/errors.d.ts +13 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/safe-json.d.ts +19 -0
- package/dist/internal/safe-json.d.ts.map +1 -0
- package/dist/internal/within-dir.d.ts +7 -0
- package/dist/internal/within-dir.d.ts.map +1 -0
- package/dist/node.js +6 -3
- package/dist/observability/context.d.ts +9 -1
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/index.js +35 -15
- package/dist/observability/sanitize.d.ts.map +1 -1
- package/dist/server/context.d.ts +3 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/event-bus.d.ts +10 -1
- package/dist/server/event-bus.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +86 -32
- package/dist/server/logger.d.ts +5 -4
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +24 -1
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cookies.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts +7 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/multipart.d.ts +2 -1
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/rate-limit.d.ts +7 -2
- package/dist/server/rate-limit.d.ts.map +1 -1
- package/dist/server/request.d.ts +27 -4
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/router.d.ts +4 -2
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/stream.d.ts +3 -1
- package/dist/server/stream.d.ts.map +1 -1
- package/dist/server/types.d.ts +12 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/coerce.d.ts +8 -4
- package/dist/tools/coerce.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/manifest.d.ts +4 -0
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/mcp-handler.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +2 -2
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +140 -93
- package/package.json +1 -1
- package/dist/index-ke4mx4ea.js +0 -38
package/dist/browser/http.d.ts
CHANGED
|
@@ -39,6 +39,11 @@ export interface HttpClientConfig {
|
|
|
39
39
|
statusCodes?: number[];
|
|
40
40
|
};
|
|
41
41
|
parseError?: (body: unknown) => ErrorEnvelope['error'] | null;
|
|
42
|
+
/**
|
|
43
|
+
* Path prefixes whose `401` must NOT emit an `unauthorized` event — the
|
|
44
|
+
* login / session endpoints, where a `401` is an expected outcome. Matched
|
|
45
|
+
* against `URL.pathname`, so the prefixes start with `/`.
|
|
46
|
+
*/
|
|
42
47
|
authEndpoints?: string[];
|
|
43
48
|
/**
|
|
44
49
|
* Extra headers added to every request. A function is re-evaluated per
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/browser/http.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/browser/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,9 +21,17 @@ function normalizeError(err) {
|
|
|
21
21
|
if (err instanceof z.ZodError) {
|
|
22
22
|
return new AppError("VALIDATION_ERROR", formatZodError(err), 400);
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
console.error("[stitchkit] unhandled error:", err);
|
|
25
|
+
return new AppError("INTERNAL_SERVER_ERROR", "Internal server error", 500);
|
|
26
|
+
}
|
|
27
|
+
function validateHandlerOutput(schema, data) {
|
|
28
|
+
const parsed = schema.safeParse(data);
|
|
29
|
+
if (parsed.success)
|
|
30
|
+
return { ok: true, data: parsed.data };
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
message: `Handler output does not match the contract: ${formatZodError(parsed.error)}`
|
|
34
|
+
};
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
export { formatZodError, normalizeError };
|
|
37
|
+
export { formatZodError, normalizeError, validateHandlerOutput };
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeError
|
|
3
|
+
} from "./index-0t460v7k.js";
|
|
4
|
+
|
|
1
5
|
// src/server/stream.ts
|
|
2
6
|
function streamSSE(generator) {
|
|
3
7
|
const encoder = new TextEncoder;
|
|
@@ -15,8 +19,8 @@ function streamSSE(generator) {
|
|
|
15
19
|
`));
|
|
16
20
|
controller.close();
|
|
17
21
|
} catch (err) {
|
|
18
|
-
const
|
|
19
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(
|
|
22
|
+
const envelope = normalizeError(err).toJSON();
|
|
23
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(envelope)}
|
|
20
24
|
|
|
21
25
|
`));
|
|
22
26
|
controller.close();
|
|
@@ -48,9 +52,9 @@ async function* parseSSE(response, options) {
|
|
|
48
52
|
buffer = lines.pop() ?? "";
|
|
49
53
|
for (const rawLine of lines) {
|
|
50
54
|
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
51
|
-
if (!line.startsWith("data:
|
|
55
|
+
if (!line.startsWith("data:"))
|
|
52
56
|
continue;
|
|
53
|
-
const data = line.slice(
|
|
57
|
+
const data = line.slice(5).replace(/^ /, "");
|
|
54
58
|
if (data === "[DONE]")
|
|
55
59
|
return;
|
|
56
60
|
try {
|
|
@@ -40,7 +40,7 @@ function createHttpClient(config) {
|
|
|
40
40
|
let ssrCookies = null;
|
|
41
41
|
let isLoggedOut = false;
|
|
42
42
|
const listeners = new Set;
|
|
43
|
-
const authEndpoints = config.authEndpoints ?? ["auth/"];
|
|
43
|
+
const authEndpoints = config.authEndpoints ?? ["/auth/"];
|
|
44
44
|
const parseError = config.parseError ?? parseApiErrorBody;
|
|
45
45
|
function emit(event) {
|
|
46
46
|
for (const fn of listeners) {
|
|
@@ -76,7 +76,7 @@ function createHttpClient(config) {
|
|
|
76
76
|
async ({ request: request2, response }) => {
|
|
77
77
|
if (response.status === 401) {
|
|
78
78
|
const url = new URL(request2.url).pathname;
|
|
79
|
-
if (!isLoggedOut && !authEndpoints.some((path) => url.
|
|
79
|
+
if (!isLoggedOut && !authEndpoints.some((path) => url.startsWith(path))) {
|
|
80
80
|
isLoggedOut = true;
|
|
81
81
|
emit({ type: "unauthorized" });
|
|
82
82
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./index-
|
|
2
|
+
isWithinDir
|
|
3
|
+
} from "./index-q6ja2qwq.js";
|
|
4
|
+
import {
|
|
5
|
+
normalizeError,
|
|
6
|
+
validateHandlerOutput
|
|
7
|
+
} from "./index-0t460v7k.js";
|
|
4
8
|
import {
|
|
5
9
|
AppError,
|
|
6
10
|
badRequest
|
|
@@ -9,62 +13,103 @@ import {
|
|
|
9
13
|
extractIp,
|
|
10
14
|
getClientInfo,
|
|
11
15
|
parseQueryParams,
|
|
16
|
+
resolveSocketIp,
|
|
12
17
|
resolveTraceId
|
|
13
|
-
} from "./index-
|
|
18
|
+
} from "./index-mwmpw6j1.js";
|
|
19
|
+
import {
|
|
20
|
+
isUnsafeKey,
|
|
21
|
+
safeJsonParse
|
|
22
|
+
} from "./index-kzfs85xp.js";
|
|
14
23
|
|
|
15
24
|
// src/server/multipart.ts
|
|
16
25
|
var DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
17
|
-
async function
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
|
|
26
|
+
async function readBodyCapped(req, maxBytes) {
|
|
27
|
+
const reader = req.body?.getReader();
|
|
28
|
+
if (!reader)
|
|
29
|
+
return new Uint8Array(0);
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let total = 0;
|
|
32
|
+
try {
|
|
33
|
+
while (true) {
|
|
34
|
+
const { done, value } = await reader.read();
|
|
35
|
+
if (done)
|
|
36
|
+
break;
|
|
37
|
+
total += value.length;
|
|
38
|
+
if (total > maxBytes) {
|
|
39
|
+
await reader.cancel();
|
|
40
|
+
badRequest(`Upload exceeds the ${Math.round(maxBytes / 1024 / 1024)} MB limit`);
|
|
41
|
+
}
|
|
42
|
+
chunks.push(value);
|
|
43
|
+
}
|
|
44
|
+
} finally {
|
|
45
|
+
reader.releaseLock();
|
|
46
|
+
}
|
|
47
|
+
const buffer = new Uint8Array(total);
|
|
48
|
+
let offset = 0;
|
|
49
|
+
for (const chunk of chunks) {
|
|
50
|
+
buffer.set(chunk, offset);
|
|
51
|
+
offset += chunk.length;
|
|
21
52
|
}
|
|
22
|
-
|
|
53
|
+
return buffer;
|
|
54
|
+
}
|
|
55
|
+
async function parseMultipart(req, fileField, fieldsSchema, maxBytes = DEFAULT_MAX_UPLOAD_BYTES) {
|
|
56
|
+
const body = await readBodyCapped(req, maxBytes);
|
|
57
|
+
const contentType = req.headers.get("content-type") ?? "";
|
|
58
|
+
const formData = await new Response(body, {
|
|
59
|
+
headers: { "content-type": contentType }
|
|
60
|
+
}).formData();
|
|
23
61
|
const file = formData.get(fileField);
|
|
24
62
|
if (!file || !(file instanceof File)) {
|
|
25
63
|
badRequest(`Missing file field: ${fileField}`);
|
|
26
64
|
}
|
|
27
|
-
if (file.size > maxBytes) {
|
|
28
|
-
badRequest(`Upload exceeds the ${Math.round(maxBytes / 1024 / 1024)} MB limit`);
|
|
29
|
-
}
|
|
30
65
|
const fields = {};
|
|
31
66
|
for (const [key, value] of formData.entries()) {
|
|
32
67
|
if (key === fileField)
|
|
33
68
|
continue;
|
|
69
|
+
if (isUnsafeKey(key))
|
|
70
|
+
continue;
|
|
34
71
|
if (typeof value === "string") {
|
|
35
72
|
try {
|
|
36
|
-
fields[key] =
|
|
73
|
+
fields[key] = safeJsonParse(value);
|
|
37
74
|
} catch {
|
|
38
75
|
fields[key] = value;
|
|
39
76
|
}
|
|
40
77
|
}
|
|
41
78
|
}
|
|
42
|
-
|
|
43
|
-
return { file, fields: parsed };
|
|
79
|
+
return { file, fields: fieldsSchema ? fieldsSchema.parse(fields) : fields };
|
|
44
80
|
}
|
|
45
81
|
|
|
46
82
|
// src/server/middleware/cors.ts
|
|
47
|
-
function
|
|
48
|
-
|
|
83
|
+
function assertCorsConfig(config) {
|
|
84
|
+
if (config.credentials && (config.origin === undefined || config.origin === "*")) {
|
|
85
|
+
throw new Error("[stitchkit] cors: `credentials: true` cannot be combined with a wildcard origin. " + "Set `origin` to an explicit string or list.");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function resolveOrigin(config, requestOrigin) {
|
|
49
89
|
if (config.origin === undefined || config.origin === "*") {
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
90
|
+
return "*";
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(config.origin)) {
|
|
93
|
+
if (!requestOrigin)
|
|
94
|
+
return;
|
|
95
|
+
const lower = requestOrigin.toLowerCase();
|
|
96
|
+
return config.origin.some((o) => o.toLowerCase() === lower) ? requestOrigin : undefined;
|
|
55
97
|
}
|
|
98
|
+
return config.origin;
|
|
99
|
+
}
|
|
100
|
+
function corsHeaders(config, requestOrigin) {
|
|
101
|
+
const allowOrigin = resolveOrigin(config, requestOrigin);
|
|
56
102
|
const headers = {
|
|
57
103
|
"Access-Control-Allow-Methods": config.methods ?? "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
58
104
|
"Access-Control-Allow-Headers": config.headers ?? "Content-Type, Authorization, X-Trace-Id"
|
|
59
105
|
};
|
|
60
106
|
if (allowOrigin !== undefined) {
|
|
61
107
|
headers["Access-Control-Allow-Origin"] = allowOrigin;
|
|
108
|
+
if (config.credentials) {
|
|
109
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
110
|
+
}
|
|
62
111
|
}
|
|
63
|
-
if (config.
|
|
64
|
-
headers["Access-Control-Allow-Credentials"] = "true";
|
|
65
|
-
}
|
|
66
|
-
const variesByOrigin = Array.isArray(config.origin) || (config.origin === undefined || config.origin === "*") && Boolean(config.credentials);
|
|
67
|
-
if (variesByOrigin) {
|
|
112
|
+
if (Array.isArray(config.origin)) {
|
|
68
113
|
headers.Vary = "Origin";
|
|
69
114
|
}
|
|
70
115
|
return headers;
|
|
@@ -77,7 +122,8 @@ function corsPreflightResponse(config, req) {
|
|
|
77
122
|
}
|
|
78
123
|
|
|
79
124
|
// src/server/router.ts
|
|
80
|
-
import {
|
|
125
|
+
import { readFile, stat } from "node:fs/promises";
|
|
126
|
+
import { extname, resolve } from "node:path";
|
|
81
127
|
function joinPath(...parts) {
|
|
82
128
|
const joined = parts.filter(Boolean).map((part) => part.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
83
129
|
return `/${joined}`;
|
|
@@ -195,25 +241,51 @@ function matchRawRoute(rawRoutes, httpMethod, pathname) {
|
|
|
195
241
|
}
|
|
196
242
|
return null;
|
|
197
243
|
}
|
|
244
|
+
var STATIC_MIME = {
|
|
245
|
+
".html": "text/html; charset=utf-8",
|
|
246
|
+
".css": "text/css; charset=utf-8",
|
|
247
|
+
".js": "text/javascript; charset=utf-8",
|
|
248
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
249
|
+
".json": "application/json; charset=utf-8",
|
|
250
|
+
".svg": "image/svg+xml",
|
|
251
|
+
".png": "image/png",
|
|
252
|
+
".jpg": "image/jpeg",
|
|
253
|
+
".jpeg": "image/jpeg",
|
|
254
|
+
".gif": "image/gif",
|
|
255
|
+
".webp": "image/webp",
|
|
256
|
+
".ico": "image/x-icon",
|
|
257
|
+
".woff2": "font/woff2",
|
|
258
|
+
".txt": "text/plain; charset=utf-8"
|
|
259
|
+
};
|
|
198
260
|
function staticRoute(prefix, dir) {
|
|
199
261
|
const cleanPrefix = prefix.replace(/\/+$/, "");
|
|
200
|
-
const
|
|
262
|
+
const root = resolve(dir.replace(/\/+$/, ""));
|
|
201
263
|
return {
|
|
202
264
|
method: "GET",
|
|
203
265
|
path: `${cleanPrefix}/*`,
|
|
204
266
|
handler: async (req) => {
|
|
205
267
|
const pathname = new URL(req.url).pathname;
|
|
206
|
-
|
|
207
|
-
|
|
268
|
+
let rel;
|
|
269
|
+
try {
|
|
270
|
+
rel = decodeURIComponent(pathname.slice(cleanPrefix.length)).replace(/^\/+/, "");
|
|
271
|
+
} catch {
|
|
272
|
+
return new Response("Bad request", { status: 400 });
|
|
273
|
+
}
|
|
208
274
|
const target = resolve(root, rel);
|
|
209
|
-
if (
|
|
275
|
+
if (!isWithinDir(root, target)) {
|
|
210
276
|
return new Response("Forbidden", { status: 403 });
|
|
211
277
|
}
|
|
212
|
-
const
|
|
213
|
-
if (!
|
|
278
|
+
const info = await stat(target).catch(() => null);
|
|
279
|
+
if (!info?.isFile()) {
|
|
214
280
|
return new Response("Not found", { status: 404 });
|
|
215
281
|
}
|
|
216
|
-
|
|
282
|
+
const body = await readFile(target);
|
|
283
|
+
return new Response(body, {
|
|
284
|
+
headers: {
|
|
285
|
+
"Content-Type": STATIC_MIME[extname(target).toLowerCase()] ?? "application/octet-stream",
|
|
286
|
+
"X-Content-Type-Options": "nosniff"
|
|
287
|
+
}
|
|
288
|
+
});
|
|
217
289
|
}
|
|
218
290
|
};
|
|
219
291
|
}
|
|
@@ -235,13 +307,16 @@ async function readJsonBody(req) {
|
|
|
235
307
|
const text = await req.text();
|
|
236
308
|
if (text.trim() === "")
|
|
237
309
|
return {};
|
|
310
|
+
if (!req.headers.get("content-type")?.includes("application/json")) {
|
|
311
|
+
badRequest("Request body must be application/json");
|
|
312
|
+
}
|
|
238
313
|
try {
|
|
239
|
-
return
|
|
314
|
+
return safeJsonParse(text);
|
|
240
315
|
} catch {
|
|
241
316
|
badRequest("Invalid JSON body");
|
|
242
317
|
}
|
|
243
318
|
}
|
|
244
|
-
async function buildContext(req, url, method, pathParams, traceId) {
|
|
319
|
+
async function buildContext(req, url, method, pathParams, traceId, clientIp) {
|
|
245
320
|
const parsedParams = method.paramsSchema ? method.paramsSchema.parse(pathParams) : undefined;
|
|
246
321
|
let parsedInput;
|
|
247
322
|
let file;
|
|
@@ -265,7 +340,7 @@ async function buildContext(req, url, method, pathParams, traceId) {
|
|
|
265
340
|
}
|
|
266
341
|
const safePathParams = {};
|
|
267
342
|
for (const [k, v] of Object.entries(pathParams)) {
|
|
268
|
-
if (!RESERVED_KEYS.has(k))
|
|
343
|
+
if (!RESERVED_KEYS.has(k) && !isUnsafeKey(k))
|
|
269
344
|
safePathParams[k] = v;
|
|
270
345
|
}
|
|
271
346
|
return {
|
|
@@ -278,10 +353,10 @@ async function buildContext(req, url, method, pathParams, traceId) {
|
|
|
278
353
|
headers: req.headers,
|
|
279
354
|
...safePathParams,
|
|
280
355
|
traceId,
|
|
281
|
-
...getClientInfo(req)
|
|
356
|
+
...getClientInfo(req, clientIp)
|
|
282
357
|
};
|
|
283
358
|
}
|
|
284
|
-
function buildErrorContext(req, url, traceId) {
|
|
359
|
+
function buildErrorContext(req, url, traceId, clientIp) {
|
|
285
360
|
return {
|
|
286
361
|
params: undefined,
|
|
287
362
|
input: undefined,
|
|
@@ -290,7 +365,7 @@ function buildErrorContext(req, url, traceId) {
|
|
|
290
365
|
url,
|
|
291
366
|
headers: req.headers,
|
|
292
367
|
traceId,
|
|
293
|
-
...getClientInfo(req)
|
|
368
|
+
...getClientInfo(req, clientIp)
|
|
294
369
|
};
|
|
295
370
|
}
|
|
296
371
|
|
|
@@ -326,7 +401,10 @@ function timestamp() {
|
|
|
326
401
|
return `${t}.${now.getMilliseconds().toString().padStart(3, "0")}`;
|
|
327
402
|
}
|
|
328
403
|
function elapsedMs(startTime) {
|
|
329
|
-
return
|
|
404
|
+
return performance.now() - startTime;
|
|
405
|
+
}
|
|
406
|
+
function safePath(pathname) {
|
|
407
|
+
return pathname.replace(/[-]/g, "");
|
|
330
408
|
}
|
|
331
409
|
function levelForStatus(status) {
|
|
332
410
|
if (status >= 500)
|
|
@@ -372,15 +450,15 @@ function shouldLog(pathname, method) {
|
|
|
372
450
|
return false;
|
|
373
451
|
return !SKIP_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
|
374
452
|
}
|
|
375
|
-
function logIncoming(req, pathname, traceId) {
|
|
376
|
-
const log = { traceId, startTime:
|
|
453
|
+
function logIncoming(req, pathname, traceId, ipAddress) {
|
|
454
|
+
const log = { traceId, startTime: performance.now() };
|
|
377
455
|
if (!isProd) {
|
|
378
456
|
const mc = METHOD_COLOR[req.method] ?? c.dim;
|
|
379
|
-
console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${traceId}${c.reset} ${c.cyan}→${c.reset} ${pathname} ${ipLabel(
|
|
457
|
+
console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${traceId}${c.reset} ${c.cyan}→${c.reset} ${safePath(pathname)} ${ipLabel(ipAddress ?? "")}`);
|
|
380
458
|
}
|
|
381
459
|
return log;
|
|
382
460
|
}
|
|
383
|
-
function logOutgoing(req, pathname, status, log) {
|
|
461
|
+
function logOutgoing(req, pathname, status, log, ipAddress) {
|
|
384
462
|
const ms = elapsedMs(log.startTime);
|
|
385
463
|
if (isProd) {
|
|
386
464
|
console.log(JSON.stringify({
|
|
@@ -388,42 +466,45 @@ function logOutgoing(req, pathname, status, log) {
|
|
|
388
466
|
level: levelForStatus(status),
|
|
389
467
|
msg: `${req.method} ${pathname} ${status}`,
|
|
390
468
|
...buildLogFields(req.method, pathname, status, Math.round(ms), log.traceId),
|
|
391
|
-
ip:
|
|
469
|
+
ip: ipAddress
|
|
392
470
|
}));
|
|
393
471
|
return;
|
|
394
472
|
}
|
|
395
473
|
const mc = METHOD_COLOR[req.method] ?? c.dim;
|
|
396
|
-
console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${log.traceId}${c.reset} ${c.cyan}←${c.reset} ${pathname} ${statusColor(status)}${status}${c.reset} ${durationColor(ms)}${formatMs(ms)}${c.reset} ${ipLabel(
|
|
474
|
+
console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${log.traceId}${c.reset} ${c.cyan}←${c.reset} ${safePath(pathname)} ${statusColor(status)}${status}${c.reset} ${durationColor(ms)}${formatMs(ms)}${c.reset} ${ipLabel(ipAddress ?? "")}`);
|
|
397
475
|
}
|
|
398
476
|
|
|
399
477
|
// src/server/create.ts
|
|
400
478
|
function createHandler(config) {
|
|
401
|
-
const { cors, hooks, logging = false } = config;
|
|
479
|
+
const { cors, hooks, logging = false, trustProxy = false } = config;
|
|
480
|
+
if (cors)
|
|
481
|
+
assertCorsConfig(cors);
|
|
402
482
|
const customLogger = typeof logging === "object" ? logging : null;
|
|
403
483
|
const useDefaultLog = logging === true;
|
|
404
484
|
const resolveId = config.traceId ?? resolveTraceId;
|
|
405
485
|
const routeMap = buildRouteMap(normalizeGroups(config));
|
|
406
486
|
validateRoutes(routeMap);
|
|
407
|
-
async function dispatch(req, url, traceId, server) {
|
|
487
|
+
async function dispatch(req, url, traceId, server, clientIp) {
|
|
408
488
|
const shouldLogRequest = logging && shouldLog(url.pathname, req.method);
|
|
489
|
+
const ipAddress = extractIp(req, clientIp) || undefined;
|
|
409
490
|
let reqLog;
|
|
410
491
|
if (shouldLogRequest && useDefaultLog) {
|
|
411
|
-
reqLog = logIncoming(req, url.pathname, traceId);
|
|
492
|
+
reqLog = logIncoming(req, url.pathname, traceId, ipAddress);
|
|
412
493
|
}
|
|
413
494
|
if (shouldLogRequest && customLogger) {
|
|
414
495
|
customLogger.debug(`${req.method} ${url.pathname}`, {
|
|
415
496
|
traceId,
|
|
416
497
|
method: req.method,
|
|
417
498
|
path: url.pathname,
|
|
418
|
-
ip:
|
|
499
|
+
ip: ipAddress
|
|
419
500
|
});
|
|
420
|
-
reqLog = { traceId, startTime:
|
|
501
|
+
reqLog = { traceId, startTime: performance.now() };
|
|
421
502
|
}
|
|
422
503
|
const logDone = (status) => {
|
|
423
504
|
if (!reqLog)
|
|
424
505
|
return;
|
|
425
506
|
if (useDefaultLog)
|
|
426
|
-
logOutgoing(req, url.pathname, status, reqLog);
|
|
507
|
+
logOutgoing(req, url.pathname, status, reqLog, ipAddress);
|
|
427
508
|
if (customLogger) {
|
|
428
509
|
const durationMs = Math.round(elapsedMs(reqLog.startTime));
|
|
429
510
|
const level = levelForStatus(status);
|
|
@@ -433,7 +514,7 @@ function createHandler(config) {
|
|
|
433
514
|
const respondError = async (err, errCtx, endpoint) => {
|
|
434
515
|
if (hooks?.onError) {
|
|
435
516
|
try {
|
|
436
|
-
const response = await hooks.onError(errCtx ?? buildErrorContext(req, url, traceId), err, endpoint);
|
|
517
|
+
const response = await hooks.onError(errCtx ?? buildErrorContext(req, url, traceId, clientIp), err, endpoint);
|
|
437
518
|
if (response instanceof Response) {
|
|
438
519
|
const withCors = applyCors(response, cors, req);
|
|
439
520
|
logDone(withCors.status);
|
|
@@ -463,7 +544,8 @@ function createHandler(config) {
|
|
|
463
544
|
try {
|
|
464
545
|
const res = await rawMatch.route.handler(req, {
|
|
465
546
|
params: rawMatch.params,
|
|
466
|
-
server
|
|
547
|
+
server,
|
|
548
|
+
ipAddress
|
|
467
549
|
});
|
|
468
550
|
const withCors = applyCors(res, cors, req);
|
|
469
551
|
logDone(withCors.status);
|
|
@@ -486,7 +568,7 @@ function createHandler(config) {
|
|
|
486
568
|
const { method, pathParams, groupHooks } = match;
|
|
487
569
|
let ctx;
|
|
488
570
|
try {
|
|
489
|
-
ctx = await buildContext(req, url, method, pathParams, traceId);
|
|
571
|
+
ctx = await buildContext(req, url, method, pathParams, traceId, clientIp);
|
|
490
572
|
if (hooks?.beforeHandle) {
|
|
491
573
|
await hooks.beforeHandle(ctx, method);
|
|
492
574
|
}
|
|
@@ -505,7 +587,11 @@ function createHandler(config) {
|
|
|
505
587
|
result = transformed;
|
|
506
588
|
}
|
|
507
589
|
if (method.outputSchema) {
|
|
508
|
-
|
|
590
|
+
const checked = validateHandlerOutput(method.outputSchema, result);
|
|
591
|
+
if (!checked.ok) {
|
|
592
|
+
throw new AppError("INTERNAL_SERVER_ERROR", checked.message, 500);
|
|
593
|
+
}
|
|
594
|
+
result = checked.data;
|
|
509
595
|
}
|
|
510
596
|
if (result === undefined || result === null) {
|
|
511
597
|
logDone(204);
|
|
@@ -520,10 +606,14 @@ function createHandler(config) {
|
|
|
520
606
|
return async (req, server) => {
|
|
521
607
|
const url = new URL(req.url, "http://localhost");
|
|
522
608
|
const traceId = resolveId(req);
|
|
523
|
-
const
|
|
524
|
-
|
|
609
|
+
const clientIp = {
|
|
610
|
+
trustProxy,
|
|
611
|
+
socketIp: resolveSocketIp(req, server)
|
|
612
|
+
};
|
|
613
|
+
const response = await dispatch(req, url, traceId, server, clientIp);
|
|
614
|
+
try {
|
|
525
615
|
response.headers.set("x-request-id", traceId);
|
|
526
|
-
}
|
|
616
|
+
} catch {}
|
|
527
617
|
return response;
|
|
528
618
|
};
|
|
529
619
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isUnsafeKey
|
|
3
|
+
} from "./index-kzfs85xp.js";
|
|
4
|
+
import {
|
|
5
|
+
isRecord
|
|
6
|
+
} from "./index-809wc1tt.js";
|
|
7
|
+
|
|
8
|
+
// src/server/request.ts
|
|
9
|
+
function generateTraceId() {
|
|
10
|
+
return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
|
|
11
|
+
}
|
|
12
|
+
function resolveTraceId(req) {
|
|
13
|
+
const header = req.headers.get("x-request-id") ?? req.headers.get("x-trace-id");
|
|
14
|
+
const trimmed = header?.trim();
|
|
15
|
+
if (trimmed && trimmed.length <= 128 && /^[\w.-]+$/.test(trimmed)) {
|
|
16
|
+
return trimmed;
|
|
17
|
+
}
|
|
18
|
+
return generateTraceId();
|
|
19
|
+
}
|
|
20
|
+
function resolveSocketIp(req, server) {
|
|
21
|
+
if (typeof server === "object" && server !== null && "requestIP" in server && typeof server.requestIP === "function") {
|
|
22
|
+
const addr = server.requestIP(req);
|
|
23
|
+
if (isRecord(addr) && typeof addr.address === "string" && addr.address) {
|
|
24
|
+
return addr.address;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if ("ip" in req && typeof req.ip === "string" && req.ip)
|
|
28
|
+
return req.ip;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
function extractIp(req, options = {}) {
|
|
32
|
+
if (options.trustProxy) {
|
|
33
|
+
const forwarded = req.headers.get("x-forwarded-for");
|
|
34
|
+
if (forwarded)
|
|
35
|
+
return (forwarded.split(",")[0] ?? "").trim().replace(/^::ffff:/, "");
|
|
36
|
+
const realIp = req.headers.get("x-real-ip");
|
|
37
|
+
if (realIp)
|
|
38
|
+
return realIp.trim().replace(/^::ffff:/, "");
|
|
39
|
+
}
|
|
40
|
+
return (options.socketIp ?? "").replace(/^::ffff:/, "");
|
|
41
|
+
}
|
|
42
|
+
function getClientInfo(req, options = {}) {
|
|
43
|
+
return {
|
|
44
|
+
ipAddress: extractIp(req, options) || undefined,
|
|
45
|
+
userAgent: req.headers.get("user-agent") ?? undefined
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function parseQueryParams(url) {
|
|
49
|
+
const query = {};
|
|
50
|
+
for (const key of new Set(url.searchParams.keys())) {
|
|
51
|
+
if (isUnsafeKey(key))
|
|
52
|
+
continue;
|
|
53
|
+
const values = url.searchParams.getAll(key);
|
|
54
|
+
const [first] = values;
|
|
55
|
+
query[key] = values.length === 1 && first !== undefined ? first : values;
|
|
56
|
+
}
|
|
57
|
+
return query;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams };
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
createClient,
|
|
4
4
|
createClients,
|
|
5
5
|
createHttpClient
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-4kz3aqpx.js";
|
|
7
7
|
import {
|
|
8
8
|
parseSSE
|
|
9
|
-
} from "./index-
|
|
10
|
-
import"./index-
|
|
9
|
+
} from "./index-4b1j82gp.js";
|
|
10
|
+
import"./index-0t460v7k.js";
|
|
11
11
|
import {
|
|
12
12
|
ALL_TRANSPORTS,
|
|
13
13
|
AppError,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
rateLimited,
|
|
22
22
|
unauthorized
|
|
23
23
|
} from "./index-kckky6zw.js";
|
|
24
|
+
import"./index-809wc1tt.js";
|
|
24
25
|
// src/browser/socket-io.ts
|
|
25
26
|
import { io } from "socket.io-client";
|
|
26
27
|
function createSocketIOClient(config) {
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
1
|
+
import { type ZodType, z } from 'zod';
|
|
2
2
|
import { AppError } from '../contract';
|
|
3
3
|
export declare function formatZodError(error: z.ZodError): string;
|
|
4
4
|
export declare function normalizeError(err: unknown): AppError;
|
|
5
|
+
/**
|
|
6
|
+
* Validate a handler's return value against the contract `output` schema. A
|
|
7
|
+
* mismatch is a **server** fault (the handler broke its own contract) — shared
|
|
8
|
+
* by the HTTP and tool transports so both report it identically.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateHandlerOutput(schema: ZodType, data: unknown): {
|
|
11
|
+
ok: true;
|
|
12
|
+
data: unknown;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
5
17
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,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;AAEvC,wBAAgB,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CASxD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAYrD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,GACZ;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAO9D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prototype-pollution defence for untrusted input. Assigning a `__proto__` key
|
|
3
|
+
* onto a plain object (`obj[key] = value`, a JSON reviver, a merge) invokes the
|
|
4
|
+
* `__proto__` setter and rewires the prototype chain — the single real
|
|
5
|
+
* pollution vector at an assignment boundary. `constructor` / `prototype` as
|
|
6
|
+
* plain own keys are inert (they shadow, they do not poison), so they are NOT
|
|
7
|
+
* stripped — dropping them would silently corrupt legitimate data fields.
|
|
8
|
+
*
|
|
9
|
+
* Every boundary that ingests client-controlled keys (JSON bodies, query
|
|
10
|
+
* strings, cookies, multipart fields, tool arguments) routes through here.
|
|
11
|
+
*/
|
|
12
|
+
/** True for a key that must never be copied onto a plain object from untrusted input. */
|
|
13
|
+
export declare function isUnsafeKey(key: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* `JSON.parse` that strips the `__proto__` key from the result. Throws on
|
|
16
|
+
* invalid JSON, exactly like `JSON.parse` — callers handle that as a 400.
|
|
17
|
+
*/
|
|
18
|
+
export declare function safeJsonParse(text: string): unknown;
|
|
19
|
+
//# sourceMappingURL=safe-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-json.d.ts","sourceRoot":"","sources":["../../src/internal/safe-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `target` (an already-`resolve`d absolute path) stays inside `root`.
|
|
3
|
+
* Blocks `..`, absolute and otherwise-escaping paths — the shared containment
|
|
4
|
+
* check behind `staticRoute` and `view_file`'s local-file branch.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isWithinDir(root: string, target: string): boolean;
|
|
7
|
+
//# sourceMappingURL=within-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"within-dir.d.ts","sourceRoot":"","sources":["../../src/internal/within-dir.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjE"}
|