@beignet/core 0.0.49 → 0.0.51

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 (145) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +213 -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 +5 -4
  6. package/dist/application/index.js.map +1 -1
  7. package/dist/events/index.d.ts +54 -5
  8. package/dist/events/index.d.ts.map +1 -1
  9. package/dist/events/index.js +188 -34
  10. package/dist/events/index.js.map +1 -1
  11. package/dist/events/payload-state.d.ts +4 -0
  12. package/dist/events/payload-state.d.ts.map +1 -0
  13. package/dist/events/payload-state.js +11 -0
  14. package/dist/events/payload-state.js.map +1 -0
  15. package/dist/idempotency/index.d.ts +7 -3
  16. package/dist/idempotency/index.d.ts.map +1 -1
  17. package/dist/idempotency/index.js +45 -12
  18. package/dist/idempotency/index.js.map +1 -1
  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/mail/index.d.ts.map +1 -1
  23. package/dist/mail/index.js +6 -3
  24. package/dist/mail/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 -152
  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/best-effort-work.d.ts +21 -0
  33. package/dist/ports/best-effort-work.d.ts.map +1 -0
  34. package/dist/ports/best-effort-work.js +2 -0
  35. package/dist/ports/best-effort-work.js.map +1 -0
  36. package/dist/ports/events.d.ts +7 -5
  37. package/dist/ports/events.d.ts.map +1 -1
  38. package/dist/ports/index.d.ts +7 -2
  39. package/dist/ports/index.d.ts.map +1 -1
  40. package/dist/ports/index.js +1 -0
  41. package/dist/ports/index.js.map +1 -1
  42. package/dist/ports/testing.d.ts +28 -0
  43. package/dist/ports/testing.d.ts.map +1 -1
  44. package/dist/ports/testing.js +50 -0
  45. package/dist/ports/testing.js.map +1 -1
  46. package/dist/ports/unit-of-work.d.ts +9 -7
  47. package/dist/ports/unit-of-work.d.ts.map +1 -1
  48. package/dist/ports/unit-of-work.js +16 -6
  49. package/dist/ports/unit-of-work.js.map +1 -1
  50. package/dist/providers/index.d.ts +1 -1
  51. package/dist/providers/index.d.ts.map +1 -1
  52. package/dist/providers/index.js.map +1 -1
  53. package/dist/providers/provider.d.ts +8 -43
  54. package/dist/providers/provider.d.ts.map +1 -1
  55. package/dist/providers/provider.js.map +1 -1
  56. package/dist/search/index.d.ts.map +1 -1
  57. package/dist/search/index.js +0 -4
  58. package/dist/search/index.js.map +1 -1
  59. package/dist/server/hooks/cors.d.ts +7 -2
  60. package/dist/server/hooks/cors.d.ts.map +1 -1
  61. package/dist/server/hooks/cors.js +31 -2
  62. package/dist/server/hooks/cors.js.map +1 -1
  63. package/dist/server/hooks/logging.d.ts +2 -2
  64. package/dist/server/hooks/logging.d.ts.map +1 -1
  65. package/dist/server/hooks/logging.js.map +1 -1
  66. package/dist/server/hooks/security.d.ts +2 -2
  67. package/dist/server/hooks/security.d.ts.map +1 -1
  68. package/dist/server/hooks/security.js.map +1 -1
  69. package/dist/server/http.d.ts +21 -2
  70. package/dist/server/http.d.ts.map +1 -1
  71. package/dist/server/index.d.ts +4 -0
  72. package/dist/server/index.d.ts.map +1 -1
  73. package/dist/server/index.js +4 -0
  74. package/dist/server/index.js.map +1 -1
  75. package/dist/server/instrumentation.d.ts.map +1 -1
  76. package/dist/server/instrumentation.js +5 -3
  77. package/dist/server/instrumentation.js.map +1 -1
  78. package/dist/server/request-executor.d.ts.map +1 -1
  79. package/dist/server/request-executor.js +9 -0
  80. package/dist/server/request-executor.js.map +1 -1
  81. package/dist/server/request-preparation.d.ts.map +1 -1
  82. package/dist/server/request-preparation.js +20 -2
  83. package/dist/server/request-preparation.js.map +1 -1
  84. package/dist/server/response-finalization.d.ts +2 -2
  85. package/dist/server/response-finalization.d.ts.map +1 -1
  86. package/dist/server/response-finalization.js +25 -8
  87. package/dist/server/response-finalization.js.map +1 -1
  88. package/dist/server/route-matching.d.ts.map +1 -1
  89. package/dist/server/route-matching.js +12 -1
  90. package/dist/server/route-matching.js.map +1 -1
  91. package/dist/server/server-sent-events.d.ts +94 -0
  92. package/dist/server/server-sent-events.d.ts.map +1 -0
  93. package/dist/server/server-sent-events.js +275 -0
  94. package/dist/server/server-sent-events.js.map +1 -0
  95. package/dist/server/server.d.ts.map +1 -1
  96. package/dist/server/server.js +75 -31
  97. package/dist/server/server.js.map +1 -1
  98. package/dist/server/trusted-proxy-internal.d.ts +4 -0
  99. package/dist/server/trusted-proxy-internal.d.ts.map +1 -1
  100. package/dist/server/trusted-proxy-internal.js +20 -0
  101. package/dist/server/trusted-proxy-internal.js.map +1 -1
  102. package/dist/server/trusted-proxy.d.ts.map +1 -1
  103. package/dist/server/trusted-proxy.js +3 -8
  104. package/dist/server/trusted-proxy.js.map +1 -1
  105. package/dist/testing/index.d.ts +17 -0
  106. package/dist/testing/index.d.ts.map +1 -1
  107. package/dist/testing/index.js +18 -7
  108. package/dist/testing/index.js.map +1 -1
  109. package/package.json +1 -1
  110. package/skills/app-architecture/SKILL.md +37 -3
  111. package/src/application/index.ts +18 -7
  112. package/src/events/index.ts +268 -40
  113. package/src/events/payload-state.ts +24 -0
  114. package/src/idempotency/index.ts +65 -17
  115. package/src/locks/index.ts +0 -4
  116. package/src/mail/index.ts +7 -3
  117. package/src/outbox/index.ts +1382 -175
  118. package/src/payments/index.ts +0 -4
  119. package/src/ports/best-effort-work.ts +21 -0
  120. package/src/ports/events.ts +9 -4
  121. package/src/ports/index.ts +12 -0
  122. package/src/ports/testing.ts +76 -0
  123. package/src/ports/unit-of-work.ts +28 -14
  124. package/src/providers/index.ts +0 -1
  125. package/src/providers/provider.ts +8 -45
  126. package/src/search/index.ts +0 -4
  127. package/src/server/hooks/cors.ts +53 -5
  128. package/src/server/hooks/logging.ts +6 -2
  129. package/src/server/hooks/security.ts +8 -4
  130. package/src/server/http.ts +23 -2
  131. package/src/server/index.ts +4 -0
  132. package/src/server/instrumentation.ts +12 -4
  133. package/src/server/request-executor.ts +14 -0
  134. package/src/server/request-preparation.ts +23 -3
  135. package/src/server/response-finalization.ts +51 -15
  136. package/src/server/route-matching.ts +24 -1
  137. package/src/server/server-sent-events.ts +415 -0
  138. package/src/server/server.ts +98 -41
  139. package/src/server/trusted-proxy-internal.ts +20 -0
  140. package/src/server/trusted-proxy.ts +6 -7
  141. package/src/testing/index.ts +50 -9
  142. package/dist/query-codec.d.ts +0 -27
  143. package/dist/query-codec.d.ts.map +0 -1
  144. package/dist/query-codec.js +0 -245
  145. package/dist/query-codec.js.map +0 -1
@@ -451,6 +451,17 @@ type CanonicalValue =
451
451
  | readonly CanonicalValue[]
452
452
  | { readonly [key: string]: CanonicalValue };
453
453
 
454
+ const CANONICAL_TYPE_KEY = "$beignet:idempotency-type";
455
+
456
+ function canonicalType(
457
+ type: "bigint" | "date" | "negative-zero" | "omitted",
458
+ value?: string,
459
+ ): CanonicalValue {
460
+ return {
461
+ [CANONICAL_TYPE_KEY]: value === undefined ? [type] : [type, value],
462
+ };
463
+ }
464
+
454
465
  type MemoryRecord = MemoryIdempotencyEntry & {
455
466
  replayTtlSec?: number;
456
467
  };
@@ -875,11 +886,15 @@ function canonicalize(
875
886
  return undefined;
876
887
  }
877
888
 
878
- if (
879
- value === null ||
880
- typeof value === "string" ||
881
- typeof value === "boolean"
882
- ) {
889
+ if (value === null) {
890
+ return null;
891
+ }
892
+
893
+ if (typeof value === "string") {
894
+ return value;
895
+ }
896
+
897
+ if (typeof value === "boolean") {
883
898
  return value;
884
899
  }
885
900
 
@@ -889,25 +904,54 @@ function canonicalize(
889
904
  "Cannot fingerprint non-finite numeric values.",
890
905
  );
891
906
  }
892
- return value;
907
+ return Object.is(value, -0) ? canonicalType("negative-zero") : value;
893
908
  }
894
909
 
895
910
  if (typeof value === "bigint") {
896
- return value.toString();
911
+ return canonicalType("bigint", value.toString());
897
912
  }
898
913
 
899
914
  if (value instanceof Date) {
900
- return value.toISOString();
915
+ if (!Number.isFinite(value.getTime())) {
916
+ throw new IdempotencyFingerprintError(
917
+ "Cannot fingerprint an invalid Date value.",
918
+ );
919
+ }
920
+ return canonicalType("date", value.toISOString());
901
921
  }
902
922
 
903
923
  if (Array.isArray(value)) {
904
- return value.map(
905
- (item, index) =>
906
- canonicalize(item, options, [...path, String(index)], seen) ?? null,
907
- );
924
+ if (seen.has(value)) {
925
+ throw new IdempotencyFingerprintError(
926
+ "Cannot fingerprint circular values.",
927
+ );
928
+ }
929
+ seen.add(value);
930
+ const result: CanonicalValue = Array.from(value, (item, index) => {
931
+ const nestedValue = canonicalize(
932
+ item,
933
+ options,
934
+ [...path, String(index)],
935
+ seen,
936
+ );
937
+ return nestedValue === undefined ? canonicalType("omitted") : nestedValue;
938
+ });
939
+ seen.delete(value);
940
+ return result;
908
941
  }
909
942
 
910
943
  if (typeof value === "object") {
944
+ const prototype = Object.getPrototypeOf(value);
945
+ if (prototype !== null && prototype !== Object.prototype) {
946
+ throw new IdempotencyFingerprintError(
947
+ "Cannot fingerprint unsupported object values. Use plain objects, arrays, Dates, and JSON primitives.",
948
+ );
949
+ }
950
+ if (Object.hasOwn(value, CANONICAL_TYPE_KEY)) {
951
+ throw new IdempotencyFingerprintError(
952
+ `Cannot fingerprint an object containing the reserved key "${CANONICAL_TYPE_KEY}".`,
953
+ );
954
+ }
911
955
  if (seen.has(value)) {
912
956
  throw new IdempotencyFingerprintError(
913
957
  "Cannot fingerprint circular values.",
@@ -915,7 +959,7 @@ function canonicalize(
915
959
  }
916
960
  seen.add(value);
917
961
 
918
- const result: Record<string, CanonicalValue> = {};
962
+ const result: Record<string, CanonicalValue> = Object.create(null);
919
963
  for (const key of Object.keys(value as Record<string, unknown>).sort()) {
920
964
  const nestedValue = canonicalize(
921
965
  (value as Record<string, unknown>)[key],
@@ -946,9 +990,13 @@ function bytesToHex(bytes: ArrayBuffer): string {
946
990
  /**
947
991
  * Create a SHA-256 fingerprint from a canonicalized value.
948
992
  *
949
- * Object keys are sorted, `undefined` and functions are omitted, `Date` values
950
- * become ISO strings, BigInts become strings, and circular or non-finite values
951
- * throw. Exact omit paths may be supplied as dotted strings or string arrays.
993
+ * Object keys are sorted, while non-JSON values carry reserved type tags so
994
+ * BigInts and Dates cannot collide with same-looking strings. Ordinary JSON
995
+ * inputs retain their existing canonical representation. `undefined` values
996
+ * and functions are omitted from objects and represented as omitted array
997
+ * positions. Circular or unsupported object values, reserved tag keys,
998
+ * invalid dates, and non-finite numbers throw. Exact omit paths may be supplied
999
+ * as dotted strings or string arrays.
952
1000
  */
953
1001
  export async function createIdempotencyFingerprint(
954
1002
  value: unknown,
@@ -961,7 +1009,7 @@ export async function createIdempotencyFingerprint(
961
1009
  }
962
1010
 
963
1011
  const canonical = canonicalize(value, options, [], new WeakSet());
964
- const json = JSON.stringify(canonical ?? null);
1012
+ const json = JSON.stringify(canonical ?? canonicalType("omitted"));
965
1013
  const digest = await globalThis.crypto.subtle.digest(
966
1014
  "SHA-256",
967
1015
  new TextEncoder().encode(json),
@@ -285,10 +285,6 @@ export function createMemoryLocksProvider(
285
285
 
286
286
  return createProvider({
287
287
  name,
288
- metadata: {
289
- ports: ["locks"],
290
- watchers: ["locks"],
291
- },
292
288
  setup({ ports }) {
293
289
  const instrumentation = createProviderInstrumentation(ports, {
294
290
  providerName: name,
package/src/mail/index.ts CHANGED
@@ -403,13 +403,17 @@ export function createMemoryMailerProvider(
403
403
  onSend,
404
404
  },
405
405
  (delivery, durationMs) => {
406
+ const recipientCount =
407
+ delivery.message.to.length +
408
+ (delivery.message.cc?.length ?? 0) +
409
+ (delivery.message.bcc?.length ?? 0);
406
410
  instrumentation.custom({
407
411
  name: "mail.sent",
408
412
  label: "Mail sent",
409
- summary: delivery.message.subject,
413
+ summary: "Mail sent",
410
414
  details: {
411
- to: delivery.message.to,
412
- subject: delivery.message.subject,
415
+ provider: "memory",
416
+ recipientCount,
413
417
  id: delivery.id,
414
418
  durationMs,
415
419
  },