@nymbalhq/types 1.2.0 → 1.4.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,11 @@ 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";
235
240
  /** Who took the money: the payments provider, the merchant recording an offline payment, or an imported history. */
236
241
  type CaptureMethod = 'provider' | 'manual' | 'import';
237
242
  type OrderRefundStatus = 'pending' | 'succeeded' | 'failed';
@@ -270,6 +275,19 @@ interface OrderHistoryEntry {
270
275
  timestamp: string;
271
276
  }
272
277
 
278
+ /** The lifecycle events an order's timeline shows, each one from an `order_history` row. */
279
+ declare const ORDER_TIMELINE_KINDS: readonly ["placed", "paid", "shipped", "delivered", "cancelled", "partially_refunded", "refunded"];
280
+ type OrderTimelineKind = (typeof ORDER_TIMELINE_KINDS)[number];
281
+ interface OrderTimelineEntry {
282
+ kind: OrderTimelineKind;
283
+ /** ISO 8601, UTC. */
284
+ at: string;
285
+ }
286
+ /** An order as a storefront reads it; `timeline` is optional so an adapter that returns a plain `Order` still fits. */
287
+ interface ShopperOrder extends Order {
288
+ timeline?: OrderTimelineEntry[];
289
+ }
290
+
273
291
  interface CustomerAddress extends Address {
274
292
  id: string;
275
293
  isDefaultBilling: boolean;
@@ -398,6 +416,11 @@ declare const EVT_PRODUCT_UPDATED = "product.updated.v1";
398
416
  declare const EVT_PRODUCT_DELETED = "product.deleted.v1";
399
417
  declare const EVT_PRODUCT_PUBLISHED = "product.published.v1";
400
418
  declare const EVT_PRODUCT_UNPUBLISHED = "product.unpublished.v1";
419
+ declare const EVT_PRODUCT_VIEWED = "product.viewed.v1";
420
+ /** A variant's stock went from 0 to above 0, in the transaction of the inventory.changed.v1 it accompanies. */
421
+ declare const EVT_PRODUCT_RESTOCKED = "product.restocked.v1";
422
+ /** A variant's price or compare-at price changed, in the transaction of the product.updated.v1 it accompanies. */
423
+ declare const EVT_PRODUCT_VARIANT_PRICE_CHANGED = "product.variant_price_changed.v1";
401
424
  declare const EVT_CATEGORY_CREATED = "category.created.v1";
402
425
  declare const EVT_CATEGORY_UPDATED = "category.updated.v1";
403
426
  declare const EVT_CATEGORY_DELETED = "category.deleted.v1";
@@ -410,6 +433,7 @@ declare const EVT_ORDER_CANCELLED = "order.cancelled.v1";
410
433
  declare const EVT_ORDER_REFUNDED = "order.refunded.v1";
411
434
  declare const EVT_ORDER_PARTIALLY_REFUNDED = "order.partially_refunded.v1";
412
435
  declare const EVT_ORDER_STATUS_CHANGED = "order.status_changed.v1";
436
+ declare const EVT_ORDER_CONFIRMATION_RESEND_REQUESTED = "order.confirmation_resend_requested.v1";
413
437
  declare const EVT_INVENTORY_CHANGED = "inventory.changed.v1";
414
438
  declare const EVT_INVENTORY_RESERVED = "inventory.reserved.v1";
415
439
  declare const EVT_INVENTORY_RELEASED = "inventory.released.v1";
@@ -419,6 +443,7 @@ declare const EVT_CUSTOMER_CREATED = "customer.created.v1";
419
443
  declare const EVT_CUSTOMER_UPDATED = "customer.updated.v1";
420
444
  declare const EVT_CUSTOMER_DELETED = "customer.deleted.v1";
421
445
  declare const EVT_CUSTOMER_REGISTERED = "customer.created.v1";
446
+ declare const EVT_VISITOR_MERGED = "visitor.merged.v1";
422
447
  declare const EVT_CART_CREATED = "cart.created.v1";
423
448
  declare const EVT_CART_ITEM_ADDED = "cart.item_added.v1";
424
449
  declare const EVT_CART_ITEM_REMOVED = "cart.item_removed.v1";
@@ -429,9 +454,21 @@ declare const EVT_PAYMENT_INTENT_CREATED = "payment.intent_created.v1";
429
454
  declare const EVT_PAYMENT_CAPTURED = "payment.captured.v1";
430
455
  declare const EVT_PAYMENT_FAILED = "payment.failed.v1";
431
456
  declare const EVT_PAYMENT_REFUNDED = "payment.refunded.v1";
457
+ declare const EVT_ALERT_REQUESTED = "alert.requested.v1";
458
+ declare const EVT_ALERT_CONFIRMED = "alert.confirmed.v1";
459
+ declare const EVT_ALERT_UNSUBSCRIBED = "alert.unsubscribed.v1";
460
+ declare const EVT_ALERT_SENT = "alert.sent.v1";
432
461
  declare const EVT_ADAPTER_ERROR = "adapter.error.v1";
433
462
  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"];
463
+ 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.confirmed.v1", "alert.unsubscribed.v1", "alert.sent.v1", "adapter.error.v1", "settings.changed.v1"];
464
+
465
+ /** What a shopper asks to be told about a variant: that it is back in stock, or that its price dropped. */
466
+ type AlertKind = 'back_in_stock' | 'price_drop';
467
+ /**
468
+ * `pending` until the confirmation's link is followed, then `active`; a back-in-stock row is `fired` once its alert is
469
+ * sent, a price-drop row stays `active`; `unsubscribed` from any state by the link every email carries.
470
+ */
471
+ type AlertStatus = 'pending' | 'active' | 'fired' | 'unsubscribed';
435
472
 
436
473
  interface ProductSnapshot extends Product {
437
474
  variants: Variant[];
@@ -455,6 +492,31 @@ interface ProductUnpublishedV1Payload {
455
492
  productId: string;
456
493
  slug: string;
457
494
  }
495
+ interface ProductRestockedV1Payload {
496
+ productId: string;
497
+ variantId: string;
498
+ previousQty: number;
499
+ newQty: number;
500
+ }
501
+ /** Prices in `currency`'s minor units; each compare-at price is the stored one, null when the variant has none. */
502
+ interface ProductVariantPriceChangedV1Payload {
503
+ productId: string;
504
+ variantId: string;
505
+ previousPriceMinor: number;
506
+ priceMinor: number;
507
+ previousCompareAtPriceMinor: number | null;
508
+ compareAtPriceMinor: number | null;
509
+ currency: string;
510
+ }
511
+ /** Whose view it is: an anonymous visitor (the `nymbal.visitor` cookie) or a signed-in customer. */
512
+ interface RecentViewer {
513
+ kind: 'visitor' | 'customer';
514
+ id: string;
515
+ }
516
+ interface ProductViewedV1Payload {
517
+ productId: string;
518
+ viewer: RecentViewer;
519
+ }
458
520
  interface CategoryCreatedV1Payload {
459
521
  category: Category;
460
522
  }
@@ -509,6 +571,10 @@ interface OrderStatusChangedV1Payload {
509
571
  from: OrderStatus;
510
572
  to: OrderStatus;
511
573
  }
574
+ /** An admin asked for the order's confirmation email again; the email's key is minted when it is sent. */
575
+ interface OrderConfirmationResendRequestedV1Payload {
576
+ orderId: string;
577
+ }
512
578
  interface InventoryChangedV1Payload {
513
579
  variantId: string;
514
580
  previousQty: number;
@@ -535,16 +601,33 @@ interface InventoryLowStockV1Payload {
535
601
  interface InventoryOutOfStockV1Payload {
536
602
  variantId: string;
537
603
  }
604
+ /** A customer as the event log keeps it: never its password hash, roles, metadata or stats. */
605
+ interface CustomerSnapshot {
606
+ id: string;
607
+ email: string;
608
+ firstName: string;
609
+ lastName: string;
610
+ phone: string;
611
+ addresses: CustomerAddress[];
612
+ createdAt: string;
613
+ }
538
614
  interface CustomerCreatedV1Payload {
539
615
  customerId: string;
540
616
  email: string;
617
+ customer?: CustomerSnapshot;
541
618
  }
542
619
  interface CustomerUpdatedV1Payload {
543
620
  customerId: string;
621
+ customer?: CustomerSnapshot;
544
622
  }
545
623
  interface CustomerDeletedV1Payload {
546
624
  customerId: string;
547
625
  }
626
+ /** A visitor's views now belong to the customer who signed in carrying its cookie. */
627
+ interface VisitorMergedV1Payload {
628
+ visitorId: string;
629
+ customerId: string;
630
+ }
548
631
  interface CartCreatedV1Payload {
549
632
  cartId: string;
550
633
  token: string;
@@ -594,6 +677,27 @@ interface PaymentRefundedV1Payload {
594
677
  amountMinor: number;
595
678
  currency: string;
596
679
  }
680
+ interface AlertRequestedV1Payload {
681
+ subscriptionId: string;
682
+ kind: AlertKind;
683
+ variantId: string;
684
+ productId: string;
685
+ }
686
+ interface AlertConfirmedV1Payload {
687
+ subscriptionId: string;
688
+ }
689
+ interface AlertUnsubscribedV1Payload {
690
+ subscriptionId: string;
691
+ }
692
+ interface AlertSentV1Payload {
693
+ subscriptionId: string;
694
+ kind: AlertKind;
695
+ /**
696
+ * The event the alert answered: a product.restocked.v1 for a back-in-stock alert, a
697
+ * product.variant_price_changed.v1 for a price-drop one.
698
+ */
699
+ triggerEventId: string;
700
+ }
597
701
  interface AdapterErrorV1Payload {
598
702
  adapterType: string;
599
703
  adapterName: string;
@@ -616,6 +720,9 @@ interface NymbalEventMap {
616
720
  [EVT_PRODUCT_DELETED]: ProductDeletedV1Payload;
617
721
  [EVT_PRODUCT_PUBLISHED]: ProductPublishedV1Payload;
618
722
  [EVT_PRODUCT_UNPUBLISHED]: ProductUnpublishedV1Payload;
723
+ [EVT_PRODUCT_VIEWED]: ProductViewedV1Payload;
724
+ [EVT_PRODUCT_RESTOCKED]: ProductRestockedV1Payload;
725
+ [EVT_PRODUCT_VARIANT_PRICE_CHANGED]: ProductVariantPriceChangedV1Payload;
619
726
  [EVT_CATEGORY_CREATED]: CategoryCreatedV1Payload;
620
727
  [EVT_CATEGORY_UPDATED]: CategoryUpdatedV1Payload;
621
728
  [EVT_CATEGORY_DELETED]: CategoryDeletedV1Payload;
@@ -628,6 +735,7 @@ interface NymbalEventMap {
628
735
  [EVT_ORDER_REFUNDED]: OrderRefundedV1Payload;
629
736
  [EVT_ORDER_PARTIALLY_REFUNDED]: OrderPartiallyRefundedV1Payload;
630
737
  [EVT_ORDER_STATUS_CHANGED]: OrderStatusChangedV1Payload;
738
+ [EVT_ORDER_CONFIRMATION_RESEND_REQUESTED]: OrderConfirmationResendRequestedV1Payload;
631
739
  [EVT_INVENTORY_CHANGED]: InventoryChangedV1Payload;
632
740
  [EVT_INVENTORY_RESERVED]: InventoryReservedV1Payload;
633
741
  [EVT_INVENTORY_RELEASED]: InventoryReleasedV1Payload;
@@ -636,6 +744,7 @@ interface NymbalEventMap {
636
744
  [EVT_CUSTOMER_CREATED]: CustomerCreatedV1Payload;
637
745
  [EVT_CUSTOMER_UPDATED]: CustomerUpdatedV1Payload;
638
746
  [EVT_CUSTOMER_DELETED]: CustomerDeletedV1Payload;
747
+ [EVT_VISITOR_MERGED]: VisitorMergedV1Payload;
639
748
  [EVT_CART_CREATED]: CartCreatedV1Payload;
640
749
  [EVT_CART_ITEM_ADDED]: CartItemAddedV1Payload;
641
750
  [EVT_CART_ITEM_REMOVED]: CartItemRemovedV1Payload;
@@ -646,6 +755,10 @@ interface NymbalEventMap {
646
755
  [EVT_PAYMENT_CAPTURED]: PaymentCapturedV1Payload;
647
756
  [EVT_PAYMENT_FAILED]: PaymentFailedV1Payload;
648
757
  [EVT_PAYMENT_REFUNDED]: PaymentRefundedV1Payload;
758
+ [EVT_ALERT_REQUESTED]: AlertRequestedV1Payload;
759
+ [EVT_ALERT_CONFIRMED]: AlertConfirmedV1Payload;
760
+ [EVT_ALERT_UNSUBSCRIBED]: AlertUnsubscribedV1Payload;
761
+ [EVT_ALERT_SENT]: AlertSentV1Payload;
649
762
  [EVT_ADAPTER_ERROR]: AdapterErrorV1Payload;
650
763
  [EVT_SETTINGS_CHANGED]: SettingsChangedV1Payload;
651
764
  }
@@ -934,19 +1047,33 @@ interface Subscription {
934
1047
  patterns: string[];
935
1048
  unsubscribe(): Promise<void>;
936
1049
  }
1050
+ /** A named subscription whose handler threw on an event. */
1051
+ interface HandlerFailure {
1052
+ subscription: string;
1053
+ message: string;
1054
+ }
1055
+ /** What `publish` rejects with when a named subscription's handler threw: code `adapter.handler_failed`. */
1056
+ declare function handlerFailedError(event: NymbalEvent<unknown>, failed: HandlerFailure[]): AdapterError;
937
1057
  /**
938
- * Delivery is at least once: a publish that is retried may reach a handler twice, so every handler is idempotent
939
- * (projections overwrite their documents, side effects use `withIdempotency`).
1058
+ * Delivery is at least once: a failed delivery is published again, to every matching subscription, so every handler
1059
+ * is idempotent (projections overwrite their documents, side effects use `withIdempotency`). The bus never retries a
1060
+ * failed handler by itself.
940
1061
  */
941
1062
  interface EventBusAdapter {
942
1063
  capabilities(): readonly string[];
943
1064
  /**
944
1065
  * Resolves only after every matching subscription this instance holds has handled the event, so a read straight
945
- * after a command sees its projections. A handler that throws is logged, and does not reject the publish.
1066
+ * after a command sees its projections. When a named subscription's handler throws, it rejects with
1067
+ * `handlerFailedError` naming each one that threw, once every matching handler has settled. An unnamed subscription's
1068
+ * throw is logged and does not reject.
946
1069
  */
947
1070
  publish<T>(event: NymbalEvent<T>): Promise<void>;
1071
+ /** Hands every event out in order, then rejects as `publish` does for the first event a named handler failed. */
948
1072
  publishBatch(events: NymbalEvent<unknown>[]): Promise<void>;
949
- /** Delivers to `derived` subscriptions only, so rebuilding derived state never triggers side effects. */
1073
+ /**
1074
+ * Delivers to `derived` subscriptions only, so rebuilding derived state never triggers side effects. A handler's
1075
+ * throw is logged and does not reject.
1076
+ */
950
1077
  replay(events: NymbalEvent<unknown>[]): Promise<void>;
951
1078
  subscribe(eventPattern: string | string[], handler: EventHandler, config?: SubscriptionConfig): Promise<Subscription>;
952
1079
  unsubscribe(subscription: Subscription): Promise<void>;
@@ -1022,8 +1149,30 @@ interface SecurityAdapter extends AdapterBase {
1022
1149
  /** The response headers every security adapter sets in process, from the config's `headers` block. */
1023
1150
  declare function buildSecurityHeaders(security: SecurityConfigLike): Record<string, string>;
1024
1151
 
1152
+ /** A webhook request's headers, names lower-cased. */
1153
+ type WebhookHeaders = Record<string, string | string[] | undefined>;
1154
+ /** One event a webhook delivery carries; the kernel builds its envelope (id, source, correlation). */
1155
+ interface WebhookEventDraft {
1156
+ type: string;
1157
+ payload: Record<string, unknown>;
1158
+ }
1159
+ interface WebhookDelivery {
1160
+ verified: boolean;
1161
+ /** The provider's id for this delivery: what makes a retry of it a replay. */
1162
+ deliveryId: string;
1163
+ events: WebhookEventDraft[];
1164
+ }
1025
1165
  interface CommerceAdapter extends AdapterBase {
1026
1166
  readonly providerName: string;
1167
+ /**
1168
+ * Takes a delivery to `POST /api/webhooks/<providerName>`: verifies its signature over `rawBody` (from whichever
1169
+ * header the provider signs in) and returns its events. The same bytes must give the same `deliveryId` and the same
1170
+ * events, so a provider's retry is processed once. Each event's type is `<providerName>.<event>.v<n>` (lower-case
1171
+ * words, `_` allowed), is listed in `producesEvents`, and its payload is a plain JSON object; a delivery breaking
1172
+ * this is refused whole. A kernel namespace (`order`, `payment`, ...) is never an adapter's. Not called on a
1173
+ * payments adapter, whose deliveries go through `parseWebhook`.
1174
+ */
1175
+ receiveWebhook?(rawBody: Buffer, headers: WebhookHeaders): Promise<WebhookDelivery>;
1027
1176
  }
1028
1177
  interface RefundOptions {
1029
1178
  /** The same key on a retry makes the provider refund at most once. */
@@ -1226,6 +1375,18 @@ interface CommerceSlotAdapters {
1226
1375
  ai: AiAdapter;
1227
1376
  }
1228
1377
 
1378
+ /** Why a delivery's events may not be published; `type` is the offending event's, when one is to blame. */
1379
+ interface WebhookDeliveryProblem {
1380
+ type?: unknown;
1381
+ reason: string;
1382
+ }
1383
+ /**
1384
+ * The first thing that makes a verified delivery from the adapter named `providerName` unpublishable, or null when
1385
+ * every event may be published: the rule of `receiveWebhook`, which the kernel refuses a delivery for and the
1386
+ * webhook contract suite certifies.
1387
+ */
1388
+ declare function webhookDeliveryProblem(providerName: string, producesEvents: readonly string[], delivery: WebhookDelivery): WebhookDeliveryProblem | null;
1389
+
1229
1390
  interface RenderedEmail {
1230
1391
  subject: string;
1231
1392
  text: string;
@@ -1250,6 +1411,10 @@ interface StoreCapabilities {
1250
1411
  sortKeys: ProductSortKey[];
1251
1412
  defaultSort: ProductSortKey;
1252
1413
  };
1414
+ /** Whether the store's email reaches a mailbox: false when its adapter only logs (`native`). */
1415
+ email: {
1416
+ delivers: boolean;
1417
+ };
1253
1418
  }
1254
1419
 
1255
1420
  /**
@@ -1355,5 +1520,40 @@ declare const IDEMPOTENT_REPLAYED_HEADER = "idempotent-replayed";
1355
1520
  /** 1-128 characters of `[A-Za-z0-9._:-]`. */
1356
1521
  declare function isValidIdempotencyKey(value: string): boolean;
1357
1522
 
1358
- 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 };
1359
- export type { AdapterBase, AdapterContext, AdapterErrorV1Payload, AdapterSetting, Address, AggregateRating, AiAdapter, AnalyticsAdapter, 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, CustomerUpdatedV1Payload, DocumentStoreAdapter, DriftReport, EmailAdapter, EmailSendResult, ErrorContext, EventBusAdapter, EventHandler, EventMetadata, Facet, FacetParams, HealthReport, HealthStatus, 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 };
1523
+ /** The records whose history the admin reads from the event log. */
1524
+ type HistoryKind = 'order' | 'product' | 'customer';
1525
+ /** One field an event changed: `path` is a dot path into the view, array items with an `id` keyed by it. */
1526
+ interface HistoryChange {
1527
+ path: string;
1528
+ from: unknown;
1529
+ to: unknown;
1530
+ }
1531
+ /** One event that changed a record's view, or the base event the view starts from (`base`, no changes). */
1532
+ interface HistoryEntry {
1533
+ seq: number;
1534
+ at: string;
1535
+ type: string;
1536
+ correlationId: string;
1537
+ /** From the `applied` audit entry of the event's correlation id; null when there is none. */
1538
+ actor: AuditActor | null;
1539
+ command: string | null;
1540
+ base: boolean;
1541
+ changes: HistoryChange[];
1542
+ }
1543
+ /** A page of `GET /api/admin/{orders,products,customers}/:id/history`, newest first. */
1544
+ interface HistoryPage {
1545
+ /** The earliest base event the log still holds; null when the record predates the log's window. */
1546
+ historyStartsAt: string | null;
1547
+ retentionDays: number;
1548
+ entries: HistoryEntry[];
1549
+ nextCursor: string | null;
1550
+ }
1551
+ /** `GET <admin detail>?at=<t>`: the record as the log folds it at `asOf`. */
1552
+ interface AsOfRead<T = Record<string, unknown>> {
1553
+ asOf: string;
1554
+ historyStartsAt: string;
1555
+ record: T;
1556
+ }
1557
+
1558
+ export { AdapterError, AuthError, CORRELATION_ID_HEADER, CheckoutError, ConfigError, DEFAULT_PRODUCT_SORT, DEFAULT_QUERY_LIMIT, EVT_ADAPTER_ERROR, EVT_ALERT_CONFIRMED, 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_KEY_HEADER, ORDER_STATUS_TRANSITIONS, ORDER_TIMELINE_KINDS, 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, handlerFailedError, isValidCorrelationId, isValidIdempotencyKey, matchesSortKey, money, mulBy, paymentState, queryDocuments, refundableMinor, renderEmailTemplate, stockLevel, sub, sumMoney, webhookDeliveryProblem };
1559
+ export type { AdapterBase, AdapterContext, AdapterErrorV1Payload, AdapterSetting, Address, AggregateRating, AiAdapter, AlertConfirmedV1Payload, AlertKind, 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, EmailSendResult, ErrorContext, EventBusAdapter, EventHandler, EventMetadata, Facet, FacetParams, HandlerFailure, 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, 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, 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,7 @@ 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";
172
173
  function paymentState(order) {
173
174
  if (order.capturedMinor <= 0) return "unpaid";
174
175
  if (order.refundedMinor <= 0) return "paid";
@@ -179,12 +180,26 @@ function refundableMinor(order, refunds) {
179
180
  return Math.max(0, order.capturedMinor - order.refundedMinor - pending);
180
181
  }
181
182
 
183
+ // src/domain/order-timeline.ts
184
+ var ORDER_TIMELINE_KINDS = [
185
+ "placed",
186
+ "paid",
187
+ "shipped",
188
+ "delivered",
189
+ "cancelled",
190
+ "partially_refunded",
191
+ "refunded"
192
+ ];
193
+
182
194
  // src/events/constants.ts
183
195
  var EVT_PRODUCT_CREATED = "product.created.v1";
184
196
  var EVT_PRODUCT_UPDATED = "product.updated.v1";
185
197
  var EVT_PRODUCT_DELETED = "product.deleted.v1";
186
198
  var EVT_PRODUCT_PUBLISHED = "product.published.v1";
187
199
  var EVT_PRODUCT_UNPUBLISHED = "product.unpublished.v1";
200
+ var EVT_PRODUCT_VIEWED = "product.viewed.v1";
201
+ var EVT_PRODUCT_RESTOCKED = "product.restocked.v1";
202
+ var EVT_PRODUCT_VARIANT_PRICE_CHANGED = "product.variant_price_changed.v1";
188
203
  var EVT_CATEGORY_CREATED = "category.created.v1";
189
204
  var EVT_CATEGORY_UPDATED = "category.updated.v1";
190
205
  var EVT_CATEGORY_DELETED = "category.deleted.v1";
@@ -197,6 +212,7 @@ var EVT_ORDER_CANCELLED = "order.cancelled.v1";
197
212
  var EVT_ORDER_REFUNDED = "order.refunded.v1";
198
213
  var EVT_ORDER_PARTIALLY_REFUNDED = "order.partially_refunded.v1";
199
214
  var EVT_ORDER_STATUS_CHANGED = "order.status_changed.v1";
215
+ var EVT_ORDER_CONFIRMATION_RESEND_REQUESTED = "order.confirmation_resend_requested.v1";
200
216
  var EVT_INVENTORY_CHANGED = "inventory.changed.v1";
201
217
  var EVT_INVENTORY_RESERVED = "inventory.reserved.v1";
202
218
  var EVT_INVENTORY_RELEASED = "inventory.released.v1";
@@ -206,6 +222,7 @@ var EVT_CUSTOMER_CREATED = "customer.created.v1";
206
222
  var EVT_CUSTOMER_UPDATED = "customer.updated.v1";
207
223
  var EVT_CUSTOMER_DELETED = "customer.deleted.v1";
208
224
  var EVT_CUSTOMER_REGISTERED = EVT_CUSTOMER_CREATED;
225
+ var EVT_VISITOR_MERGED = "visitor.merged.v1";
209
226
  var EVT_CART_CREATED = "cart.created.v1";
210
227
  var EVT_CART_ITEM_ADDED = "cart.item_added.v1";
211
228
  var EVT_CART_ITEM_REMOVED = "cart.item_removed.v1";
@@ -216,6 +233,10 @@ var EVT_PAYMENT_INTENT_CREATED = "payment.intent_created.v1";
216
233
  var EVT_PAYMENT_CAPTURED = "payment.captured.v1";
217
234
  var EVT_PAYMENT_FAILED = "payment.failed.v1";
218
235
  var EVT_PAYMENT_REFUNDED = "payment.refunded.v1";
236
+ var EVT_ALERT_REQUESTED = "alert.requested.v1";
237
+ var EVT_ALERT_CONFIRMED = "alert.confirmed.v1";
238
+ var EVT_ALERT_UNSUBSCRIBED = "alert.unsubscribed.v1";
239
+ var EVT_ALERT_SENT = "alert.sent.v1";
219
240
  var EVT_ADAPTER_ERROR = "adapter.error.v1";
220
241
  var EVT_SETTINGS_CHANGED = "settings.changed.v1";
221
242
  var STANDARD_EVENT_TYPES = [
@@ -224,6 +245,9 @@ var STANDARD_EVENT_TYPES = [
224
245
  EVT_PRODUCT_DELETED,
225
246
  EVT_PRODUCT_PUBLISHED,
226
247
  EVT_PRODUCT_UNPUBLISHED,
248
+ EVT_PRODUCT_VIEWED,
249
+ EVT_PRODUCT_RESTOCKED,
250
+ EVT_PRODUCT_VARIANT_PRICE_CHANGED,
227
251
  EVT_CATEGORY_CREATED,
228
252
  EVT_CATEGORY_UPDATED,
229
253
  EVT_CATEGORY_DELETED,
@@ -236,6 +260,7 @@ var STANDARD_EVENT_TYPES = [
236
260
  EVT_ORDER_REFUNDED,
237
261
  EVT_ORDER_PARTIALLY_REFUNDED,
238
262
  EVT_ORDER_STATUS_CHANGED,
263
+ EVT_ORDER_CONFIRMATION_RESEND_REQUESTED,
239
264
  EVT_INVENTORY_CHANGED,
240
265
  EVT_INVENTORY_RESERVED,
241
266
  EVT_INVENTORY_RELEASED,
@@ -244,6 +269,7 @@ var STANDARD_EVENT_TYPES = [
244
269
  EVT_CUSTOMER_CREATED,
245
270
  EVT_CUSTOMER_UPDATED,
246
271
  EVT_CUSTOMER_DELETED,
272
+ EVT_VISITOR_MERGED,
247
273
  EVT_CART_CREATED,
248
274
  EVT_CART_ITEM_ADDED,
249
275
  EVT_CART_ITEM_REMOVED,
@@ -254,6 +280,10 @@ var STANDARD_EVENT_TYPES = [
254
280
  EVT_PAYMENT_CAPTURED,
255
281
  EVT_PAYMENT_FAILED,
256
282
  EVT_PAYMENT_REFUNDED,
283
+ EVT_ALERT_REQUESTED,
284
+ EVT_ALERT_CONFIRMED,
285
+ EVT_ALERT_UNSUBSCRIBED,
286
+ EVT_ALERT_SENT,
257
287
  EVT_ADAPTER_ERROR,
258
288
  EVT_SETTINGS_CHANGED
259
289
  ];
@@ -451,6 +481,15 @@ function queryDocuments(documents, params) {
451
481
  return { items: page.map((m) => m.doc), nextCursor };
452
482
  }
453
483
 
484
+ // src/adapters/event-bus.ts
485
+ function handlerFailedError(event, failed) {
486
+ return new AdapterError(
487
+ "handler_failed",
488
+ `${event.type} (${event.id}) failed in ${failed.map((f) => f.subscription).join(", ")}`,
489
+ { context: { eventId: event.id, eventType: event.type, failed } }
490
+ );
491
+ }
492
+
454
493
  // src/adapters/http.ts
455
494
  var CORRELATION_ID_HEADER = "x-correlation-id";
456
495
  var CORRELATION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
@@ -473,6 +512,39 @@ function buildSecurityHeaders(security) {
473
512
  return headers;
474
513
  }
475
514
 
515
+ // src/adapters/webhook-delivery.ts
516
+ var VERSIONED_EVENT = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*\.v[1-9][0-9]*$/;
517
+ function isPlainObject(value) {
518
+ if (typeof value !== "object" || value === null) return false;
519
+ const proto = Object.getPrototypeOf(value);
520
+ return proto === Object.prototype || proto === null;
521
+ }
522
+ function webhookDeliveryProblem(providerName, producesEvents, delivery) {
523
+ if (typeof delivery.deliveryId !== "string" || delivery.deliveryId === "") {
524
+ return { reason: "the delivery id is empty" };
525
+ }
526
+ if (!Array.isArray(delivery.events)) return { reason: "events is not an array" };
527
+ for (const event of delivery.events) {
528
+ const { type, payload } = isPlainObject(event) ? event : {};
529
+ if (typeof type !== "string") return { type, reason: "an event has no type" };
530
+ const dot = type.indexOf(".");
531
+ if (dot === -1 || type.slice(0, dot) !== providerName) {
532
+ return { type, reason: `the type is not in the ${providerName} namespace` };
533
+ }
534
+ if (STANDARD_EVENT_TYPES.some((standard) => standard.split(".")[0] === providerName)) {
535
+ return { type, reason: "the type is in a kernel namespace" };
536
+ }
537
+ if (!VERSIONED_EVENT.test(type.slice(dot + 1))) {
538
+ return { type, reason: "the type is not <provider>.<event>.v<n>" };
539
+ }
540
+ if (!producesEvents.includes(type)) {
541
+ return { type, reason: "the type is not listed in producesEvents" };
542
+ }
543
+ if (!isPlainObject(payload)) return { type, reason: "the payload is not a JSON object" };
544
+ }
545
+ return null;
546
+ }
547
+
476
548
  // src/adapters/email-templates.ts
477
549
  function render(template, vars) {
478
550
  return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => {
@@ -491,11 +563,12 @@ function render(template, vars) {
491
563
  var TEMPLATES = {
492
564
  "order-placed": {
493
565
  subject: "Order {{orderNumber}} confirmed",
566
+ // `orderLink` is the whole "View your order" paragraph, absent when the store has no URL to link from.
494
567
  body: `Hi {{customerName}},
495
568
 
496
569
  Thank you for your order {{orderNumber}}. Total: {{totalFormatted}}.
497
570
 
498
- We'll let you know when it ships.`
571
+ We'll let you know when it ships.{{orderLink}}`
499
572
  },
500
573
  "order-shipped": {
501
574
  subject: "Order {{orderNumber}} is on its way",
@@ -513,6 +586,55 @@ Tracking: {{trackingNumber}}`
513
586
  body: `Hi,
514
587
 
515
588
  You left items in your cart. Pick up where you left off: {{resumeUrl}}`
589
+ },
590
+ // Each link on a line of its own, so a mail client never runs the next word into it.
591
+ "alert-confirm": {
592
+ subject: "Confirm your alert for {{productName}}",
593
+ body: `Hi,
594
+
595
+ You asked {{storeName}} to email you when {{productName}} is back in stock. Confirm by following this link:
596
+
597
+ {{confirmUrl}}
598
+
599
+ If you did not ask, ignore this email and you will hear nothing more.
600
+
601
+ Stop this alert:
602
+ {{unsubscribeUrl}}`
603
+ },
604
+ "alert-back-in-stock": {
605
+ subject: "{{productName}} is back in stock",
606
+ body: `Hi,
607
+
608
+ {{productName}} is back in stock at {{storeName}}:
609
+
610
+ {{productUrl}}
611
+
612
+ This was a one-time alert. Stop it:
613
+ {{unsubscribeUrl}}`
614
+ },
615
+ "alert-confirm-price-drop": {
616
+ subject: "Confirm your price alert for {{productName}}",
617
+ body: `Hi,
618
+
619
+ 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:
620
+
621
+ {{confirmUrl}}
622
+
623
+ If you did not ask, ignore this email and you will hear nothing more.
624
+
625
+ Stop this alert:
626
+ {{unsubscribeUrl}}`
627
+ },
628
+ "alert-price-drop": {
629
+ subject: "Price drop: {{productName}} is now {{priceFormatted}}",
630
+ body: `Hi,
631
+
632
+ The price of {{productName}} at {{storeName}} has dropped from {{previousPriceFormatted}} to {{priceFormatted}}:
633
+
634
+ {{productUrl}}
635
+
636
+ We'll email you again if it drops below {{priceFormatted}}. Stop this alert:
637
+ {{unsubscribeUrl}}`
516
638
  }
517
639
  };
518
640
  function renderEmailTemplate(template, vars) {
@@ -557,6 +679,10 @@ export {
557
679
  DEFAULT_PRODUCT_SORT,
558
680
  DEFAULT_QUERY_LIMIT,
559
681
  EVT_ADAPTER_ERROR,
682
+ EVT_ALERT_CONFIRMED,
683
+ EVT_ALERT_REQUESTED,
684
+ EVT_ALERT_SENT,
685
+ EVT_ALERT_UNSUBSCRIBED,
560
686
  EVT_CART_ABANDONED,
561
687
  EVT_CART_CONVERTED,
562
688
  EVT_CART_CREATED,
@@ -576,6 +702,7 @@ export {
576
702
  EVT_INVENTORY_RELEASED,
577
703
  EVT_INVENTORY_RESERVED,
578
704
  EVT_ORDER_CANCELLED,
705
+ EVT_ORDER_CONFIRMATION_RESEND_REQUESTED,
579
706
  EVT_ORDER_DELIVERED,
580
707
  EVT_ORDER_FULFILLED,
581
708
  EVT_ORDER_PAID,
@@ -591,16 +718,22 @@ export {
591
718
  EVT_PRODUCT_CREATED,
592
719
  EVT_PRODUCT_DELETED,
593
720
  EVT_PRODUCT_PUBLISHED,
721
+ EVT_PRODUCT_RESTOCKED,
594
722
  EVT_PRODUCT_UNPUBLISHED,
595
723
  EVT_PRODUCT_UPDATED,
724
+ EVT_PRODUCT_VARIANT_PRICE_CHANGED,
725
+ EVT_PRODUCT_VIEWED,
596
726
  EVT_SETTINGS_CHANGED,
727
+ EVT_VISITOR_MERGED,
597
728
  HEALTH_CHECK_TIMEOUT_MS,
598
729
  HttpError,
599
730
  IDEMPOTENCY_KEY_HEADER,
600
731
  IDEMPOTENT_REPLAYED_HEADER,
601
732
  NotFoundError,
602
733
  NymbalError,
734
+ ORDER_KEY_HEADER,
603
735
  ORDER_STATUS_TRANSITIONS,
736
+ ORDER_TIMELINE_KINDS,
604
737
  OrderConflictError,
605
738
  PRODUCT_SORT_KEYS,
606
739
  PRODUCT_SORT_LABELS,
@@ -622,6 +755,7 @@ export {
622
755
  displayUrl,
623
756
  documentValueAt,
624
757
  errorText,
758
+ handlerFailedError,
625
759
  isValidCorrelationId,
626
760
  isValidIdempotencyKey,
627
761
  matchesSortKey,
@@ -633,6 +767,7 @@ export {
633
767
  renderEmailTemplate,
634
768
  stockLevel,
635
769
  sub,
636
- sumMoney
770
+ sumMoney,
771
+ webhookDeliveryProblem
637
772
  };
638
773
  //# 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":";AAEO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC5B;AAAA,EACA;AAAA,EAET,YACE,MACA,SACA,UAAuD,CAAC,GACxD;AACA,UAAM,SAAS,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,MAAS;AACjF,SAAK,OAAO,KAAK,YAAY;AAC7B,SAAK,OAAO;AACZ,SAAK,UAAU,QAAQ,WAAW,CAAC;AAAA,EACrC;AAAA,EAEA,SAAiF;AAC/E,WAAO,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,SAAS,KAAK,SAAS,SAAS,KAAK,QAAQ;AAAA,EAC1F;AACF;AAEO,IAAM,cAAN,cAA0B,YAAY;AAAA,EAC3C,YACE,SACA,UAII,CAAC,GACL;AACA,UAAM,QAAQ,QAAQ,kBAAkB,SAAS,OAAO;AAAA,EAC1D;AACF;AAEO,IAAM,kBAAN,cAA8B,YAAY;AAAA,EAC/C,YAAY,SAAiB,UAAuD,CAAC,GAAG;AACtF,UAAM,qBAAqB,SAAS,OAAO;AAAA,EAC7C;AACF;AAEO,IAAM,gBAAN,cAA4B,YAAY;AAAA,EAC7C,YACE,UACA,YACA,UAAuD,CAAC,GACxD;AACA,UAAM,aAAa,GAAG,QAAQ,eAAe,OAAO,UAAU,CAAC,IAAI;AAAA,MACjE,GAAG;AAAA,MACH,SAAS,EAAE,UAAU,YAAY,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,IAC9D,CAAC;AAAA,EACH;AACF;AAEO,IAAM,eAAN,cAA2B,YAAY;AAAA,EAC5C,YACE,MACA,SACA,UAAuD,CAAC,GACxD;AACA,UAAM,WAAW,IAAI,IAAI,SAAS,OAAO;AAAA,EAC3C;AACF;AAEO,IAAM,YAAN,cAAwB,YAAY;AAAA,EAChC;AAAA,EAET,YACE,QACA,SACA,UAAsE,CAAC,GACvE;AACA,UAAM,QAAQ,QAAQ,QAAQ,MAAM,IAAI,SAAS,OAAO;AACxD,SAAK,SAAS;AAAA,EAChB;AACF;AAEO,IAAM,YAAN,cAAwB,UAAU;AAAA,EACvC,YACE,MACA,SACA,UAAuD,CAAC,GACxD;AACA,UAAM,SAAS,SAAS,cAAc,MAAM;AAC5C,UAAM,QAAQ,SAAS,EAAE,GAAG,SAAS,MAAM,QAAQ,IAAI,GAAG,CAAC;AAAA,EAC7D;AACF;AAEO,IAAM,eAAN,cAA2B,YAAY;AAAA,EAC5C,YACE,MAUA,SACA,UAAuD,CAAC,GACxD;AACA,UAAM,WAAW,IAAI,IAAI,SAAS,OAAO;AAAA,EAC3C;AACF;AAGO,IAAM,qBAAN,cAAiC,YAAY;AAAA,EAClD,YACE,MAQA,SACA,UAAuD,CAAC,GACxD;AACA,UAAM,MAAM,SAAS,OAAO;AAAA,EAC9B;AACF;AAEO,IAAM,gBAAN,cAA4B,YAAY;AAAA,EAC7C,YACE,MAQA,SACA,UAAuD,CAAC,GACxD;AACA,UAAM,YAAY,IAAI,IAAI,SAAS,OAAO;AAAA,EAC5C;AACF;AAEO,IAAM,wBAAN,cAAoC,YAAY;AAAA,EACrD,YACE,QACA,YACA,UACA,UAAuD,CAAC,GACxD;AACA;AAAA,MACE;AAAA,MACA,WAAW,MAAM,uBAAuB,UAAU,WAAM,QAAQ;AAAA,MAChE;AAAA,QACE,GAAG;AAAA,QACH,SAAS,EAAE,QAAQ,YAAY,UAAU,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;;;ACvJO,SAAS,MAAM,QAAgB,UAAyB;AAC7D,mBAAiB,MAAM;AACvB,SAAO,EAAE,QAAQ,SAAS;AAC5B;AAEO,SAAS,iBAAiB,QAAsB;AACrD,MAAI,CAAC,OAAO,UAAU,MAAM,GAAG;AAC7B,UAAM,IAAI,gBAAgB,yDAAyD,MAAM,EAAE;AAAA,EAC7F;AACF;AAEA,SAAS,mBAAmB,GAAU,GAAgB;AACpD,MAAI,EAAE,aAAa,EAAE,UAAU;AAC7B,UAAM,IAAI;AAAA,MACR,sBAAsB,EAAE,QAAQ,OAAO,EAAE,QAAQ;AAAA,IACnD;AAAA,EACF;AACF;AAEO,SAAS,IAAI,GAAU,GAAiB;AAC7C,qBAAmB,GAAG,CAAC;AACvB,SAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,UAAU,EAAE,SAAS;AAC7D;AAEO,SAAS,IAAI,GAAU,GAAiB;AAC7C,qBAAmB,GAAG,CAAC;AACvB,SAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,UAAU,EAAE,SAAS;AAC7D;AAEO,SAAS,MAAM,GAAU,QAAuB;AACrD,MAAI,CAAC,OAAO,SAAS,MAAM,GAAG;AAC5B,UAAM,IAAI,gBAAgB,8BAA8B,MAAM,EAAE;AAAA,EAClE;AACA,SAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,SAAS,MAAM,GAAG,UAAU,EAAE,SAAS;AACvE;AAEO,SAAS,SAAS,QAAiB,UAAyB;AACjE,QAAM,QAAQ,OAAO,OAAO,CAAC,KAAK,MAAM;AACtC,uBAAmB,GAAG,EAAE,QAAQ,GAAG,SAAS,CAAC;AAC7C,WAAO,MAAM,EAAE;AAAA,EACjB,GAAG,CAAC;AACJ,SAAO,EAAE,QAAQ,OAAO,SAAS;AACnC;AAEO,SAAS,SAAS,GAAU,SAA4B;AAC7D,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAClC,QAAM,cAAc,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACrD,MAAI,eAAe,GAAG;AACpB,UAAM,IAAI,gBAAgB,sCAAsC;AAAA,EAClE;AACA,QAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,KAAK,MAAO,EAAE,SAAS,IAAK,WAAW,CAAC;AAC5E,MAAI,YAAY,EAAE,SAAS,SAAS,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAC7D,QAAM,SAAS,SAAS,MAAM;AAC9B,MAAI,MAAM;AACV,SAAO,cAAc,KAAK,MAAM,OAAO,QAAQ;AAC7C,UAAM,QAAQ,YAAY,IAAI,IAAI;AAClC,WAAO,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK;AACnC,iBAAa;AACb,WAAO;AAAA,EACT;AACA,SAAO,OAAO,IAAI,CAAC,YAAY,EAAE,QAAQ,UAAU,EAAE,SAAS,EAAE;AAClE;;;ACbO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuC;AAE7C,IAAM,sBAAgE;AAAA,EAC3E,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa;AACf;;;AC1DO,IAAM,2BAAwE;AAAA,EACnF,SAAS,CAAC,aAAa,WAAW;AAAA,EAClC,WAAW,CAAC,cAAc,aAAa,YAAY,oBAAoB;AAAA,EACvE,YAAY,CAAC,WAAW,aAAa,YAAY,oBAAoB;AAAA,EACrE,SAAS,CAAC,aAAa,YAAY,oBAAoB;AAAA,EACvD,WAAW,CAAC,YAAY,oBAAoB;AAAA,EAC5C,WAAW,CAAC;AAAA,EACZ,UAAU,CAAC;AAAA,EACX,oBAAoB,CAAC,UAAU;AACjC;AAEO,SAAS,mBAAmB,MAAmB,IAA0B;AAC9E,SAAO,yBAAyB,IAAI,EAAE,SAAS,EAAE;AACnD;AAEO,SAAS,sBAAsB,MAAmB,IAAuB;AAC9E,MAAI,CAAC,mBAAmB,MAAM,EAAE,GAAG;AACjC,UAAM,IAAI,sBAAsB,SAAS,MAAM,EAAE;AAAA,EACnD;AACF;AAwFO,SAAS,aAAa,OAAqE;AAChG,MAAI,MAAM,iBAAiB,EAAG,QAAO;AACrC,MAAI,MAAM,iBAAiB,EAAG,QAAO;AACrC,SAAO,MAAM,gBAAgB,MAAM,gBAAgB,uBAAuB;AAC5E;AAGO,SAAS,gBACd,OACA,SACQ;AACR,QAAM,UAAU,QACb,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EACpC,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC;AAC5C,SAAO,KAAK,IAAI,GAAG,MAAM,gBAAgB,MAAM,gBAAgB,OAAO;AACxE;;;ACtIO,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAC9B,IAAM,0BAA0B;AAGhC,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAG7B,IAAM,mBAAmB;AACzB,IAAM,iBAAiB;AACvB,IAAM,sBAAsB;AAC5B,IAAM,oBAAoB;AAC1B,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,qBAAqB;AAC3B,IAAM,+BAA+B;AACrC,IAAM,2BAA2B;AAGjC,IAAM,wBAAwB;AAC9B,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB;AAC/B,IAAM,0BAA0B;AAChC,IAAM,6BAA6B;AAGnC,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAE7B,IAAM,0BAA0B;AAGhC,IAAM,mBAAmB;AACzB,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAC9B,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAG3B,IAAM,6BAA6B;AACnC,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAG7B,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAE7B,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACnFO,IAAM,0BAA0B;AAUhC,SAAS,UAAU,OAAwB;AAChD,MAAI,EAAE,iBAAiB,OAAQ,QAAO,OAAO,KAAK;AAClD,MAAI,MAAM,YAAY,GAAI,QAAO,MAAM;AACvC,QAAM,QAAS,MAA+B;AAC9C,MAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,EAAG,QAAO,MAAM,IAAI,SAAS,EAAE,KAAK,IAAI;AACnF,QAAM,OAAQ,MAA6B;AAC3C,SAAO,OAAO,SAAS,WAAW,OAAO,MAAM;AACjD;AAMO,IAAM,oBAAoB;AAO1B,SAAS,kBAAkB,KAAa,UAA8B,CAAC,GAAa;AACzF,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,GAAG;AAAA,EACtB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAAM;AAAA,IACV,GAAI,QAAQ,SAAS,QAAQ,CAAC,IAAI,CAAC,OAAO,QAAQ;AAAA,IAClD,OAAO;AAAA,IACP,GAAG,OAAO,aAAa,OAAO;AAAA,EAChC;AACA,QAAM,UAAU,IAAI,IAAI,CAAC,UAAU;AACjC,QAAI;AACF,aAAO,mBAAmB,KAAK;AAAA,IACjC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACD,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,MAAM,UAAU,iBAAiB;AAC/F;AAMO,SAAS,WAAW,KAAqB;AAC9C,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,WAAO,GAAG,OAAO,QAAQ,KAAK,OAAO,IAAI,GAAG,OAAO,aAAa,MAAM,KAAK,OAAO,QAAQ;AAAA,EAC5F,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,mBACpB,OACA,UAAqC,CAAC,GACf;AACvB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,aAAa,IAAI,gBAAgB;AACvC,MAAI;AACJ,QAAM,WAAW,IAAI,QAAsB,CAAC,YAAY;AACtD,YAAQ,WAAW,MAAM;AACvB,iBAAW,MAAM;AACjB,cAAQ,EAAE,QAAQ,QAAQ,QAAQ,mBAAmB,SAAS,MAAM,CAAC;AAAA,IACvE,GAAG,SAAS;AAAA,EACd,CAAC;AACD,QAAM,SAAS,CAAC,UAAiC;AAC/C,QAAI,SAAS,UAAU,KAAK;AAC5B,eAAW,UAAU,QAAQ,UAAU,CAAC,GAAG;AACzC,UAAI,WAAW,GAAI,UAAS,OAAO,MAAM,MAAM,EAAE,KAAK,KAAK;AAAA,IAC7D;AACA,WAAO,EAAE,QAAQ,QAAQ,OAAO;AAAA,EAClC;AACA,MAAI;AAGF,UAAM,UAAU,QAAQ,QAAQ,EAC7B,KAAK,MAAM,MAAM,WAAW,MAAM,CAAC,EACnC,MAAM,MAAM;AACf,WAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,CAAC;AAAA,EAC/C,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;;;ACvGA,IAAM,WAAW,IAAI,KAAK,SAAS,QAAW,EAAE,aAAa,OAAO,CAAC;AAGrE,SAAS,OAAO,OAAwB;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,SAAO;AACT;AAEA,SAAS,KAAK,GAAmB;AAC/B,SAAO,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK;AAClC;AAEA,SAAS,aAAa,GAAY,GAAoB;AACpD,QAAM,KAAK,OAAO;AAClB,QAAM,KAAK,OAAO;AAClB,MAAI,OAAO,GAAI,QAAO,KAAK,KAAK,KAAK;AACrC,MAAI,OAAO,UAAW,QAAO,OAAO,CAAC,IAAI,OAAO,CAAC;AACjD,QAAM,KAAK,KAAK,UAAU,CAAC,KAAK;AAChC,QAAM,KAAK,KAAK,UAAU,CAAC,KAAK;AAChC,SAAO,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI;AACtC;AAMO,SAAS,sBAAsB,GAAY,GAAoB;AACpE,QAAM,KAAK,OAAO,CAAC;AACnB,QAAM,KAAK,OAAO,CAAC;AACnB,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO,KAAM,IAAgB,CAAY;AAAA,IAC3C,KAAK;AACH,aAAO,KAAK,SAAS,QAAQ,GAAa,CAAW,CAAC;AAAA,IACxD,KAAK;AACH,aAAO,KAAK,aAAa,GAAG,CAAC,CAAC;AAAA,IAChC;AACE,aAAO;AAAA,EACX;AACF;AAKO,SAAS,eAAe,OAAgB,WAAsC;AACnF,QAAM,SAAS,UAAU;AACzB,UAAQ,UAAU,UAAU;AAAA,IAC1B,KAAK;AACH,aAAO,UAAU;AAAA,IACnB,KAAK;AACH,aAAO,sBAAsB,OAAO,MAAM,IAAI;AAAA,IAChD,KAAK;AACH,aAAO,sBAAsB,OAAO,MAAM,IAAI;AAAA,IAChD,KAAK,WAAW;AACd,UAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,EAAG,QAAO;AAC1D,YAAM,CAAC,KAAK,IAAI,IAAI;AACpB,aAAO,sBAAsB,OAAO,GAAG,KAAK,KAAK,sBAAsB,OAAO,IAAI,KAAK;AAAA,IACzF;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAGO,SAAS,gBAAgB,KAAc,MAAuB;AACnE,MAAI,QAAiB;AACrB,aAAW,QAAQ,KAAK,MAAM,GAAG,GAAG;AAClC,QAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,YAAS,MAAkC,IAAI;AAAA,EACjD;AACA,SAAO;AACT;AAEO,IAAM,sBAAsB;AAanC,SAAS,QAAQ,QAA4B;AAC3C,MAAI,OAAO,YAAY,QAAW;AAChC,WAAO,EAAE,OAAO,OAAO,QAAQ,OAAO,WAAW,OAAO,QAAQ,UAAU;AAAA,EAC5E;AACA,QAAM,YAAa,OAAO,aAAa,OAAQ,QAAQ;AACvD,SAAO,EAAE,OAAO,OAAO,SAAS,SAAS,MAAM,UAAU;AAC3D;AAEA,IAAM,YAAY,CAAC,UAA4B,UAAU,QAAQ,UAAU;AAG3E,SAAS,iBAAiB,GAAa,GAAa,OAAsB;AACxE,MAAI,MAAM,UAAU,MAAM;AACxB,UAAM,KAAK,UAAU,EAAE,KAAK;AAC5B,UAAM,KAAK,UAAU,EAAE,KAAK;AAC5B,QAAI,OAAO,GAAI,QAAO,KAAK,IAAI;AAC/B,QAAIA,KAAI,KAAK,IAAI,sBAAsB,EAAE,OAAO,EAAE,KAAK;AACvD,QAAI,MAAM,cAAc,OAAQ,CAAAA,KAAI,CAACA;AACrC,QAAIA,OAAM,EAAG,QAAOA;AACpB,WAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI;AAAA,EAClD;AACA,QAAM,IAAI,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI;AACnD,SAAO,MAAM,cAAc,SAAS,CAAC,IAAI;AAC3C;AAGA,SAAS,YAAY,OAAc,QAA6B;AAC9D,SAAO,KAAK,UAAU,CAAC,MAAM,OAAO,MAAM,WAAW,OAAO,YAAY,CAAC;AAC3E;AAEA,SAAS,aAAa,QAAgB,OAAyB;AAC7D,MAAI;AACJ,MAAI;AACF,cAAU,KAAK,MAAM,OAAO,KAAK,QAAQ,WAAW,EAAE,SAAS,MAAM,CAAC;AAAA,EACxE,QAAQ;AACN,cAAU;AAAA,EACZ;AACA,QAAM,IAAI;AACV,MAAI,MAAM,QAAQ,OAAO,MAAM,YAAY,OAAO,EAAE,MAAM,YAAY,OAAO,EAAE,MAAM,UAAU;AAC7F,UAAM,IAAI,gBAAgB,2BAA2B,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAAA,EAC9E;AACA,MAAI,EAAE,MAAM,OAAO;AACjB,UAAM,IAAI,gBAAgB,6CAA6C,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAAA,EAChG;AACA,SAAO,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,KAAK,KAAK;AACxC;AAMO,SAAS,eACd,WACA,QACgB;AAChB,QAAM,QAAQ,OAAO,SAAS;AAC9B,QAAM,QAAQ,QAAQ,MAAM;AAC5B,QAAM,QAAQ,YAAY,OAAO,MAAM;AACvC,QAAM,QAAQ,OAAO,SAAS,aAAa,OAAO,QAAQ,KAAK,IAAI;AACnE,QAAM,EAAE,cAAc,QAAQ,IAAI;AAElC,QAAM,UAAuD,CAAC;AAC9D,aAAW,CAAC,KAAK,GAAG,KAAK,WAAW;AAClC,UAAM,SAAU,OAAO,CAAC;AACxB,QAAI,aAAa,UAAU,OAAO,OAAO,aAAa,KAAK,MAAM,aAAa,MAAO;AACrF,QAAI,YAAY,UAAa,CAAC,eAAe,OAAO,QAAQ,KAAK,GAAG,OAAO,EAAG;AAC9E,UAAM,QAAQ,MAAM,UAAU,OAAO,OAAQ,gBAAgB,QAAQ,MAAM,KAAK,KAAK;AACrF,UAAM,WAAW,EAAE,KAAK,MAAM;AAC9B,QAAI,UAAU,QAAQ,iBAAiB,UAAU,OAAO,KAAK,KAAK,EAAG;AACrE,YAAQ,KAAK,EAAE,UAAU,IAAI,CAAC;AAAA,EAChC;AACA,UAAQ,KAAK,CAAC,GAAG,MAAM,iBAAiB,EAAE,UAAU,EAAE,UAAU,KAAK,CAAC;AAEtE,QAAM,OAAO,QAAQ,MAAM,GAAG,KAAK;AACnC,QAAM,OAAO,KAAK,KAAK,SAAS,CAAC;AACjC,QAAM,aACJ,QAAQ,SAAS,SAAS,SAAS,SAC/B,OAAO;AAAA,IACL,KAAK,UAAU,EAAE,GAAG,OAAO,GAAG,KAAK,SAAS,KAAK,GAAG,KAAK,SAAS,MAAM,CAAC;AAAA,IACzE;AAAA,EACF,EAAE,SAAS,WAAW,IACtB;AACN,SAAO,EAAE,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,GAAQ,GAAG,WAAW;AAC1D;;;AC/KO,IAAM,wBAAwB;AAErC,IAAM,iBAAiB;AAMhB,SAAS,qBAAqB,GAAoB;AACvD,SAAO,eAAe,KAAK,CAAC;AAC9B;;;ACuCO,SAAS,qBAAqB,UAAsD;AACzF,QAAM,UAAkC,CAAC;AACzC,MAAI,SAAS,QAAQ,MAAM;AACzB,YAAQ,2BAA2B,IAAI;AAAA,EACzC;AACA,QAAM,MAAM,SAAS,QAAQ;AAC7B,QAAM,WACJ,QAAQ,WACJ,qKACA,QAAQ,YACN,oHACA,QAAQ,QACN,OACA;AACV,MAAI,SAAU,SAAQ,yBAAyB,IAAI;AACnD,UAAQ,iBAAiB,IAAI,SAAS,QAAQ;AAC9C,UAAQ,iBAAiB,IAAI,SAAS,QAAQ,kBAAkB,SAAS,SAAS;AAClF,UAAQ,wBAAwB,IAAI;AACpC,SAAO;AACT;;;AChEA,SAAS,OAAO,UAAkB,MAAuC;AACvE,SAAO,SAAS,QAAQ,2BAA2B,CAAC,GAAG,SAAiB;AACtE,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAI,MAAe;AACnB,eAAW,KAAK,OAAO;AACrB,UAAI,OAAO,OAAO,QAAQ,YAAY,KAAM,KAAiC;AAC3E,cAAO,IAAgC,CAAC;AAAA,MAC1C,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO,OAAO,OAAO,KAAK,OAAO,GAAG;AAAA,EACtC,CAAC;AACH;AAEA,IAAM,YAA+D;AAAA,EACnE,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,EACR;AAAA,EACA,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,MAAM;AAAA;AAAA;AAAA;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AAAA,EACA,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA;AAAA;AAAA,EACR;AACF;AAEO,SAAS,oBACd,UACA,MACe;AACf,QAAM,IAAI,UAAU,QAAQ;AAC5B,MAAI,CAAC,GAAG;AACN,WAAO;AAAA,MACL,SAAS,YAAY,QAAQ;AAAA,MAC7B,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB,MAAM,QAAQ,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,IAC7C;AAAA,EACF;AACA,QAAM,UAAU,OAAO,EAAE,SAAS,IAAI;AACtC,QAAM,OAAO,OAAO,EAAE,MAAM,IAAI;AAChC,QAAM,OAAO,QAAQ,KAAK,QAAQ,MAAM,MAAM,CAAC;AAC/C,SAAO,EAAE,SAAS,MAAM,KAAK;AAC/B;;;ACtDA,IAAM,kBAAkB;AAGjB,SAAS,WAAW,OAA2B;AACpD,MAAI,SAAS,EAAG,QAAO;AACvB,SAAO,QAAQ,kBAAkB,QAAQ;AAC3C;AAEO,IAAM,eAA2C;AAAA,EACtD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,IAAI;AACN;;;ACiBO,IAAM,yBAAyB;AAC/B,IAAM,6BAA6B;AAGnC,SAAS,sBAAsB,OAAwB;AAC5D,SAAO,2BAA2B,KAAK,KAAK;AAC9C;","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-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/** 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 { 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/** An order as a storefront reads it; `timeline` is optional so an adapter that returns a plain `Order` still fits. */\nexport interface ShopperOrder extends Order {\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'\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_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 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","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","// 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nymbalhq/types",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Shared TypeScript types and adapter contracts for Nymbal",
5
5
  "license": "MIT",
6
6
  "repository": {