@beignet/core 0.0.32 → 0.0.33

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 (115) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +344 -19
  3. package/dist/application/index.d.ts.map +1 -1
  4. package/dist/application/index.js +63 -47
  5. package/dist/application/index.js.map +1 -1
  6. package/dist/events/index.d.ts +6 -0
  7. package/dist/events/index.d.ts.map +1 -1
  8. package/dist/events/index.js +20 -10
  9. package/dist/events/index.js.map +1 -1
  10. package/dist/jobs/index.d.ts +415 -4
  11. package/dist/jobs/index.d.ts.map +1 -1
  12. package/dist/jobs/index.js +392 -3
  13. package/dist/jobs/index.js.map +1 -1
  14. package/dist/outbox/index.d.ts +150 -1
  15. package/dist/outbox/index.d.ts.map +1 -1
  16. package/dist/outbox/index.js +170 -1
  17. package/dist/outbox/index.js.map +1 -1
  18. package/dist/ports/index.d.ts +6 -1
  19. package/dist/ports/index.d.ts.map +1 -1
  20. package/dist/ports/index.js +1 -0
  21. package/dist/ports/index.js.map +1 -1
  22. package/dist/providers/instrumentation.d.ts +4 -0
  23. package/dist/providers/instrumentation.d.ts.map +1 -1
  24. package/dist/providers/instrumentation.js.map +1 -1
  25. package/dist/schedules/index.d.ts +8 -7
  26. package/dist/schedules/index.d.ts.map +1 -1
  27. package/dist/schedules/index.js +47 -16
  28. package/dist/schedules/index.js.map +1 -1
  29. package/dist/server/hooks/index.d.ts +1 -0
  30. package/dist/server/hooks/index.d.ts.map +1 -1
  31. package/dist/server/hooks/index.js +1 -0
  32. package/dist/server/hooks/index.js.map +1 -1
  33. package/dist/server/hooks/rate-limit.d.ts +26 -13
  34. package/dist/server/hooks/rate-limit.d.ts.map +1 -1
  35. package/dist/server/hooks/rate-limit.js +28 -34
  36. package/dist/server/hooks/rate-limit.js.map +1 -1
  37. package/dist/server/hooks/security.d.ts +179 -0
  38. package/dist/server/hooks/security.d.ts.map +1 -0
  39. package/dist/server/hooks/security.js +225 -0
  40. package/dist/server/hooks/security.js.map +1 -0
  41. package/dist/server/index.d.ts +8 -0
  42. package/dist/server/index.d.ts.map +1 -1
  43. package/dist/server/index.js +8 -0
  44. package/dist/server/index.js.map +1 -1
  45. package/dist/server/instrumentation.d.ts.map +1 -1
  46. package/dist/server/instrumentation.js +22 -6
  47. package/dist/server/instrumentation.js.map +1 -1
  48. package/dist/server/request-context.d.ts +4 -0
  49. package/dist/server/request-context.d.ts.map +1 -1
  50. package/dist/server/request-context.js +3 -0
  51. package/dist/server/request-context.js.map +1 -1
  52. package/dist/server/runtime-integrity.d.ts +84 -0
  53. package/dist/server/runtime-integrity.d.ts.map +1 -0
  54. package/dist/server/runtime-integrity.js +160 -0
  55. package/dist/server/runtime-integrity.js.map +1 -0
  56. package/dist/server/server.d.ts +10 -0
  57. package/dist/server/server.d.ts.map +1 -1
  58. package/dist/server/server.js +55 -1
  59. package/dist/server/server.js.map +1 -1
  60. package/dist/server/trusted-proxy.d.ts +77 -0
  61. package/dist/server/trusted-proxy.d.ts.map +1 -0
  62. package/dist/server/trusted-proxy.js +129 -0
  63. package/dist/server/trusted-proxy.js.map +1 -0
  64. package/dist/tasks/index.d.ts +6 -7
  65. package/dist/tasks/index.d.ts.map +1 -1
  66. package/dist/tasks/index.js +22 -5
  67. package/dist/tasks/index.js.map +1 -1
  68. package/dist/tenancy/index.d.ts +41 -0
  69. package/dist/tenancy/index.d.ts.map +1 -0
  70. package/dist/tenancy/index.js +34 -0
  71. package/dist/tenancy/index.js.map +1 -0
  72. package/dist/tracing/execution.d.ts +14 -0
  73. package/dist/tracing/execution.d.ts.map +1 -0
  74. package/dist/tracing/execution.js +17 -0
  75. package/dist/tracing/execution.js.map +1 -0
  76. package/dist/tracing/index.d.ts +49 -0
  77. package/dist/tracing/index.d.ts.map +1 -1
  78. package/dist/tracing/index.js +59 -0
  79. package/dist/tracing/index.js.map +1 -1
  80. package/dist/uploads/client.d.ts.map +1 -1
  81. package/dist/uploads/client.js +44 -6
  82. package/dist/uploads/client.js.map +1 -1
  83. package/dist/uploads/index.d.ts +71 -0
  84. package/dist/uploads/index.d.ts.map +1 -1
  85. package/dist/uploads/index.js +344 -15
  86. package/dist/uploads/index.js.map +1 -1
  87. package/dist/webhooks/index.d.ts +55 -4
  88. package/dist/webhooks/index.d.ts.map +1 -1
  89. package/dist/webhooks/index.js +106 -2
  90. package/dist/webhooks/index.js.map +1 -1
  91. package/package.json +5 -1
  92. package/skills/app-architecture/SKILL.md +23 -2
  93. package/src/application/index.ts +88 -56
  94. package/src/events/index.ts +27 -14
  95. package/src/jobs/index.ts +895 -7
  96. package/src/outbox/index.ts +358 -2
  97. package/src/ports/index.ts +17 -0
  98. package/src/providers/instrumentation.ts +4 -0
  99. package/src/schedules/index.ts +60 -26
  100. package/src/server/hooks/index.ts +10 -0
  101. package/src/server/hooks/rate-limit.ts +52 -46
  102. package/src/server/hooks/security.ts +503 -0
  103. package/src/server/index.ts +8 -0
  104. package/src/server/instrumentation.ts +25 -5
  105. package/src/server/request-context.ts +7 -0
  106. package/src/server/runtime-integrity.ts +322 -0
  107. package/src/server/server.ts +86 -1
  108. package/src/server/trusted-proxy.ts +249 -0
  109. package/src/tasks/index.ts +29 -12
  110. package/src/tenancy/index.ts +55 -0
  111. package/src/tracing/execution.ts +37 -0
  112. package/src/tracing/index.ts +137 -0
  113. package/src/uploads/client.ts +65 -6
  114. package/src/uploads/index.ts +539 -14
  115. package/src/webhooks/index.ts +238 -9
@@ -105,11 +105,72 @@ export interface DefineWebhookOptions<
105
105
  /**
106
106
  * Options accepted by `verifyWebhook(...)`.
107
107
  */
108
- export interface VerifyWebhookOptions {
108
+ export interface VerifyWebhookOptions<
109
+ AllowUnknownEvents extends boolean = boolean,
110
+ > {
109
111
  verifier?: WebhookVerifier;
110
- allowUnknownEvents?: boolean;
112
+ allowUnknownEvents?: AllowUnknownEvents;
111
113
  }
112
114
 
115
+ type InferWebhookVerificationResult<
116
+ TWebhook extends WebhookDef<string, WebhookEventSchemas>,
117
+ AllowUnknownEvents extends boolean,
118
+ > = true extends AllowUnknownEvents
119
+ ? InferWebhookEvent<TWebhook> | WebhookEvent
120
+ : InferWebhookEvent<TWebhook>;
121
+
122
+ /**
123
+ * Timestamp formats accepted by the generic HMAC webhook verifier.
124
+ */
125
+ export type HmacWebhookTimestampFormat =
126
+ | "unix-seconds"
127
+ | "unix-milliseconds"
128
+ | "iso8601";
129
+
130
+ /**
131
+ * Timestamp source and tolerance for generic HMAC replay protection.
132
+ */
133
+ export type HmacWebhookTimestampOptions =
134
+ | {
135
+ /**
136
+ * Header that carries the provider event timestamp.
137
+ */
138
+ header: string;
139
+ payloadPath?: never;
140
+ /**
141
+ * Timestamp format.
142
+ *
143
+ * @default "unix-seconds"
144
+ */
145
+ format?: HmacWebhookTimestampFormat;
146
+ /**
147
+ * Maximum absolute clock skew between the event timestamp and receipt.
148
+ *
149
+ * @default 300
150
+ */
151
+ toleranceSec?: number;
152
+ }
153
+ | {
154
+ header?: never;
155
+ /**
156
+ * Dot path used to read the provider event timestamp from the JSON
157
+ * payload.
158
+ */
159
+ payloadPath: string;
160
+ /**
161
+ * Timestamp format.
162
+ *
163
+ * @default "unix-seconds"
164
+ */
165
+ format?: HmacWebhookTimestampFormat;
166
+ /**
167
+ * Maximum absolute clock skew between the event timestamp and receipt.
168
+ *
169
+ * @default 300
170
+ */
171
+ toleranceSec?: number;
172
+ };
173
+
113
174
  /**
114
175
  * Options for the in-memory webhook verifier.
115
176
  */
@@ -163,6 +224,10 @@ export interface CreateHmacWebhookVerifierOptions {
163
224
  * @default "type"
164
225
  */
165
226
  eventTypePath?: string;
227
+ /**
228
+ * Optional timestamp source used to reject replayed generic HMAC webhooks.
229
+ */
230
+ timestamp?: HmacWebhookTimestampOptions;
166
231
  }
167
232
 
168
233
  /**
@@ -250,11 +315,12 @@ export function defineWebhook<
250
315
  */
251
316
  export async function verifyWebhook<
252
317
  TWebhook extends WebhookDef<string, WebhookEventSchemas>,
318
+ AllowUnknownEvents extends boolean = false,
253
319
  >(
254
320
  webhook: TWebhook,
255
321
  input: VerifyWebhookInput,
256
- options: VerifyWebhookOptions = {},
257
- ): Promise<InferWebhookEvent<TWebhook> | WebhookEvent> {
322
+ options: VerifyWebhookOptions<AllowUnknownEvents> = {},
323
+ ): Promise<InferWebhookVerificationResult<TWebhook, AllowUnknownEvents>> {
258
324
  validateWebhook(webhook);
259
325
  const verifier = options.verifier ?? webhook.verifier;
260
326
  if (!verifier) {
@@ -285,17 +351,26 @@ export async function verifyWebhook<
285
351
  */
286
352
  export async function parseWebhookEvent<
287
353
  TWebhook extends WebhookDef<string, WebhookEventSchemas>,
354
+ AllowUnknownEvents extends boolean = false,
288
355
  >(
289
356
  webhook: TWebhook,
290
357
  event: WebhookEvent,
291
- options: Pick<VerifyWebhookOptions, "allowUnknownEvents"> = {},
292
- ): Promise<InferWebhookEvent<TWebhook> | WebhookEvent> {
358
+ options: Pick<
359
+ VerifyWebhookOptions<AllowUnknownEvents>,
360
+ "allowUnknownEvents"
361
+ > = {},
362
+ ): Promise<InferWebhookVerificationResult<TWebhook, AllowUnknownEvents>> {
293
363
  validateWebhook(webhook);
294
364
  validateEvent(webhook, event);
295
365
 
296
366
  const schema = webhook.events[event.type];
297
367
  if (!schema) {
298
- if (options.allowUnknownEvents ?? true) return event;
368
+ if (options.allowUnknownEvents === true) {
369
+ return event as InferWebhookVerificationResult<
370
+ TWebhook,
371
+ AllowUnknownEvents
372
+ >;
373
+ }
299
374
  throw new WebhookVerificationError({
300
375
  message: `Webhook "${webhook.name}" received unknown event type "${event.type}".`,
301
376
  webhookName: webhook.name,
@@ -320,7 +395,7 @@ export async function parseWebhookEvent<
320
395
  return {
321
396
  ...event,
322
397
  payload: result.value,
323
- };
398
+ } as InferWebhookVerificationResult<TWebhook, AllowUnknownEvents>;
324
399
  }
325
400
 
326
401
  /**
@@ -373,6 +448,7 @@ export function createHmacWebhookVerifier(
373
448
  const algorithm = options.algorithm ?? "SHA-256";
374
449
  const eventIdPath = options.eventIdPath ?? "id";
375
450
  const eventTypePath = options.eventTypePath ?? "type";
451
+ const timestamp = normalizeHmacTimestampOptions(options.timestamp);
376
452
 
377
453
  return {
378
454
  async verify(input) {
@@ -396,6 +472,12 @@ export function createHmacWebhookVerifier(
396
472
  }
397
473
 
398
474
  const payload = parseJsonBody(input.rawBody);
475
+ const createdAt = validateHmacTimestamp({
476
+ input,
477
+ payload,
478
+ provider: options.provider,
479
+ timestamp,
480
+ });
399
481
  const id = readStringPath(payload, eventIdPath);
400
482
  const type = readStringPath(payload, eventTypePath);
401
483
 
@@ -418,6 +500,7 @@ export function createHmacWebhookVerifier(
418
500
  id,
419
501
  type,
420
502
  provider: options.provider,
503
+ ...(createdAt ? { createdAt } : {}),
421
504
  payload,
422
505
  raw: payload,
423
506
  };
@@ -502,7 +585,7 @@ function parseJsonBody(rawBody: WebhookRawBody): unknown {
502
585
  }
503
586
  }
504
587
 
505
- function readStringPath(input: unknown, path: string): string | undefined {
588
+ function readPath(input: unknown, path: string): unknown {
506
589
  let value = input;
507
590
  for (const segment of path.split(".")) {
508
591
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -511,9 +594,155 @@ function readStringPath(input: unknown, path: string): string | undefined {
511
594
  value = (value as Record<string, unknown>)[segment];
512
595
  }
513
596
 
597
+ return value;
598
+ }
599
+
600
+ function readStringPath(input: unknown, path: string): string | undefined {
601
+ const value = readPath(input, path);
514
602
  return typeof value === "string" && value.length > 0 ? value : undefined;
515
603
  }
516
604
 
605
+ type NormalizedHmacTimestampOptions =
606
+ | {
607
+ source: "header";
608
+ key: string;
609
+ format: HmacWebhookTimestampFormat;
610
+ toleranceSec: number;
611
+ }
612
+ | {
613
+ source: "payload";
614
+ path: string;
615
+ format: HmacWebhookTimestampFormat;
616
+ toleranceSec: number;
617
+ };
618
+
619
+ function normalizeHmacTimestampOptions(
620
+ timestamp: HmacWebhookTimestampOptions | undefined,
621
+ ): NormalizedHmacTimestampOptions | undefined {
622
+ if (!timestamp) return undefined;
623
+
624
+ const format = timestamp.format ?? "unix-seconds";
625
+ if (
626
+ format !== "unix-seconds" &&
627
+ format !== "unix-milliseconds" &&
628
+ format !== "iso8601"
629
+ ) {
630
+ throw new WebhookOptionsError(
631
+ "Webhook HMAC timestamp format must be unix-seconds, unix-milliseconds, or iso8601.",
632
+ );
633
+ }
634
+ const toleranceSec = timestamp.toleranceSec ?? 300;
635
+ if (!Number.isInteger(toleranceSec) || toleranceSec <= 0) {
636
+ throw new WebhookOptionsError(
637
+ "Webhook HMAC timestamp toleranceSec must be a positive integer.",
638
+ );
639
+ }
640
+
641
+ if ("header" in timestamp) {
642
+ if (!timestamp.header) {
643
+ throw new WebhookOptionsError(
644
+ "Webhook HMAC timestamp header is required.",
645
+ );
646
+ }
647
+
648
+ return {
649
+ source: "header",
650
+ key: normalizeHeaderName(timestamp.header),
651
+ format,
652
+ toleranceSec,
653
+ };
654
+ }
655
+
656
+ if (!timestamp.payloadPath) {
657
+ throw new WebhookOptionsError(
658
+ "Webhook HMAC timestamp payloadPath is required.",
659
+ );
660
+ }
661
+
662
+ return {
663
+ source: "payload",
664
+ path: timestamp.payloadPath,
665
+ format,
666
+ toleranceSec,
667
+ };
668
+ }
669
+
670
+ function validateHmacTimestamp(args: {
671
+ input: VerifyWebhookInput;
672
+ payload: unknown;
673
+ provider: string | undefined;
674
+ timestamp: NormalizedHmacTimestampOptions | undefined;
675
+ }): Date | undefined {
676
+ const { timestamp } = args;
677
+ if (!timestamp) return undefined;
678
+
679
+ const value =
680
+ timestamp.source === "header"
681
+ ? args.input.headers?.[timestamp.key]
682
+ : readPath(args.payload, timestamp.path);
683
+
684
+ if (value === undefined || value === null || value === "") {
685
+ throw new WebhookVerificationError({
686
+ message:
687
+ timestamp.source === "header"
688
+ ? `Missing ${timestamp.key} header.`
689
+ : `Webhook payload is missing timestamp at "${timestamp.path}".`,
690
+ provider: args.provider,
691
+ code: "missing_timestamp",
692
+ });
693
+ }
694
+
695
+ const createdAt = parseHmacTimestampValue(
696
+ value,
697
+ timestamp.format,
698
+ args.provider,
699
+ );
700
+ const receivedAt = args.input.receivedAt ?? new Date();
701
+ const skewMs = Math.abs(receivedAt.getTime() - createdAt.getTime());
702
+ if (skewMs > timestamp.toleranceSec * 1000) {
703
+ throw new WebhookVerificationError({
704
+ message: `Webhook timestamp is outside the ${timestamp.toleranceSec}s tolerance.`,
705
+ provider: args.provider,
706
+ code: "timestamp_outside_tolerance",
707
+ });
708
+ }
709
+
710
+ return createdAt;
711
+ }
712
+
713
+ function parseHmacTimestampValue(
714
+ value: unknown,
715
+ format: HmacWebhookTimestampFormat,
716
+ provider: string | undefined,
717
+ ): Date {
718
+ const fail = () =>
719
+ new WebhookVerificationError({
720
+ message: "Webhook timestamp is invalid.",
721
+ provider,
722
+ code: "invalid_timestamp",
723
+ });
724
+
725
+ let millis: number;
726
+ if (format === "iso8601") {
727
+ if (typeof value !== "string") throw fail();
728
+ millis = Date.parse(value);
729
+ } else {
730
+ const numeric =
731
+ typeof value === "number"
732
+ ? value
733
+ : typeof value === "string"
734
+ ? Number(value)
735
+ : Number.NaN;
736
+ if (!Number.isFinite(numeric)) throw fail();
737
+ millis = format === "unix-seconds" ? numeric * 1000 : numeric;
738
+ }
739
+
740
+ if (!Number.isFinite(millis)) throw fail();
741
+ const date = new Date(millis);
742
+ if (Number.isNaN(date.getTime())) throw fail();
743
+ return date;
744
+ }
745
+
517
746
  async function hmacHex(
518
747
  algorithm: CreateHmacWebhookVerifierOptions["algorithm"],
519
748
  secret: string,