theokit 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/actions-virtual-module-E6PU47GV.js +175 -0
  2. package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
  3. package/dist/actions-virtual-module-E7IELVHL.js +174 -0
  4. package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
  5. package/dist/app-typed-client-4L4L4BWD.js +289 -0
  6. package/dist/app-typed-client-4L4L4BWD.js.map +1 -0
  7. package/dist/app-typed-client-6PAT6VOL.js +289 -0
  8. package/dist/app-typed-client-6PAT6VOL.js.map +1 -0
  9. package/dist/{build-IQQYCA3B.js → build-EWZDEDT3.js} +37 -11
  10. package/dist/build-EWZDEDT3.js.map +1 -0
  11. package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
  12. package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
  13. package/dist/build-request-body-preview-II2235E6.js +54 -0
  14. package/dist/build-request-body-preview-II2235E6.js.map +1 -0
  15. package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
  16. package/dist/chunk-2BQYEBCK.js.map +1 -0
  17. package/dist/{chunk-FRBYJANT.js → chunk-4W447H7M.js} +195 -41
  18. package/dist/chunk-4W447H7M.js.map +1 -0
  19. package/dist/chunk-7DHGV77J.js +140 -0
  20. package/dist/chunk-7DHGV77J.js.map +1 -0
  21. package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
  22. package/dist/chunk-C27AZN46.js.map +1 -0
  23. package/dist/chunk-ERAZNHHR.js +73 -0
  24. package/dist/chunk-ERAZNHHR.js.map +1 -0
  25. package/dist/chunk-F3TG5FJV.js +97 -0
  26. package/dist/chunk-F3TG5FJV.js.map +1 -0
  27. package/dist/{chunk-OK2PJOLK.js → chunk-KJVEJILQ.js} +7 -3
  28. package/dist/chunk-KJVEJILQ.js.map +1 -0
  29. package/dist/{chunk-JBC7PFV4.js → chunk-M23FP2AS.js} +2 -2
  30. package/dist/chunk-M23FP2AS.js.map +1 -0
  31. package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
  32. package/dist/chunk-MOT42NAH.js +165 -0
  33. package/dist/chunk-MOT42NAH.js.map +1 -0
  34. package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
  35. package/dist/chunk-OJHO2X7C.js.map +1 -0
  36. package/dist/chunk-QLGES2HC.js +189 -0
  37. package/dist/chunk-QLGES2HC.js.map +1 -0
  38. package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
  39. package/dist/chunk-SEVYARIU.js.map +1 -0
  40. package/dist/{chunk-ZK5JCE5J.js → chunk-TFECHXR5.js} +201 -43
  41. package/dist/chunk-TFECHXR5.js.map +1 -0
  42. package/dist/chunk-WSJKACWB.js +17 -0
  43. package/dist/chunk-WSJKACWB.js.map +1 -0
  44. package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
  45. package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
  46. package/dist/chunk-ZOXNJV2O.js +358 -0
  47. package/dist/chunk-ZOXNJV2O.js.map +1 -0
  48. package/dist/cli/index.js +22 -7
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/client/index.d.ts +31 -1
  51. package/dist/client/index.js +100 -0
  52. package/dist/client/index.js.map +1 -1
  53. package/dist/{dev-VN6WS3CM.js → dev-74KPI5C5.js} +15 -7
  54. package/dist/dev-74KPI5C5.js.map +1 -0
  55. package/dist/dev-emit-3CHM73TV.js +390 -0
  56. package/dist/dev-emit-3CHM73TV.js.map +1 -0
  57. package/dist/dev-emit-I3CKOHML.js +44 -0
  58. package/dist/dev-emit-I3CKOHML.js.map +1 -0
  59. package/dist/devtools/entry.js +545 -181
  60. package/dist/devtools/entry.js.map +1 -1
  61. package/dist/{dispatcher-IK3FVMX5.js → dispatcher-AQJGI3HU.js} +2 -2
  62. package/dist/dispatcher-E6QV32BO.js +83 -0
  63. package/dist/dispatcher-E6QV32BO.js.map +1 -0
  64. package/dist/{dist-Q5VBFWVL.js → dist-R2Q7GVDW.js} +10357 -12068
  65. package/dist/dist-R2Q7GVDW.js.map +1 -0
  66. package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
  67. package/dist/generate-DT4IIAHF.js.map +1 -0
  68. package/dist/index.d.ts +108 -1
  69. package/dist/index.js +5 -2
  70. package/dist/index.js.map +1 -1
  71. package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
  72. package/dist/openapi-CBASKC7W.js +84 -0
  73. package/dist/openapi-CBASKC7W.js.map +1 -0
  74. package/dist/{registry-BB3UYAOG.js → registry-QGSNLJKJ.js} +2 -2
  75. package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
  76. package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
  77. package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
  78. package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
  79. package/dist/server/cost/index.js +1 -1
  80. package/dist/server/index.d.ts +219 -47
  81. package/dist/server/index.js +35 -116
  82. package/dist/server/index.js.map +1 -1
  83. package/dist/{start-ZGWW5MOL.js → start-5JTFWF4T.js} +20 -14
  84. package/dist/start-5JTFWF4T.js.map +1 -0
  85. package/dist/{static-BH6JQNM2.js → static-6PZZBY7G.js} +4 -4
  86. package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
  87. package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
  88. package/dist/vite-plugin/index.d.ts +25 -1
  89. package/dist/vite-plugin/index.js +5 -2
  90. package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-3EQP2JYM.js} +7 -6
  91. package/package.json +4 -2
  92. package/dist/build-IQQYCA3B.js.map +0 -1
  93. package/dist/chunk-FRBYJANT.js.map +0 -1
  94. package/dist/chunk-GQSTRXXB.js.map +0 -1
  95. package/dist/chunk-JBC7PFV4.js.map +0 -1
  96. package/dist/chunk-JZ5WEG3S.js.map +0 -1
  97. package/dist/chunk-OEB3OIYP.js.map +0 -1
  98. package/dist/chunk-OK2PJOLK.js.map +0 -1
  99. package/dist/chunk-WQCPKA2L.js.map +0 -1
  100. package/dist/chunk-ZK5JCE5J.js.map +0 -1
  101. package/dist/dev-VN6WS3CM.js.map +0 -1
  102. package/dist/dist-Q5VBFWVL.js.map +0 -1
  103. package/dist/generate-SIO4JSKJ.js.map +0 -1
  104. package/dist/start-ZGWW5MOL.js.map +0 -1
  105. package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
  106. /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
  107. /package/dist/{dispatcher-IK3FVMX5.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
  108. /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
  109. /package/dist/{registry-BB3UYAOG.js.map → registry-QGSNLJKJ.js.map} +0 -0
  110. /package/dist/{schema-MVB2FVK6.js.map → schema-JAKF23GR.js.map} +0 -0
  111. /package/dist/{static-BH6JQNM2.js.map → static-6PZZBY7G.js.map} +0 -0
  112. /package/dist/{vite-plugin-WA3G45YY.js.map → vite-plugin-3EQP2JYM.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
- void import("./broadcast-QOQGUNNK.js").then(({ broadcastRequest }) => {
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 body = parsed.json !== void 0 ? parsed.json : parsed.fields;
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
- sendError(
1366
- res,
1367
- "VALIDATION_ERROR",
1368
- "Invalid action input",
1369
- 400,
1370
- result.error?.issues,
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
- const handlerResult = await args.actionConfig.handler({ input: args.input, ctx: args.ctx });
1389
- const status = handlerResult === void 0 || handlerResult === null ? 204 : 200;
1390
- sendJson(args.res, handlerResult ?? null, status);
1391
- if (args.pluginRunner) {
1392
- await args.pluginRunner.runOnResponse(args.buildPluginCtx(args.ctx));
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-WQCPKA2L.js.map
1940
+ //# sourceMappingURL=chunk-C27AZN46.js.map