@nymbalhq/types 1.3.0 → 1.5.0

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/index.d.ts CHANGED
@@ -232,6 +232,13 @@ interface Order {
232
232
  createdAt: string;
233
233
  updatedAt: string;
234
234
  }
235
+ /**
236
+ * The request header a shopper's order key travels in to `GET /api/orders/:orderNumber`. Never a query parameter, so
237
+ * the key stays out of access logs that record request URLs.
238
+ */
239
+ declare const ORDER_KEY_HEADER = "x-nymbal-order-key";
240
+ /** The request header that carries the order's email beside an expired order key. Never a query parameter either. */
241
+ declare const ORDER_EMAIL_HEADER = "x-nymbal-order-email";
235
242
  /** Who took the money: the payments provider, the merchant recording an offline payment, or an imported history. */
236
243
  type CaptureMethod = 'provider' | 'manual' | 'import';
237
244
  type OrderRefundStatus = 'pending' | 'succeeded' | 'failed';
@@ -270,6 +277,28 @@ interface OrderHistoryEntry {
270
277
  timestamp: string;
271
278
  }
272
279
 
280
+ /** The lifecycle events an order's timeline shows, each one from an `order_history` row. */
281
+ declare const ORDER_TIMELINE_KINDS: readonly ["placed", "paid", "shipped", "delivered", "cancelled", "partially_refunded", "refunded"];
282
+ type OrderTimelineKind = (typeof ORDER_TIMELINE_KINDS)[number];
283
+ interface OrderTimelineEntry {
284
+ kind: OrderTimelineKind;
285
+ /** ISO 8601, UTC. */
286
+ at: string;
287
+ }
288
+ /** A shipping address as an order's key holder reads it: no phone, no company. */
289
+ type ShopperAddress = Omit<Address, 'phone' | 'company'>;
290
+ /** What only the order's signed-in owner reads, or the store's own records, beside what its key holder reads. */
291
+ type OwnerOrStoreField = 'id' | 'email' | 'billingAddress' | 'customerId' | 'sequence' | 'capturedMinor' | 'capturedAt' | 'refundedMinor' | 'captureMethod' | 'notes' | 'metadata' | 'paymentIntentId' | 'updatedAt';
292
+ /**
293
+ * An order as a storefront reads it. A key holder reads the confirmation page's fields; its owner's session adds the
294
+ * email, both full addresses and the order's id. Every other field is optional, so an adapter that returns a plain
295
+ * `Order` still fits.
296
+ */
297
+ interface ShopperOrder extends Omit<Order, OwnerOrStoreField | 'shippingAddress'>, Partial<Pick<Order, OwnerOrStoreField>> {
298
+ shippingAddress: ShopperAddress;
299
+ timeline?: OrderTimelineEntry[];
300
+ }
301
+
273
302
  interface CustomerAddress extends Address {
274
303
  id: string;
275
304
  isDefaultBilling: boolean;
@@ -398,6 +427,11 @@ declare const EVT_PRODUCT_UPDATED = "product.updated.v1";
398
427
  declare const EVT_PRODUCT_DELETED = "product.deleted.v1";
399
428
  declare const EVT_PRODUCT_PUBLISHED = "product.published.v1";
400
429
  declare const EVT_PRODUCT_UNPUBLISHED = "product.unpublished.v1";
430
+ declare const EVT_PRODUCT_VIEWED = "product.viewed.v1";
431
+ /** A variant's stock went from 0 to above 0, in the transaction of the inventory.changed.v1 it accompanies. */
432
+ declare const EVT_PRODUCT_RESTOCKED = "product.restocked.v1";
433
+ /** A variant's price or compare-at price changed, in the transaction of the product.updated.v1 it accompanies. */
434
+ declare const EVT_PRODUCT_VARIANT_PRICE_CHANGED = "product.variant_price_changed.v1";
401
435
  declare const EVT_CATEGORY_CREATED = "category.created.v1";
402
436
  declare const EVT_CATEGORY_UPDATED = "category.updated.v1";
403
437
  declare const EVT_CATEGORY_DELETED = "category.deleted.v1";
@@ -410,6 +444,7 @@ declare const EVT_ORDER_CANCELLED = "order.cancelled.v1";
410
444
  declare const EVT_ORDER_REFUNDED = "order.refunded.v1";
411
445
  declare const EVT_ORDER_PARTIALLY_REFUNDED = "order.partially_refunded.v1";
412
446
  declare const EVT_ORDER_STATUS_CHANGED = "order.status_changed.v1";
447
+ declare const EVT_ORDER_CONFIRMATION_RESEND_REQUESTED = "order.confirmation_resend_requested.v1";
413
448
  declare const EVT_INVENTORY_CHANGED = "inventory.changed.v1";
414
449
  declare const EVT_INVENTORY_RESERVED = "inventory.reserved.v1";
415
450
  declare const EVT_INVENTORY_RELEASED = "inventory.released.v1";
@@ -419,6 +454,7 @@ declare const EVT_CUSTOMER_CREATED = "customer.created.v1";
419
454
  declare const EVT_CUSTOMER_UPDATED = "customer.updated.v1";
420
455
  declare const EVT_CUSTOMER_DELETED = "customer.deleted.v1";
421
456
  declare const EVT_CUSTOMER_REGISTERED = "customer.created.v1";
457
+ declare const EVT_VISITOR_MERGED = "visitor.merged.v1";
422
458
  declare const EVT_CART_CREATED = "cart.created.v1";
423
459
  declare const EVT_CART_ITEM_ADDED = "cart.item_added.v1";
424
460
  declare const EVT_CART_ITEM_REMOVED = "cart.item_removed.v1";
@@ -429,9 +465,23 @@ declare const EVT_PAYMENT_INTENT_CREATED = "payment.intent_created.v1";
429
465
  declare const EVT_PAYMENT_CAPTURED = "payment.captured.v1";
430
466
  declare const EVT_PAYMENT_FAILED = "payment.failed.v1";
431
467
  declare const EVT_PAYMENT_REFUNDED = "payment.refunded.v1";
468
+ declare const EVT_ALERT_REQUESTED = "alert.requested.v1";
469
+ /** A subscribe for an alert already active: the subscription stands and nothing is sent. */
470
+ declare const EVT_ALERT_REPEATED = "alert.repeated.v1";
471
+ declare const EVT_ALERT_CONFIRMED = "alert.confirmed.v1";
472
+ declare const EVT_ALERT_UNSUBSCRIBED = "alert.unsubscribed.v1";
473
+ declare const EVT_ALERT_SENT = "alert.sent.v1";
432
474
  declare const EVT_ADAPTER_ERROR = "adapter.error.v1";
433
475
  declare const EVT_SETTINGS_CHANGED = "settings.changed.v1";
434
- declare const STANDARD_EVENT_TYPES: readonly ["product.created.v1", "product.updated.v1", "product.deleted.v1", "product.published.v1", "product.unpublished.v1", "category.created.v1", "category.updated.v1", "category.deleted.v1", "order.placed.v1", "order.paid.v1", "order.fulfilled.v1", "order.shipped.v1", "order.delivered.v1", "order.cancelled.v1", "order.refunded.v1", "order.partially_refunded.v1", "order.status_changed.v1", "inventory.changed.v1", "inventory.reserved.v1", "inventory.released.v1", "inventory.low_stock.v1", "inventory.out_of_stock.v1", "customer.created.v1", "customer.updated.v1", "customer.deleted.v1", "cart.created.v1", "cart.item_added.v1", "cart.item_removed.v1", "cart.updated.v1", "cart.abandoned.v1", "cart.converted.v1", "payment.intent_created.v1", "payment.captured.v1", "payment.failed.v1", "payment.refunded.v1", "adapter.error.v1", "settings.changed.v1"];
476
+ declare const STANDARD_EVENT_TYPES: readonly ["product.created.v1", "product.updated.v1", "product.deleted.v1", "product.published.v1", "product.unpublished.v1", "product.viewed.v1", "product.restocked.v1", "product.variant_price_changed.v1", "category.created.v1", "category.updated.v1", "category.deleted.v1", "order.placed.v1", "order.paid.v1", "order.fulfilled.v1", "order.shipped.v1", "order.delivered.v1", "order.cancelled.v1", "order.refunded.v1", "order.partially_refunded.v1", "order.status_changed.v1", "order.confirmation_resend_requested.v1", "inventory.changed.v1", "inventory.reserved.v1", "inventory.released.v1", "inventory.low_stock.v1", "inventory.out_of_stock.v1", "customer.created.v1", "customer.updated.v1", "customer.deleted.v1", "visitor.merged.v1", "cart.created.v1", "cart.item_added.v1", "cart.item_removed.v1", "cart.updated.v1", "cart.abandoned.v1", "cart.converted.v1", "payment.intent_created.v1", "payment.captured.v1", "payment.failed.v1", "payment.refunded.v1", "alert.requested.v1", "alert.repeated.v1", "alert.confirmed.v1", "alert.unsubscribed.v1", "alert.sent.v1", "adapter.error.v1", "settings.changed.v1"];
477
+
478
+ /** What a shopper asks to be told about a variant: that it is back in stock, or that its price dropped. */
479
+ type AlertKind = 'back_in_stock' | 'price_drop';
480
+ /**
481
+ * `pending` until the confirmation's link is followed, then `active`; a back-in-stock row is `fired` once its alert is
482
+ * sent, a price-drop row stays `active`; `unsubscribed` from any state by the link every email carries.
483
+ */
484
+ type AlertStatus = 'pending' | 'active' | 'fired' | 'unsubscribed';
435
485
 
436
486
  interface ProductSnapshot extends Product {
437
487
  variants: Variant[];
@@ -455,6 +505,31 @@ interface ProductUnpublishedV1Payload {
455
505
  productId: string;
456
506
  slug: string;
457
507
  }
508
+ interface ProductRestockedV1Payload {
509
+ productId: string;
510
+ variantId: string;
511
+ previousQty: number;
512
+ newQty: number;
513
+ }
514
+ /** Prices in `currency`'s minor units; each compare-at price is the stored one, null when the variant has none. */
515
+ interface ProductVariantPriceChangedV1Payload {
516
+ productId: string;
517
+ variantId: string;
518
+ previousPriceMinor: number;
519
+ priceMinor: number;
520
+ previousCompareAtPriceMinor: number | null;
521
+ compareAtPriceMinor: number | null;
522
+ currency: string;
523
+ }
524
+ /** Whose view it is: an anonymous visitor (the `nymbal.visitor` cookie) or a signed-in customer. */
525
+ interface RecentViewer {
526
+ kind: 'visitor' | 'customer';
527
+ id: string;
528
+ }
529
+ interface ProductViewedV1Payload {
530
+ productId: string;
531
+ viewer: RecentViewer;
532
+ }
458
533
  interface CategoryCreatedV1Payload {
459
534
  category: Category;
460
535
  }
@@ -509,6 +584,10 @@ interface OrderStatusChangedV1Payload {
509
584
  from: OrderStatus;
510
585
  to: OrderStatus;
511
586
  }
587
+ /** An admin asked for the order's confirmation email again; the email's key is minted when it is sent. */
588
+ interface OrderConfirmationResendRequestedV1Payload {
589
+ orderId: string;
590
+ }
512
591
  interface InventoryChangedV1Payload {
513
592
  variantId: string;
514
593
  previousQty: number;
@@ -557,6 +636,11 @@ interface CustomerUpdatedV1Payload {
557
636
  interface CustomerDeletedV1Payload {
558
637
  customerId: string;
559
638
  }
639
+ /** A visitor's views now belong to the customer who signed in carrying its cookie. */
640
+ interface VisitorMergedV1Payload {
641
+ visitorId: string;
642
+ customerId: string;
643
+ }
560
644
  interface CartCreatedV1Payload {
561
645
  cartId: string;
562
646
  token: string;
@@ -606,6 +690,28 @@ interface PaymentRefundedV1Payload {
606
690
  amountMinor: number;
607
691
  currency: string;
608
692
  }
693
+ interface AlertRequestedV1Payload {
694
+ subscriptionId: string;
695
+ kind: AlertKind;
696
+ variantId: string;
697
+ productId: string;
698
+ }
699
+ type AlertRepeatedV1Payload = AlertRequestedV1Payload;
700
+ interface AlertConfirmedV1Payload {
701
+ subscriptionId: string;
702
+ }
703
+ interface AlertUnsubscribedV1Payload {
704
+ subscriptionId: string;
705
+ }
706
+ interface AlertSentV1Payload {
707
+ subscriptionId: string;
708
+ kind: AlertKind;
709
+ /**
710
+ * The event the alert answered: a product.restocked.v1 for a back-in-stock alert, a
711
+ * product.variant_price_changed.v1 for a price-drop one.
712
+ */
713
+ triggerEventId: string;
714
+ }
609
715
  interface AdapterErrorV1Payload {
610
716
  adapterType: string;
611
717
  adapterName: string;
@@ -628,6 +734,9 @@ interface NymbalEventMap {
628
734
  [EVT_PRODUCT_DELETED]: ProductDeletedV1Payload;
629
735
  [EVT_PRODUCT_PUBLISHED]: ProductPublishedV1Payload;
630
736
  [EVT_PRODUCT_UNPUBLISHED]: ProductUnpublishedV1Payload;
737
+ [EVT_PRODUCT_VIEWED]: ProductViewedV1Payload;
738
+ [EVT_PRODUCT_RESTOCKED]: ProductRestockedV1Payload;
739
+ [EVT_PRODUCT_VARIANT_PRICE_CHANGED]: ProductVariantPriceChangedV1Payload;
631
740
  [EVT_CATEGORY_CREATED]: CategoryCreatedV1Payload;
632
741
  [EVT_CATEGORY_UPDATED]: CategoryUpdatedV1Payload;
633
742
  [EVT_CATEGORY_DELETED]: CategoryDeletedV1Payload;
@@ -640,6 +749,7 @@ interface NymbalEventMap {
640
749
  [EVT_ORDER_REFUNDED]: OrderRefundedV1Payload;
641
750
  [EVT_ORDER_PARTIALLY_REFUNDED]: OrderPartiallyRefundedV1Payload;
642
751
  [EVT_ORDER_STATUS_CHANGED]: OrderStatusChangedV1Payload;
752
+ [EVT_ORDER_CONFIRMATION_RESEND_REQUESTED]: OrderConfirmationResendRequestedV1Payload;
643
753
  [EVT_INVENTORY_CHANGED]: InventoryChangedV1Payload;
644
754
  [EVT_INVENTORY_RESERVED]: InventoryReservedV1Payload;
645
755
  [EVT_INVENTORY_RELEASED]: InventoryReleasedV1Payload;
@@ -648,6 +758,7 @@ interface NymbalEventMap {
648
758
  [EVT_CUSTOMER_CREATED]: CustomerCreatedV1Payload;
649
759
  [EVT_CUSTOMER_UPDATED]: CustomerUpdatedV1Payload;
650
760
  [EVT_CUSTOMER_DELETED]: CustomerDeletedV1Payload;
761
+ [EVT_VISITOR_MERGED]: VisitorMergedV1Payload;
651
762
  [EVT_CART_CREATED]: CartCreatedV1Payload;
652
763
  [EVT_CART_ITEM_ADDED]: CartItemAddedV1Payload;
653
764
  [EVT_CART_ITEM_REMOVED]: CartItemRemovedV1Payload;
@@ -658,6 +769,11 @@ interface NymbalEventMap {
658
769
  [EVT_PAYMENT_CAPTURED]: PaymentCapturedV1Payload;
659
770
  [EVT_PAYMENT_FAILED]: PaymentFailedV1Payload;
660
771
  [EVT_PAYMENT_REFUNDED]: PaymentRefundedV1Payload;
772
+ [EVT_ALERT_REQUESTED]: AlertRequestedV1Payload;
773
+ [EVT_ALERT_REPEATED]: AlertRepeatedV1Payload;
774
+ [EVT_ALERT_CONFIRMED]: AlertConfirmedV1Payload;
775
+ [EVT_ALERT_UNSUBSCRIBED]: AlertUnsubscribedV1Payload;
776
+ [EVT_ALERT_SENT]: AlertSentV1Payload;
661
777
  [EVT_ADAPTER_ERROR]: AdapterErrorV1Payload;
662
778
  [EVT_SETTINGS_CHANGED]: SettingsChangedV1Payload;
663
779
  }
@@ -818,8 +934,9 @@ type CommerceSlot = 'payments' | 'email' | 'reviews' | 'search' | 'analytics' |
818
934
  type Slot = CommerceSlot | 'documentStore' | 'eventBus' | 'media' | 'security';
819
935
  /**
820
936
  * What the kernel hands an adapter package's `createAdapter`. A package that serves more than one slot switches on
821
- * `slot`; `config` is a commerce slot's `config` block from nymbal.config.ts, a primitive's connection block, or for
822
- * `security` the block named after the adapter (`security.cloudflare`).
937
+ * `slot`; `config` is a commerce slot's `config` block from nymbal.config.ts, a primitive's connection block (or a
938
+ * third-party primitive's `config`), or for `security` the block named after the adapter (`security.cloudflare`) or a
939
+ * third-party adapter's `security.config`.
823
940
  */
824
941
  interface AdapterContext {
825
942
  slot: Slot;
@@ -845,11 +962,25 @@ interface AdapterSetting {
845
962
  */
846
963
  optional?: boolean;
847
964
  }
848
- /** What a provider package exports: `createAdapter`, and the fields it lets Settings fill. */
965
+ /**
966
+ * What a provider package exports: `createAdapter`, the slot and provider it serves (checked when it is loaded), and
967
+ * the fields it lets Settings fill.
968
+ */
849
969
  interface ProviderPackageModule {
850
970
  createAdapter(ctx: AdapterContext): unknown;
971
+ slot: Slot;
972
+ provider: string;
851
973
  settings?: readonly AdapterSetting[];
852
974
  }
975
+ /**
976
+ * How `nymbal.config.ts` names an infrastructure slot's adapter (`documentStore`, `eventBus`, `media`): the provider,
977
+ * the npm package it comes from when not `@nymbalhq/<slot>-<provider>`, and a third-party adapter's `ctx.config`.
978
+ */
979
+ interface InfrastructureBlock {
980
+ provider: string;
981
+ package?: string;
982
+ config?: Record<string, unknown>;
983
+ }
853
984
 
854
985
  interface PutOptions {
855
986
  ttl?: number;
@@ -946,19 +1077,33 @@ interface Subscription {
946
1077
  patterns: string[];
947
1078
  unsubscribe(): Promise<void>;
948
1079
  }
1080
+ /** A named subscription whose handler threw on an event. */
1081
+ interface HandlerFailure {
1082
+ subscription: string;
1083
+ message: string;
1084
+ }
1085
+ /** What `publish` rejects with when a named subscription's handler threw: code `adapter.handler_failed`. */
1086
+ declare function handlerFailedError(event: NymbalEvent<unknown>, failed: HandlerFailure[]): AdapterError;
949
1087
  /**
950
- * Delivery is at least once: a publish that is retried may reach a handler twice, so every handler is idempotent
951
- * (projections overwrite their documents, side effects use `withIdempotency`).
1088
+ * Delivery is at least once: a failed delivery is published again, to every matching subscription, so every handler
1089
+ * is idempotent (projections overwrite their documents, side effects use `withIdempotency`). The bus never retries a
1090
+ * failed handler by itself.
952
1091
  */
953
1092
  interface EventBusAdapter {
954
1093
  capabilities(): readonly string[];
955
1094
  /**
956
1095
  * Resolves only after every matching subscription this instance holds has handled the event, so a read straight
957
- * after a command sees its projections. A handler that throws is logged, and does not reject the publish.
1096
+ * after a command sees its projections. When a named subscription's handler throws, it rejects with
1097
+ * `handlerFailedError` naming each one that threw, once every matching handler has settled. An unnamed subscription's
1098
+ * throw is logged and does not reject.
958
1099
  */
959
1100
  publish<T>(event: NymbalEvent<T>): Promise<void>;
1101
+ /** Hands every event out in order, then rejects as `publish` does for the first event a named handler failed. */
960
1102
  publishBatch(events: NymbalEvent<unknown>[]): Promise<void>;
961
- /** Delivers to `derived` subscriptions only, so rebuilding derived state never triggers side effects. */
1103
+ /**
1104
+ * Delivers to `derived` subscriptions only, so rebuilding derived state never triggers side effects. A handler's
1105
+ * throw is logged and does not reject.
1106
+ */
962
1107
  replay(events: NymbalEvent<unknown>[]): Promise<void>;
963
1108
  subscribe(eventPattern: string | string[], handler: EventHandler, config?: SubscriptionConfig): Promise<Subscription>;
964
1109
  unsubscribe(subscription: Subscription): Promise<void>;
@@ -1034,8 +1179,30 @@ interface SecurityAdapter extends AdapterBase {
1034
1179
  /** The response headers every security adapter sets in process, from the config's `headers` block. */
1035
1180
  declare function buildSecurityHeaders(security: SecurityConfigLike): Record<string, string>;
1036
1181
 
1182
+ /** A webhook request's headers, names lower-cased. */
1183
+ type WebhookHeaders = Record<string, string | string[] | undefined>;
1184
+ /** One event a webhook delivery carries; the kernel builds its envelope (id, source, correlation). */
1185
+ interface WebhookEventDraft {
1186
+ type: string;
1187
+ payload: Record<string, unknown>;
1188
+ }
1189
+ interface WebhookDelivery {
1190
+ verified: boolean;
1191
+ /** The provider's id for this delivery: what makes a retry of it a replay. */
1192
+ deliveryId: string;
1193
+ events: WebhookEventDraft[];
1194
+ }
1037
1195
  interface CommerceAdapter extends AdapterBase {
1038
1196
  readonly providerName: string;
1197
+ /**
1198
+ * Takes a delivery to `POST /api/webhooks/<providerName>`: verifies its signature over `rawBody` (from whichever
1199
+ * header the provider signs in) and returns its events. The same bytes must give the same `deliveryId` and the same
1200
+ * events, so a provider's retry is processed once. Each event's type is `<providerName>.<event>.v<n>` (lower-case
1201
+ * words, `_` allowed), is listed in `producesEvents`, and its payload is a plain JSON object; a delivery breaking
1202
+ * this is refused whole. A kernel namespace (`order`, `payment`, ...) is never an adapter's. Not called on a
1203
+ * payments adapter, whose deliveries go through `parseWebhook`.
1204
+ */
1205
+ receiveWebhook?(rawBody: Buffer, headers: WebhookHeaders): Promise<WebhookDelivery>;
1039
1206
  }
1040
1207
  interface RefundOptions {
1041
1208
  /** The same key on a retry makes the provider refund at most once. */
@@ -1238,6 +1405,70 @@ interface CommerceSlotAdapters {
1238
1405
  ai: AiAdapter;
1239
1406
  }
1240
1407
 
1408
+ /** Why a delivery's events may not be published; `type` is the offending event's, when one is to blame. */
1409
+ interface WebhookDeliveryProblem {
1410
+ type?: unknown;
1411
+ reason: string;
1412
+ }
1413
+ /**
1414
+ * The first thing that makes a verified delivery from the adapter named `providerName` unpublishable, or null when
1415
+ * every event may be published: the rule of `receiveWebhook`, which the kernel refuses a delivery for and the
1416
+ * webhook contract suite certifies.
1417
+ */
1418
+ declare function webhookDeliveryProblem(providerName: string, producesEvents: readonly string[], delivery: WebhookDelivery): WebhookDeliveryProblem | null;
1419
+
1420
+ /**
1421
+ * `<providerName>.bounced.v1`: a message to `recipient` bounced. `hard` is the provider's permanent failure, `soft`
1422
+ * its transient one; a classification the adapter cannot map is `soft`, so a doubtful bounce never suppresses.
1423
+ */
1424
+ interface EmailBouncedV1Payload {
1425
+ recipient: string;
1426
+ bounce: 'hard' | 'soft';
1427
+ /** The `EmailSendResult.messageId` the bounce answers, when the provider gives one. */
1428
+ messageId: string | null;
1429
+ /** ISO 8601. */
1430
+ occurredAt: string;
1431
+ }
1432
+ /** `<providerName>.complained.v1`: `recipient` marked a message as spam. */
1433
+ interface EmailComplainedV1Payload {
1434
+ recipient: string;
1435
+ messageId: string | null;
1436
+ /** ISO 8601. */
1437
+ occurredAt: string;
1438
+ }
1439
+ type EmailFeedbackKind = 'bounced' | 'complained';
1440
+ /** The event type an email adapter publishes a bounce or a complaint under. */
1441
+ declare function emailFeedbackType(providerName: string, what: EmailFeedbackKind): string;
1442
+ /**
1443
+ * The first bounce or complaint in a verified delivery from the email adapter `providerName` that the kernel cannot
1444
+ * act on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's
1445
+ * retry after the adapter is fixed is still received; the email feedback contract suite certifies the same rule.
1446
+ */
1447
+ declare function emailFeedbackProblem(providerName: string, delivery: WebhookDelivery): WebhookDeliveryProblem | null;
1448
+
1449
+ /** The statuses a shipping adapter maps its carrier's onto; one it cannot map is `unknown`. */
1450
+ declare const SHIPPING_TRACKING_STATUSES: readonly ["pre_transit", "in_transit", "out_for_delivery", "delivered", "exception", "returned", "unknown"];
1451
+ type ShippingTrackingStatus = (typeof SHIPPING_TRACKING_STATUSES)[number];
1452
+ /** `<providerName>.tracking.v1`: the carrier reported `status` for the parcel `trackingNumber`. */
1453
+ interface ShippingTrackingV1Payload {
1454
+ trackingNumber: string;
1455
+ /** The carrier's code as the adapter's provider gives it, which need not match the merchant's own. */
1456
+ carrier: string | null;
1457
+ status: ShippingTrackingStatus;
1458
+ /** ISO 8601. */
1459
+ occurredAt: string;
1460
+ }
1461
+ /** The event type a shipping adapter publishes a tracking update under. */
1462
+ declare function shippingTrackingType(providerName: string): string;
1463
+ /** A tracking number as stored and looked up: no whitespace, upper case, so a carrier's spelling finds the merchant's. */
1464
+ declare function normaliseTrackingNumber(value: string): string;
1465
+ /**
1466
+ * The first tracking update in a verified delivery from the shipping adapter `providerName` that the kernel cannot act
1467
+ * on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's retry
1468
+ * after the adapter is fixed is still received; the shipping tracking contract suite certifies the same rule.
1469
+ */
1470
+ declare function shippingTrackingProblem(providerName: string, delivery: WebhookDelivery): WebhookDeliveryProblem | null;
1471
+
1241
1472
  interface RenderedEmail {
1242
1473
  subject: string;
1243
1474
  text: string;
@@ -1262,6 +1493,10 @@ interface StoreCapabilities {
1262
1493
  sortKeys: ProductSortKey[];
1263
1494
  defaultSort: ProductSortKey;
1264
1495
  };
1496
+ /** Whether the store's email reaches a mailbox: false when its adapter only logs (`native`). */
1497
+ email: {
1498
+ delivers: boolean;
1499
+ };
1265
1500
  }
1266
1501
 
1267
1502
  /**
@@ -1282,7 +1517,7 @@ interface Recipe {
1282
1517
  summary: string;
1283
1518
  /** npm packages to install, SDKs included; empty when the service is built in. */
1284
1519
  install: string[];
1285
- /** Config paths and the values they take, e.g. `infrastructure.documentStore` = `'redis'`. */
1520
+ /** Config paths and the values they take, e.g. `infrastructure.documentStore.provider` = `'redis'`. */
1286
1521
  set: Array<{
1287
1522
  path: string;
1288
1523
  value: RecipeValue;
@@ -1402,5 +1637,5 @@ interface AsOfRead<T = Record<string, unknown>> {
1402
1637
  record: T;
1403
1638
  }
1404
1639
 
1405
- export { AdapterError, AuthError, CORRELATION_ID_HEADER, CheckoutError, ConfigError, DEFAULT_PRODUCT_SORT, DEFAULT_QUERY_LIMIT, EVT_ADAPTER_ERROR, EVT_CART_ABANDONED, EVT_CART_CONVERTED, EVT_CART_CREATED, EVT_CART_ITEM_ADDED, EVT_CART_ITEM_REMOVED, EVT_CART_UPDATED, EVT_CATEGORY_CREATED, EVT_CATEGORY_DELETED, EVT_CATEGORY_UPDATED, EVT_CUSTOMER_CREATED, EVT_CUSTOMER_DELETED, EVT_CUSTOMER_REGISTERED, EVT_CUSTOMER_UPDATED, EVT_INVENTORY_CHANGED, EVT_INVENTORY_LOW_STOCK, EVT_INVENTORY_OUT_OF_STOCK, EVT_INVENTORY_RELEASED, EVT_INVENTORY_RESERVED, EVT_ORDER_CANCELLED, EVT_ORDER_DELIVERED, EVT_ORDER_FULFILLED, EVT_ORDER_PAID, EVT_ORDER_PARTIALLY_REFUNDED, EVT_ORDER_PLACED, EVT_ORDER_REFUNDED, EVT_ORDER_SHIPPED, EVT_ORDER_STATUS_CHANGED, EVT_PAYMENT_CAPTURED, EVT_PAYMENT_FAILED, EVT_PAYMENT_INTENT_CREATED, EVT_PAYMENT_REFUNDED, EVT_PRODUCT_CREATED, EVT_PRODUCT_DELETED, EVT_PRODUCT_PUBLISHED, EVT_PRODUCT_UNPUBLISHED, EVT_PRODUCT_UPDATED, EVT_SETTINGS_CHANGED, HEALTH_CHECK_TIMEOUT_MS, HttpError, IDEMPOTENCY_KEY_HEADER, IDEMPOTENT_REPLAYED_HEADER, NotFoundError, NymbalError, ORDER_STATUS_TRANSITIONS, OrderConflictError, PRODUCT_SORT_KEYS, PRODUCT_SORT_LABELS, PaymentError, SECRET_MIN_LENGTH, STANDARD_EVENT_TYPES, STOCK_LABELS, StatusTransitionError, ValidationError, add, allocate, assertMinorUnits, assertOrderTransition, boundedHealthCheck, buildSecurityHeaders, canTransitionOrder, compareDocumentValues, connectionSecrets, displayUrl, documentValueAt, errorText, isValidCorrelationId, isValidIdempotencyKey, matchesSortKey, money, mulBy, paymentState, queryDocuments, refundableMinor, renderEmailTemplate, stockLevel, sub, sumMoney };
1406
- export type { AdapterBase, AdapterContext, AdapterErrorV1Payload, AdapterSetting, Address, AggregateRating, AiAdapter, AnalyticsAdapter, AsOfRead, AuditActor, AuditEntry, AuditOutcome, AuditSubject, BoundedHealthCheckOptions, CaptureMethod, Cart, CartAbandonedV1Payload, CartConvertedV1Payload, CartCreatedV1Payload, CartItem, CartItemAddedV1Payload, CartItemRemovedV1Payload, CartUpdatedV1Payload, Category, CategoryCreatedV1Payload, CategoryDeletedV1Payload, CategoryUpdatedV1Payload, CheckoutPayment, CommerceAdapter, CommerceSlot, CommerceSlotAdapters, Customer, CustomerAddress, CustomerCreatedV1Payload, CustomerDeletedV1Payload, CustomerProfile, CustomerSnapshot, CustomerUpdatedV1Payload, DocumentStoreAdapter, DriftReport, EmailAdapter, EmailSendResult, ErrorContext, EventBusAdapter, EventHandler, EventMetadata, Facet, FacetParams, HealthReport, HealthStatus, HistoryChange, HistoryEntry, HistoryKind, HistoryPage, HttpAdapter, HttpMethod, IndexResult, InventoryChangedV1Payload, InventoryLowStockV1Payload, InventoryOutOfStockV1Payload, InventoryReleasedV1Payload, InventoryReservation, InventoryReservedV1Payload, Logger, MediaStorage, MiddlewareHandler, Money, NymbalEnvironment, NymbalEvent, NymbalEventMap, NymbalEventType, Order, OrderCancelledV1Payload, OrderDeliveredV1Payload, OrderFulfilledV1Payload, OrderHistoryEntry, OrderLineItem, OrderPaidV1Payload, OrderPartiallyRefundedV1Payload, OrderPayment, OrderPlacedV1Payload, OrderRefund, OrderRefundStatus, OrderRefundedV1Payload, OrderShippedV1Payload, OrderStatus, OrderStatusChangedV1Payload, PaginationParams, PayloadOf, PaymentCapture, PaymentCapturedV1Payload, PaymentClient, PaymentFailedV1Payload, PaymentIntent, PaymentIntentCreatedV1Payload, PaymentIntentParams, PaymentRefundedV1Payload, PaymentState, PaymentStatus, PaymentsAdapter, Product, ProductCreatedV1Payload, ProductDeletedV1Payload, ProductMedia, ProductPublishedV1Payload, ProductSnapshot, ProductSortKey, ProductStatus, ProductType, ProductUnpublishedV1Payload, ProductUpdatedV1Payload, ProviderPackageModule, PutOptions, QueryParams, QueryResult, RateLimitAction, RateLimitDecision, RateLimitRule, Recipe, RecipeValue, Refund, RefundOptions, RenderedEmail, RequestAuth, RequestContext, ResponseCookie, ResponseEnvelope, RetryPolicy, Review, ReviewModerationStatus, ReviewResult, ReviewSubmission, ReviewsAdapter, ReviewsResult, Role, RouteHandler, RouteOptions, SearchAdapter, SearchDocument, SearchParams, SearchResult, SecurityAdapter, SecurityConfigLike, SecurityState, SettingFieldView, SettingSourceName, SettingWriteResult, SettingsChangedV1Payload, SettingsListing, SettingsSlotView, ShippingAdapter, ShippingLabel, ShippingLabelParams, ShippingRate, ShippingRateParams, ShippingTracking, Slot, SortKeyCondition, SseEvent, StockAdjustment, StockAdjustmentReason, StockLevel, StockStatus, StoreCapabilities, StreamContext, StreamHandler, Subscription, SubscriptionConfig, TaxAdapter, TaxCalculationParams, TaxCalculationResult, TaxLine, TransactionalEmail, ValidationResult, Variant, VariantDimensions, VariantOption, VariantStatus, WebhookResult };
1640
+ export { AdapterError, AuthError, CORRELATION_ID_HEADER, CheckoutError, ConfigError, DEFAULT_PRODUCT_SORT, DEFAULT_QUERY_LIMIT, EVT_ADAPTER_ERROR, EVT_ALERT_CONFIRMED, EVT_ALERT_REPEATED, EVT_ALERT_REQUESTED, EVT_ALERT_SENT, EVT_ALERT_UNSUBSCRIBED, EVT_CART_ABANDONED, EVT_CART_CONVERTED, EVT_CART_CREATED, EVT_CART_ITEM_ADDED, EVT_CART_ITEM_REMOVED, EVT_CART_UPDATED, EVT_CATEGORY_CREATED, EVT_CATEGORY_DELETED, EVT_CATEGORY_UPDATED, EVT_CUSTOMER_CREATED, EVT_CUSTOMER_DELETED, EVT_CUSTOMER_REGISTERED, EVT_CUSTOMER_UPDATED, EVT_INVENTORY_CHANGED, EVT_INVENTORY_LOW_STOCK, EVT_INVENTORY_OUT_OF_STOCK, EVT_INVENTORY_RELEASED, EVT_INVENTORY_RESERVED, EVT_ORDER_CANCELLED, EVT_ORDER_CONFIRMATION_RESEND_REQUESTED, EVT_ORDER_DELIVERED, EVT_ORDER_FULFILLED, EVT_ORDER_PAID, EVT_ORDER_PARTIALLY_REFUNDED, EVT_ORDER_PLACED, EVT_ORDER_REFUNDED, EVT_ORDER_SHIPPED, EVT_ORDER_STATUS_CHANGED, EVT_PAYMENT_CAPTURED, EVT_PAYMENT_FAILED, EVT_PAYMENT_INTENT_CREATED, EVT_PAYMENT_REFUNDED, EVT_PRODUCT_CREATED, EVT_PRODUCT_DELETED, EVT_PRODUCT_PUBLISHED, EVT_PRODUCT_RESTOCKED, EVT_PRODUCT_UNPUBLISHED, EVT_PRODUCT_UPDATED, EVT_PRODUCT_VARIANT_PRICE_CHANGED, EVT_PRODUCT_VIEWED, EVT_SETTINGS_CHANGED, EVT_VISITOR_MERGED, HEALTH_CHECK_TIMEOUT_MS, HttpError, IDEMPOTENCY_KEY_HEADER, IDEMPOTENT_REPLAYED_HEADER, NotFoundError, NymbalError, ORDER_EMAIL_HEADER, ORDER_KEY_HEADER, ORDER_STATUS_TRANSITIONS, ORDER_TIMELINE_KINDS, OrderConflictError, PRODUCT_SORT_KEYS, PRODUCT_SORT_LABELS, PaymentError, SECRET_MIN_LENGTH, SHIPPING_TRACKING_STATUSES, STANDARD_EVENT_TYPES, STOCK_LABELS, StatusTransitionError, ValidationError, add, allocate, assertMinorUnits, assertOrderTransition, boundedHealthCheck, buildSecurityHeaders, canTransitionOrder, compareDocumentValues, connectionSecrets, displayUrl, documentValueAt, emailFeedbackProblem, emailFeedbackType, errorText, handlerFailedError, isValidCorrelationId, isValidIdempotencyKey, matchesSortKey, money, mulBy, normaliseTrackingNumber, paymentState, queryDocuments, refundableMinor, renderEmailTemplate, shippingTrackingProblem, shippingTrackingType, stockLevel, sub, sumMoney, webhookDeliveryProblem };
1641
+ export type { AdapterBase, AdapterContext, AdapterErrorV1Payload, AdapterSetting, Address, AggregateRating, AiAdapter, AlertConfirmedV1Payload, AlertKind, AlertRepeatedV1Payload, AlertRequestedV1Payload, AlertSentV1Payload, AlertStatus, AlertUnsubscribedV1Payload, AnalyticsAdapter, AsOfRead, AuditActor, AuditEntry, AuditOutcome, AuditSubject, BoundedHealthCheckOptions, CaptureMethod, Cart, CartAbandonedV1Payload, CartConvertedV1Payload, CartCreatedV1Payload, CartItem, CartItemAddedV1Payload, CartItemRemovedV1Payload, CartUpdatedV1Payload, Category, CategoryCreatedV1Payload, CategoryDeletedV1Payload, CategoryUpdatedV1Payload, CheckoutPayment, CommerceAdapter, CommerceSlot, CommerceSlotAdapters, Customer, CustomerAddress, CustomerCreatedV1Payload, CustomerDeletedV1Payload, CustomerProfile, CustomerSnapshot, CustomerUpdatedV1Payload, DocumentStoreAdapter, DriftReport, EmailAdapter, EmailBouncedV1Payload, EmailComplainedV1Payload, EmailFeedbackKind, EmailSendResult, ErrorContext, EventBusAdapter, EventHandler, EventMetadata, Facet, FacetParams, HandlerFailure, HealthReport, HealthStatus, HistoryChange, HistoryEntry, HistoryKind, HistoryPage, HttpAdapter, HttpMethod, IndexResult, InfrastructureBlock, InventoryChangedV1Payload, InventoryLowStockV1Payload, InventoryOutOfStockV1Payload, InventoryReleasedV1Payload, InventoryReservation, InventoryReservedV1Payload, Logger, MediaStorage, MiddlewareHandler, Money, NymbalEnvironment, NymbalEvent, NymbalEventMap, NymbalEventType, Order, OrderCancelledV1Payload, OrderConfirmationResendRequestedV1Payload, OrderDeliveredV1Payload, OrderFulfilledV1Payload, OrderHistoryEntry, OrderLineItem, OrderPaidV1Payload, OrderPartiallyRefundedV1Payload, OrderPayment, OrderPlacedV1Payload, OrderRefund, OrderRefundStatus, OrderRefundedV1Payload, OrderShippedV1Payload, OrderStatus, OrderStatusChangedV1Payload, OrderTimelineEntry, OrderTimelineKind, PaginationParams, PayloadOf, PaymentCapture, PaymentCapturedV1Payload, PaymentClient, PaymentFailedV1Payload, PaymentIntent, PaymentIntentCreatedV1Payload, PaymentIntentParams, PaymentRefundedV1Payload, PaymentState, PaymentStatus, PaymentsAdapter, Product, ProductCreatedV1Payload, ProductDeletedV1Payload, ProductMedia, ProductPublishedV1Payload, ProductRestockedV1Payload, ProductSnapshot, ProductSortKey, ProductStatus, ProductType, ProductUnpublishedV1Payload, ProductUpdatedV1Payload, ProductVariantPriceChangedV1Payload, ProductViewedV1Payload, ProviderPackageModule, PutOptions, QueryParams, QueryResult, RateLimitAction, RateLimitDecision, RateLimitRule, RecentViewer, Recipe, RecipeValue, Refund, RefundOptions, RenderedEmail, RequestAuth, RequestContext, ResponseCookie, ResponseEnvelope, RetryPolicy, Review, ReviewModerationStatus, ReviewResult, ReviewSubmission, ReviewsAdapter, ReviewsResult, Role, RouteHandler, RouteOptions, SearchAdapter, SearchDocument, SearchParams, SearchResult, SecurityAdapter, SecurityConfigLike, SecurityState, SettingFieldView, SettingSourceName, SettingWriteResult, SettingsChangedV1Payload, SettingsListing, SettingsSlotView, ShippingAdapter, ShippingLabel, ShippingLabelParams, ShippingRate, ShippingRateParams, ShippingTracking, ShippingTrackingStatus, ShippingTrackingV1Payload, ShopperAddress, ShopperOrder, Slot, SortKeyCondition, SseEvent, StockAdjustment, StockAdjustmentReason, StockLevel, StockStatus, StoreCapabilities, StreamContext, StreamHandler, Subscription, SubscriptionConfig, TaxAdapter, TaxCalculationParams, TaxCalculationResult, TaxLine, TransactionalEmail, ValidationResult, Variant, VariantDimensions, VariantOption, VariantStatus, VisitorMergedV1Payload, WebhookDelivery, WebhookDeliveryProblem, WebhookEventDraft, WebhookHeaders, WebhookResult };
package/dist/index.js CHANGED
@@ -169,6 +169,8 @@ function assertOrderTransition(from, to) {
169
169
  throw new StatusTransitionError("order", from, to);
170
170
  }
171
171
  }
172
+ var ORDER_KEY_HEADER = "x-nymbal-order-key";
173
+ var ORDER_EMAIL_HEADER = "x-nymbal-order-email";
172
174
  function paymentState(order) {
173
175
  if (order.capturedMinor <= 0) return "unpaid";
174
176
  if (order.refundedMinor <= 0) return "paid";
@@ -179,12 +181,26 @@ function refundableMinor(order, refunds) {
179
181
  return Math.max(0, order.capturedMinor - order.refundedMinor - pending);
180
182
  }
181
183
 
184
+ // src/domain/order-timeline.ts
185
+ var ORDER_TIMELINE_KINDS = [
186
+ "placed",
187
+ "paid",
188
+ "shipped",
189
+ "delivered",
190
+ "cancelled",
191
+ "partially_refunded",
192
+ "refunded"
193
+ ];
194
+
182
195
  // src/events/constants.ts
183
196
  var EVT_PRODUCT_CREATED = "product.created.v1";
184
197
  var EVT_PRODUCT_UPDATED = "product.updated.v1";
185
198
  var EVT_PRODUCT_DELETED = "product.deleted.v1";
186
199
  var EVT_PRODUCT_PUBLISHED = "product.published.v1";
187
200
  var EVT_PRODUCT_UNPUBLISHED = "product.unpublished.v1";
201
+ var EVT_PRODUCT_VIEWED = "product.viewed.v1";
202
+ var EVT_PRODUCT_RESTOCKED = "product.restocked.v1";
203
+ var EVT_PRODUCT_VARIANT_PRICE_CHANGED = "product.variant_price_changed.v1";
188
204
  var EVT_CATEGORY_CREATED = "category.created.v1";
189
205
  var EVT_CATEGORY_UPDATED = "category.updated.v1";
190
206
  var EVT_CATEGORY_DELETED = "category.deleted.v1";
@@ -197,6 +213,7 @@ var EVT_ORDER_CANCELLED = "order.cancelled.v1";
197
213
  var EVT_ORDER_REFUNDED = "order.refunded.v1";
198
214
  var EVT_ORDER_PARTIALLY_REFUNDED = "order.partially_refunded.v1";
199
215
  var EVT_ORDER_STATUS_CHANGED = "order.status_changed.v1";
216
+ var EVT_ORDER_CONFIRMATION_RESEND_REQUESTED = "order.confirmation_resend_requested.v1";
200
217
  var EVT_INVENTORY_CHANGED = "inventory.changed.v1";
201
218
  var EVT_INVENTORY_RESERVED = "inventory.reserved.v1";
202
219
  var EVT_INVENTORY_RELEASED = "inventory.released.v1";
@@ -206,6 +223,7 @@ var EVT_CUSTOMER_CREATED = "customer.created.v1";
206
223
  var EVT_CUSTOMER_UPDATED = "customer.updated.v1";
207
224
  var EVT_CUSTOMER_DELETED = "customer.deleted.v1";
208
225
  var EVT_CUSTOMER_REGISTERED = EVT_CUSTOMER_CREATED;
226
+ var EVT_VISITOR_MERGED = "visitor.merged.v1";
209
227
  var EVT_CART_CREATED = "cart.created.v1";
210
228
  var EVT_CART_ITEM_ADDED = "cart.item_added.v1";
211
229
  var EVT_CART_ITEM_REMOVED = "cart.item_removed.v1";
@@ -216,6 +234,11 @@ var EVT_PAYMENT_INTENT_CREATED = "payment.intent_created.v1";
216
234
  var EVT_PAYMENT_CAPTURED = "payment.captured.v1";
217
235
  var EVT_PAYMENT_FAILED = "payment.failed.v1";
218
236
  var EVT_PAYMENT_REFUNDED = "payment.refunded.v1";
237
+ var EVT_ALERT_REQUESTED = "alert.requested.v1";
238
+ var EVT_ALERT_REPEATED = "alert.repeated.v1";
239
+ var EVT_ALERT_CONFIRMED = "alert.confirmed.v1";
240
+ var EVT_ALERT_UNSUBSCRIBED = "alert.unsubscribed.v1";
241
+ var EVT_ALERT_SENT = "alert.sent.v1";
219
242
  var EVT_ADAPTER_ERROR = "adapter.error.v1";
220
243
  var EVT_SETTINGS_CHANGED = "settings.changed.v1";
221
244
  var STANDARD_EVENT_TYPES = [
@@ -224,6 +247,9 @@ var STANDARD_EVENT_TYPES = [
224
247
  EVT_PRODUCT_DELETED,
225
248
  EVT_PRODUCT_PUBLISHED,
226
249
  EVT_PRODUCT_UNPUBLISHED,
250
+ EVT_PRODUCT_VIEWED,
251
+ EVT_PRODUCT_RESTOCKED,
252
+ EVT_PRODUCT_VARIANT_PRICE_CHANGED,
227
253
  EVT_CATEGORY_CREATED,
228
254
  EVT_CATEGORY_UPDATED,
229
255
  EVT_CATEGORY_DELETED,
@@ -236,6 +262,7 @@ var STANDARD_EVENT_TYPES = [
236
262
  EVT_ORDER_REFUNDED,
237
263
  EVT_ORDER_PARTIALLY_REFUNDED,
238
264
  EVT_ORDER_STATUS_CHANGED,
265
+ EVT_ORDER_CONFIRMATION_RESEND_REQUESTED,
239
266
  EVT_INVENTORY_CHANGED,
240
267
  EVT_INVENTORY_RESERVED,
241
268
  EVT_INVENTORY_RELEASED,
@@ -244,6 +271,7 @@ var STANDARD_EVENT_TYPES = [
244
271
  EVT_CUSTOMER_CREATED,
245
272
  EVT_CUSTOMER_UPDATED,
246
273
  EVT_CUSTOMER_DELETED,
274
+ EVT_VISITOR_MERGED,
247
275
  EVT_CART_CREATED,
248
276
  EVT_CART_ITEM_ADDED,
249
277
  EVT_CART_ITEM_REMOVED,
@@ -254,6 +282,11 @@ var STANDARD_EVENT_TYPES = [
254
282
  EVT_PAYMENT_CAPTURED,
255
283
  EVT_PAYMENT_FAILED,
256
284
  EVT_PAYMENT_REFUNDED,
285
+ EVT_ALERT_REQUESTED,
286
+ EVT_ALERT_REPEATED,
287
+ EVT_ALERT_CONFIRMED,
288
+ EVT_ALERT_UNSUBSCRIBED,
289
+ EVT_ALERT_SENT,
257
290
  EVT_ADAPTER_ERROR,
258
291
  EVT_SETTINGS_CHANGED
259
292
  ];
@@ -324,7 +357,7 @@ async function boundedHealthCheck(check, options = {}) {
324
357
  }
325
358
 
326
359
  // src/adapters/document-order.ts
327
- var collator = new Intl.Collator(void 0, { sensitivity: "base" });
360
+ var collator;
328
361
  function rankOf(value) {
329
362
  if (typeof value === "number") return 0;
330
363
  if (typeof value === "string") return 1;
@@ -351,6 +384,7 @@ function compareDocumentValues(a, b) {
351
384
  case 0:
352
385
  return sign(a - b);
353
386
  case 1:
387
+ collator ??= new Intl.Collator(void 0, { sensitivity: "base" });
354
388
  return sign(collator.compare(a, b));
355
389
  case 2:
356
390
  return sign(compareOther(a, b));
@@ -451,6 +485,15 @@ function queryDocuments(documents, params) {
451
485
  return { items: page.map((m) => m.doc), nextCursor };
452
486
  }
453
487
 
488
+ // src/adapters/event-bus.ts
489
+ function handlerFailedError(event, failed) {
490
+ return new AdapterError(
491
+ "handler_failed",
492
+ `${event.type} (${event.id}) failed in ${failed.map((f) => f.subscription).join(", ")}`,
493
+ { context: { eventId: event.id, eventType: event.type, failed } }
494
+ );
495
+ }
496
+
454
497
  // src/adapters/http.ts
455
498
  var CORRELATION_ID_HEADER = "x-correlation-id";
456
499
  var CORRELATION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
@@ -473,6 +516,115 @@ function buildSecurityHeaders(security) {
473
516
  return headers;
474
517
  }
475
518
 
519
+ // src/adapters/webhook-delivery.ts
520
+ var VERSIONED_EVENT = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*\.v[1-9][0-9]*$/;
521
+ function isPlainObject(value) {
522
+ if (typeof value !== "object" || value === null) return false;
523
+ const proto = Object.getPrototypeOf(value);
524
+ return proto === Object.prototype || proto === null;
525
+ }
526
+ function webhookDeliveryProblem(providerName, producesEvents, delivery) {
527
+ if (typeof delivery.deliveryId !== "string" || delivery.deliveryId === "") {
528
+ return { reason: "the delivery id is empty" };
529
+ }
530
+ if (!Array.isArray(delivery.events)) return { reason: "events is not an array" };
531
+ for (const event of delivery.events) {
532
+ const { type, payload } = isPlainObject(event) ? event : {};
533
+ if (typeof type !== "string") return { type, reason: "an event has no type" };
534
+ const dot = type.indexOf(".");
535
+ if (dot === -1 || type.slice(0, dot) !== providerName) {
536
+ return { type, reason: `the type is not in the ${providerName} namespace` };
537
+ }
538
+ if (STANDARD_EVENT_TYPES.some((standard) => standard.split(".")[0] === providerName)) {
539
+ return { type, reason: "the type is in a kernel namespace" };
540
+ }
541
+ if (!VERSIONED_EVENT.test(type.slice(dot + 1))) {
542
+ return { type, reason: "the type is not <provider>.<event>.v<n>" };
543
+ }
544
+ if (!producesEvents.includes(type)) {
545
+ return { type, reason: "the type is not listed in producesEvents" };
546
+ }
547
+ if (!isPlainObject(payload)) return { type, reason: "the payload is not a JSON object" };
548
+ }
549
+ return null;
550
+ }
551
+
552
+ // src/adapters/email-feedback.ts
553
+ function emailFeedbackType(providerName, what) {
554
+ return `${providerName}.${what}.v1`;
555
+ }
556
+ function payloadOf(event) {
557
+ const payload = event?.payload;
558
+ return typeof payload === "object" && payload !== null ? payload : {};
559
+ }
560
+ function emailFeedbackProblem(providerName, delivery) {
561
+ const bounced = emailFeedbackType(providerName, "bounced");
562
+ const complained = emailFeedbackType(providerName, "complained");
563
+ if (!Array.isArray(delivery.events)) return null;
564
+ for (const event of delivery.events) {
565
+ const type = event?.type;
566
+ if (type !== bounced && type !== complained) continue;
567
+ const payload = payloadOf(event);
568
+ const { recipient, messageId, occurredAt } = payload;
569
+ if (typeof recipient !== "string" || recipient.trim() === "") {
570
+ return { type, reason: "the recipient is missing or empty" };
571
+ }
572
+ if (type === bounced && payload["bounce"] !== "hard" && payload["bounce"] !== "soft") {
573
+ return { type, reason: "the bounce is neither 'hard' nor 'soft'" };
574
+ }
575
+ if (messageId !== null && typeof messageId !== "string") {
576
+ return { type, reason: "the messageId is neither a string nor null" };
577
+ }
578
+ if (typeof occurredAt !== "string" || Number.isNaN(Date.parse(occurredAt))) {
579
+ return { type, reason: "occurredAt is not a readable date" };
580
+ }
581
+ }
582
+ return null;
583
+ }
584
+
585
+ // src/adapters/shipping-tracking.ts
586
+ var SHIPPING_TRACKING_STATUSES = [
587
+ "pre_transit",
588
+ "in_transit",
589
+ "out_for_delivery",
590
+ "delivered",
591
+ "exception",
592
+ "returned",
593
+ "unknown"
594
+ ];
595
+ function shippingTrackingType(providerName) {
596
+ return `${providerName}.tracking.v1`;
597
+ }
598
+ function normaliseTrackingNumber(value) {
599
+ return value.replace(/\s+/g, "").toUpperCase();
600
+ }
601
+ function payloadOf2(event) {
602
+ const payload = event?.payload;
603
+ return typeof payload === "object" && payload !== null ? payload : {};
604
+ }
605
+ function shippingTrackingProblem(providerName, delivery) {
606
+ const tracking = shippingTrackingType(providerName);
607
+ if (!Array.isArray(delivery.events)) return null;
608
+ for (const event of delivery.events) {
609
+ const type = event?.type;
610
+ if (type !== tracking) continue;
611
+ const { trackingNumber, carrier, status, occurredAt } = payloadOf2(event);
612
+ if (typeof trackingNumber !== "string" || normaliseTrackingNumber(trackingNumber) === "") {
613
+ return { type, reason: "the trackingNumber is missing or empty" };
614
+ }
615
+ if (carrier !== null && typeof carrier !== "string") {
616
+ return { type, reason: "the carrier is neither a string nor null" };
617
+ }
618
+ if (!SHIPPING_TRACKING_STATUSES.includes(status)) {
619
+ return { type, reason: "the status is not one of the tracking statuses" };
620
+ }
621
+ if (typeof occurredAt !== "string" || Number.isNaN(Date.parse(occurredAt))) {
622
+ return { type, reason: "occurredAt is not a readable date" };
623
+ }
624
+ }
625
+ return null;
626
+ }
627
+
476
628
  // src/adapters/email-templates.ts
477
629
  function render(template, vars) {
478
630
  return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => {
@@ -491,11 +643,12 @@ function render(template, vars) {
491
643
  var TEMPLATES = {
492
644
  "order-placed": {
493
645
  subject: "Order {{orderNumber}} confirmed",
646
+ // `orderLink` is the whole "View your order" paragraph, absent when the store has no URL to link from.
494
647
  body: `Hi {{customerName}},
495
648
 
496
649
  Thank you for your order {{orderNumber}}. Total: {{totalFormatted}}.
497
650
 
498
- We'll let you know when it ships.`
651
+ We'll let you know when it ships.{{orderLink}}`
499
652
  },
500
653
  "order-shipped": {
501
654
  subject: "Order {{orderNumber}} is on its way",
@@ -513,6 +666,55 @@ Tracking: {{trackingNumber}}`
513
666
  body: `Hi,
514
667
 
515
668
  You left items in your cart. Pick up where you left off: {{resumeUrl}}`
669
+ },
670
+ // Each link on a line of its own, so a mail client never runs the next word into it.
671
+ "alert-confirm": {
672
+ subject: "Confirm your alert for {{productName}}",
673
+ body: `Hi,
674
+
675
+ You asked {{storeName}} to email you when {{productName}} is back in stock. Confirm by following this link:
676
+
677
+ {{confirmUrl}}
678
+
679
+ If you did not ask, ignore this email and you will hear nothing more.
680
+
681
+ Stop this alert:
682
+ {{unsubscribeUrl}}`
683
+ },
684
+ "alert-back-in-stock": {
685
+ subject: "{{productName}} is back in stock",
686
+ body: `Hi,
687
+
688
+ {{productName}} is back in stock at {{storeName}}:
689
+
690
+ {{productUrl}}
691
+
692
+ This was a one-time alert. Stop it:
693
+ {{unsubscribeUrl}}`
694
+ },
695
+ "alert-confirm-price-drop": {
696
+ subject: "Confirm your price alert for {{productName}}",
697
+ body: `Hi,
698
+
699
+ You asked {{storeName}} to email you when the price of {{productName}} drops, each time it falls below the last price we told you. Confirm by following this link:
700
+
701
+ {{confirmUrl}}
702
+
703
+ If you did not ask, ignore this email and you will hear nothing more.
704
+
705
+ Stop this alert:
706
+ {{unsubscribeUrl}}`
707
+ },
708
+ "alert-price-drop": {
709
+ subject: "Price drop: {{productName}} is now {{priceFormatted}}",
710
+ body: `Hi,
711
+
712
+ The price of {{productName}} at {{storeName}} has dropped from {{previousPriceFormatted}} to {{priceFormatted}}:
713
+
714
+ {{productUrl}}
715
+
716
+ We'll email you again if it drops below {{priceFormatted}}. Stop this alert:
717
+ {{unsubscribeUrl}}`
516
718
  }
517
719
  };
518
720
  function renderEmailTemplate(template, vars) {
@@ -557,6 +759,11 @@ export {
557
759
  DEFAULT_PRODUCT_SORT,
558
760
  DEFAULT_QUERY_LIMIT,
559
761
  EVT_ADAPTER_ERROR,
762
+ EVT_ALERT_CONFIRMED,
763
+ EVT_ALERT_REPEATED,
764
+ EVT_ALERT_REQUESTED,
765
+ EVT_ALERT_SENT,
766
+ EVT_ALERT_UNSUBSCRIBED,
560
767
  EVT_CART_ABANDONED,
561
768
  EVT_CART_CONVERTED,
562
769
  EVT_CART_CREATED,
@@ -576,6 +783,7 @@ export {
576
783
  EVT_INVENTORY_RELEASED,
577
784
  EVT_INVENTORY_RESERVED,
578
785
  EVT_ORDER_CANCELLED,
786
+ EVT_ORDER_CONFIRMATION_RESEND_REQUESTED,
579
787
  EVT_ORDER_DELIVERED,
580
788
  EVT_ORDER_FULFILLED,
581
789
  EVT_ORDER_PAID,
@@ -591,21 +799,29 @@ export {
591
799
  EVT_PRODUCT_CREATED,
592
800
  EVT_PRODUCT_DELETED,
593
801
  EVT_PRODUCT_PUBLISHED,
802
+ EVT_PRODUCT_RESTOCKED,
594
803
  EVT_PRODUCT_UNPUBLISHED,
595
804
  EVT_PRODUCT_UPDATED,
805
+ EVT_PRODUCT_VARIANT_PRICE_CHANGED,
806
+ EVT_PRODUCT_VIEWED,
596
807
  EVT_SETTINGS_CHANGED,
808
+ EVT_VISITOR_MERGED,
597
809
  HEALTH_CHECK_TIMEOUT_MS,
598
810
  HttpError,
599
811
  IDEMPOTENCY_KEY_HEADER,
600
812
  IDEMPOTENT_REPLAYED_HEADER,
601
813
  NotFoundError,
602
814
  NymbalError,
815
+ ORDER_EMAIL_HEADER,
816
+ ORDER_KEY_HEADER,
603
817
  ORDER_STATUS_TRANSITIONS,
818
+ ORDER_TIMELINE_KINDS,
604
819
  OrderConflictError,
605
820
  PRODUCT_SORT_KEYS,
606
821
  PRODUCT_SORT_LABELS,
607
822
  PaymentError,
608
823
  SECRET_MIN_LENGTH,
824
+ SHIPPING_TRACKING_STATUSES,
609
825
  STANDARD_EVENT_TYPES,
610
826
  STOCK_LABELS,
611
827
  StatusTransitionError,
@@ -621,18 +837,25 @@ export {
621
837
  connectionSecrets,
622
838
  displayUrl,
623
839
  documentValueAt,
840
+ emailFeedbackProblem,
841
+ emailFeedbackType,
624
842
  errorText,
843
+ handlerFailedError,
625
844
  isValidCorrelationId,
626
845
  isValidIdempotencyKey,
627
846
  matchesSortKey,
628
847
  money,
629
848
  mulBy,
849
+ normaliseTrackingNumber,
630
850
  paymentState,
631
851
  queryDocuments,
632
852
  refundableMinor,
633
853
  renderEmailTemplate,
854
+ shippingTrackingProblem,
855
+ shippingTrackingType,
634
856
  stockLevel,
635
857
  sub,
636
- sumMoney
858
+ sumMoney,
859
+ webhookDeliveryProblem
637
860
  };
638
861
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/domain/money.ts","../src/domain/product.ts","../src/domain/order.ts","../src/events/constants.ts","../src/adapters/adapter-base.ts","../src/adapters/document-order.ts","../src/adapters/http.ts","../src/adapters/security.ts","../src/adapters/email-templates.ts","../src/stock-level.ts","../src/audit.ts"],"sourcesContent":["export type ErrorContext = Record<string, unknown>\n\nexport class NymbalError extends Error {\n readonly code: string\n readonly context: ErrorContext\n\n constructor(\n code: string,\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = this.constructor.name\n this.code = code\n this.context = options.context ?? {}\n }\n\n toJSON(): { name: string; code: string; message: string; context: ErrorContext } {\n return { name: this.name, code: this.code, message: this.message, context: this.context }\n }\n}\n\nexport class ConfigError extends NymbalError {\n constructor(\n message: string,\n options: {\n cause?: unknown\n context?: ErrorContext\n code?: `config.${string}` | `adapter.${string}`\n } = {},\n ) {\n super(options.code ?? 'config.invalid', message, options)\n }\n}\n\nexport class ValidationError extends NymbalError {\n constructor(message: string, options: { cause?: unknown; context?: ErrorContext } = {}) {\n super('validation.failed', message, options)\n }\n}\n\nexport class NotFoundError extends NymbalError {\n constructor(\n resource: string,\n identifier: unknown,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super('not_found', `${resource} not found: ${String(identifier)}`, {\n ...options,\n context: { resource, identifier, ...(options.context ?? {}) },\n })\n }\n}\n\nexport class AdapterError extends NymbalError {\n constructor(\n code: string,\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`adapter.${code}`, message, options)\n }\n}\n\nexport class HttpError extends NymbalError {\n readonly status: number\n\n constructor(\n status: number,\n message: string,\n options: { cause?: unknown; context?: ErrorContext; code?: string } = {},\n ) {\n super(options.code ?? `http.${status}`, message, options)\n this.status = status\n }\n}\n\nexport class AuthError extends HttpError {\n constructor(\n code: 'unauthorized' | 'forbidden' | 'invalid_credentials' | 'token_expired' | 'token_invalid',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n const status = code === 'forbidden' ? 403 : 401\n super(status, message, { ...options, code: `auth.${code}` })\n }\n}\n\nexport class PaymentError extends NymbalError {\n constructor(\n code:\n | 'intent_failed'\n | 'capture_failed'\n // A definite refusal: no money moved.\n | 'refund_rejected'\n // An outcome the shop cannot know (a timeout, a provider fault): money may have moved.\n | 'refund_unknown'\n | 'cancel_refused'\n | 'webhook_invalid'\n | 'not_configured',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`payment.${code}`, message, options)\n }\n}\n\n/** A command the order's money does not allow (confirm unpaid, cancel paid, refund beyond the capture). */\nexport class OrderConflictError extends NymbalError {\n constructor(\n code:\n | 'order.unpaid'\n | 'order.paid'\n | 'order.stock_unavailable'\n | 'order.payment_in_progress'\n | 'refund.not_captured'\n | 'refund.exceeds_captured'\n | 'refund.request_reused',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(code, message, options)\n }\n}\n\nexport class CheckoutError extends NymbalError {\n constructor(\n code:\n | 'cart_empty'\n | 'in_progress'\n | 'payment_in_progress'\n | 'variant_missing'\n | 'out_of_stock'\n | 'reservation_failed'\n | 'finalize_failed',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`checkout.${code}`, message, options)\n }\n}\n\nexport class StatusTransitionError extends NymbalError {\n constructor(\n entity: string,\n fromStatus: string,\n toStatus: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(\n 'status.invalid_transition',\n `Invalid ${entity} status transition: ${fromStatus} → ${toStatus}`,\n {\n ...options,\n context: { entity, fromStatus, toStatus, ...(options.context ?? {}) },\n },\n )\n }\n}\n","import { ValidationError } from '../errors.js'\n\nexport interface Money {\n amount: number\n currency: string\n}\n\nexport function money(amount: number, currency: string): Money {\n assertMinorUnits(amount)\n return { amount, currency }\n}\n\nexport function assertMinorUnits(amount: number): void {\n if (!Number.isInteger(amount)) {\n throw new ValidationError(`Monetary amount must be an integer (minor units), got ${amount}`)\n }\n}\n\nfunction assertSameCurrency(a: Money, b: Money): void {\n if (a.currency !== b.currency) {\n throw new ValidationError(\n `Currency mismatch: ${a.currency} vs ${b.currency}`,\n )\n }\n}\n\nexport function add(a: Money, b: Money): Money {\n assertSameCurrency(a, b)\n return { amount: a.amount + b.amount, currency: a.currency }\n}\n\nexport function sub(a: Money, b: Money): Money {\n assertSameCurrency(a, b)\n return { amount: a.amount - b.amount, currency: a.currency }\n}\n\nexport function mulBy(m: Money, scalar: number): Money {\n if (!Number.isFinite(scalar)) {\n throw new ValidationError(`Scalar must be finite, got ${scalar}`)\n }\n return { amount: Math.round(m.amount * scalar), currency: m.currency }\n}\n\nexport function sumMoney(values: Money[], currency: string): Money {\n const total = values.reduce((acc, m) => {\n assertSameCurrency(m, { amount: 0, currency })\n return acc + m.amount\n }, 0)\n return { amount: total, currency }\n}\n\nexport function allocate(m: Money, weights: number[]): Money[] {\n if (weights.length === 0) return []\n const totalWeight = weights.reduce((s, w) => s + w, 0)\n if (totalWeight <= 0) {\n throw new ValidationError('allocate: sum of weights must be > 0')\n }\n const portions = weights.map((w) => Math.floor((m.amount * w) / totalWeight))\n let remainder = m.amount - portions.reduce((s, p) => s + p, 0)\n const result = portions.slice()\n let idx = 0\n while (remainder !== 0 && idx < result.length) {\n const delta = remainder > 0 ? 1 : -1\n result[idx] = (result[idx] ?? 0) + delta\n remainder -= delta\n idx += 1\n }\n return result.map((amount) => ({ amount, currency: m.currency }))\n}\n","export type ProductStatus = 'draft' | 'active' | 'archived'\nexport type ProductType = 'simple' | 'variable'\nexport type VariantStatus = 'active' | 'inactive'\n\nexport interface ProductMedia {\n url: string\n altText: string\n position: number\n}\n\nexport interface VariantOption {\n name: string\n value: string\n}\n\nexport interface VariantDimensions {\n lengthMm?: number\n widthMm?: number\n heightMm?: number\n}\n\nexport interface Variant {\n id: string\n productId: string\n sku: string\n name: string\n priceMinor: number\n compareAtPriceMinor: number | null\n weightGrams: number | null\n dimensions: VariantDimensions | null\n stock: number\n lowStockThreshold: number\n options: VariantOption[]\n status: VariantStatus\n createdAt: string\n updatedAt: string\n}\n\nexport interface Product {\n id: string\n slug: string\n name: string\n description: string\n shortDescription: string\n status: ProductStatus\n type: ProductType\n seoTitle: string\n seoDescription: string\n media: ProductMedia[]\n metadata: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\n/** The orders a product listing can be requested in; ties break by slug, ascending. */\nexport const PRODUCT_SORT_KEYS = [\n 'newest',\n 'price-asc',\n 'price-desc',\n 'name-asc',\n 'name-desc',\n] as const\nexport type ProductSortKey = (typeof PRODUCT_SORT_KEYS)[number]\nexport const DEFAULT_PRODUCT_SORT: ProductSortKey = 'newest'\n/** What a storefront shows for each sort. */\nexport const PRODUCT_SORT_LABELS: Readonly<Record<ProductSortKey, string>> = {\n newest: 'Newest',\n 'price-asc': 'Price: Low to High',\n 'price-desc': 'Price: High to Low',\n 'name-asc': 'Name: A to Z',\n 'name-desc': 'Name: Z to A',\n}\n","import { StatusTransitionError } from '../errors.js'\n\nexport type OrderStatus =\n | 'pending'\n | 'confirmed'\n | 'processing'\n | 'shipped'\n | 'delivered'\n | 'cancelled'\n | 'refunded'\n | 'partially_refunded'\n\n// A captured order is refunded from any status between confirmed and delivered; a partial refund leaves room for more.\nexport const ORDER_STATUS_TRANSITIONS: Record<OrderStatus, readonly OrderStatus[]> = {\n pending: ['confirmed', 'cancelled'],\n confirmed: ['processing', 'cancelled', 'refunded', 'partially_refunded'],\n processing: ['shipped', 'cancelled', 'refunded', 'partially_refunded'],\n shipped: ['delivered', 'refunded', 'partially_refunded'],\n delivered: ['refunded', 'partially_refunded'],\n cancelled: [],\n refunded: [],\n partially_refunded: ['refunded'],\n}\n\nexport function canTransitionOrder(from: OrderStatus, to: OrderStatus): boolean {\n return ORDER_STATUS_TRANSITIONS[from].includes(to)\n}\n\nexport function assertOrderTransition(from: OrderStatus, to: OrderStatus): void {\n if (!canTransitionOrder(from, to)) {\n throw new StatusTransitionError('order', from, to)\n }\n}\n\nexport interface Address {\n firstName: string\n lastName: string\n company?: string\n addressLine1: string\n addressLine2?: string\n city: string\n region: string\n postalCode: string\n country: string\n phone?: string\n}\n\nexport interface OrderLineItem {\n variantId: string\n productId: string\n productName: string\n variantName: string\n sku: string\n qty: number\n unitPriceMinor: number\n lineSubtotalMinor: number\n lineTaxMinor: number\n lineTotalMinor: number\n imageUrl: string\n}\n\nexport interface Order {\n id: string\n orderNumber: string\n sequence: number\n customerId: string | null\n status: OrderStatus\n email: string\n billingAddress: Address\n shippingAddress: Address\n lineItems: OrderLineItem[]\n subtotalMinor: number\n taxTotalMinor: number\n shippingTotalMinor: number\n discountTotalMinor: number\n totalMinor: number\n currency: string\n notes: string\n metadata: Record<string, unknown>\n paymentIntentId: string | null\n /** Money taken for the order, in minor units of its currency; 0 while nothing is captured. */\n capturedMinor: number\n capturedAt: string | null\n /** Σ of the order's succeeded refunds (or, for an import or a pre-refund-table order, what was refunded then). */\n refundedMinor: number\n captureMethod: CaptureMethod | null\n createdAt: string\n updatedAt: string\n}\n\n/** Who took the money: the payments provider, the merchant recording an offline payment, or an imported history. */\nexport type CaptureMethod = 'provider' | 'manual' | 'import'\n\nexport type OrderRefundStatus = 'pending' | 'succeeded' | 'failed'\n\n/** One refund of an order. `pending` until the provider's answer settles it, and counted against what is refundable. */\nexport interface OrderRefund {\n id: string\n orderId: string\n amountMinor: number\n currency: string\n providerRefundId: string | null\n status: OrderRefundStatus\n reason: string | null\n actor: string\n createdAt: string\n}\n\nexport type PaymentState = 'unpaid' | 'paid' | 'partially_refunded' | 'refunded'\n\n/** What the admin shows about an order's money: derived from the stored amounts and refunds alone. */\nexport interface OrderPayment {\n state: PaymentState\n capturedMinor: number\n refundedMinor: number\n refundableMinor: number\n currency: string\n refunds: OrderRefund[]\n}\n\nexport function paymentState(order: Pick<Order, 'capturedMinor' | 'refundedMinor'>): PaymentState {\n if (order.capturedMinor <= 0) return 'unpaid'\n if (order.refundedMinor <= 0) return 'paid'\n return order.refundedMinor < order.capturedMinor ? 'partially_refunded' : 'refunded'\n}\n\n/** What can still be refunded: the capture less what was refunded and what is still pending with the provider. */\nexport function refundableMinor(\n order: Pick<Order, 'capturedMinor' | 'refundedMinor'>,\n refunds: ReadonlyArray<Pick<OrderRefund, 'amountMinor' | 'status'>>,\n): number {\n const pending = refunds\n .filter((r) => r.status === 'pending')\n .reduce((sum, r) => sum + r.amountMinor, 0)\n return Math.max(0, order.capturedMinor - order.refundedMinor - pending)\n}\n\nexport interface OrderHistoryEntry {\n id: string\n orderId: string\n fromStatus: OrderStatus | null\n toStatus: OrderStatus\n actor: string\n note: string\n timestamp: string\n}\n","// Product\nexport const EVT_PRODUCT_CREATED = 'product.created.v1'\nexport const EVT_PRODUCT_UPDATED = 'product.updated.v1'\nexport const EVT_PRODUCT_DELETED = 'product.deleted.v1'\nexport const EVT_PRODUCT_PUBLISHED = 'product.published.v1'\nexport const EVT_PRODUCT_UNPUBLISHED = 'product.unpublished.v1'\n\n// Category\nexport const EVT_CATEGORY_CREATED = 'category.created.v1'\nexport const EVT_CATEGORY_UPDATED = 'category.updated.v1'\nexport const EVT_CATEGORY_DELETED = 'category.deleted.v1'\n\n// Order\nexport const EVT_ORDER_PLACED = 'order.placed.v1'\nexport const EVT_ORDER_PAID = 'order.paid.v1'\nexport const EVT_ORDER_FULFILLED = 'order.fulfilled.v1'\nexport const EVT_ORDER_SHIPPED = 'order.shipped.v1'\nexport const EVT_ORDER_DELIVERED = 'order.delivered.v1'\nexport const EVT_ORDER_CANCELLED = 'order.cancelled.v1'\nexport const EVT_ORDER_REFUNDED = 'order.refunded.v1'\nexport const EVT_ORDER_PARTIALLY_REFUNDED = 'order.partially_refunded.v1'\nexport const EVT_ORDER_STATUS_CHANGED = 'order.status_changed.v1'\n\n// Inventory\nexport const EVT_INVENTORY_CHANGED = 'inventory.changed.v1'\nexport const EVT_INVENTORY_RESERVED = 'inventory.reserved.v1'\nexport const EVT_INVENTORY_RELEASED = 'inventory.released.v1'\nexport const EVT_INVENTORY_LOW_STOCK = 'inventory.low_stock.v1'\nexport const EVT_INVENTORY_OUT_OF_STOCK = 'inventory.out_of_stock.v1'\n\n// Customer\nexport const EVT_CUSTOMER_CREATED = 'customer.created.v1'\nexport const EVT_CUSTOMER_UPDATED = 'customer.updated.v1'\nexport const EVT_CUSTOMER_DELETED = 'customer.deleted.v1'\n// Legacy alias (kept so Prompt 1 seed code continues to compile).\nexport const EVT_CUSTOMER_REGISTERED = EVT_CUSTOMER_CREATED\n\n// Cart\nexport const EVT_CART_CREATED = 'cart.created.v1'\nexport const EVT_CART_ITEM_ADDED = 'cart.item_added.v1'\nexport const EVT_CART_ITEM_REMOVED = 'cart.item_removed.v1'\nexport const EVT_CART_UPDATED = 'cart.updated.v1'\nexport const EVT_CART_ABANDONED = 'cart.abandoned.v1'\nexport const EVT_CART_CONVERTED = 'cart.converted.v1'\n\n// Payment\nexport const EVT_PAYMENT_INTENT_CREATED = 'payment.intent_created.v1'\nexport const EVT_PAYMENT_CAPTURED = 'payment.captured.v1'\nexport const EVT_PAYMENT_FAILED = 'payment.failed.v1'\nexport const EVT_PAYMENT_REFUNDED = 'payment.refunded.v1'\n\n// System\nexport const EVT_ADAPTER_ERROR = 'adapter.error.v1'\nexport const EVT_SETTINGS_CHANGED = 'settings.changed.v1'\n\nexport const STANDARD_EVENT_TYPES = [\n EVT_PRODUCT_CREATED,\n EVT_PRODUCT_UPDATED,\n EVT_PRODUCT_DELETED,\n EVT_PRODUCT_PUBLISHED,\n EVT_PRODUCT_UNPUBLISHED,\n EVT_CATEGORY_CREATED,\n EVT_CATEGORY_UPDATED,\n EVT_CATEGORY_DELETED,\n EVT_ORDER_PLACED,\n EVT_ORDER_PAID,\n EVT_ORDER_FULFILLED,\n EVT_ORDER_SHIPPED,\n EVT_ORDER_DELIVERED,\n EVT_ORDER_CANCELLED,\n EVT_ORDER_REFUNDED,\n EVT_ORDER_PARTIALLY_REFUNDED,\n EVT_ORDER_STATUS_CHANGED,\n EVT_INVENTORY_CHANGED,\n EVT_INVENTORY_RESERVED,\n EVT_INVENTORY_RELEASED,\n EVT_INVENTORY_LOW_STOCK,\n EVT_INVENTORY_OUT_OF_STOCK,\n EVT_CUSTOMER_CREATED,\n EVT_CUSTOMER_UPDATED,\n EVT_CUSTOMER_DELETED,\n EVT_CART_CREATED,\n EVT_CART_ITEM_ADDED,\n EVT_CART_ITEM_REMOVED,\n EVT_CART_UPDATED,\n EVT_CART_ABANDONED,\n EVT_CART_CONVERTED,\n EVT_PAYMENT_INTENT_CREATED,\n EVT_PAYMENT_CAPTURED,\n EVT_PAYMENT_FAILED,\n EVT_PAYMENT_REFUNDED,\n EVT_ADAPTER_ERROR,\n EVT_SETTINGS_CHANGED,\n] as const\n","import type { Logger } from './http.js'\n\nexport type HealthStatus = 'healthy' | 'degraded' | 'down'\n\nexport interface HealthReport {\n status: HealthStatus\n detail?: string\n}\n\n/** How long a health check may run before it reports `down`. */\nexport const HEALTH_CHECK_TIMEOUT_MS = 5000\n\nexport interface BoundedHealthCheckOptions {\n /** `HEALTH_CHECK_TIMEOUT_MS` when not given. */\n timeoutMs?: number\n /** Values an error's message must never show (a URL's user, password or query values); each becomes `***`. */\n redact?: readonly string[]\n}\n\n/** An error's message for a report; an AggregateError (a refused connection to every address of a name) lists its own. */\nexport function errorText(error: unknown): string {\n if (!(error instanceof Error)) return String(error)\n if (error.message !== '') return error.message\n const inner = (error as { errors?: unknown }).errors\n if (Array.isArray(inner) && inner.length > 0) return inner.map(errorText).join('; ')\n const code = (error as { code?: unknown }).code\n return typeof code === 'string' ? code : error.name\n}\n\n/**\n * The shortest value worth removing from text as a secret. A shorter one is too likely to be an ordinary word (a local\n * password of `nymbal`), whose removal garbles the text; user info and query strings go with the URL rewrite anyway.\n */\nexport const SECRET_MIN_LENGTH = 8\n\n/**\n * The values in a connection URL that are credentials or may carry one: its password and its query values, and its\n * user unless `user` is false, each as written and decoded, if at least `SECRET_MIN_LENGTH` long. A user is worth\n * hiding in a driver's error, not in our own text.\n */\nexport function connectionSecrets(url: string, options: { user?: boolean } = {}): string[] {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return []\n }\n const raw = [\n ...(options.user === false ? [] : [parsed.username]),\n parsed.password,\n ...parsed.searchParams.values(),\n ]\n const decoded = raw.map((value) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n })\n return [...new Set([...raw, ...decoded])].filter((value) => value.length >= SECRET_MIN_LENGTH)\n}\n\n/**\n * A connection URL as it may be printed: `scheme://host:port/path`, with no user, password, query or fragment. Text\n * that is not a URL comes back as `<unparsable url>`, since it may be a URL with a secret the parser could not read.\n */\nexport function displayUrl(url: string): string {\n try {\n const parsed = new URL(url)\n return `${parsed.protocol}//${parsed.host}${parsed.pathname === '/' ? '' : parsed.pathname}`\n } catch {\n return '<unparsable url>'\n }\n}\n\n/**\n * Runs `check` within the bound. A check that throws is `down` with the error's message, every `redact` value in it\n * replaced; one still running when the bound passes is `down` with `timed out after <n> ms`, and its signal aborted.\n */\nexport async function boundedHealthCheck(\n check: (signal: AbortSignal) => Promise<HealthReport> | HealthReport,\n options: BoundedHealthCheckOptions = {},\n): Promise<HealthReport> {\n const timeoutMs = options.timeoutMs ?? HEALTH_CHECK_TIMEOUT_MS\n const controller = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const timedOut = new Promise<HealthReport>((resolve) => {\n timer = setTimeout(() => {\n controller.abort()\n resolve({ status: 'down', detail: `timed out after ${timeoutMs} ms` })\n }, timeoutMs)\n })\n const failed = (error: unknown): HealthReport => {\n let detail = errorText(error)\n for (const secret of options.redact ?? []) {\n if (secret !== '') detail = detail.split(secret).join('***')\n }\n return { status: 'down', detail }\n }\n try {\n // Started inside a promise so a check that throws before returning one, or returns a plain report, is still\n // mapped rather than escaping the caller.\n const checked = Promise.resolve()\n .then(() => check(controller.signal))\n .catch(failed)\n return await Promise.race([checked, timedOut])\n } finally {\n clearTimeout(timer)\n }\n}\n\nexport interface AdapterBase {\n /** What this adapter offers, for the app's per-slot report; each adapter answers for itself. */\n capabilities(): readonly string[]\n readonly producesEvents: readonly string[]\n readonly consumesEvents: readonly string[]\n initialize(config: unknown): Promise<void>\n healthCheck(): Promise<HealthReport>\n}\n\n/** The commerce slots a store's config names a provider for. */\nexport type CommerceSlot =\n | 'payments'\n | 'email'\n | 'reviews'\n | 'search'\n | 'analytics'\n | 'shipping'\n | 'tax'\n | 'ai'\n\n/**\n * Every slot an adapter package can fill: the commerce slots, the infrastructure primitives and `security`, whose\n * choice is `security.adapter`.\n */\nexport type Slot = CommerceSlot | 'documentStore' | 'eventBus' | 'media' | 'security'\n\n/**\n * What the kernel hands an adapter package's `createAdapter`. A package that serves more than one slot switches on\n * `slot`; `config` is a commerce slot's `config` block from nymbal.config.ts, a primitive's connection block, or for\n * `security` the block named after the adapter (`security.cloudflare`).\n */\nexport interface AdapterContext {\n slot: Slot\n config: Record<string, unknown>\n logger: Logger\n env: NodeJS.ProcessEnv\n}\n\n/**\n * One field of a slot's config block that a store owner may fill from the admin's Settings, as a provider package\n * declares it in its `settings` export. The config block wins, then `env`, then a saved value.\n */\nexport interface AdapterSetting {\n /** A dot path inside the slot's config block: `secretKey`, `smtp.pass`. */\n path: string\n label: string\n /** Stored encrypted and never shown again once saved. */\n secret: boolean\n /** The environment variable that sets the field when the config block does not. */\n env?: string\n /**\n * The adapter works without it (SMTP without authentication), so a secret left unset does not leave the slot\n * unconfigured.\n */\n optional?: boolean\n}\n\n/** What a provider package exports: `createAdapter`, and the fields it lets Settings fill. */\nexport interface ProviderPackageModule {\n createAdapter(ctx: AdapterContext): unknown\n settings?: readonly AdapterSetting[]\n}\n","import { ValidationError } from '../errors.js'\nimport type { QueryParams, QueryResult } from './document-store.js'\n\n// The one order and filter every document store is held to: the in-memory store runs it, and stores that cannot\n// order or filter the same way on their server fetch candidates and run it in the process.\n\nconst collator = new Intl.Collator(undefined, { sensitivity: 'base' })\n\n/** Number, then string, then any other type, then null and missing: so values of different types never tie. */\nfunction rankOf(value: unknown): number {\n if (typeof value === 'number') return 0\n if (typeof value === 'string') return 1\n if (value === null || value === undefined) return 3\n return 2\n}\n\nfunction sign(n: number): number {\n return n > 0 ? 1 : n < 0 ? -1 : 0\n}\n\nfunction compareOther(a: unknown, b: unknown): number {\n const ta = typeof a\n const tb = typeof b\n if (ta !== tb) return ta < tb ? -1 : 1\n if (ta === 'boolean') return Number(a) - Number(b)\n const ja = JSON.stringify(a) ?? ''\n const jb = JSON.stringify(b) ?? ''\n return ja < jb ? -1 : ja > jb ? 1 : 0\n}\n\n/**\n * A total order over document values: numbers numerically, strings by `Intl.Collator` at base sensitivity (case and\n * accents tie), other types after strings, and null or missing last. Values of different types rank by type first.\n */\nexport function compareDocumentValues(a: unknown, b: unknown): number {\n const ra = rankOf(a)\n const rb = rankOf(b)\n if (ra !== rb) return ra - rb\n switch (ra) {\n case 0:\n return sign((a as number) - (b as number))\n case 1:\n return sign(collator.compare(a as string, b as string))\n case 2:\n return sign(compareOther(a, b))\n default:\n return 0\n }\n}\n\nexport type SortKeyCondition = Pick<NonNullable<QueryParams['sortKey']>, 'operator' | 'value'>\n\n/** `eq` is `===`; `gt`, `lt` and `between` (inclusive, `[low, high]`) are `compareDocumentValues`. */\nexport function matchesSortKey(value: unknown, condition: SortKeyCondition): boolean {\n const target = condition.value\n switch (condition.operator) {\n case 'eq':\n return value === target\n case 'gt':\n return compareDocumentValues(value, target) > 0\n case 'lt':\n return compareDocumentValues(value, target) < 0\n case 'between': {\n if (!Array.isArray(target) || target.length !== 2) return false\n const [low, high] = target as [unknown, unknown]\n return compareDocumentValues(value, low) >= 0 && compareDocumentValues(value, high) <= 0\n }\n default:\n return false\n }\n}\n\n/** The value at a dotted path (`priceRange.minMinor`); missing anywhere along it is `undefined`. */\nexport function documentValueAt(doc: unknown, path: string): unknown {\n let value: unknown = doc\n for (const part of path.split('.')) {\n if (value === null || typeof value !== 'object') return undefined\n value = (value as Record<string, unknown>)[part]\n }\n return value\n}\n\nexport const DEFAULT_QUERY_LIMIT = 50\n\ninterface Order {\n /** The field ordered by, or null for the document key alone. */\n field: string | null\n direction: 'asc' | 'desc'\n}\n\ninterface Position {\n key: string\n value: unknown\n}\n\nfunction orderOf(params: QueryParams): Order {\n if (params.orderBy !== undefined) {\n return { field: params.orderBy.field, direction: params.orderBy.direction }\n }\n const direction = (params.ascending ?? true) ? 'asc' : 'desc'\n return { field: params.sortKey?.field ?? null, direction }\n}\n\nconst isNullish = (value: unknown): boolean => value === null || value === undefined\n\n/** Nulls and missing values last in either direction; ties by key ascending, so no two documents tie. */\nfunction comparePositions(a: Position, b: Position, order: Order): number {\n if (order.field !== null) {\n const an = isNullish(a.value)\n const bn = isNullish(b.value)\n if (an !== bn) return an ? 1 : -1\n let c = an ? 0 : compareDocumentValues(a.value, b.value)\n if (order.direction === 'desc') c = -c\n if (c !== 0) return c\n return a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n }\n const c = a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n return order.direction === 'desc' ? -c : c\n}\n\n/** What a cursor is bound to: the order and the partition, so a cursor reused under another is refused. */\nfunction cursorScope(order: Order, params: QueryParams): string {\n return JSON.stringify([order.field, order.direction, params.partitionKey])\n}\n\nfunction decodeCursor(cursor: string, scope: string): Position {\n let decoded: unknown\n try {\n decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'))\n } catch {\n decoded = null\n }\n const c = decoded as { o?: unknown; k?: unknown; v?: unknown } | null\n if (c === null || typeof c !== 'object' || typeof c.o !== 'string' || typeof c.k !== 'string') {\n throw new ValidationError('The cursor is not valid', { context: { cursor } })\n }\n if (c.o !== scope) {\n throw new ValidationError('The cursor was issued under another order', { context: { cursor } })\n }\n return { key: c.k, value: c.v ?? null }\n}\n\n/**\n * One page of `params` over the documents of a collection: filter by partition (`'*'` for all) and sort key, order,\n * then seek past the cursor by value, so a document deleted between pages neither restarts nor repeats the walk.\n */\nexport function queryDocuments<T>(\n documents: Iterable<readonly [string, unknown]>,\n params: QueryParams,\n): QueryResult<T> {\n const limit = params.limit ?? DEFAULT_QUERY_LIMIT\n const order = orderOf(params)\n const scope = cursorScope(order, params)\n const after = params.cursor ? decodeCursor(params.cursor, scope) : null\n const { partitionKey, sortKey } = params\n\n const matches: Array<{ position: Position; doc: unknown }> = []\n for (const [key, doc] of documents) {\n const record = (doc ?? {}) as Record<string, unknown>\n if (partitionKey.value !== '*' && record[partitionKey.field] !== partitionKey.value) continue\n if (sortKey !== undefined && !matchesSortKey(record[sortKey.field], sortKey)) continue\n const value = order.field === null ? null : (documentValueAt(record, order.field) ?? null)\n const position = { key, value }\n if (after !== null && comparePositions(position, after, order) <= 0) continue\n matches.push({ position, doc })\n }\n matches.sort((a, b) => comparePositions(a.position, b.position, order))\n\n const page = matches.slice(0, limit)\n const last = page[page.length - 1]\n const nextCursor =\n matches.length > limit && last !== undefined\n ? Buffer.from(\n JSON.stringify({ o: scope, k: last.position.key, v: last.position.value }),\n 'utf8',\n ).toString('base64url')\n : null\n return { items: page.map((m) => m.doc as T), nextCursor }\n}\n","export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'\n\n/** The request header a caller may name its own correlation id in, and the response header that echoes the one used. */\nexport const CORRELATION_ID_HEADER = 'x-correlation-id'\n\nconst CORRELATION_ID = /^[A-Za-z0-9._:-]{1,128}$/\n\n/**\n * Whether `s` may be used as a correlation id: it reaches logs and the event log, so an inbound value that fails is\n * replaced, never cleaned. The id is a label only; nothing reads it to decide access.\n */\nexport function isValidCorrelationId(s: string): boolean {\n return CORRELATION_ID.test(s)\n}\n\nexport interface RequestAuth {\n userId?: string\n roles?: string[]\n token?: string\n}\n\nexport interface Logger {\n trace(obj: unknown, msg?: string): void\n debug(obj: unknown, msg?: string): void\n info(obj: unknown, msg?: string): void\n warn(obj: unknown, msg?: string): void\n error(obj: unknown, msg?: string): void\n child(bindings: Record<string, unknown>): Logger\n}\n\nexport interface RequestContext<TBody = unknown, TQuery = Record<string, string | string[] | undefined>, TParams = Record<string, string>> {\n method: HttpMethod\n path: string\n url: string\n params: TParams\n query: TQuery\n body: TBody\n rawBody?: Buffer\n headers: Record<string, string | string[] | undefined>\n cookies: Record<string, string>\n auth?: RequestAuth\n requestId: string\n /** The inbound `x-correlation-id` when `isValidCorrelationId` accepts it, else the request id. */\n correlationId: string\n logger: Logger\n ip: string\n}\n\nexport interface ResponseCookie {\n name: string\n value: string\n path?: string\n domain?: string\n maxAge?: number\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n}\n\nexport interface ResponseEnvelope<T = unknown> {\n status: number\n headers?: Record<string, string>\n body?: T\n cookies?: ResponseCookie[]\n}\n\nexport type RouteHandler<TBody = unknown, TQuery = Record<string, string | string[] | undefined>, TParams = Record<string, string>, TResponse = unknown> = (\n ctx: RequestContext<TBody, TQuery, TParams>,\n) => Promise<ResponseEnvelope<TResponse>> | ResponseEnvelope<TResponse>\n\nexport interface RouteOptions {\n name?: string\n csrf?: boolean\n rateLimit?: string\n rawBody?: boolean\n}\n\nexport type MiddlewareHandler = (\n ctx: RequestContext,\n) => Promise<ResponseEnvelope | void> | ResponseEnvelope | void\n\nexport interface SseEvent {\n id?: string\n event?: string\n data: unknown\n}\n\nexport interface StreamContext {\n send(event: SseEvent): void\n close(): void\n onClose(callback: () => void): void\n}\n\nexport type StreamHandler = (\n ctx: RequestContext,\n stream: StreamContext,\n) => Promise<void> | void\n\nexport interface HttpAdapter {\n registerRoute(\n method: HttpMethod,\n path: string,\n handler: RouteHandler,\n options?: RouteOptions,\n ): void\n registerStreamRoute(\n method: 'GET',\n path: string,\n handler: StreamHandler,\n options?: RouteOptions,\n ): void\n registerMiddleware(middleware: MiddlewareHandler): void\n setDefaultHeaders(headers: Record<string, string>): void\n start(port: number, host?: string): Promise<void>\n stop(): Promise<void>\n}\n","import type { AdapterBase } from './adapter-base.js'\nimport type { HttpAdapter } from './http.js'\n\nexport type RateLimitAction = 'block' | 'challenge' | 'throttle'\n\nexport interface RateLimitRule {\n requests: number\n window: string\n action: RateLimitAction\n}\n\nexport interface RateLimitDecision {\n allowed: boolean\n retryAfterSeconds?: number\n action: RateLimitAction\n}\n\nexport interface SecurityConfigLike {\n rateLimit: Record<string, RateLimitRule>\n csrf: boolean\n headers: {\n hsts: boolean\n contentSecurityPolicy: string\n referrerPolicy: string\n xFrameOptions: string\n }\n [key: string]: unknown\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: Array<{ code: string; message: string; path?: string }>\n}\n\nexport interface DriftReport {\n drift: boolean\n differences: Array<{ path: string; configured: unknown; actual: unknown }>\n}\n\nexport interface SecurityState {\n rulesApplied: string[]\n lastValidatedAt: string\n}\n\nexport interface SecurityAdapter extends AdapterBase {\n applyMiddleware(app: HttpAdapter, config: SecurityConfigLike): Promise<void>\n validate(config: SecurityConfigLike): Promise<ValidationResult>\n drift?(config: SecurityConfigLike): Promise<DriftReport>\n inspect?(): Promise<SecurityState>\n}\n\n/** The response headers every security adapter sets in process, from the config's `headers` block. */\nexport function buildSecurityHeaders(security: SecurityConfigLike): Record<string, string> {\n const headers: Record<string, string> = {}\n if (security.headers.hsts) {\n headers['strict-transport-security'] = 'max-age=31536000; includeSubDomains'\n }\n const csp = security.headers.contentSecurityPolicy\n const cspValue =\n csp === 'strict'\n ? \"default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'\"\n : csp === 'relaxed'\n ? \"default-src 'self' https:; img-src * data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'\"\n : csp === 'off'\n ? null\n : csp\n if (cspValue) headers['content-security-policy'] = cspValue\n headers['referrer-policy'] = security.headers.referrerPolicy\n headers['x-frame-options'] = security.headers.xFrameOptions === 'deny' ? 'DENY' : 'SAMEORIGIN'\n headers['x-content-type-options'] = 'nosniff'\n return headers\n}\n","// The kernel's transactional email templates, shared by every email adapter (the console one and the SMTP package).\nexport interface RenderedEmail {\n subject: string\n text: string\n html: string\n}\n\nfunction render(template: string, vars: Record<string, unknown>): string {\n return template.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, (_, path: string) => {\n const parts = path.split('.')\n let cur: unknown = vars\n for (const p of parts) {\n if (cur && typeof cur === 'object' && p in (cur as Record<string, unknown>)) {\n cur = (cur as Record<string, unknown>)[p]\n } else {\n return ''\n }\n }\n return cur == null ? '' : String(cur)\n })\n}\n\nconst TEMPLATES: Record<string, { subject: string; body: string }> = {\n 'order-placed': {\n subject: 'Order {{orderNumber}} confirmed',\n body: `Hi {{customerName}},\\n\\nThank you for your order {{orderNumber}}. Total: {{totalFormatted}}.\\n\\nWe'll let you know when it ships.`,\n },\n 'order-shipped': {\n subject: 'Order {{orderNumber}} is on its way',\n body: `Hi {{customerName}},\\n\\nYour order {{orderNumber}} has shipped via {{carrier}}.\\nTracking: {{trackingNumber}}`,\n },\n welcome: {\n subject: 'Welcome to {{storeName}}',\n body: `Welcome {{customerName}}! Your account is ready.`,\n },\n 'cart-abandoned': {\n subject: \"Don't forget your cart at {{storeName}}\",\n body: `Hi,\\n\\nYou left items in your cart. Pick up where you left off: {{resumeUrl}}`,\n },\n}\n\nexport function renderEmailTemplate(\n template: string,\n vars: Record<string, unknown>,\n): RenderedEmail {\n const t = TEMPLATES[template]\n if (!t) {\n return {\n subject: `[nymbal] ${template}`,\n text: JSON.stringify(vars),\n html: `<pre>${JSON.stringify(vars, null, 2)}</pre>`,\n }\n }\n const subject = render(t.subject, vars)\n const text = render(t.body, vars)\n const html = `<pre>${text.replace(/</g, '&lt;')}</pre>`\n return { subject, text, html }\n}\n","export type StockLevel = 'out' | 'low' | 'in'\n\n/** Below this many units a variant is low on stock. */\nconst LOW_STOCK_BELOW = 5\n\n/** The one stock rule every storefront renders from. */\nexport function stockLevel(stock: number): StockLevel {\n if (stock <= 0) return 'out'\n return stock < LOW_STOCK_BELOW ? 'low' : 'in'\n}\n\nexport const STOCK_LABELS: Record<StockLevel, string> = {\n out: 'Out of Stock',\n low: 'Low Stock',\n in: 'In Stock',\n}\n","/** Who a command ran for. A guest is `guest:anonymous`: a cart's token is a bearer credential and is never an id. */\nexport interface AuditActor {\n kind: 'admin' | 'customer' | 'guest' | 'system' | 'import'\n id: string\n}\n\n/** `applied`: the command ran and committed. `refused`: its service refused it. `replayed`: a keyed retry answered. */\nexport type AuditOutcome = 'applied' | 'refused' | 'replayed'\n\n/** A record a command touched, by id only. */\nexport interface AuditSubject {\n type: string\n id: string\n}\n\n/**\n * One audit entry as `GET /api/admin/activity` lists it and the exports print it. A stored response is shown by its\n * status only; its body never leaves the store.\n */\nexport interface AuditEntry {\n id: string\n at: string\n actor: AuditActor\n command: string\n subjects: AuditSubject[]\n correlationId: string | null\n idempotencyKey: string | null\n outcome: AuditOutcome\n responseStatus: number | null\n}\n\n/** The request header an admin write carries to be applied once, and the response header a replay carries. */\nexport const IDEMPOTENCY_KEY_HEADER = 'idempotency-key'\nexport const IDEMPOTENT_REPLAYED_HEADER = 'idempotent-replayed'\n\n/** 1-128 characters of `[A-Za-z0-9._:-]`. */\nexport function isValidIdempotencyKey(value: string): boolean {\n return /^[A-Za-z0-9._:-]{1,128}$/.test(value)\n}\n"],"mappings":"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","names":["c"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/domain/money.ts","../src/domain/product.ts","../src/domain/order.ts","../src/domain/order-timeline.ts","../src/events/constants.ts","../src/adapters/adapter-base.ts","../src/adapters/document-order.ts","../src/adapters/event-bus.ts","../src/adapters/http.ts","../src/adapters/security.ts","../src/adapters/webhook-delivery.ts","../src/adapters/email-feedback.ts","../src/adapters/shipping-tracking.ts","../src/adapters/email-templates.ts","../src/stock-level.ts","../src/audit.ts"],"sourcesContent":["export type ErrorContext = Record<string, unknown>\n\nexport class NymbalError extends Error {\n readonly code: string\n readonly context: ErrorContext\n\n constructor(\n code: string,\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = this.constructor.name\n this.code = code\n this.context = options.context ?? {}\n }\n\n toJSON(): { name: string; code: string; message: string; context: ErrorContext } {\n return { name: this.name, code: this.code, message: this.message, context: this.context }\n }\n}\n\nexport class ConfigError extends NymbalError {\n constructor(\n message: string,\n options: {\n cause?: unknown\n context?: ErrorContext\n code?: `config.${string}` | `adapter.${string}`\n } = {},\n ) {\n super(options.code ?? 'config.invalid', message, options)\n }\n}\n\nexport class ValidationError extends NymbalError {\n constructor(message: string, options: { cause?: unknown; context?: ErrorContext } = {}) {\n super('validation.failed', message, options)\n }\n}\n\nexport class NotFoundError extends NymbalError {\n constructor(\n resource: string,\n identifier: unknown,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super('not_found', `${resource} not found: ${String(identifier)}`, {\n ...options,\n context: { resource, identifier, ...(options.context ?? {}) },\n })\n }\n}\n\nexport class AdapterError extends NymbalError {\n constructor(\n code: string,\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`adapter.${code}`, message, options)\n }\n}\n\nexport class HttpError extends NymbalError {\n readonly status: number\n\n constructor(\n status: number,\n message: string,\n options: { cause?: unknown; context?: ErrorContext; code?: string } = {},\n ) {\n super(options.code ?? `http.${status}`, message, options)\n this.status = status\n }\n}\n\nexport class AuthError extends HttpError {\n constructor(\n code: 'unauthorized' | 'forbidden' | 'invalid_credentials' | 'token_expired' | 'token_invalid',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n const status = code === 'forbidden' ? 403 : 401\n super(status, message, { ...options, code: `auth.${code}` })\n }\n}\n\nexport class PaymentError extends NymbalError {\n constructor(\n code:\n | 'intent_failed'\n | 'capture_failed'\n // A definite refusal: no money moved.\n | 'refund_rejected'\n // An outcome the shop cannot know (a timeout, a provider fault): money may have moved.\n | 'refund_unknown'\n | 'cancel_refused'\n | 'webhook_invalid'\n | 'not_configured',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`payment.${code}`, message, options)\n }\n}\n\n/** A command the order's money does not allow (confirm unpaid, cancel paid, refund beyond the capture). */\nexport class OrderConflictError extends NymbalError {\n constructor(\n code:\n | 'order.unpaid'\n | 'order.paid'\n | 'order.stock_unavailable'\n | 'order.payment_in_progress'\n | 'refund.not_captured'\n | 'refund.exceeds_captured'\n | 'refund.request_reused',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(code, message, options)\n }\n}\n\nexport class CheckoutError extends NymbalError {\n constructor(\n code:\n | 'cart_empty'\n | 'in_progress'\n | 'payment_in_progress'\n | 'variant_missing'\n | 'out_of_stock'\n | 'reservation_failed'\n | 'finalize_failed',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`checkout.${code}`, message, options)\n }\n}\n\nexport class StatusTransitionError extends NymbalError {\n constructor(\n entity: string,\n fromStatus: string,\n toStatus: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(\n 'status.invalid_transition',\n `Invalid ${entity} status transition: ${fromStatus} → ${toStatus}`,\n {\n ...options,\n context: { entity, fromStatus, toStatus, ...(options.context ?? {}) },\n },\n )\n }\n}\n","import { ValidationError } from '../errors.js'\n\nexport interface Money {\n amount: number\n currency: string\n}\n\nexport function money(amount: number, currency: string): Money {\n assertMinorUnits(amount)\n return { amount, currency }\n}\n\nexport function assertMinorUnits(amount: number): void {\n if (!Number.isInteger(amount)) {\n throw new ValidationError(`Monetary amount must be an integer (minor units), got ${amount}`)\n }\n}\n\nfunction assertSameCurrency(a: Money, b: Money): void {\n if (a.currency !== b.currency) {\n throw new ValidationError(\n `Currency mismatch: ${a.currency} vs ${b.currency}`,\n )\n }\n}\n\nexport function add(a: Money, b: Money): Money {\n assertSameCurrency(a, b)\n return { amount: a.amount + b.amount, currency: a.currency }\n}\n\nexport function sub(a: Money, b: Money): Money {\n assertSameCurrency(a, b)\n return { amount: a.amount - b.amount, currency: a.currency }\n}\n\nexport function mulBy(m: Money, scalar: number): Money {\n if (!Number.isFinite(scalar)) {\n throw new ValidationError(`Scalar must be finite, got ${scalar}`)\n }\n return { amount: Math.round(m.amount * scalar), currency: m.currency }\n}\n\nexport function sumMoney(values: Money[], currency: string): Money {\n const total = values.reduce((acc, m) => {\n assertSameCurrency(m, { amount: 0, currency })\n return acc + m.amount\n }, 0)\n return { amount: total, currency }\n}\n\nexport function allocate(m: Money, weights: number[]): Money[] {\n if (weights.length === 0) return []\n const totalWeight = weights.reduce((s, w) => s + w, 0)\n if (totalWeight <= 0) {\n throw new ValidationError('allocate: sum of weights must be > 0')\n }\n const portions = weights.map((w) => Math.floor((m.amount * w) / totalWeight))\n let remainder = m.amount - portions.reduce((s, p) => s + p, 0)\n const result = portions.slice()\n let idx = 0\n while (remainder !== 0 && idx < result.length) {\n const delta = remainder > 0 ? 1 : -1\n result[idx] = (result[idx] ?? 0) + delta\n remainder -= delta\n idx += 1\n }\n return result.map((amount) => ({ amount, currency: m.currency }))\n}\n","export type ProductStatus = 'draft' | 'active' | 'archived'\nexport type ProductType = 'simple' | 'variable'\nexport type VariantStatus = 'active' | 'inactive'\n\nexport interface ProductMedia {\n url: string\n altText: string\n position: number\n}\n\nexport interface VariantOption {\n name: string\n value: string\n}\n\nexport interface VariantDimensions {\n lengthMm?: number\n widthMm?: number\n heightMm?: number\n}\n\nexport interface Variant {\n id: string\n productId: string\n sku: string\n name: string\n priceMinor: number\n compareAtPriceMinor: number | null\n weightGrams: number | null\n dimensions: VariantDimensions | null\n stock: number\n lowStockThreshold: number\n options: VariantOption[]\n status: VariantStatus\n createdAt: string\n updatedAt: string\n}\n\nexport interface Product {\n id: string\n slug: string\n name: string\n description: string\n shortDescription: string\n status: ProductStatus\n type: ProductType\n seoTitle: string\n seoDescription: string\n media: ProductMedia[]\n metadata: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\n/** The orders a product listing can be requested in; ties break by slug, ascending. */\nexport const PRODUCT_SORT_KEYS = [\n 'newest',\n 'price-asc',\n 'price-desc',\n 'name-asc',\n 'name-desc',\n] as const\nexport type ProductSortKey = (typeof PRODUCT_SORT_KEYS)[number]\nexport const DEFAULT_PRODUCT_SORT: ProductSortKey = 'newest'\n/** What a storefront shows for each sort. */\nexport const PRODUCT_SORT_LABELS: Readonly<Record<ProductSortKey, string>> = {\n newest: 'Newest',\n 'price-asc': 'Price: Low to High',\n 'price-desc': 'Price: High to Low',\n 'name-asc': 'Name: A to Z',\n 'name-desc': 'Name: Z to A',\n}\n","import { StatusTransitionError } from '../errors.js'\n\nexport type OrderStatus =\n | 'pending'\n | 'confirmed'\n | 'processing'\n | 'shipped'\n | 'delivered'\n | 'cancelled'\n | 'refunded'\n | 'partially_refunded'\n\n// A captured order is refunded from any status between confirmed and delivered; a partial refund leaves room for more.\nexport const ORDER_STATUS_TRANSITIONS: Record<OrderStatus, readonly OrderStatus[]> = {\n pending: ['confirmed', 'cancelled'],\n confirmed: ['processing', 'cancelled', 'refunded', 'partially_refunded'],\n processing: ['shipped', 'cancelled', 'refunded', 'partially_refunded'],\n shipped: ['delivered', 'refunded', 'partially_refunded'],\n delivered: ['refunded', 'partially_refunded'],\n cancelled: [],\n refunded: [],\n partially_refunded: ['refunded'],\n}\n\nexport function canTransitionOrder(from: OrderStatus, to: OrderStatus): boolean {\n return ORDER_STATUS_TRANSITIONS[from].includes(to)\n}\n\nexport function assertOrderTransition(from: OrderStatus, to: OrderStatus): void {\n if (!canTransitionOrder(from, to)) {\n throw new StatusTransitionError('order', from, to)\n }\n}\n\nexport interface Address {\n firstName: string\n lastName: string\n company?: string\n addressLine1: string\n addressLine2?: string\n city: string\n region: string\n postalCode: string\n country: string\n phone?: string\n}\n\nexport interface OrderLineItem {\n variantId: string\n productId: string\n productName: string\n variantName: string\n sku: string\n qty: number\n unitPriceMinor: number\n lineSubtotalMinor: number\n lineTaxMinor: number\n lineTotalMinor: number\n imageUrl: string\n}\n\nexport interface Order {\n id: string\n orderNumber: string\n sequence: number\n customerId: string | null\n status: OrderStatus\n email: string\n billingAddress: Address\n shippingAddress: Address\n lineItems: OrderLineItem[]\n subtotalMinor: number\n taxTotalMinor: number\n shippingTotalMinor: number\n discountTotalMinor: number\n totalMinor: number\n currency: string\n notes: string\n metadata: Record<string, unknown>\n paymentIntentId: string | null\n /** Money taken for the order, in minor units of its currency; 0 while nothing is captured. */\n capturedMinor: number\n capturedAt: string | null\n /** Σ of the order's succeeded refunds (or, for an import or a pre-refund-table order, what was refunded then). */\n refundedMinor: number\n captureMethod: CaptureMethod | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * The request header a shopper's order key travels in to `GET /api/orders/:orderNumber`. Never a query parameter, so\n * the key stays out of access logs that record request URLs.\n */\nexport const ORDER_KEY_HEADER = 'x-nymbal-order-key'\n\n/** The request header that carries the order's email beside an expired order key. Never a query parameter either. */\nexport const ORDER_EMAIL_HEADER = 'x-nymbal-order-email'\n\n/** Who took the money: the payments provider, the merchant recording an offline payment, or an imported history. */\nexport type CaptureMethod = 'provider' | 'manual' | 'import'\n\nexport type OrderRefundStatus = 'pending' | 'succeeded' | 'failed'\n\n/** One refund of an order. `pending` until the provider's answer settles it, and counted against what is refundable. */\nexport interface OrderRefund {\n id: string\n orderId: string\n amountMinor: number\n currency: string\n providerRefundId: string | null\n status: OrderRefundStatus\n reason: string | null\n actor: string\n createdAt: string\n}\n\nexport type PaymentState = 'unpaid' | 'paid' | 'partially_refunded' | 'refunded'\n\n/** What the admin shows about an order's money: derived from the stored amounts and refunds alone. */\nexport interface OrderPayment {\n state: PaymentState\n capturedMinor: number\n refundedMinor: number\n refundableMinor: number\n currency: string\n refunds: OrderRefund[]\n}\n\nexport function paymentState(order: Pick<Order, 'capturedMinor' | 'refundedMinor'>): PaymentState {\n if (order.capturedMinor <= 0) return 'unpaid'\n if (order.refundedMinor <= 0) return 'paid'\n return order.refundedMinor < order.capturedMinor ? 'partially_refunded' : 'refunded'\n}\n\n/** What can still be refunded: the capture less what was refunded and what is still pending with the provider. */\nexport function refundableMinor(\n order: Pick<Order, 'capturedMinor' | 'refundedMinor'>,\n refunds: ReadonlyArray<Pick<OrderRefund, 'amountMinor' | 'status'>>,\n): number {\n const pending = refunds\n .filter((r) => r.status === 'pending')\n .reduce((sum, r) => sum + r.amountMinor, 0)\n return Math.max(0, order.capturedMinor - order.refundedMinor - pending)\n}\n\nexport interface OrderHistoryEntry {\n id: string\n orderId: string\n fromStatus: OrderStatus | null\n toStatus: OrderStatus\n actor: string\n note: string\n timestamp: string\n}\n","import type { Address, Order } from './order.js'\n\n/** The lifecycle events an order's timeline shows, each one from an `order_history` row. */\nexport const ORDER_TIMELINE_KINDS = [\n 'placed',\n 'paid',\n 'shipped',\n 'delivered',\n 'cancelled',\n 'partially_refunded',\n 'refunded',\n] as const\n\nexport type OrderTimelineKind = (typeof ORDER_TIMELINE_KINDS)[number]\n\nexport interface OrderTimelineEntry {\n kind: OrderTimelineKind\n /** ISO 8601, UTC. */\n at: string\n}\n\n/** A shipping address as an order's key holder reads it: no phone, no company. */\nexport type ShopperAddress = Omit<Address, 'phone' | 'company'>\n\n/** What only the order's signed-in owner reads, or the store's own records, beside what its key holder reads. */\ntype OwnerOrStoreField =\n | 'id'\n | 'email'\n | 'billingAddress'\n | 'customerId'\n | 'sequence'\n | 'capturedMinor'\n | 'capturedAt'\n | 'refundedMinor'\n | 'captureMethod'\n | 'notes'\n | 'metadata'\n | 'paymentIntentId'\n | 'updatedAt'\n\n/**\n * An order as a storefront reads it. A key holder reads the confirmation page's fields; its owner's session adds the\n * email, both full addresses and the order's id. Every other field is optional, so an adapter that returns a plain\n * `Order` still fits.\n */\nexport interface ShopperOrder\n extends\n Omit<Order, OwnerOrStoreField | 'shippingAddress'>,\n Partial<Pick<Order, OwnerOrStoreField>> {\n shippingAddress: ShopperAddress\n timeline?: OrderTimelineEntry[]\n}\n","// Product\nexport const EVT_PRODUCT_CREATED = 'product.created.v1'\nexport const EVT_PRODUCT_UPDATED = 'product.updated.v1'\nexport const EVT_PRODUCT_DELETED = 'product.deleted.v1'\nexport const EVT_PRODUCT_PUBLISHED = 'product.published.v1'\nexport const EVT_PRODUCT_UNPUBLISHED = 'product.unpublished.v1'\nexport const EVT_PRODUCT_VIEWED = 'product.viewed.v1'\n/** A variant's stock went from 0 to above 0, in the transaction of the inventory.changed.v1 it accompanies. */\nexport const EVT_PRODUCT_RESTOCKED = 'product.restocked.v1'\n/** A variant's price or compare-at price changed, in the transaction of the product.updated.v1 it accompanies. */\nexport const EVT_PRODUCT_VARIANT_PRICE_CHANGED = 'product.variant_price_changed.v1'\n\n// Category\nexport const EVT_CATEGORY_CREATED = 'category.created.v1'\nexport const EVT_CATEGORY_UPDATED = 'category.updated.v1'\nexport const EVT_CATEGORY_DELETED = 'category.deleted.v1'\n\n// Order\nexport const EVT_ORDER_PLACED = 'order.placed.v1'\nexport const EVT_ORDER_PAID = 'order.paid.v1'\nexport const EVT_ORDER_FULFILLED = 'order.fulfilled.v1'\nexport const EVT_ORDER_SHIPPED = 'order.shipped.v1'\nexport const EVT_ORDER_DELIVERED = 'order.delivered.v1'\nexport const EVT_ORDER_CANCELLED = 'order.cancelled.v1'\nexport const EVT_ORDER_REFUNDED = 'order.refunded.v1'\nexport const EVT_ORDER_PARTIALLY_REFUNDED = 'order.partially_refunded.v1'\nexport const EVT_ORDER_STATUS_CHANGED = 'order.status_changed.v1'\nexport const EVT_ORDER_CONFIRMATION_RESEND_REQUESTED = 'order.confirmation_resend_requested.v1'\n\n// Inventory\nexport const EVT_INVENTORY_CHANGED = 'inventory.changed.v1'\nexport const EVT_INVENTORY_RESERVED = 'inventory.reserved.v1'\nexport const EVT_INVENTORY_RELEASED = 'inventory.released.v1'\nexport const EVT_INVENTORY_LOW_STOCK = 'inventory.low_stock.v1'\nexport const EVT_INVENTORY_OUT_OF_STOCK = 'inventory.out_of_stock.v1'\n\n// Customer\nexport const EVT_CUSTOMER_CREATED = 'customer.created.v1'\nexport const EVT_CUSTOMER_UPDATED = 'customer.updated.v1'\nexport const EVT_CUSTOMER_DELETED = 'customer.deleted.v1'\n// Legacy alias (kept so Prompt 1 seed code continues to compile).\nexport const EVT_CUSTOMER_REGISTERED = EVT_CUSTOMER_CREATED\n\n// Visitor\nexport const EVT_VISITOR_MERGED = 'visitor.merged.v1'\n\n// Cart\nexport const EVT_CART_CREATED = 'cart.created.v1'\nexport const EVT_CART_ITEM_ADDED = 'cart.item_added.v1'\nexport const EVT_CART_ITEM_REMOVED = 'cart.item_removed.v1'\nexport const EVT_CART_UPDATED = 'cart.updated.v1'\nexport const EVT_CART_ABANDONED = 'cart.abandoned.v1'\nexport const EVT_CART_CONVERTED = 'cart.converted.v1'\n\n// Payment\nexport const EVT_PAYMENT_INTENT_CREATED = 'payment.intent_created.v1'\nexport const EVT_PAYMENT_CAPTURED = 'payment.captured.v1'\nexport const EVT_PAYMENT_FAILED = 'payment.failed.v1'\nexport const EVT_PAYMENT_REFUNDED = 'payment.refunded.v1'\n\n// Alerts\nexport const EVT_ALERT_REQUESTED = 'alert.requested.v1'\n/** A subscribe for an alert already active: the subscription stands and nothing is sent. */\nexport const EVT_ALERT_REPEATED = 'alert.repeated.v1'\nexport const EVT_ALERT_CONFIRMED = 'alert.confirmed.v1'\nexport const EVT_ALERT_UNSUBSCRIBED = 'alert.unsubscribed.v1'\nexport const EVT_ALERT_SENT = 'alert.sent.v1'\n\n// System\nexport const EVT_ADAPTER_ERROR = 'adapter.error.v1'\nexport const EVT_SETTINGS_CHANGED = 'settings.changed.v1'\n\nexport const STANDARD_EVENT_TYPES = [\n EVT_PRODUCT_CREATED,\n EVT_PRODUCT_UPDATED,\n EVT_PRODUCT_DELETED,\n EVT_PRODUCT_PUBLISHED,\n EVT_PRODUCT_UNPUBLISHED,\n EVT_PRODUCT_VIEWED,\n EVT_PRODUCT_RESTOCKED,\n EVT_PRODUCT_VARIANT_PRICE_CHANGED,\n EVT_CATEGORY_CREATED,\n EVT_CATEGORY_UPDATED,\n EVT_CATEGORY_DELETED,\n EVT_ORDER_PLACED,\n EVT_ORDER_PAID,\n EVT_ORDER_FULFILLED,\n EVT_ORDER_SHIPPED,\n EVT_ORDER_DELIVERED,\n EVT_ORDER_CANCELLED,\n EVT_ORDER_REFUNDED,\n EVT_ORDER_PARTIALLY_REFUNDED,\n EVT_ORDER_STATUS_CHANGED,\n EVT_ORDER_CONFIRMATION_RESEND_REQUESTED,\n EVT_INVENTORY_CHANGED,\n EVT_INVENTORY_RESERVED,\n EVT_INVENTORY_RELEASED,\n EVT_INVENTORY_LOW_STOCK,\n EVT_INVENTORY_OUT_OF_STOCK,\n EVT_CUSTOMER_CREATED,\n EVT_CUSTOMER_UPDATED,\n EVT_CUSTOMER_DELETED,\n EVT_VISITOR_MERGED,\n EVT_CART_CREATED,\n EVT_CART_ITEM_ADDED,\n EVT_CART_ITEM_REMOVED,\n EVT_CART_UPDATED,\n EVT_CART_ABANDONED,\n EVT_CART_CONVERTED,\n EVT_PAYMENT_INTENT_CREATED,\n EVT_PAYMENT_CAPTURED,\n EVT_PAYMENT_FAILED,\n EVT_PAYMENT_REFUNDED,\n EVT_ALERT_REQUESTED,\n EVT_ALERT_REPEATED,\n EVT_ALERT_CONFIRMED,\n EVT_ALERT_UNSUBSCRIBED,\n EVT_ALERT_SENT,\n EVT_ADAPTER_ERROR,\n EVT_SETTINGS_CHANGED,\n] as const\n","import type { Logger } from './http.js'\n\nexport type HealthStatus = 'healthy' | 'degraded' | 'down'\n\nexport interface HealthReport {\n status: HealthStatus\n detail?: string\n}\n\n/** How long a health check may run before it reports `down`. */\nexport const HEALTH_CHECK_TIMEOUT_MS = 5000\n\nexport interface BoundedHealthCheckOptions {\n /** `HEALTH_CHECK_TIMEOUT_MS` when not given. */\n timeoutMs?: number\n /** Values an error's message must never show (a URL's user, password or query values); each becomes `***`. */\n redact?: readonly string[]\n}\n\n/** An error's message for a report; an AggregateError (a refused connection to every address of a name) lists its own. */\nexport function errorText(error: unknown): string {\n if (!(error instanceof Error)) return String(error)\n if (error.message !== '') return error.message\n const inner = (error as { errors?: unknown }).errors\n if (Array.isArray(inner) && inner.length > 0) return inner.map(errorText).join('; ')\n const code = (error as { code?: unknown }).code\n return typeof code === 'string' ? code : error.name\n}\n\n/**\n * The shortest value worth removing from text as a secret. A shorter one is too likely to be an ordinary word (a local\n * password of `nymbal`), whose removal garbles the text; user info and query strings go with the URL rewrite anyway.\n */\nexport const SECRET_MIN_LENGTH = 8\n\n/**\n * The values in a connection URL that are credentials or may carry one: its password and its query values, and its\n * user unless `user` is false, each as written and decoded, if at least `SECRET_MIN_LENGTH` long. A user is worth\n * hiding in a driver's error, not in our own text.\n */\nexport function connectionSecrets(url: string, options: { user?: boolean } = {}): string[] {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return []\n }\n const raw = [\n ...(options.user === false ? [] : [parsed.username]),\n parsed.password,\n ...parsed.searchParams.values(),\n ]\n const decoded = raw.map((value) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n })\n return [...new Set([...raw, ...decoded])].filter((value) => value.length >= SECRET_MIN_LENGTH)\n}\n\n/**\n * A connection URL as it may be printed: `scheme://host:port/path`, with no user, password, query or fragment. Text\n * that is not a URL comes back as `<unparsable url>`, since it may be a URL with a secret the parser could not read.\n */\nexport function displayUrl(url: string): string {\n try {\n const parsed = new URL(url)\n return `${parsed.protocol}//${parsed.host}${parsed.pathname === '/' ? '' : parsed.pathname}`\n } catch {\n return '<unparsable url>'\n }\n}\n\n/**\n * Runs `check` within the bound. A check that throws is `down` with the error's message, every `redact` value in it\n * replaced; one still running when the bound passes is `down` with `timed out after <n> ms`, and its signal aborted.\n */\nexport async function boundedHealthCheck(\n check: (signal: AbortSignal) => Promise<HealthReport> | HealthReport,\n options: BoundedHealthCheckOptions = {},\n): Promise<HealthReport> {\n const timeoutMs = options.timeoutMs ?? HEALTH_CHECK_TIMEOUT_MS\n const controller = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const timedOut = new Promise<HealthReport>((resolve) => {\n timer = setTimeout(() => {\n controller.abort()\n resolve({ status: 'down', detail: `timed out after ${timeoutMs} ms` })\n }, timeoutMs)\n })\n const failed = (error: unknown): HealthReport => {\n let detail = errorText(error)\n for (const secret of options.redact ?? []) {\n if (secret !== '') detail = detail.split(secret).join('***')\n }\n return { status: 'down', detail }\n }\n try {\n // Started inside a promise so a check that throws before returning one, or returns a plain report, is still\n // mapped rather than escaping the caller.\n const checked = Promise.resolve()\n .then(() => check(controller.signal))\n .catch(failed)\n return await Promise.race([checked, timedOut])\n } finally {\n clearTimeout(timer)\n }\n}\n\nexport interface AdapterBase {\n /** What this adapter offers, for the app's per-slot report; each adapter answers for itself. */\n capabilities(): readonly string[]\n readonly producesEvents: readonly string[]\n readonly consumesEvents: readonly string[]\n initialize(config: unknown): Promise<void>\n healthCheck(): Promise<HealthReport>\n}\n\n/** The commerce slots a store's config names a provider for. */\nexport type CommerceSlot =\n | 'payments'\n | 'email'\n | 'reviews'\n | 'search'\n | 'analytics'\n | 'shipping'\n | 'tax'\n | 'ai'\n\n/**\n * Every slot an adapter package can fill: the commerce slots, the infrastructure primitives and `security`, whose\n * choice is `security.adapter`.\n */\nexport type Slot = CommerceSlot | 'documentStore' | 'eventBus' | 'media' | 'security'\n\n/**\n * What the kernel hands an adapter package's `createAdapter`. A package that serves more than one slot switches on\n * `slot`; `config` is a commerce slot's `config` block from nymbal.config.ts, a primitive's connection block (or a\n * third-party primitive's `config`), or for `security` the block named after the adapter (`security.cloudflare`) or a\n * third-party adapter's `security.config`.\n */\nexport interface AdapterContext {\n slot: Slot\n config: Record<string, unknown>\n logger: Logger\n env: NodeJS.ProcessEnv\n}\n\n/**\n * One field of a slot's config block that a store owner may fill from the admin's Settings, as a provider package\n * declares it in its `settings` export. The config block wins, then `env`, then a saved value.\n */\nexport interface AdapterSetting {\n /** A dot path inside the slot's config block: `secretKey`, `smtp.pass`. */\n path: string\n label: string\n /** Stored encrypted and never shown again once saved. */\n secret: boolean\n /** The environment variable that sets the field when the config block does not. */\n env?: string\n /**\n * The adapter works without it (SMTP without authentication), so a secret left unset does not leave the slot\n * unconfigured.\n */\n optional?: boolean\n}\n\n/**\n * What a provider package exports: `createAdapter`, the slot and provider it serves (checked when it is loaded), and\n * the fields it lets Settings fill.\n */\nexport interface ProviderPackageModule {\n createAdapter(ctx: AdapterContext): unknown\n slot: Slot\n provider: string\n settings?: readonly AdapterSetting[]\n}\n\n/**\n * How `nymbal.config.ts` names an infrastructure slot's adapter (`documentStore`, `eventBus`, `media`): the provider,\n * the npm package it comes from when not `@nymbalhq/<slot>-<provider>`, and a third-party adapter's `ctx.config`.\n */\nexport interface InfrastructureBlock {\n provider: string\n package?: string\n config?: Record<string, unknown>\n}\n","import { ValidationError } from '../errors.js'\nimport type { QueryParams, QueryResult } from './document-store.js'\n\n// The one order and filter every document store is held to: the in-memory store runs it, and stores that cannot\n// order or filter the same way on their server fetch candidates and run it in the process.\n\n// Made on first use: a top-level `new` is a side effect bundlers keep, so every storefront bundle that imports\n// these types would carry it.\nlet collator: Intl.Collator | undefined\n\n/** Number, then string, then any other type, then null and missing: so values of different types never tie. */\nfunction rankOf(value: unknown): number {\n if (typeof value === 'number') return 0\n if (typeof value === 'string') return 1\n if (value === null || value === undefined) return 3\n return 2\n}\n\nfunction sign(n: number): number {\n return n > 0 ? 1 : n < 0 ? -1 : 0\n}\n\nfunction compareOther(a: unknown, b: unknown): number {\n const ta = typeof a\n const tb = typeof b\n if (ta !== tb) return ta < tb ? -1 : 1\n if (ta === 'boolean') return Number(a) - Number(b)\n const ja = JSON.stringify(a) ?? ''\n const jb = JSON.stringify(b) ?? ''\n return ja < jb ? -1 : ja > jb ? 1 : 0\n}\n\n/**\n * A total order over document values: numbers numerically, strings by `Intl.Collator` at base sensitivity (case and\n * accents tie), other types after strings, and null or missing last. Values of different types rank by type first.\n */\nexport function compareDocumentValues(a: unknown, b: unknown): number {\n const ra = rankOf(a)\n const rb = rankOf(b)\n if (ra !== rb) return ra - rb\n switch (ra) {\n case 0:\n return sign((a as number) - (b as number))\n case 1:\n collator ??= new Intl.Collator(undefined, { sensitivity: 'base' })\n return sign(collator.compare(a as string, b as string))\n case 2:\n return sign(compareOther(a, b))\n default:\n return 0\n }\n}\n\nexport type SortKeyCondition = Pick<NonNullable<QueryParams['sortKey']>, 'operator' | 'value'>\n\n/** `eq` is `===`; `gt`, `lt` and `between` (inclusive, `[low, high]`) are `compareDocumentValues`. */\nexport function matchesSortKey(value: unknown, condition: SortKeyCondition): boolean {\n const target = condition.value\n switch (condition.operator) {\n case 'eq':\n return value === target\n case 'gt':\n return compareDocumentValues(value, target) > 0\n case 'lt':\n return compareDocumentValues(value, target) < 0\n case 'between': {\n if (!Array.isArray(target) || target.length !== 2) return false\n const [low, high] = target as [unknown, unknown]\n return compareDocumentValues(value, low) >= 0 && compareDocumentValues(value, high) <= 0\n }\n default:\n return false\n }\n}\n\n/** The value at a dotted path (`priceRange.minMinor`); missing anywhere along it is `undefined`. */\nexport function documentValueAt(doc: unknown, path: string): unknown {\n let value: unknown = doc\n for (const part of path.split('.')) {\n if (value === null || typeof value !== 'object') return undefined\n value = (value as Record<string, unknown>)[part]\n }\n return value\n}\n\nexport const DEFAULT_QUERY_LIMIT = 50\n\ninterface Order {\n /** The field ordered by, or null for the document key alone. */\n field: string | null\n direction: 'asc' | 'desc'\n}\n\ninterface Position {\n key: string\n value: unknown\n}\n\nfunction orderOf(params: QueryParams): Order {\n if (params.orderBy !== undefined) {\n return { field: params.orderBy.field, direction: params.orderBy.direction }\n }\n const direction = (params.ascending ?? true) ? 'asc' : 'desc'\n return { field: params.sortKey?.field ?? null, direction }\n}\n\nconst isNullish = (value: unknown): boolean => value === null || value === undefined\n\n/** Nulls and missing values last in either direction; ties by key ascending, so no two documents tie. */\nfunction comparePositions(a: Position, b: Position, order: Order): number {\n if (order.field !== null) {\n const an = isNullish(a.value)\n const bn = isNullish(b.value)\n if (an !== bn) return an ? 1 : -1\n let c = an ? 0 : compareDocumentValues(a.value, b.value)\n if (order.direction === 'desc') c = -c\n if (c !== 0) return c\n return a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n }\n const c = a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n return order.direction === 'desc' ? -c : c\n}\n\n/** What a cursor is bound to: the order and the partition, so a cursor reused under another is refused. */\nfunction cursorScope(order: Order, params: QueryParams): string {\n return JSON.stringify([order.field, order.direction, params.partitionKey])\n}\n\nfunction decodeCursor(cursor: string, scope: string): Position {\n let decoded: unknown\n try {\n decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'))\n } catch {\n decoded = null\n }\n const c = decoded as { o?: unknown; k?: unknown; v?: unknown } | null\n if (c === null || typeof c !== 'object' || typeof c.o !== 'string' || typeof c.k !== 'string') {\n throw new ValidationError('The cursor is not valid', { context: { cursor } })\n }\n if (c.o !== scope) {\n throw new ValidationError('The cursor was issued under another order', { context: { cursor } })\n }\n return { key: c.k, value: c.v ?? null }\n}\n\n/**\n * One page of `params` over the documents of a collection: filter by partition (`'*'` for all) and sort key, order,\n * then seek past the cursor by value, so a document deleted between pages neither restarts nor repeats the walk.\n */\nexport function queryDocuments<T>(\n documents: Iterable<readonly [string, unknown]>,\n params: QueryParams,\n): QueryResult<T> {\n const limit = params.limit ?? DEFAULT_QUERY_LIMIT\n const order = orderOf(params)\n const scope = cursorScope(order, params)\n const after = params.cursor ? decodeCursor(params.cursor, scope) : null\n const { partitionKey, sortKey } = params\n\n const matches: Array<{ position: Position; doc: unknown }> = []\n for (const [key, doc] of documents) {\n const record = (doc ?? {}) as Record<string, unknown>\n if (partitionKey.value !== '*' && record[partitionKey.field] !== partitionKey.value) continue\n if (sortKey !== undefined && !matchesSortKey(record[sortKey.field], sortKey)) continue\n const value = order.field === null ? null : (documentValueAt(record, order.field) ?? null)\n const position = { key, value }\n if (after !== null && comparePositions(position, after, order) <= 0) continue\n matches.push({ position, doc })\n }\n matches.sort((a, b) => comparePositions(a.position, b.position, order))\n\n const page = matches.slice(0, limit)\n const last = page[page.length - 1]\n const nextCursor =\n matches.length > limit && last !== undefined\n ? Buffer.from(\n JSON.stringify({ o: scope, k: last.position.key, v: last.position.value }),\n 'utf8',\n ).toString('base64url')\n : null\n return { items: page.map((m) => m.doc as T), nextCursor }\n}\n","import { AdapterError } from '../errors.js'\nimport type { NymbalEvent, EventHandler } from '../events/envelope.js'\nimport type { HealthReport } from './adapter-base.js'\n\nexport interface RetryPolicy {\n maxAttempts: number\n backoff: 'exponential' | 'linear' | 'fixed'\n initialDelayMs?: number\n}\n\nexport interface SubscriptionConfig {\n retryPolicy?: RetryPolicy\n deadLetterQueue?: string\n batchSize?: number\n /**\n * A named subscription is handled once per name across every bus instance (one consumer group); an unnamed one is\n * handled by every subscriber on every instance. On a single-process bus the two are the same.\n */\n name?: string\n /** The subscriber only maintains derived state (a projection, a search index): it also receives `replay`. */\n derived?: boolean\n}\n\nexport interface Subscription {\n id: string\n patterns: string[]\n unsubscribe(): Promise<void>\n}\n\n/** A named subscription whose handler threw on an event. */\nexport interface HandlerFailure {\n subscription: string\n message: string\n}\n\n/** What `publish` rejects with when a named subscription's handler threw: code `adapter.handler_failed`. */\nexport function handlerFailedError(\n event: NymbalEvent<unknown>,\n failed: HandlerFailure[],\n): AdapterError {\n return new AdapterError(\n 'handler_failed',\n `${event.type} (${event.id}) failed in ${failed.map((f) => f.subscription).join(', ')}`,\n { context: { eventId: event.id, eventType: event.type, failed } },\n )\n}\n\n/**\n * Delivery is at least once: a failed delivery is published again, to every matching subscription, so every handler\n * is idempotent (projections overwrite their documents, side effects use `withIdempotency`). The bus never retries a\n * failed handler by itself.\n */\nexport interface EventBusAdapter {\n capabilities(): readonly string[]\n /**\n * Resolves only after every matching subscription this instance holds has handled the event, so a read straight\n * after a command sees its projections. When a named subscription's handler throws, it rejects with\n * `handlerFailedError` naming each one that threw, once every matching handler has settled. An unnamed subscription's\n * throw is logged and does not reject.\n */\n publish<T>(event: NymbalEvent<T>): Promise<void>\n /** Hands every event out in order, then rejects as `publish` does for the first event a named handler failed. */\n publishBatch(events: NymbalEvent<unknown>[]): Promise<void>\n /**\n * Delivers to `derived` subscriptions only, so rebuilding derived state never triggers side effects. A handler's\n * throw is logged and does not reject.\n */\n replay(events: NymbalEvent<unknown>[]): Promise<void>\n subscribe(\n eventPattern: string | string[],\n handler: EventHandler,\n config?: SubscriptionConfig,\n ): Promise<Subscription>\n unsubscribe(subscription: Subscription): Promise<void>\n close(): Promise<void>\n /**\n * Whether the bus can deliver, within `HEALTH_CHECK_TIMEOUT_MS`. Optional, so a bus written before it existed still\n * type-checks; `nymbal doctor` reports one without it as `no healthCheck`.\n */\n healthCheck?(): Promise<HealthReport>\n}\n","export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'\n\n/** The request header a caller may name its own correlation id in, and the response header that echoes the one used. */\nexport const CORRELATION_ID_HEADER = 'x-correlation-id'\n\nconst CORRELATION_ID = /^[A-Za-z0-9._:-]{1,128}$/\n\n/**\n * Whether `s` may be used as a correlation id: it reaches logs and the event log, so an inbound value that fails is\n * replaced, never cleaned. The id is a label only; nothing reads it to decide access.\n */\nexport function isValidCorrelationId(s: string): boolean {\n return CORRELATION_ID.test(s)\n}\n\nexport interface RequestAuth {\n userId?: string\n roles?: string[]\n token?: string\n}\n\nexport interface Logger {\n trace(obj: unknown, msg?: string): void\n debug(obj: unknown, msg?: string): void\n info(obj: unknown, msg?: string): void\n warn(obj: unknown, msg?: string): void\n error(obj: unknown, msg?: string): void\n child(bindings: Record<string, unknown>): Logger\n}\n\nexport interface RequestContext<TBody = unknown, TQuery = Record<string, string | string[] | undefined>, TParams = Record<string, string>> {\n method: HttpMethod\n path: string\n url: string\n params: TParams\n query: TQuery\n body: TBody\n rawBody?: Buffer\n headers: Record<string, string | string[] | undefined>\n cookies: Record<string, string>\n auth?: RequestAuth\n requestId: string\n /** The inbound `x-correlation-id` when `isValidCorrelationId` accepts it, else the request id. */\n correlationId: string\n logger: Logger\n ip: string\n}\n\nexport interface ResponseCookie {\n name: string\n value: string\n path?: string\n domain?: string\n maxAge?: number\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n}\n\nexport interface ResponseEnvelope<T = unknown> {\n status: number\n headers?: Record<string, string>\n body?: T\n cookies?: ResponseCookie[]\n}\n\nexport type RouteHandler<TBody = unknown, TQuery = Record<string, string | string[] | undefined>, TParams = Record<string, string>, TResponse = unknown> = (\n ctx: RequestContext<TBody, TQuery, TParams>,\n) => Promise<ResponseEnvelope<TResponse>> | ResponseEnvelope<TResponse>\n\nexport interface RouteOptions {\n name?: string\n csrf?: boolean\n rateLimit?: string\n rawBody?: boolean\n}\n\nexport type MiddlewareHandler = (\n ctx: RequestContext,\n) => Promise<ResponseEnvelope | void> | ResponseEnvelope | void\n\nexport interface SseEvent {\n id?: string\n event?: string\n data: unknown\n}\n\nexport interface StreamContext {\n send(event: SseEvent): void\n close(): void\n onClose(callback: () => void): void\n}\n\nexport type StreamHandler = (\n ctx: RequestContext,\n stream: StreamContext,\n) => Promise<void> | void\n\nexport interface HttpAdapter {\n registerRoute(\n method: HttpMethod,\n path: string,\n handler: RouteHandler,\n options?: RouteOptions,\n ): void\n registerStreamRoute(\n method: 'GET',\n path: string,\n handler: StreamHandler,\n options?: RouteOptions,\n ): void\n registerMiddleware(middleware: MiddlewareHandler): void\n setDefaultHeaders(headers: Record<string, string>): void\n start(port: number, host?: string): Promise<void>\n stop(): Promise<void>\n}\n","import type { AdapterBase } from './adapter-base.js'\nimport type { HttpAdapter } from './http.js'\n\nexport type RateLimitAction = 'block' | 'challenge' | 'throttle'\n\nexport interface RateLimitRule {\n requests: number\n window: string\n action: RateLimitAction\n}\n\nexport interface RateLimitDecision {\n allowed: boolean\n retryAfterSeconds?: number\n action: RateLimitAction\n}\n\nexport interface SecurityConfigLike {\n rateLimit: Record<string, RateLimitRule>\n csrf: boolean\n headers: {\n hsts: boolean\n contentSecurityPolicy: string\n referrerPolicy: string\n xFrameOptions: string\n }\n [key: string]: unknown\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: Array<{ code: string; message: string; path?: string }>\n}\n\nexport interface DriftReport {\n drift: boolean\n differences: Array<{ path: string; configured: unknown; actual: unknown }>\n}\n\nexport interface SecurityState {\n rulesApplied: string[]\n lastValidatedAt: string\n}\n\nexport interface SecurityAdapter extends AdapterBase {\n applyMiddleware(app: HttpAdapter, config: SecurityConfigLike): Promise<void>\n validate(config: SecurityConfigLike): Promise<ValidationResult>\n drift?(config: SecurityConfigLike): Promise<DriftReport>\n inspect?(): Promise<SecurityState>\n}\n\n/** The response headers every security adapter sets in process, from the config's `headers` block. */\nexport function buildSecurityHeaders(security: SecurityConfigLike): Record<string, string> {\n const headers: Record<string, string> = {}\n if (security.headers.hsts) {\n headers['strict-transport-security'] = 'max-age=31536000; includeSubDomains'\n }\n const csp = security.headers.contentSecurityPolicy\n const cspValue =\n csp === 'strict'\n ? \"default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'\"\n : csp === 'relaxed'\n ? \"default-src 'self' https:; img-src * data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'\"\n : csp === 'off'\n ? null\n : csp\n if (cspValue) headers['content-security-policy'] = cspValue\n headers['referrer-policy'] = security.headers.referrerPolicy\n headers['x-frame-options'] = security.headers.xFrameOptions === 'deny' ? 'DENY' : 'SAMEORIGIN'\n headers['x-content-type-options'] = 'nosniff'\n return headers\n}\n","import { STANDARD_EVENT_TYPES } from '../events/constants.js'\nimport type { WebhookDelivery } from './commerce.js'\n\n/** Why a delivery's events may not be published; `type` is the offending event's, when one is to blame. */\nexport interface WebhookDeliveryProblem {\n type?: unknown\n reason: string\n}\n\nconst VERSIONED_EVENT = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*\\.v[1-9][0-9]*$/\n\n/** Plain JSON objects only: a class instance would not survive the event log's serialisation as itself. */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false\n const proto = Object.getPrototypeOf(value) as unknown\n return proto === Object.prototype || proto === null\n}\n\n/**\n * The first thing that makes a verified delivery from the adapter named `providerName` unpublishable, or null when\n * every event may be published: the rule of `receiveWebhook`, which the kernel refuses a delivery for and the\n * webhook contract suite certifies.\n */\nexport function webhookDeliveryProblem(\n providerName: string,\n producesEvents: readonly string[],\n delivery: WebhookDelivery,\n): WebhookDeliveryProblem | null {\n if (typeof delivery.deliveryId !== 'string' || delivery.deliveryId === '') {\n return { reason: 'the delivery id is empty' }\n }\n if (!Array.isArray(delivery.events)) return { reason: 'events is not an array' }\n for (const event of delivery.events as unknown[]) {\n const { type, payload } = (isPlainObject(event) ? event : {}) as {\n type?: unknown\n payload?: unknown\n }\n if (typeof type !== 'string') return { type, reason: 'an event has no type' }\n const dot = type.indexOf('.')\n // A provider name holding a dot never equals the text before the first dot, so it can publish nothing.\n if (dot === -1 || type.slice(0, dot) !== providerName) {\n return { type, reason: `the type is not in the ${providerName} namespace` }\n }\n // Computed here, not at module level: a top-level Set would keep every event type in each storefront bundle.\n if (STANDARD_EVENT_TYPES.some((standard) => standard.split('.')[0] === providerName)) {\n return { type, reason: 'the type is in a kernel namespace' }\n }\n if (!VERSIONED_EVENT.test(type.slice(dot + 1))) {\n return { type, reason: 'the type is not <provider>.<event>.v<n>' }\n }\n if (!producesEvents.includes(type)) {\n return { type, reason: 'the type is not listed in producesEvents' }\n }\n if (!isPlainObject(payload)) return { type, reason: 'the payload is not a JSON object' }\n }\n return null\n}\n","import type { WebhookDelivery } from './commerce.js'\nimport type { WebhookDeliveryProblem } from './webhook-delivery.js'\n\n/**\n * `<providerName>.bounced.v1`: a message to `recipient` bounced. `hard` is the provider's permanent failure, `soft`\n * its transient one; a classification the adapter cannot map is `soft`, so a doubtful bounce never suppresses.\n */\nexport interface EmailBouncedV1Payload {\n recipient: string\n bounce: 'hard' | 'soft'\n /** The `EmailSendResult.messageId` the bounce answers, when the provider gives one. */\n messageId: string | null\n /** ISO 8601. */\n occurredAt: string\n}\n\n/** `<providerName>.complained.v1`: `recipient` marked a message as spam. */\nexport interface EmailComplainedV1Payload {\n recipient: string\n messageId: string | null\n /** ISO 8601. */\n occurredAt: string\n}\n\nexport type EmailFeedbackKind = 'bounced' | 'complained'\n\n/** The event type an email adapter publishes a bounce or a complaint under. */\nexport function emailFeedbackType(providerName: string, what: EmailFeedbackKind): string {\n return `${providerName}.${what}.v1`\n}\n\nfunction payloadOf(event: unknown): Record<string, unknown> {\n const payload = (event as { payload?: unknown } | null)?.payload\n return typeof payload === 'object' && payload !== null ? (payload as Record<string, unknown>) : {}\n}\n\n/**\n * The first bounce or complaint in a verified delivery from the email adapter `providerName` that the kernel cannot\n * act on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's\n * retry after the adapter is fixed is still received; the email feedback contract suite certifies the same rule.\n */\nexport function emailFeedbackProblem(\n providerName: string,\n delivery: WebhookDelivery,\n): WebhookDeliveryProblem | null {\n const bounced = emailFeedbackType(providerName, 'bounced')\n const complained = emailFeedbackType(providerName, 'complained')\n if (!Array.isArray(delivery.events)) return null\n for (const event of delivery.events as unknown[]) {\n const type = (event as { type?: unknown } | null)?.type\n if (type !== bounced && type !== complained) continue\n const payload = payloadOf(event)\n const { recipient, messageId, occurredAt } = payload\n if (typeof recipient !== 'string' || recipient.trim() === '') {\n return { type, reason: 'the recipient is missing or empty' }\n }\n if (type === bounced && payload['bounce'] !== 'hard' && payload['bounce'] !== 'soft') {\n return { type, reason: \"the bounce is neither 'hard' nor 'soft'\" }\n }\n if (messageId !== null && typeof messageId !== 'string') {\n return { type, reason: 'the messageId is neither a string nor null' }\n }\n if (typeof occurredAt !== 'string' || Number.isNaN(Date.parse(occurredAt))) {\n return { type, reason: 'occurredAt is not a readable date' }\n }\n }\n return null\n}\n","import type { WebhookDelivery } from './commerce.js'\nimport type { WebhookDeliveryProblem } from './webhook-delivery.js'\n\n/** The statuses a shipping adapter maps its carrier's onto; one it cannot map is `unknown`. */\nexport const SHIPPING_TRACKING_STATUSES = [\n 'pre_transit',\n 'in_transit',\n 'out_for_delivery',\n 'delivered',\n 'exception',\n 'returned',\n 'unknown',\n] as const\n\nexport type ShippingTrackingStatus = (typeof SHIPPING_TRACKING_STATUSES)[number]\n\n/** `<providerName>.tracking.v1`: the carrier reported `status` for the parcel `trackingNumber`. */\nexport interface ShippingTrackingV1Payload {\n trackingNumber: string\n /** The carrier's code as the adapter's provider gives it, which need not match the merchant's own. */\n carrier: string | null\n status: ShippingTrackingStatus\n /** ISO 8601. */\n occurredAt: string\n}\n\n/** The event type a shipping adapter publishes a tracking update under. */\nexport function shippingTrackingType(providerName: string): string {\n return `${providerName}.tracking.v1`\n}\n\n/** A tracking number as stored and looked up: no whitespace, upper case, so a carrier's spelling finds the merchant's. */\nexport function normaliseTrackingNumber(value: string): string {\n return value.replace(/\\s+/g, '').toUpperCase()\n}\n\nfunction payloadOf(event: unknown): Record<string, unknown> {\n const payload = (event as { payload?: unknown } | null)?.payload\n return typeof payload === 'object' && payload !== null ? (payload as Record<string, unknown>) : {}\n}\n\n/**\n * The first tracking update in a verified delivery from the shipping adapter `providerName` that the kernel cannot act\n * on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's retry\n * after the adapter is fixed is still received; the shipping tracking contract suite certifies the same rule.\n */\nexport function shippingTrackingProblem(\n providerName: string,\n delivery: WebhookDelivery,\n): WebhookDeliveryProblem | null {\n const tracking = shippingTrackingType(providerName)\n if (!Array.isArray(delivery.events)) return null\n for (const event of delivery.events as unknown[]) {\n const type = (event as { type?: unknown } | null)?.type\n if (type !== tracking) continue\n const { trackingNumber, carrier, status, occurredAt } = payloadOf(event)\n if (typeof trackingNumber !== 'string' || normaliseTrackingNumber(trackingNumber) === '') {\n return { type, reason: 'the trackingNumber is missing or empty' }\n }\n if (carrier !== null && typeof carrier !== 'string') {\n return { type, reason: 'the carrier is neither a string nor null' }\n }\n if (!(SHIPPING_TRACKING_STATUSES as readonly unknown[]).includes(status)) {\n return { type, reason: 'the status is not one of the tracking statuses' }\n }\n if (typeof occurredAt !== 'string' || Number.isNaN(Date.parse(occurredAt))) {\n return { type, reason: 'occurredAt is not a readable date' }\n }\n }\n return null\n}\n","// The kernel's transactional email templates, shared by every email adapter (the console one and the SMTP package).\nexport interface RenderedEmail {\n subject: string\n text: string\n html: string\n}\n\nfunction render(template: string, vars: Record<string, unknown>): string {\n return template.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, (_, path: string) => {\n const parts = path.split('.')\n let cur: unknown = vars\n for (const p of parts) {\n if (cur && typeof cur === 'object' && p in (cur as Record<string, unknown>)) {\n cur = (cur as Record<string, unknown>)[p]\n } else {\n return ''\n }\n }\n return cur == null ? '' : String(cur)\n })\n}\n\nconst TEMPLATES: Record<string, { subject: string; body: string }> = {\n 'order-placed': {\n subject: 'Order {{orderNumber}} confirmed',\n // `orderLink` is the whole \"View your order\" paragraph, absent when the store has no URL to link from.\n body: `Hi {{customerName}},\\n\\nThank you for your order {{orderNumber}}. Total: {{totalFormatted}}.\\n\\nWe'll let you know when it ships.{{orderLink}}`,\n },\n 'order-shipped': {\n subject: 'Order {{orderNumber}} is on its way',\n body: `Hi {{customerName}},\\n\\nYour order {{orderNumber}} has shipped via {{carrier}}.\\nTracking: {{trackingNumber}}`,\n },\n welcome: {\n subject: 'Welcome to {{storeName}}',\n body: `Welcome {{customerName}}! Your account is ready.`,\n },\n 'cart-abandoned': {\n subject: \"Don't forget your cart at {{storeName}}\",\n body: `Hi,\\n\\nYou left items in your cart. Pick up where you left off: {{resumeUrl}}`,\n },\n // Each link on a line of its own, so a mail client never runs the next word into it.\n 'alert-confirm': {\n subject: 'Confirm your alert for {{productName}}',\n body: `Hi,\\n\\nYou asked {{storeName}} to email you when {{productName}} is back in stock. Confirm by following this link:\\n\\n{{confirmUrl}}\\n\\nIf you did not ask, ignore this email and you will hear nothing more.\\n\\nStop this alert:\\n{{unsubscribeUrl}}`,\n },\n 'alert-back-in-stock': {\n subject: '{{productName}} is back in stock',\n body: `Hi,\\n\\n{{productName}} is back in stock at {{storeName}}:\\n\\n{{productUrl}}\\n\\nThis was a one-time alert. Stop it:\\n{{unsubscribeUrl}}`,\n },\n 'alert-confirm-price-drop': {\n subject: 'Confirm your price alert for {{productName}}',\n body: `Hi,\\n\\nYou asked {{storeName}} to email you when the price of {{productName}} drops, each time it falls below the last price we told you. Confirm by following this link:\\n\\n{{confirmUrl}}\\n\\nIf you did not ask, ignore this email and you will hear nothing more.\\n\\nStop this alert:\\n{{unsubscribeUrl}}`,\n },\n 'alert-price-drop': {\n subject: 'Price drop: {{productName}} is now {{priceFormatted}}',\n body: `Hi,\\n\\nThe price of {{productName}} at {{storeName}} has dropped from {{previousPriceFormatted}} to {{priceFormatted}}:\\n\\n{{productUrl}}\\n\\nWe'll email you again if it drops below {{priceFormatted}}. Stop this alert:\\n{{unsubscribeUrl}}`,\n },\n}\n\nexport function renderEmailTemplate(\n template: string,\n vars: Record<string, unknown>,\n): RenderedEmail {\n const t = TEMPLATES[template]\n if (!t) {\n return {\n subject: `[nymbal] ${template}`,\n text: JSON.stringify(vars),\n html: `<pre>${JSON.stringify(vars, null, 2)}</pre>`,\n }\n }\n const subject = render(t.subject, vars)\n const text = render(t.body, vars)\n const html = `<pre>${text.replace(/</g, '&lt;')}</pre>`\n return { subject, text, html }\n}\n","export type StockLevel = 'out' | 'low' | 'in'\n\n/** Below this many units a variant is low on stock. */\nconst LOW_STOCK_BELOW = 5\n\n/** The one stock rule every storefront renders from. */\nexport function stockLevel(stock: number): StockLevel {\n if (stock <= 0) return 'out'\n return stock < LOW_STOCK_BELOW ? 'low' : 'in'\n}\n\nexport const STOCK_LABELS: Record<StockLevel, string> = {\n out: 'Out of Stock',\n low: 'Low Stock',\n in: 'In Stock',\n}\n","/** Who a command ran for. A guest is `guest:anonymous`: a cart's token is a bearer credential and is never an id. */\nexport interface AuditActor {\n kind: 'admin' | 'customer' | 'guest' | 'system' | 'import'\n id: string\n}\n\n/** `applied`: the command ran and committed. `refused`: its service refused it. `replayed`: a keyed retry answered. */\nexport type AuditOutcome = 'applied' | 'refused' | 'replayed'\n\n/** A record a command touched, by id only. */\nexport interface AuditSubject {\n type: string\n id: string\n}\n\n/**\n * One audit entry as `GET /api/admin/activity` lists it and the exports print it. A stored response is shown by its\n * status only; its body never leaves the store.\n */\nexport interface AuditEntry {\n id: string\n at: string\n actor: AuditActor\n command: string\n subjects: AuditSubject[]\n correlationId: string | null\n idempotencyKey: string | null\n outcome: AuditOutcome\n responseStatus: number | null\n}\n\n/** The request header an admin write carries to be applied once, and the response header a replay carries. */\nexport const IDEMPOTENCY_KEY_HEADER = 'idempotency-key'\nexport const IDEMPOTENT_REPLAYED_HEADER = 'idempotent-replayed'\n\n/** 1-128 characters of `[A-Za-z0-9._:-]`. */\nexport function isValidIdempotencyKey(value: string): boolean {\n return /^[A-Za-z0-9._:-]{1,128}$/.test(value)\n}\n"],"mappings":"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","names":["c","payloadOf"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nymbalhq/types",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Shared TypeScript types and adapter contracts for Nymbal",
5
5
  "license": "MIT",
6
6
  "repository": {