theokit 0.2.0 → 0.2.2
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/actions-virtual-module-E6PU47GV.js +175 -0
- package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
- package/dist/actions-virtual-module-E7IELVHL.js +174 -0
- package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
- package/dist/app-typed-client-RZMVWHJ3.js +289 -0
- package/dist/app-typed-client-RZMVWHJ3.js.map +1 -0
- package/dist/app-typed-client-YIBP6JWH.js +289 -0
- package/dist/app-typed-client-YIBP6JWH.js.map +1 -0
- package/dist/{aws-lambda-PONVF3UX.js → aws-lambda-XYCGZH3I.js} +2 -2
- package/dist/{build-IQQYCA3B.js → build-NCGIV4LL.js} +41 -14
- package/dist/build-NCGIV4LL.js.map +1 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
- package/dist/build-request-body-preview-II2235E6.js +54 -0
- package/dist/build-request-body-preview-II2235E6.js.map +1 -0
- package/dist/{bun-DITMZMKL.js → bun-K73TQEWC.js} +2 -2
- package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
- package/dist/chunk-2BQYEBCK.js.map +1 -0
- package/dist/chunk-7DHGV77J.js +140 -0
- package/dist/chunk-7DHGV77J.js.map +1 -0
- package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
- package/dist/chunk-C27AZN46.js.map +1 -0
- package/dist/chunk-ERAZNHHR.js +73 -0
- package/dist/chunk-ERAZNHHR.js.map +1 -0
- package/dist/{chunk-ZK5JCE5J.js → chunk-EXJWO2HB.js} +201 -43
- package/dist/chunk-EXJWO2HB.js.map +1 -0
- package/dist/chunk-F3TG5FJV.js +97 -0
- package/dist/chunk-F3TG5FJV.js.map +1 -0
- package/dist/{chunk-OK2PJOLK.js → chunk-KJVEJILQ.js} +7 -3
- package/dist/chunk-KJVEJILQ.js.map +1 -0
- package/dist/{chunk-JBC7PFV4.js → chunk-M23FP2AS.js} +2 -2
- package/dist/chunk-M23FP2AS.js.map +1 -0
- package/dist/{chunk-FRBYJANT.js → chunk-M6MYZSD2.js} +195 -41
- package/dist/chunk-M6MYZSD2.js.map +1 -0
- package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
- package/dist/chunk-MOT42NAH.js +165 -0
- package/dist/chunk-MOT42NAH.js.map +1 -0
- package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
- package/dist/chunk-OJHO2X7C.js.map +1 -0
- package/dist/chunk-QLGES2HC.js +189 -0
- package/dist/chunk-QLGES2HC.js.map +1 -0
- package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
- package/dist/chunk-SEVYARIU.js.map +1 -0
- package/dist/chunk-WSJKACWB.js +17 -0
- package/dist/chunk-WSJKACWB.js.map +1 -0
- package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
- package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
- package/dist/{chunk-Y5KE6YFP.js → chunk-ZJW5FFYJ.js} +3 -3
- package/dist/chunk-ZJW5FFYJ.js.map +1 -0
- package/dist/chunk-ZOXNJV2O.js +358 -0
- package/dist/chunk-ZOXNJV2O.js.map +1 -0
- package/dist/cli/index.js +22 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +31 -1
- package/dist/client/index.js +100 -0
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-RBANU3SE.js → cloudflare-LCAOTRYZ.js} +2 -2
- package/dist/{deno-deploy-J7LI6HNL.js → deno-deploy-ZN4RE5HF.js} +2 -2
- package/dist/{dev-VN6WS3CM.js → dev-WDXC74CK.js} +15 -7
- package/dist/dev-WDXC74CK.js.map +1 -0
- package/dist/dev-emit-3CHM73TV.js +390 -0
- package/dist/dev-emit-3CHM73TV.js.map +1 -0
- package/dist/dev-emit-I3CKOHML.js +44 -0
- package/dist/dev-emit-I3CKOHML.js.map +1 -0
- package/dist/devtools/entry.js +545 -181
- package/dist/devtools/entry.js.map +1 -1
- package/dist/{dispatcher-IK3FVMX5.js → dispatcher-AQJGI3HU.js} +2 -2
- package/dist/dispatcher-E6QV32BO.js +83 -0
- package/dist/dispatcher-E6QV32BO.js.map +1 -0
- package/dist/{dist-Q5VBFWVL.js → dist-R2Q7GVDW.js} +10357 -12068
- package/dist/dist-R2Q7GVDW.js.map +1 -0
- package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
- package/dist/generate-DT4IIAHF.js.map +1 -0
- package/dist/index.d.ts +108 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
- package/dist/{netlify-IFY6ZGFV.js → netlify-5VQ22Z2P.js} +2 -2
- package/dist/{node-KWZI3CKV.js → node-3APTLGWB.js} +2 -2
- package/dist/openapi-CBASKC7W.js +84 -0
- package/dist/openapi-CBASKC7W.js.map +1 -0
- package/dist/registry-5QFTLOL2.js +25 -0
- package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
- package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
- package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
- package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
- package/dist/server/cost/index.js +1 -1
- package/dist/server/index.d.ts +219 -47
- package/dist/server/index.js +35 -116
- package/dist/server/index.js.map +1 -1
- package/dist/{start-ZGWW5MOL.js → start-5JTFWF4T.js} +20 -14
- package/dist/start-5JTFWF4T.js.map +1 -0
- package/dist/{static-BH6JQNM2.js → static-TZBVBDTB.js} +4 -4
- package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
- package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
- package/dist/{vercel-RHADE5IP.js → vercel-MWW24ABC.js} +2 -2
- package/dist/vite-plugin/index.d.ts +25 -1
- package/dist/vite-plugin/index.js +5 -2
- package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-4WZ3C2AC.js} +7 -6
- package/package.json +4 -2
- package/dist/build-IQQYCA3B.js.map +0 -1
- package/dist/chunk-FRBYJANT.js.map +0 -1
- package/dist/chunk-GQSTRXXB.js.map +0 -1
- package/dist/chunk-JBC7PFV4.js.map +0 -1
- package/dist/chunk-JZ5WEG3S.js.map +0 -1
- package/dist/chunk-OEB3OIYP.js.map +0 -1
- package/dist/chunk-OK2PJOLK.js.map +0 -1
- package/dist/chunk-WQCPKA2L.js.map +0 -1
- package/dist/chunk-Y5KE6YFP.js.map +0 -1
- package/dist/chunk-ZK5JCE5J.js.map +0 -1
- package/dist/dev-VN6WS3CM.js.map +0 -1
- package/dist/dist-Q5VBFWVL.js.map +0 -1
- package/dist/generate-SIO4JSKJ.js.map +0 -1
- package/dist/registry-BB3UYAOG.js +0 -25
- package/dist/start-ZGWW5MOL.js.map +0 -1
- package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
- /package/dist/{aws-lambda-PONVF3UX.js.map → aws-lambda-XYCGZH3I.js.map} +0 -0
- /package/dist/{bun-DITMZMKL.js.map → bun-K73TQEWC.js.map} +0 -0
- /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
- /package/dist/{cloudflare-RBANU3SE.js.map → cloudflare-LCAOTRYZ.js.map} +0 -0
- /package/dist/{deno-deploy-J7LI6HNL.js.map → deno-deploy-ZN4RE5HF.js.map} +0 -0
- /package/dist/{dispatcher-IK3FVMX5.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
- /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
- /package/dist/{netlify-IFY6ZGFV.js.map → netlify-5VQ22Z2P.js.map} +0 -0
- /package/dist/{node-KWZI3CKV.js.map → node-3APTLGWB.js.map} +0 -0
- /package/dist/{registry-BB3UYAOG.js.map → registry-5QFTLOL2.js.map} +0 -0
- /package/dist/{schema-MVB2FVK6.js.map → schema-JAKF23GR.js.map} +0 -0
- /package/dist/{static-BH6JQNM2.js.map → static-TZBVBDTB.js.map} +0 -0
- /package/dist/{vercel-RHADE5IP.js.map → vercel-MWW24ABC.js.map} +0 -0
- /package/dist/{vite-plugin-WA3G45YY.js.map → vite-plugin-4WZ3C2AC.js.map} +0 -0
|
@@ -317,11 +317,172 @@ function resolveTransformer(selector) {
|
|
|
317
317
|
return selector;
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
// src/server/body-parser.ts
|
|
321
|
+
import { basename } from "path";
|
|
322
|
+
import Busboy from "busboy";
|
|
323
|
+
var METHODS_WITH_BODY = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
324
|
+
var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
325
|
+
var DEFAULT_MAX_FILES = 10;
|
|
326
|
+
var DEFAULT_MAX_FIELD_SIZE = 1 * 1024 * 1024;
|
|
327
|
+
var FileTooLargeError = class extends Error {
|
|
328
|
+
constructor(message, truncatedFilenames, maxFileSize) {
|
|
329
|
+
super(message);
|
|
330
|
+
this.truncatedFilenames = truncatedFilenames;
|
|
331
|
+
this.maxFileSize = maxFileSize;
|
|
332
|
+
this.name = "FileTooLargeError";
|
|
333
|
+
}
|
|
334
|
+
truncatedFilenames;
|
|
335
|
+
maxFileSize;
|
|
336
|
+
code = "FILE_TOO_LARGE";
|
|
337
|
+
status = 413;
|
|
338
|
+
};
|
|
339
|
+
function stashBodyPreview(req, body) {
|
|
340
|
+
try {
|
|
341
|
+
;
|
|
342
|
+
req[DEVTOOLS_BODY_PREVIEW] = body;
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function parseJsonBody(req) {
|
|
347
|
+
return new Promise((resolve, reject) => {
|
|
348
|
+
const chunks = [];
|
|
349
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
350
|
+
req.on("end", () => {
|
|
351
|
+
const raw = Buffer.concat(chunks).toString();
|
|
352
|
+
if (!raw) {
|
|
353
|
+
resolve(void 0);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
resolve(JSON.parse(raw));
|
|
358
|
+
} catch {
|
|
359
|
+
reject(new Error("Invalid JSON body"));
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
req.on("error", reject);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function parseMultipartBody(req, contentType, options) {
|
|
366
|
+
if (!contentType.includes("boundary=")) {
|
|
367
|
+
return Promise.reject(new Error("Missing multipart boundary"));
|
|
368
|
+
}
|
|
369
|
+
return new Promise((resolve, reject) => {
|
|
370
|
+
const fields = {};
|
|
371
|
+
const files = [];
|
|
372
|
+
const truncatedFilenames = [];
|
|
373
|
+
let fileCount = 0;
|
|
374
|
+
const bb = Busboy({
|
|
375
|
+
headers: req.headers,
|
|
376
|
+
limits: {
|
|
377
|
+
fileSize: options.maxFileSize,
|
|
378
|
+
files: options.maxFiles,
|
|
379
|
+
fieldSize: options.maxFieldSize
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
bb.on("field", (name, value) => {
|
|
383
|
+
fields[name] = value;
|
|
384
|
+
});
|
|
385
|
+
bb.on(
|
|
386
|
+
"file",
|
|
387
|
+
(fieldname, stream, info) => {
|
|
388
|
+
fileCount++;
|
|
389
|
+
if (fileCount > options.maxFiles) {
|
|
390
|
+
stream.resume();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const safeName = basename(info.filename || "unnamed");
|
|
394
|
+
const chunks = [];
|
|
395
|
+
let size = 0;
|
|
396
|
+
let truncated = false;
|
|
397
|
+
stream.on("data", (chunk) => {
|
|
398
|
+
size += chunk.length;
|
|
399
|
+
if (size > options.maxFileSize) {
|
|
400
|
+
truncated = true;
|
|
401
|
+
stream.resume();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
chunks.push(chunk);
|
|
405
|
+
});
|
|
406
|
+
stream.on("end", () => {
|
|
407
|
+
if (truncated) {
|
|
408
|
+
truncatedFilenames.push(safeName);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const buffer = Buffer.concat(chunks);
|
|
412
|
+
files.push({
|
|
413
|
+
fieldname,
|
|
414
|
+
filename: safeName,
|
|
415
|
+
encoding: info.encoding,
|
|
416
|
+
mimeType: info.mimeType,
|
|
417
|
+
buffer,
|
|
418
|
+
size: buffer.length
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
bb.on("filesLimit", () => {
|
|
424
|
+
reject(new Error(`Too many files. Maximum: ${options.maxFiles}`));
|
|
425
|
+
});
|
|
426
|
+
bb.on("error", (err) => {
|
|
427
|
+
reject(err);
|
|
428
|
+
});
|
|
429
|
+
bb.on("close", () => {
|
|
430
|
+
if (truncatedFilenames.length > 0) {
|
|
431
|
+
reject(
|
|
432
|
+
new FileTooLargeError(
|
|
433
|
+
`File too large (max ${options.maxFileSize} bytes): ${truncatedFilenames.join(", ")}`,
|
|
434
|
+
truncatedFilenames,
|
|
435
|
+
options.maxFileSize
|
|
436
|
+
)
|
|
437
|
+
);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
resolve({ fields, files });
|
|
441
|
+
});
|
|
442
|
+
req.pipe(bb);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
var DEVTOOLS_BODY_PREVIEW = /* @__PURE__ */ Symbol("theo:devtools:bodyPreview");
|
|
446
|
+
async function parseRequestBody(req, options) {
|
|
447
|
+
const method = req.method?.toUpperCase() ?? "GET";
|
|
448
|
+
if (!METHODS_WITH_BODY.has(method)) {
|
|
449
|
+
return { fields: {}, files: [], json: void 0 };
|
|
450
|
+
}
|
|
451
|
+
const contentType = req.headers["content-type"] ?? "";
|
|
452
|
+
if (contentType.includes("application/json")) {
|
|
453
|
+
const json = await parseJsonBody(req);
|
|
454
|
+
stashBodyPreview(req, json);
|
|
455
|
+
return { fields: {}, files: [], json };
|
|
456
|
+
}
|
|
457
|
+
if (contentType.includes("multipart/form-data")) {
|
|
458
|
+
const limits = {
|
|
459
|
+
maxFileSize: options?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,
|
|
460
|
+
maxFiles: options?.maxFiles ?? DEFAULT_MAX_FILES,
|
|
461
|
+
maxFieldSize: options?.maxFieldSize ?? DEFAULT_MAX_FIELD_SIZE
|
|
462
|
+
};
|
|
463
|
+
const { fields, files } = await parseMultipartBody(req, contentType, limits);
|
|
464
|
+
stashBodyPreview(req, {
|
|
465
|
+
fields,
|
|
466
|
+
files: files.map((f) => ({
|
|
467
|
+
fieldname: f.fieldname,
|
|
468
|
+
filename: f.filename,
|
|
469
|
+
mimeType: f.mimeType,
|
|
470
|
+
size: f.size
|
|
471
|
+
}))
|
|
472
|
+
});
|
|
473
|
+
return { fields, files };
|
|
474
|
+
}
|
|
475
|
+
if (!contentType) {
|
|
476
|
+
return { fields: {}, files: [] };
|
|
477
|
+
}
|
|
478
|
+
throw new Error(`Unsupported Content-Type: ${contentType}`);
|
|
479
|
+
}
|
|
480
|
+
|
|
320
481
|
// src/server/observability/request-log.ts
|
|
321
482
|
var defaultLoggerFn = (log) => {
|
|
322
483
|
console.log(JSON.stringify(log));
|
|
323
484
|
};
|
|
324
|
-
function logRequest(info, customLogger) {
|
|
485
|
+
function logRequest(info, customLogger, req) {
|
|
325
486
|
const log = {
|
|
326
487
|
level: "info",
|
|
327
488
|
...info,
|
|
@@ -329,13 +490,24 @@ function logRequest(info, customLogger) {
|
|
|
329
490
|
};
|
|
330
491
|
const logger = customLogger ?? defaultLoggerFn;
|
|
331
492
|
logger(log);
|
|
332
|
-
broadcastRequestToDevtools(info);
|
|
493
|
+
broadcastRequestToDevtools(info, req);
|
|
333
494
|
}
|
|
334
495
|
function randomRequestId() {
|
|
335
496
|
return `req-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
|
336
497
|
}
|
|
337
|
-
function broadcastRequestToDevtools(info) {
|
|
338
|
-
|
|
498
|
+
function broadcastRequestToDevtools(info, req) {
|
|
499
|
+
const headers = req?.headers ? Object.fromEntries(
|
|
500
|
+
Object.entries(req.headers).map(([k, v]) => [
|
|
501
|
+
k,
|
|
502
|
+
Array.isArray(v) ? v.join(", ") : v ?? ""
|
|
503
|
+
])
|
|
504
|
+
) : void 0;
|
|
505
|
+
const stashedBody = req ? req[DEVTOOLS_BODY_PREVIEW] : void 0;
|
|
506
|
+
void Promise.all([
|
|
507
|
+
import("./broadcast-QOQGUNNK.js"),
|
|
508
|
+
import("./build-request-body-preview-II2235E6.js")
|
|
509
|
+
]).then(([{ broadcastRequest }, { buildRequestBodyPreview }]) => {
|
|
510
|
+
const bodyInfo = buildRequestBodyPreview(stashedBody);
|
|
339
511
|
broadcastRequest({
|
|
340
512
|
id: randomRequestId(),
|
|
341
513
|
traceId: info.requestId,
|
|
@@ -343,7 +515,13 @@ function broadcastRequestToDevtools(info) {
|
|
|
343
515
|
path: info.url,
|
|
344
516
|
status: info.status,
|
|
345
517
|
durationMs: info.duration,
|
|
346
|
-
startedAt: Date.now() - info.duration
|
|
518
|
+
startedAt: Date.now() - info.duration,
|
|
519
|
+
...headers ? { headers } : {},
|
|
520
|
+
...bodyInfo ? {
|
|
521
|
+
bodyPreview: bodyInfo.preview,
|
|
522
|
+
bodyLength: bodyInfo.length,
|
|
523
|
+
bodyTruncated: bodyInfo.truncated
|
|
524
|
+
} : {}
|
|
347
525
|
});
|
|
348
526
|
}).catch(() => {
|
|
349
527
|
});
|
|
@@ -670,149 +848,6 @@ function enforceCsrf(req, mode, logger, disallowed, auditLogger) {
|
|
|
670
848
|
return { allow: false, reason: check.reason };
|
|
671
849
|
}
|
|
672
850
|
|
|
673
|
-
// src/server/body-parser.ts
|
|
674
|
-
import { basename } from "path";
|
|
675
|
-
import Busboy from "busboy";
|
|
676
|
-
var METHODS_WITH_BODY = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
677
|
-
var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
678
|
-
var DEFAULT_MAX_FILES = 10;
|
|
679
|
-
var DEFAULT_MAX_FIELD_SIZE = 1 * 1024 * 1024;
|
|
680
|
-
var FileTooLargeError = class extends Error {
|
|
681
|
-
constructor(message, truncatedFilenames, maxFileSize) {
|
|
682
|
-
super(message);
|
|
683
|
-
this.truncatedFilenames = truncatedFilenames;
|
|
684
|
-
this.maxFileSize = maxFileSize;
|
|
685
|
-
this.name = "FileTooLargeError";
|
|
686
|
-
}
|
|
687
|
-
truncatedFilenames;
|
|
688
|
-
maxFileSize;
|
|
689
|
-
code = "FILE_TOO_LARGE";
|
|
690
|
-
status = 413;
|
|
691
|
-
};
|
|
692
|
-
function parseJsonBody(req) {
|
|
693
|
-
return new Promise((resolve, reject) => {
|
|
694
|
-
const chunks = [];
|
|
695
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
696
|
-
req.on("end", () => {
|
|
697
|
-
const raw = Buffer.concat(chunks).toString();
|
|
698
|
-
if (!raw) {
|
|
699
|
-
resolve(void 0);
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
try {
|
|
703
|
-
resolve(JSON.parse(raw));
|
|
704
|
-
} catch {
|
|
705
|
-
reject(new Error("Invalid JSON body"));
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
req.on("error", reject);
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
function parseMultipartBody(req, contentType, options) {
|
|
712
|
-
if (!contentType.includes("boundary=")) {
|
|
713
|
-
return Promise.reject(new Error("Missing multipart boundary"));
|
|
714
|
-
}
|
|
715
|
-
return new Promise((resolve, reject) => {
|
|
716
|
-
const fields = {};
|
|
717
|
-
const files = [];
|
|
718
|
-
const truncatedFilenames = [];
|
|
719
|
-
let fileCount = 0;
|
|
720
|
-
const bb = Busboy({
|
|
721
|
-
headers: req.headers,
|
|
722
|
-
limits: {
|
|
723
|
-
fileSize: options.maxFileSize,
|
|
724
|
-
files: options.maxFiles,
|
|
725
|
-
fieldSize: options.maxFieldSize
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
bb.on("field", (name, value) => {
|
|
729
|
-
fields[name] = value;
|
|
730
|
-
});
|
|
731
|
-
bb.on(
|
|
732
|
-
"file",
|
|
733
|
-
(fieldname, stream, info) => {
|
|
734
|
-
fileCount++;
|
|
735
|
-
if (fileCount > options.maxFiles) {
|
|
736
|
-
stream.resume();
|
|
737
|
-
return;
|
|
738
|
-
}
|
|
739
|
-
const safeName = basename(info.filename || "unnamed");
|
|
740
|
-
const chunks = [];
|
|
741
|
-
let size = 0;
|
|
742
|
-
let truncated = false;
|
|
743
|
-
stream.on("data", (chunk) => {
|
|
744
|
-
size += chunk.length;
|
|
745
|
-
if (size > options.maxFileSize) {
|
|
746
|
-
truncated = true;
|
|
747
|
-
stream.resume();
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
chunks.push(chunk);
|
|
751
|
-
});
|
|
752
|
-
stream.on("end", () => {
|
|
753
|
-
if (truncated) {
|
|
754
|
-
truncatedFilenames.push(safeName);
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
const buffer = Buffer.concat(chunks);
|
|
758
|
-
files.push({
|
|
759
|
-
fieldname,
|
|
760
|
-
filename: safeName,
|
|
761
|
-
encoding: info.encoding,
|
|
762
|
-
mimeType: info.mimeType,
|
|
763
|
-
buffer,
|
|
764
|
-
size: buffer.length
|
|
765
|
-
});
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
);
|
|
769
|
-
bb.on("filesLimit", () => {
|
|
770
|
-
reject(new Error(`Too many files. Maximum: ${options.maxFiles}`));
|
|
771
|
-
});
|
|
772
|
-
bb.on("error", (err) => {
|
|
773
|
-
reject(err);
|
|
774
|
-
});
|
|
775
|
-
bb.on("close", () => {
|
|
776
|
-
if (truncatedFilenames.length > 0) {
|
|
777
|
-
reject(
|
|
778
|
-
new FileTooLargeError(
|
|
779
|
-
`File too large (max ${options.maxFileSize} bytes): ${truncatedFilenames.join(", ")}`,
|
|
780
|
-
truncatedFilenames,
|
|
781
|
-
options.maxFileSize
|
|
782
|
-
)
|
|
783
|
-
);
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
resolve({ fields, files });
|
|
787
|
-
});
|
|
788
|
-
req.pipe(bb);
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
async function parseRequestBody(req, options) {
|
|
792
|
-
const method = req.method?.toUpperCase() ?? "GET";
|
|
793
|
-
if (!METHODS_WITH_BODY.has(method)) {
|
|
794
|
-
return { fields: {}, files: [], json: void 0 };
|
|
795
|
-
}
|
|
796
|
-
const contentType = req.headers["content-type"] ?? "";
|
|
797
|
-
if (contentType.includes("application/json")) {
|
|
798
|
-
const json = await parseJsonBody(req);
|
|
799
|
-
return { fields: {}, files: [], json };
|
|
800
|
-
}
|
|
801
|
-
if (contentType.includes("multipart/form-data")) {
|
|
802
|
-
const limits = {
|
|
803
|
-
maxFileSize: options?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,
|
|
804
|
-
maxFiles: options?.maxFiles ?? DEFAULT_MAX_FILES,
|
|
805
|
-
maxFieldSize: options?.maxFieldSize ?? DEFAULT_MAX_FIELD_SIZE
|
|
806
|
-
};
|
|
807
|
-
const { fields, files } = await parseMultipartBody(req, contentType, limits);
|
|
808
|
-
return { fields, files };
|
|
809
|
-
}
|
|
810
|
-
if (!contentType) {
|
|
811
|
-
return { fields: {}, files: [] };
|
|
812
|
-
}
|
|
813
|
-
throw new Error(`Unsupported Content-Type: ${contentType}`);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
851
|
// src/server/http/execute-stages.ts
|
|
817
852
|
async function parseQueryAndBody(req, res, requestId) {
|
|
818
853
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -1190,6 +1225,215 @@ async function executeRoute(ctx) {
|
|
|
1190
1225
|
}
|
|
1191
1226
|
}
|
|
1192
1227
|
|
|
1228
|
+
// src/core/contracts/action-protocol.ts
|
|
1229
|
+
var CODE_TO_STATUS = {
|
|
1230
|
+
VALIDATION_ERROR: 422,
|
|
1231
|
+
BAD_REQUEST: 400,
|
|
1232
|
+
UNAUTHORIZED: 401,
|
|
1233
|
+
FORBIDDEN: 403,
|
|
1234
|
+
NOT_FOUND: 404,
|
|
1235
|
+
METHOD_NOT_ALLOWED: 405,
|
|
1236
|
+
CONFLICT: 409,
|
|
1237
|
+
CONTENT_TOO_LARGE: 413,
|
|
1238
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
1239
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
1240
|
+
TOO_MANY_REQUESTS: 429,
|
|
1241
|
+
INTERNAL_SERVER_ERROR: 500
|
|
1242
|
+
};
|
|
1243
|
+
var STATUS_TO_CODE = {
|
|
1244
|
+
400: "BAD_REQUEST",
|
|
1245
|
+
401: "UNAUTHORIZED",
|
|
1246
|
+
403: "FORBIDDEN",
|
|
1247
|
+
404: "NOT_FOUND",
|
|
1248
|
+
405: "METHOD_NOT_ALLOWED",
|
|
1249
|
+
409: "CONFLICT",
|
|
1250
|
+
413: "PAYLOAD_TOO_LARGE",
|
|
1251
|
+
415: "UNSUPPORTED_MEDIA_TYPE",
|
|
1252
|
+
422: "VALIDATION_ERROR",
|
|
1253
|
+
429: "TOO_MANY_REQUESTS",
|
|
1254
|
+
500: "INTERNAL_SERVER_ERROR"
|
|
1255
|
+
};
|
|
1256
|
+
var ActionError = class _ActionError extends Error {
|
|
1257
|
+
// Discriminator widened to the full union so subclasses can narrow to their
|
|
1258
|
+
// specific literal (TypeScript would otherwise reject the override). Concrete
|
|
1259
|
+
// values are still always exact literals at runtime.
|
|
1260
|
+
type = "TheoActionError";
|
|
1261
|
+
code;
|
|
1262
|
+
status;
|
|
1263
|
+
constructor(params) {
|
|
1264
|
+
super(params.message ?? params.code);
|
|
1265
|
+
this.code = params.code;
|
|
1266
|
+
this.status = _ActionError.codeToStatus(params.code);
|
|
1267
|
+
if (params.stack) {
|
|
1268
|
+
this.stack = params.stack;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
static codeToStatus(code) {
|
|
1272
|
+
return CODE_TO_STATUS[code];
|
|
1273
|
+
}
|
|
1274
|
+
static statusToCode(status) {
|
|
1275
|
+
return STATUS_TO_CODE[status] ?? "INTERNAL_SERVER_ERROR";
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Parse a serialized error JSON back into the typed class hierarchy.
|
|
1279
|
+
* Distinguishes `TheoActionInputError` (with `issues` array) from
|
|
1280
|
+
* `TheoActionError` via the `type` discriminator. Falls back to
|
|
1281
|
+
* `INTERNAL_SERVER_ERROR` for malformed bodies (non-object, missing
|
|
1282
|
+
* `type`, unknown `code`).
|
|
1283
|
+
*/
|
|
1284
|
+
static fromJson(body) {
|
|
1285
|
+
if (typeof body !== "object" || body === null) {
|
|
1286
|
+
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
1287
|
+
}
|
|
1288
|
+
const obj = body;
|
|
1289
|
+
if (obj.type === "TheoActionInputError" && Array.isArray(obj.issues)) {
|
|
1290
|
+
return new ActionInputError(obj.issues);
|
|
1291
|
+
}
|
|
1292
|
+
if (obj.type === "TheoActionError" && typeof obj.code === "string" && obj.code in CODE_TO_STATUS) {
|
|
1293
|
+
return new _ActionError({
|
|
1294
|
+
code: obj.code,
|
|
1295
|
+
message: typeof obj.message === "string" ? obj.message : void 0
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
return new _ActionError({ code: "INTERNAL_SERVER_ERROR" });
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
var ActionInputError = class extends ActionError {
|
|
1302
|
+
type = "TheoActionInputError";
|
|
1303
|
+
issues;
|
|
1304
|
+
fields;
|
|
1305
|
+
constructor(rawIssues) {
|
|
1306
|
+
super({ code: "VALIDATION_ERROR", message: "Validation failed" });
|
|
1307
|
+
this.issues = extractUniversalIssues(rawIssues);
|
|
1308
|
+
this.fields = buildFieldsMap(this.issues);
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
function buildFieldsMap(issues) {
|
|
1312
|
+
const fields = {};
|
|
1313
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1314
|
+
for (const issue of issues) {
|
|
1315
|
+
const key = issue.path.length === 0 ? "" : issue.path.join(".");
|
|
1316
|
+
const dedupeKey = `${key}\0${issue.message}`;
|
|
1317
|
+
if (seen.has(dedupeKey)) continue;
|
|
1318
|
+
seen.add(dedupeKey);
|
|
1319
|
+
const bucket = fields[key] ?? [];
|
|
1320
|
+
bucket.push(issue.message);
|
|
1321
|
+
fields[key] = bucket;
|
|
1322
|
+
}
|
|
1323
|
+
return fields;
|
|
1324
|
+
}
|
|
1325
|
+
function extractUniversalIssues(raw) {
|
|
1326
|
+
if (!Array.isArray(raw)) return [];
|
|
1327
|
+
const out = [];
|
|
1328
|
+
for (const entry of raw) {
|
|
1329
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
1330
|
+
const obj = entry;
|
|
1331
|
+
if (!Array.isArray(obj.path)) continue;
|
|
1332
|
+
if (typeof obj.message !== "string") continue;
|
|
1333
|
+
const path = [];
|
|
1334
|
+
let pathValid = true;
|
|
1335
|
+
for (const seg of obj.path) {
|
|
1336
|
+
if (typeof seg === "string" || typeof seg === "number") {
|
|
1337
|
+
path.push(seg);
|
|
1338
|
+
} else {
|
|
1339
|
+
pathValid = false;
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (!pathValid) continue;
|
|
1344
|
+
out.push({
|
|
1345
|
+
path,
|
|
1346
|
+
message: obj.message,
|
|
1347
|
+
code: typeof obj.code === "string" ? obj.code : void 0
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
return out;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/server/http/form-data-to-object.ts
|
|
1354
|
+
import { z } from "zod";
|
|
1355
|
+
function formDataToObject(formData, schema, prefix = "") {
|
|
1356
|
+
const shape = schema._def.shape();
|
|
1357
|
+
const out = {};
|
|
1358
|
+
for (const [key, rawValidator] of Object.entries(shape)) {
|
|
1359
|
+
const fullKey = prefix + key;
|
|
1360
|
+
const validator = unwrapWrappers(rawValidator);
|
|
1361
|
+
if (validator instanceof z.ZodObject) {
|
|
1362
|
+
const nestedPrefix = `${fullKey}.`;
|
|
1363
|
+
const hasNestedKeys = [...formData.keys()].some((k) => k.startsWith(nestedPrefix));
|
|
1364
|
+
if (hasNestedKeys) {
|
|
1365
|
+
out[key] = formDataToObject(formData, validator, nestedPrefix);
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
out[key] = unwrapMissingDefault(rawValidator);
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
if (validator instanceof z.ZodArray) {
|
|
1372
|
+
const values = formData.getAll(fullKey);
|
|
1373
|
+
out[key] = coerceArrayElements(values, validator);
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
if (validator instanceof z.ZodBoolean) {
|
|
1377
|
+
out[key] = coerceBoolean(formData, fullKey);
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
if (formData.has(fullKey)) {
|
|
1381
|
+
const raw = formData.get(fullKey);
|
|
1382
|
+
out[key] = coerceScalar(raw, validator);
|
|
1383
|
+
} else {
|
|
1384
|
+
out[key] = unwrapMissingDefault(rawValidator);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return out;
|
|
1388
|
+
}
|
|
1389
|
+
function unwrapWrappers(validator) {
|
|
1390
|
+
let inner = validator;
|
|
1391
|
+
while (inner instanceof z.ZodOptional || inner instanceof z.ZodNullable || inner instanceof z.ZodDefault) {
|
|
1392
|
+
inner = inner._def.innerType;
|
|
1393
|
+
}
|
|
1394
|
+
return inner;
|
|
1395
|
+
}
|
|
1396
|
+
function unwrapMissingDefault(validator) {
|
|
1397
|
+
let cursor = validator;
|
|
1398
|
+
while (cursor instanceof z.ZodOptional || cursor instanceof z.ZodNullable || cursor instanceof z.ZodDefault) {
|
|
1399
|
+
if (cursor instanceof z.ZodDefault) {
|
|
1400
|
+
const def = cursor._def.defaultValue;
|
|
1401
|
+
return typeof def === "function" ? def() : def;
|
|
1402
|
+
}
|
|
1403
|
+
if (cursor instanceof z.ZodNullable) return null;
|
|
1404
|
+
cursor = cursor._def.innerType;
|
|
1405
|
+
}
|
|
1406
|
+
return void 0;
|
|
1407
|
+
}
|
|
1408
|
+
function coerceScalar(raw, validator) {
|
|
1409
|
+
if (raw === null) return void 0;
|
|
1410
|
+
if (validator instanceof z.ZodNumber) {
|
|
1411
|
+
return typeof raw === "string" ? Number(raw) : raw;
|
|
1412
|
+
}
|
|
1413
|
+
return raw;
|
|
1414
|
+
}
|
|
1415
|
+
function coerceArrayElements(values, arrayValidator) {
|
|
1416
|
+
const elementType = unwrapWrappers(arrayValidator._def.type);
|
|
1417
|
+
if (elementType instanceof z.ZodNumber) {
|
|
1418
|
+
return values.map((v) => typeof v === "string" ? Number(v) : v);
|
|
1419
|
+
}
|
|
1420
|
+
if (elementType instanceof z.ZodBoolean) {
|
|
1421
|
+
return values.map((v) => {
|
|
1422
|
+
if (v === "true") return true;
|
|
1423
|
+
if (v === "false") return false;
|
|
1424
|
+
return Boolean(v);
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
return values;
|
|
1428
|
+
}
|
|
1429
|
+
function coerceBoolean(formData, key) {
|
|
1430
|
+
if (!formData.has(key)) return void 0;
|
|
1431
|
+
const val = formData.get(key);
|
|
1432
|
+
if (val === "true") return true;
|
|
1433
|
+
if (val === "false") return false;
|
|
1434
|
+
return Boolean(val);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1193
1437
|
// src/server/http/handle-request-error.ts
|
|
1194
1438
|
async function handleRequestError(err, c) {
|
|
1195
1439
|
if (c.pluginRunner) {
|
|
@@ -1235,7 +1479,78 @@ async function handleRequestError(err, c) {
|
|
|
1235
1479
|
}
|
|
1236
1480
|
}
|
|
1237
1481
|
|
|
1482
|
+
// src/server/http/serialize-action-result.ts
|
|
1483
|
+
import { stringify as devalueStringify } from "devalue";
|
|
1484
|
+
var DEFAULT_RESPONSE_BODY_SIZE_LIMIT = 5 * 1024 * 1024;
|
|
1485
|
+
function serializeActionResult(result, options = {}) {
|
|
1486
|
+
const limit = options.responseBodySizeLimit ?? DEFAULT_RESPONSE_BODY_SIZE_LIMIT;
|
|
1487
|
+
if (result.error !== void 0) {
|
|
1488
|
+
const body2 = result.error instanceof ActionInputError ? JSON.stringify({
|
|
1489
|
+
type: result.error.type,
|
|
1490
|
+
code: result.error.code,
|
|
1491
|
+
message: result.error.message,
|
|
1492
|
+
issues: result.error.issues,
|
|
1493
|
+
fields: result.error.fields
|
|
1494
|
+
}) : JSON.stringify({
|
|
1495
|
+
type: result.error.type,
|
|
1496
|
+
code: result.error.code,
|
|
1497
|
+
message: result.error.message
|
|
1498
|
+
});
|
|
1499
|
+
if (Buffer.byteLength(body2, "utf8") > limit) {
|
|
1500
|
+
throw new ActionError({
|
|
1501
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
1502
|
+
message: `Serialized error body exceeds limit (${limit} bytes)`
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
return {
|
|
1506
|
+
type: "error",
|
|
1507
|
+
status: result.error.status,
|
|
1508
|
+
contentType: "application/json",
|
|
1509
|
+
body: body2
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
if (result.data === void 0) {
|
|
1513
|
+
return { type: "empty", status: 204 };
|
|
1514
|
+
}
|
|
1515
|
+
if (result.data instanceof Response) {
|
|
1516
|
+
throw new ActionError({
|
|
1517
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
1518
|
+
message: "Action handler cannot serialize Response objects \u2014 return plain data instead"
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
let body;
|
|
1522
|
+
try {
|
|
1523
|
+
body = devalueStringify(result.data, {
|
|
1524
|
+
URL: (value) => value instanceof URL && value.href
|
|
1525
|
+
});
|
|
1526
|
+
} catch (e) {
|
|
1527
|
+
throw new ActionError({
|
|
1528
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
1529
|
+
message: `Action data not serializable: ${e instanceof Error ? e.message : String(e)}`
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
if (Buffer.byteLength(body, "utf8") > limit) {
|
|
1533
|
+
throw new ActionError({
|
|
1534
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
1535
|
+
message: `Serialized response body exceeds limit (${limit} bytes)`
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
return {
|
|
1539
|
+
type: "data",
|
|
1540
|
+
status: 200,
|
|
1541
|
+
contentType: "application/json+devalue",
|
|
1542
|
+
body
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1238
1546
|
// src/server/http/action-execute.ts
|
|
1547
|
+
var __IS_DEV = (() => {
|
|
1548
|
+
try {
|
|
1549
|
+
return import.meta.env?.DEV === true;
|
|
1550
|
+
} catch {
|
|
1551
|
+
return process.env.NODE_ENV !== "production";
|
|
1552
|
+
}
|
|
1553
|
+
})();
|
|
1239
1554
|
function isActionConfig(value) {
|
|
1240
1555
|
if (typeof value !== "object" || value === null) return false;
|
|
1241
1556
|
const candidate = value;
|
|
@@ -1302,16 +1617,72 @@ async function runPreHandlerPipeline(p) {
|
|
|
1302
1617
|
}
|
|
1303
1618
|
return true;
|
|
1304
1619
|
}
|
|
1305
|
-
async function readActionBody(req, res, requestId) {
|
|
1620
|
+
async function readActionBody(req, res, requestId, actionConfig) {
|
|
1306
1621
|
try {
|
|
1307
1622
|
const parsed = await parseRequestBody(req);
|
|
1308
|
-
const
|
|
1623
|
+
const accept = actionConfig.accept ?? "json";
|
|
1624
|
+
let body;
|
|
1625
|
+
if (accept === "form") {
|
|
1626
|
+
body = formDataToObject(
|
|
1627
|
+
synthesizeFormData(parsed),
|
|
1628
|
+
actionConfig.input
|
|
1629
|
+
);
|
|
1630
|
+
} else if (parsed.json !== void 0) {
|
|
1631
|
+
body = parsed.json;
|
|
1632
|
+
} else {
|
|
1633
|
+
body = parsed.fields;
|
|
1634
|
+
}
|
|
1309
1635
|
return { ok: true, body };
|
|
1310
1636
|
} catch (err) {
|
|
1311
1637
|
sendError(res, "VALIDATION_ERROR", err.message, 400, void 0, requestId);
|
|
1312
1638
|
return { ok: false };
|
|
1313
1639
|
}
|
|
1314
1640
|
}
|
|
1641
|
+
function synthesizeFormData(parsed) {
|
|
1642
|
+
const fd = new FormData();
|
|
1643
|
+
for (const [name, value] of Object.entries(parsed.fields)) {
|
|
1644
|
+
fd.append(name, value);
|
|
1645
|
+
}
|
|
1646
|
+
for (const file of parsed.files) {
|
|
1647
|
+
const blob = new Blob([file.buffer], {
|
|
1648
|
+
type: file.mimeType
|
|
1649
|
+
});
|
|
1650
|
+
fd.append(file.fieldname, blob, file.filename);
|
|
1651
|
+
}
|
|
1652
|
+
return fd;
|
|
1653
|
+
}
|
|
1654
|
+
function writeSerialized(res, serialized) {
|
|
1655
|
+
if (serialized.type === "empty") {
|
|
1656
|
+
res.statusCode = serialized.status;
|
|
1657
|
+
res.end();
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
res.statusCode = serialized.status;
|
|
1661
|
+
res.setHeader("Content-Type", serialized.contentType);
|
|
1662
|
+
res.end(serialized.body);
|
|
1663
|
+
}
|
|
1664
|
+
async function emitActionCallTelemetry(name, startedAt, input, outcome) {
|
|
1665
|
+
if (!__IS_DEV) return;
|
|
1666
|
+
try {
|
|
1667
|
+
const mod = await import("./dispatcher-E6QV32BO.js");
|
|
1668
|
+
mod.dispatcher.onActionCall({
|
|
1669
|
+
// eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id
|
|
1670
|
+
id: `act-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1671
|
+
timestamp: startedAt,
|
|
1672
|
+
name,
|
|
1673
|
+
input,
|
|
1674
|
+
output: outcome.status === "success" ? outcome.output : void 0,
|
|
1675
|
+
error: outcome.status === "error" ? {
|
|
1676
|
+
code: outcome.error.code,
|
|
1677
|
+
message: outcome.error.message,
|
|
1678
|
+
fields: outcome.error instanceof ActionInputError ? outcome.error.fields : void 0
|
|
1679
|
+
} : void 0,
|
|
1680
|
+
durationMs: Date.now() - startedAt,
|
|
1681
|
+
status: outcome.status
|
|
1682
|
+
});
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1315
1686
|
async function executeActionWithOptions(opts) {
|
|
1316
1687
|
const {
|
|
1317
1688
|
filePath,
|
|
@@ -1358,22 +1729,24 @@ async function executeActionWithOptions(opts) {
|
|
|
1358
1729
|
};
|
|
1359
1730
|
if (!await runPreHandlerPipeline(pipeline)) return;
|
|
1360
1731
|
ctx = pipeline.ctx;
|
|
1361
|
-
const bodyOutcome = await readActionBody(req, res, requestId);
|
|
1732
|
+
const bodyOutcome = await readActionBody(req, res, requestId, actionConfig);
|
|
1362
1733
|
if (!bodyOutcome.ok) return;
|
|
1734
|
+
const actionName = exportName === "default" ? deriveActionNameFromPath(filePath) : exportName;
|
|
1735
|
+
const startedAt = Date.now();
|
|
1363
1736
|
const result = actionConfig.input.safeParse(bodyOutcome.body);
|
|
1364
1737
|
if (!result.success) {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
requestId
|
|
1372
|
-
);
|
|
1738
|
+
const inputErr = new ActionInputError(result.error?.issues ?? []);
|
|
1739
|
+
writeSerialized(res, serializeActionResult({ data: void 0, error: inputErr }));
|
|
1740
|
+
await emitActionCallTelemetry(actionName, startedAt, bodyOutcome.body, {
|
|
1741
|
+
status: "error",
|
|
1742
|
+
error: inputErr
|
|
1743
|
+
});
|
|
1373
1744
|
return;
|
|
1374
1745
|
}
|
|
1375
1746
|
await runActionHandler({
|
|
1376
1747
|
actionConfig,
|
|
1748
|
+
actionName,
|
|
1749
|
+
startedAt,
|
|
1377
1750
|
input: result.data,
|
|
1378
1751
|
ctx,
|
|
1379
1752
|
res,
|
|
@@ -1384,12 +1757,44 @@ async function executeActionWithOptions(opts) {
|
|
|
1384
1757
|
await handleActionError(err, { req, res, ctx, requestId, pluginRunner, buildPluginCtx });
|
|
1385
1758
|
}
|
|
1386
1759
|
}
|
|
1760
|
+
function deriveActionNameFromPath(filePath) {
|
|
1761
|
+
const base = filePath.split(/[\\/]/).pop() ?? "unknown";
|
|
1762
|
+
return base.replace(/\.[jt]sx?$/, "");
|
|
1763
|
+
}
|
|
1764
|
+
function isActionErrorLike(err) {
|
|
1765
|
+
if (err === null || typeof err !== "object") return false;
|
|
1766
|
+
const obj = err;
|
|
1767
|
+
return (obj.type === "TheoActionError" || obj.type === "TheoActionInputError") && typeof obj.code === "string" && typeof obj.status === "number";
|
|
1768
|
+
}
|
|
1387
1769
|
async function runActionHandler(args) {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1770
|
+
try {
|
|
1771
|
+
const handlerResult = await args.actionConfig.handler({ input: args.input, ctx: args.ctx });
|
|
1772
|
+
writeSerialized(args.res, serializeActionResult({ data: handlerResult, error: void 0 }));
|
|
1773
|
+
if (args.pluginRunner) {
|
|
1774
|
+
await args.pluginRunner.runOnResponse(args.buildPluginCtx(args.ctx));
|
|
1775
|
+
}
|
|
1776
|
+
await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
|
|
1777
|
+
status: "success",
|
|
1778
|
+
output: handlerResult
|
|
1779
|
+
});
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
if (isActionErrorLike(err)) {
|
|
1782
|
+
writeSerialized(args.res, serializeActionResult({ data: void 0, error: err }));
|
|
1783
|
+
await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
|
|
1784
|
+
status: "error",
|
|
1785
|
+
error: err
|
|
1786
|
+
});
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const wrapped = new ActionError({
|
|
1790
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
1791
|
+
message: err instanceof Error ? err.message : "Action handler threw"
|
|
1792
|
+
});
|
|
1793
|
+
await emitActionCallTelemetry(args.actionName, args.startedAt, args.input, {
|
|
1794
|
+
status: "error",
|
|
1795
|
+
error: wrapped
|
|
1796
|
+
});
|
|
1797
|
+
throw err;
|
|
1393
1798
|
}
|
|
1394
1799
|
}
|
|
1395
1800
|
async function handleActionError(err, c) {
|
|
@@ -1532,4 +1937,4 @@ export {
|
|
|
1532
1937
|
resolveTransformer,
|
|
1533
1938
|
generateNonce
|
|
1534
1939
|
};
|
|
1535
|
-
//# sourceMappingURL=chunk-
|
|
1940
|
+
//# sourceMappingURL=chunk-C27AZN46.js.map
|