@beignet/core 0.0.50 → 0.0.52

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 (102) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +99 -32
  3. package/dist/application/index.d.ts +5 -5
  4. package/dist/application/index.d.ts.map +1 -1
  5. package/dist/application/index.js +8 -4
  6. package/dist/application/index.js.map +1 -1
  7. package/dist/events/index.d.ts +26 -2
  8. package/dist/events/index.d.ts.map +1 -1
  9. package/dist/events/index.js +84 -7
  10. package/dist/events/index.js.map +1 -1
  11. package/dist/events/payload-state.d.ts +16 -0
  12. package/dist/events/payload-state.d.ts.map +1 -0
  13. package/dist/events/payload-state.js +123 -0
  14. package/dist/events/payload-state.js.map +1 -0
  15. package/dist/events/transport.d.ts +25 -0
  16. package/dist/events/transport.d.ts.map +1 -0
  17. package/dist/events/transport.js +192 -0
  18. package/dist/events/transport.js.map +1 -0
  19. package/dist/locks/index.d.ts.map +1 -1
  20. package/dist/locks/index.js +0 -4
  21. package/dist/locks/index.js.map +1 -1
  22. package/dist/openapi/index.d.ts.map +1 -1
  23. package/dist/openapi/index.js +27 -4
  24. package/dist/openapi/index.js.map +1 -1
  25. package/dist/outbox/index.d.ts +163 -15
  26. package/dist/outbox/index.d.ts.map +1 -1
  27. package/dist/outbox/index.js +1009 -155
  28. package/dist/outbox/index.js.map +1 -1
  29. package/dist/payments/index.d.ts.map +1 -1
  30. package/dist/payments/index.js +0 -4
  31. package/dist/payments/index.js.map +1 -1
  32. package/dist/ports/events.d.ts +4 -1
  33. package/dist/ports/events.d.ts.map +1 -1
  34. package/dist/ports/index.d.ts +1 -1
  35. package/dist/ports/index.d.ts.map +1 -1
  36. package/dist/ports/index.js.map +1 -1
  37. package/dist/ports/storage.d.ts +7 -0
  38. package/dist/ports/storage.d.ts.map +1 -1
  39. package/dist/ports/storage.js +4 -0
  40. package/dist/ports/storage.js.map +1 -1
  41. package/dist/ports/testing.d.ts +16 -2
  42. package/dist/ports/testing.d.ts.map +1 -1
  43. package/dist/ports/testing.js +19 -4
  44. package/dist/ports/testing.js.map +1 -1
  45. package/dist/ports/unit-of-work.d.ts +9 -7
  46. package/dist/ports/unit-of-work.d.ts.map +1 -1
  47. package/dist/ports/unit-of-work.js +16 -7
  48. package/dist/ports/unit-of-work.js.map +1 -1
  49. package/dist/providers/index.d.ts +1 -1
  50. package/dist/providers/index.d.ts.map +1 -1
  51. package/dist/providers/index.js.map +1 -1
  52. package/dist/providers/provider.d.ts +0 -38
  53. package/dist/providers/provider.d.ts.map +1 -1
  54. package/dist/providers/provider.js.map +1 -1
  55. package/dist/search/index.d.ts.map +1 -1
  56. package/dist/search/index.js +2 -6
  57. package/dist/search/index.js.map +1 -1
  58. package/dist/server/hooks/cors.d.ts +5 -0
  59. package/dist/server/hooks/cors.d.ts.map +1 -1
  60. package/dist/server/hooks/cors.js +29 -1
  61. package/dist/server/hooks/cors.js.map +1 -1
  62. package/dist/server/instrumentation.d.ts +5 -5
  63. package/dist/server/instrumentation.d.ts.map +1 -1
  64. package/dist/server/instrumentation.js +3 -4
  65. package/dist/server/instrumentation.js.map +1 -1
  66. package/dist/server/response-finalization.d.ts.map +1 -1
  67. package/dist/server/response-finalization.js +25 -13
  68. package/dist/server/response-finalization.js.map +1 -1
  69. package/dist/server/server.d.ts +8 -5
  70. package/dist/server/server.d.ts.map +1 -1
  71. package/dist/server/server.js +35 -12
  72. package/dist/server/server.js.map +1 -1
  73. package/dist/testing/index.d.ts.map +1 -1
  74. package/dist/testing/index.js +12 -6
  75. package/dist/testing/index.js.map +1 -1
  76. package/dist/uploads/index.d.ts.map +1 -1
  77. package/dist/uploads/index.js +37 -16
  78. package/dist/uploads/index.js.map +1 -1
  79. package/package.json +2 -2
  80. package/skills/app-architecture/SKILL.md +16 -0
  81. package/src/application/index.ts +39 -7
  82. package/src/events/index.ts +137 -7
  83. package/src/events/payload-state.ts +223 -0
  84. package/src/events/transport.ts +242 -0
  85. package/src/locks/index.ts +0 -4
  86. package/src/openapi/index.ts +41 -3
  87. package/src/outbox/index.ts +1393 -178
  88. package/src/payments/index.ts +0 -4
  89. package/src/ports/events.ts +4 -1
  90. package/src/ports/index.ts +3 -0
  91. package/src/ports/storage.ts +10 -0
  92. package/src/ports/testing.ts +42 -4
  93. package/src/ports/unit-of-work.ts +33 -15
  94. package/src/providers/index.ts +0 -1
  95. package/src/providers/provider.ts +0 -40
  96. package/src/search/index.ts +2 -6
  97. package/src/server/hooks/cors.ts +42 -0
  98. package/src/server/instrumentation.ts +10 -8
  99. package/src/server/response-finalization.ts +33 -15
  100. package/src/server/server.ts +61 -27
  101. package/src/testing/index.ts +20 -9
  102. package/src/uploads/index.ts +34 -16
@@ -204,15 +204,18 @@ export type CreateServerOptions<
204
204
  * `ports.devtools`) when one is installed.
205
205
  *
206
206
  * Pass `false` to disable headers and event recording. Context factories
207
- * still receive `requestId` and `trace` arguments.
207
+ * still receive `requestId` and `trace` arguments, and request-scoped
208
+ * correlation remains available to ambient audit wrappers.
208
209
  */
209
210
  instrumentation?: ServerInstrumentationOptions<Ctx> | false;
210
211
  /**
211
- * Whether route-owned responses are validated against the contract's
212
- * declared statuses and response schemas before they are sent.
212
+ * Whether route-owned responses are parsed against the contract's declared
213
+ * statuses and response schemas before they are sent. The parsed schema
214
+ * output becomes the response body.
213
215
  *
214
- * Disable this to trade response guarantees for throughput, mirroring the
215
- * client-side `validateResponses` option.
216
+ * Disable this to send route-owned handler bodies as-is without validation,
217
+ * unknown-key stripping, or transforms, mirroring the client-side
218
+ * `validateResponses` option.
216
219
  *
217
220
  * @default true
218
221
  */
@@ -407,9 +410,7 @@ export async function createServer<
407
410
  options.instrumentation,
408
411
  );
409
412
  const hooks = [
410
- ...(instrumentation.hook
411
- ? [instrumentation.hook as ServerHook<Ctx, FinalPorts>]
412
- : []),
413
+ instrumentation.hook as ServerHook<Ctx, FinalPorts>,
413
414
  ...((options.hooks ?? []) as ServerHook<Ctx, FinalPorts>[]),
414
415
  ];
415
416
  const contracts = options.routes ? contractsFromRoutes(options.routes) : [];
@@ -669,24 +670,6 @@ export async function createServer<
669
670
  registryNeedsSort = true;
670
671
  };
671
672
 
672
- const createBuilder = <C extends HttpContractConfig>(
673
- contract: C,
674
- shouldRegister: boolean,
675
- ): RouteBuilder<Ctx, C> => ({
676
- handle: (fn) => {
677
- const wrapped = buildHandler(
678
- options,
679
- finalPorts,
680
- contextRuntime,
681
- contract,
682
- fn,
683
- hooks,
684
- );
685
- if (shouldRegister) registerRoute(contract, fn);
686
- return wrapped;
687
- },
688
- });
689
-
690
673
  if (options.routes) {
691
674
  try {
692
675
  for (const route of options.routes) {
@@ -917,7 +900,58 @@ export async function createServer<
917
900
  api,
918
901
  route: (contractLike) => {
919
902
  const contract = resolveContract(contractLike);
920
- return createBuilder(contract, true);
903
+ return {
904
+ handle: (fn) => {
905
+ const wrapped = buildHandler(
906
+ options,
907
+ finalPorts,
908
+ contextRuntime,
909
+ contract,
910
+ fn,
911
+ hooks,
912
+ );
913
+ registerRoute(contract, fn);
914
+ const compiled = compilePath(contract.path);
915
+
916
+ return async (req: HttpRequestLike) => {
917
+ const method = req.method.toUpperCase();
918
+ let url: URL;
919
+ try {
920
+ url = parseHttpRequestUrl(req.url);
921
+ } catch (error) {
922
+ return await executeFallback(
923
+ {
924
+ contract: fallbackContract("invalidRequestUrl", method, "/"),
925
+ handler: async () => {
926
+ throw error;
927
+ },
928
+ },
929
+ req,
930
+ {},
931
+ );
932
+ }
933
+
934
+ const requestMethod = method;
935
+ const contractMethod = contract.method.toUpperCase();
936
+ const methodMatches =
937
+ requestMethod === contractMethod ||
938
+ (requestMethod === "HEAD" && contractMethod === "GET");
939
+ if (!methodMatches || !compiled.pattern.test(url.pathname)) {
940
+ const pathname = url.pathname || "/";
941
+ return await executeFallback(
942
+ {
943
+ contract: fallbackContract("notFound", method, pathname),
944
+ handler: notFoundHandler,
945
+ },
946
+ req,
947
+ {},
948
+ );
949
+ }
950
+
951
+ return await wrapped(req);
952
+ };
953
+ },
954
+ };
921
955
  },
922
956
  rawRoute: (init) => ({
923
957
  handle: (fn) => {
@@ -15,7 +15,7 @@ import {
15
15
  } from "../notifications/index.js";
16
16
  import {
17
17
  createMemoryOutbox,
18
- enqueueEvent,
18
+ createOutboxEventRecorder,
19
19
  type MemoryOutboxPort,
20
20
  } from "../outbox/index.js";
21
21
  import {
@@ -489,10 +489,17 @@ export function createTestPorts<
489
489
  outbox: MemoryOutboxPort;
490
490
  }
491
491
  ).outbox;
492
- for (const entry of events.entries()) {
493
- await enqueueEvent(outbox, entry.event, entry.payload);
494
- }
495
- events.clear();
492
+ const outboxRecorder = createOutboxEventRecorder(outbox);
493
+ await events.flush({
494
+ publish(event, payload, options) {
495
+ return outboxRecorder.record(event, payload, options);
496
+ },
497
+ subscribe() {
498
+ throw new Error(
499
+ "The test outbox event publisher does not support subscriptions.",
500
+ );
501
+ },
502
+ });
496
503
  }
497
504
  await transactionOptions?.afterCommit?.(tx);
498
505
  },
@@ -619,18 +626,19 @@ function completeTestPortOverrides<Ports extends AnyPorts>(
619
626
 
620
627
  function resolveBufferedTransactionEvents(
621
628
  tx: unknown,
622
- ): Pick<BufferedDomainEventRecorder, "entries" | "clear"> {
629
+ ): Pick<BufferedDomainEventRecorder, "entries" | "clear" | "flush"> {
623
630
  const events =
624
631
  tx && typeof tx === "object"
625
632
  ? (tx as { events?: unknown }).events
626
633
  : undefined;
627
634
  const recorder = events as
628
- | Partial<Pick<BufferedDomainEventRecorder, "entries" | "clear">>
635
+ | Partial<Pick<BufferedDomainEventRecorder, "entries" | "clear" | "flush">>
629
636
  | undefined;
630
637
  if (
631
638
  !recorder ||
632
639
  typeof recorder.entries !== "function" ||
633
- typeof recorder.clear !== "function"
640
+ typeof recorder.clear !== "function" ||
641
+ typeof recorder.flush !== "function"
634
642
  ) {
635
643
  throw new Error(
636
644
  "createTestPorts transaction.outbox requires tx.events to be a buffered domain event recorder. " +
@@ -638,7 +646,10 @@ function resolveBufferedTransactionEvents(
638
646
  );
639
647
  }
640
648
 
641
- return recorder as Pick<BufferedDomainEventRecorder, "entries" | "clear">;
649
+ return recorder as Pick<
650
+ BufferedDomainEventRecorder,
651
+ "entries" | "clear" | "flush"
652
+ >;
642
653
  }
643
654
 
644
655
  /**
@@ -968,29 +968,47 @@ export function createUploadRouter<Ctx>(
968
968
  },
969
969
  });
970
970
  }
971
- const object = needsUploadBodyVerification(upload, file)
971
+ const verifyBody = needsUploadBodyVerification(upload, file);
972
+ const objectBody = verifyBody
972
973
  ? await options.storage.get(file.key)
974
+ : null;
975
+ const object = verifyBody
976
+ ? objectBody
973
977
  : await options.storage.stat(file.key);
974
978
  if (!object) {
975
979
  throw new UploadObjectNotFoundError({
976
980
  message: `Uploaded object "${file.key}" was not found.`,
977
981
  });
978
982
  }
979
- assertStoredObject(upload, file, object);
980
- const verified = await verifyStoredUploadFile(upload, file, object);
981
- const completedObject = storageObjectMetadata(object);
982
- const completedFile = {
983
- ...file,
984
- ...(verified.checksum ? { checksum: verified.checksum } : {}),
985
- object: completedObject,
986
- };
987
- await assertVerifiedFile(upload, {
988
- ctx,
989
- metadata,
990
- file: completedFile,
991
- storage: options.storage,
992
- });
993
- files.push(completedFile);
983
+ let operationFailed = false;
984
+ try {
985
+ assertStoredObject(upload, file, object);
986
+ const verified = await verifyStoredUploadFile(upload, file, object);
987
+ const completedObject = storageObjectMetadata(object);
988
+ const completedFile = {
989
+ ...file,
990
+ ...(verified.checksum ? { checksum: verified.checksum } : {}),
991
+ object: completedObject,
992
+ };
993
+ await assertVerifiedFile(upload, {
994
+ ctx,
995
+ metadata,
996
+ file: completedFile,
997
+ storage: options.storage,
998
+ });
999
+ files.push(completedFile);
1000
+ } catch (error) {
1001
+ operationFailed = true;
1002
+ throw error;
1003
+ } finally {
1004
+ if (objectBody && !objectBody.bodyUsed) {
1005
+ if (operationFailed) {
1006
+ await objectBody.cancel().catch(() => undefined);
1007
+ } else {
1008
+ await objectBody.cancel();
1009
+ }
1010
+ }
1011
+ }
994
1012
  }
995
1013
 
996
1014
  const result = await upload.onComplete?.({ ctx, metadata, files });