@nymbalhq/types 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -237,6 +237,8 @@ interface Order {
237
237
  * the key stays out of access logs that record request URLs.
238
238
  */
239
239
  declare const ORDER_KEY_HEADER = "x-nymbal-order-key";
240
+ /** The request header that carries the order's email beside an expired order key. Never a query parameter either. */
241
+ declare const ORDER_EMAIL_HEADER = "x-nymbal-order-email";
240
242
  /** Who took the money: the payments provider, the merchant recording an offline payment, or an imported history. */
241
243
  type CaptureMethod = 'provider' | 'manual' | 'import';
242
244
  type OrderRefundStatus = 'pending' | 'succeeded' | 'failed';
@@ -283,8 +285,17 @@ interface OrderTimelineEntry {
283
285
  /** ISO 8601, UTC. */
284
286
  at: string;
285
287
  }
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
+ /** A shipping address as an order's key holder reads it: no phone, no company. */
289
+ type ShopperAddress = Omit<Address, 'phone' | 'company'>;
290
+ /** What only the order's signed-in owner reads, or the store's own records, beside what its key holder reads. */
291
+ type OwnerOrStoreField = 'id' | 'email' | 'billingAddress' | 'customerId' | 'sequence' | 'capturedMinor' | 'capturedAt' | 'refundedMinor' | 'captureMethod' | 'notes' | 'metadata' | 'paymentIntentId' | 'updatedAt';
292
+ /**
293
+ * An order as a storefront reads it. A key holder reads the confirmation page's fields; its owner's session adds the
294
+ * email, both full addresses and the order's id. Every other field is optional, so an adapter that returns a plain
295
+ * `Order` still fits.
296
+ */
297
+ interface ShopperOrder extends Omit<Order, OwnerOrStoreField | 'shippingAddress'>, Partial<Pick<Order, OwnerOrStoreField>> {
298
+ shippingAddress: ShopperAddress;
288
299
  timeline?: OrderTimelineEntry[];
289
300
  }
290
301
 
@@ -455,12 +466,14 @@ declare const EVT_PAYMENT_CAPTURED = "payment.captured.v1";
455
466
  declare const EVT_PAYMENT_FAILED = "payment.failed.v1";
456
467
  declare const EVT_PAYMENT_REFUNDED = "payment.refunded.v1";
457
468
  declare const EVT_ALERT_REQUESTED = "alert.requested.v1";
469
+ /** A subscribe for an alert already active: the subscription stands and nothing is sent. */
470
+ declare const EVT_ALERT_REPEATED = "alert.repeated.v1";
458
471
  declare const EVT_ALERT_CONFIRMED = "alert.confirmed.v1";
459
472
  declare const EVT_ALERT_UNSUBSCRIBED = "alert.unsubscribed.v1";
460
473
  declare const EVT_ALERT_SENT = "alert.sent.v1";
461
474
  declare const EVT_ADAPTER_ERROR = "adapter.error.v1";
462
475
  declare const EVT_SETTINGS_CHANGED = "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"];
476
+ declare const STANDARD_EVENT_TYPES: readonly ["product.created.v1", "product.updated.v1", "product.deleted.v1", "product.published.v1", "product.unpublished.v1", "product.viewed.v1", "product.restocked.v1", "product.variant_price_changed.v1", "category.created.v1", "category.updated.v1", "category.deleted.v1", "order.placed.v1", "order.paid.v1", "order.fulfilled.v1", "order.shipped.v1", "order.delivered.v1", "order.cancelled.v1", "order.refunded.v1", "order.partially_refunded.v1", "order.status_changed.v1", "order.confirmation_resend_requested.v1", "inventory.changed.v1", "inventory.reserved.v1", "inventory.released.v1", "inventory.low_stock.v1", "inventory.out_of_stock.v1", "customer.created.v1", "customer.updated.v1", "customer.deleted.v1", "visitor.merged.v1", "cart.created.v1", "cart.item_added.v1", "cart.item_removed.v1", "cart.updated.v1", "cart.abandoned.v1", "cart.converted.v1", "payment.intent_created.v1", "payment.captured.v1", "payment.failed.v1", "payment.refunded.v1", "alert.requested.v1", "alert.repeated.v1", "alert.confirmed.v1", "alert.unsubscribed.v1", "alert.sent.v1", "adapter.error.v1", "settings.changed.v1"];
464
477
 
465
478
  /** What a shopper asks to be told about a variant: that it is back in stock, or that its price dropped. */
466
479
  type AlertKind = 'back_in_stock' | 'price_drop';
@@ -683,6 +696,7 @@ interface AlertRequestedV1Payload {
683
696
  variantId: string;
684
697
  productId: string;
685
698
  }
699
+ type AlertRepeatedV1Payload = AlertRequestedV1Payload;
686
700
  interface AlertConfirmedV1Payload {
687
701
  subscriptionId: string;
688
702
  }
@@ -756,6 +770,7 @@ interface NymbalEventMap {
756
770
  [EVT_PAYMENT_FAILED]: PaymentFailedV1Payload;
757
771
  [EVT_PAYMENT_REFUNDED]: PaymentRefundedV1Payload;
758
772
  [EVT_ALERT_REQUESTED]: AlertRequestedV1Payload;
773
+ [EVT_ALERT_REPEATED]: AlertRepeatedV1Payload;
759
774
  [EVT_ALERT_CONFIRMED]: AlertConfirmedV1Payload;
760
775
  [EVT_ALERT_UNSUBSCRIBED]: AlertUnsubscribedV1Payload;
761
776
  [EVT_ALERT_SENT]: AlertSentV1Payload;
@@ -919,8 +934,9 @@ type CommerceSlot = 'payments' | 'email' | 'reviews' | 'search' | 'analytics' |
919
934
  type Slot = CommerceSlot | 'documentStore' | 'eventBus' | 'media' | 'security';
920
935
  /**
921
936
  * What the kernel hands an adapter package's `createAdapter`. A package that serves more than one slot switches on
922
- * `slot`; `config` is a commerce slot's `config` block from nymbal.config.ts, a primitive's connection block, or for
923
- * `security` the block named after the adapter (`security.cloudflare`).
937
+ * `slot`; `config` is a commerce slot's `config` block from nymbal.config.ts, a primitive's connection block (or a
938
+ * third-party primitive's `config`), or for `security` the block named after the adapter (`security.cloudflare`) or a
939
+ * third-party adapter's `security.config`.
924
940
  */
925
941
  interface AdapterContext {
926
942
  slot: Slot;
@@ -946,11 +962,25 @@ interface AdapterSetting {
946
962
  */
947
963
  optional?: boolean;
948
964
  }
949
- /** What a provider package exports: `createAdapter`, and the fields it lets Settings fill. */
965
+ /**
966
+ * What a provider package exports: `createAdapter`, the slot and provider it serves (checked when it is loaded), and
967
+ * the fields it lets Settings fill.
968
+ */
950
969
  interface ProviderPackageModule {
951
970
  createAdapter(ctx: AdapterContext): unknown;
971
+ slot: Slot;
972
+ provider: string;
952
973
  settings?: readonly AdapterSetting[];
953
974
  }
975
+ /**
976
+ * How `nymbal.config.ts` names an infrastructure slot's adapter (`documentStore`, `eventBus`, `media`): the provider,
977
+ * the npm package it comes from when not `@nymbalhq/<slot>-<provider>`, and a third-party adapter's `ctx.config`.
978
+ */
979
+ interface InfrastructureBlock {
980
+ provider: string;
981
+ package?: string;
982
+ config?: Record<string, unknown>;
983
+ }
954
984
 
955
985
  interface PutOptions {
956
986
  ttl?: number;
@@ -1387,6 +1417,58 @@ interface WebhookDeliveryProblem {
1387
1417
  */
1388
1418
  declare function webhookDeliveryProblem(providerName: string, producesEvents: readonly string[], delivery: WebhookDelivery): WebhookDeliveryProblem | null;
1389
1419
 
1420
+ /**
1421
+ * `<providerName>.bounced.v1`: a message to `recipient` bounced. `hard` is the provider's permanent failure, `soft`
1422
+ * its transient one; a classification the adapter cannot map is `soft`, so a doubtful bounce never suppresses.
1423
+ */
1424
+ interface EmailBouncedV1Payload {
1425
+ recipient: string;
1426
+ bounce: 'hard' | 'soft';
1427
+ /** The `EmailSendResult.messageId` the bounce answers, when the provider gives one. */
1428
+ messageId: string | null;
1429
+ /** ISO 8601. */
1430
+ occurredAt: string;
1431
+ }
1432
+ /** `<providerName>.complained.v1`: `recipient` marked a message as spam. */
1433
+ interface EmailComplainedV1Payload {
1434
+ recipient: string;
1435
+ messageId: string | null;
1436
+ /** ISO 8601. */
1437
+ occurredAt: string;
1438
+ }
1439
+ type EmailFeedbackKind = 'bounced' | 'complained';
1440
+ /** The event type an email adapter publishes a bounce or a complaint under. */
1441
+ declare function emailFeedbackType(providerName: string, what: EmailFeedbackKind): string;
1442
+ /**
1443
+ * The first bounce or complaint in a verified delivery from the email adapter `providerName` that the kernel cannot
1444
+ * act on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's
1445
+ * retry after the adapter is fixed is still received; the email feedback contract suite certifies the same rule.
1446
+ */
1447
+ declare function emailFeedbackProblem(providerName: string, delivery: WebhookDelivery): WebhookDeliveryProblem | null;
1448
+
1449
+ /** The statuses a shipping adapter maps its carrier's onto; one it cannot map is `unknown`. */
1450
+ declare const SHIPPING_TRACKING_STATUSES: readonly ["pre_transit", "in_transit", "out_for_delivery", "delivered", "exception", "returned", "unknown"];
1451
+ type ShippingTrackingStatus = (typeof SHIPPING_TRACKING_STATUSES)[number];
1452
+ /** `<providerName>.tracking.v1`: the carrier reported `status` for the parcel `trackingNumber`. */
1453
+ interface ShippingTrackingV1Payload {
1454
+ trackingNumber: string;
1455
+ /** The carrier's code as the adapter's provider gives it, which need not match the merchant's own. */
1456
+ carrier: string | null;
1457
+ status: ShippingTrackingStatus;
1458
+ /** ISO 8601. */
1459
+ occurredAt: string;
1460
+ }
1461
+ /** The event type a shipping adapter publishes a tracking update under. */
1462
+ declare function shippingTrackingType(providerName: string): string;
1463
+ /** A tracking number as stored and looked up: no whitespace, upper case, so a carrier's spelling finds the merchant's. */
1464
+ declare function normaliseTrackingNumber(value: string): string;
1465
+ /**
1466
+ * The first tracking update in a verified delivery from the shipping adapter `providerName` that the kernel cannot act
1467
+ * on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's retry
1468
+ * after the adapter is fixed is still received; the shipping tracking contract suite certifies the same rule.
1469
+ */
1470
+ declare function shippingTrackingProblem(providerName: string, delivery: WebhookDelivery): WebhookDeliveryProblem | null;
1471
+
1390
1472
  interface RenderedEmail {
1391
1473
  subject: string;
1392
1474
  text: string;
@@ -1435,7 +1517,7 @@ interface Recipe {
1435
1517
  summary: string;
1436
1518
  /** npm packages to install, SDKs included; empty when the service is built in. */
1437
1519
  install: string[];
1438
- /** Config paths and the values they take, e.g. `infrastructure.documentStore` = `'redis'`. */
1520
+ /** Config paths and the values they take, e.g. `infrastructure.documentStore.provider` = `'redis'`. */
1439
1521
  set: Array<{
1440
1522
  path: string;
1441
1523
  value: RecipeValue;
@@ -1555,5 +1637,5 @@ interface AsOfRead<T = Record<string, unknown>> {
1555
1637
  record: T;
1556
1638
  }
1557
1639
 
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 };
1640
+ export { AdapterError, AuthError, CORRELATION_ID_HEADER, CheckoutError, ConfigError, DEFAULT_PRODUCT_SORT, DEFAULT_QUERY_LIMIT, EVT_ADAPTER_ERROR, EVT_ALERT_CONFIRMED, EVT_ALERT_REPEATED, EVT_ALERT_REQUESTED, EVT_ALERT_SENT, EVT_ALERT_UNSUBSCRIBED, EVT_CART_ABANDONED, EVT_CART_CONVERTED, EVT_CART_CREATED, EVT_CART_ITEM_ADDED, EVT_CART_ITEM_REMOVED, EVT_CART_UPDATED, EVT_CATEGORY_CREATED, EVT_CATEGORY_DELETED, EVT_CATEGORY_UPDATED, EVT_CUSTOMER_CREATED, EVT_CUSTOMER_DELETED, EVT_CUSTOMER_REGISTERED, EVT_CUSTOMER_UPDATED, EVT_INVENTORY_CHANGED, EVT_INVENTORY_LOW_STOCK, EVT_INVENTORY_OUT_OF_STOCK, EVT_INVENTORY_RELEASED, EVT_INVENTORY_RESERVED, EVT_ORDER_CANCELLED, EVT_ORDER_CONFIRMATION_RESEND_REQUESTED, EVT_ORDER_DELIVERED, EVT_ORDER_FULFILLED, EVT_ORDER_PAID, EVT_ORDER_PARTIALLY_REFUNDED, EVT_ORDER_PLACED, EVT_ORDER_REFUNDED, EVT_ORDER_SHIPPED, EVT_ORDER_STATUS_CHANGED, EVT_PAYMENT_CAPTURED, EVT_PAYMENT_FAILED, EVT_PAYMENT_INTENT_CREATED, EVT_PAYMENT_REFUNDED, EVT_PRODUCT_CREATED, EVT_PRODUCT_DELETED, EVT_PRODUCT_PUBLISHED, EVT_PRODUCT_RESTOCKED, EVT_PRODUCT_UNPUBLISHED, EVT_PRODUCT_UPDATED, EVT_PRODUCT_VARIANT_PRICE_CHANGED, EVT_PRODUCT_VIEWED, EVT_SETTINGS_CHANGED, EVT_VISITOR_MERGED, HEALTH_CHECK_TIMEOUT_MS, HttpError, IDEMPOTENCY_KEY_HEADER, IDEMPOTENT_REPLAYED_HEADER, NotFoundError, NymbalError, ORDER_EMAIL_HEADER, ORDER_KEY_HEADER, ORDER_STATUS_TRANSITIONS, ORDER_TIMELINE_KINDS, OrderConflictError, PRODUCT_SORT_KEYS, PRODUCT_SORT_LABELS, PaymentError, SECRET_MIN_LENGTH, SHIPPING_TRACKING_STATUSES, STANDARD_EVENT_TYPES, STOCK_LABELS, StatusTransitionError, ValidationError, add, allocate, assertMinorUnits, assertOrderTransition, boundedHealthCheck, buildSecurityHeaders, canTransitionOrder, compareDocumentValues, connectionSecrets, displayUrl, documentValueAt, emailFeedbackProblem, emailFeedbackType, errorText, handlerFailedError, isValidCorrelationId, isValidIdempotencyKey, matchesSortKey, money, mulBy, normaliseTrackingNumber, paymentState, queryDocuments, refundableMinor, renderEmailTemplate, shippingTrackingProblem, shippingTrackingType, stockLevel, sub, sumMoney, webhookDeliveryProblem };
1641
+ export type { AdapterBase, AdapterContext, AdapterErrorV1Payload, AdapterSetting, Address, AggregateRating, AiAdapter, AlertConfirmedV1Payload, AlertKind, AlertRepeatedV1Payload, AlertRequestedV1Payload, AlertSentV1Payload, AlertStatus, AlertUnsubscribedV1Payload, AnalyticsAdapter, AsOfRead, AuditActor, AuditEntry, AuditOutcome, AuditSubject, BoundedHealthCheckOptions, CaptureMethod, Cart, CartAbandonedV1Payload, CartConvertedV1Payload, CartCreatedV1Payload, CartItem, CartItemAddedV1Payload, CartItemRemovedV1Payload, CartUpdatedV1Payload, Category, CategoryCreatedV1Payload, CategoryDeletedV1Payload, CategoryUpdatedV1Payload, CheckoutPayment, CommerceAdapter, CommerceSlot, CommerceSlotAdapters, Customer, CustomerAddress, CustomerCreatedV1Payload, CustomerDeletedV1Payload, CustomerProfile, CustomerSnapshot, CustomerUpdatedV1Payload, DocumentStoreAdapter, DriftReport, EmailAdapter, EmailBouncedV1Payload, EmailComplainedV1Payload, EmailFeedbackKind, EmailSendResult, ErrorContext, EventBusAdapter, EventHandler, EventMetadata, Facet, FacetParams, HandlerFailure, HealthReport, HealthStatus, HistoryChange, HistoryEntry, HistoryKind, HistoryPage, HttpAdapter, HttpMethod, IndexResult, InfrastructureBlock, InventoryChangedV1Payload, InventoryLowStockV1Payload, InventoryOutOfStockV1Payload, InventoryReleasedV1Payload, InventoryReservation, InventoryReservedV1Payload, Logger, MediaStorage, MiddlewareHandler, Money, NymbalEnvironment, NymbalEvent, NymbalEventMap, NymbalEventType, Order, OrderCancelledV1Payload, OrderConfirmationResendRequestedV1Payload, OrderDeliveredV1Payload, OrderFulfilledV1Payload, OrderHistoryEntry, OrderLineItem, OrderPaidV1Payload, OrderPartiallyRefundedV1Payload, OrderPayment, OrderPlacedV1Payload, OrderRefund, OrderRefundStatus, OrderRefundedV1Payload, OrderShippedV1Payload, OrderStatus, OrderStatusChangedV1Payload, OrderTimelineEntry, OrderTimelineKind, PaginationParams, PayloadOf, PaymentCapture, PaymentCapturedV1Payload, PaymentClient, PaymentFailedV1Payload, PaymentIntent, PaymentIntentCreatedV1Payload, PaymentIntentParams, PaymentRefundedV1Payload, PaymentState, PaymentStatus, PaymentsAdapter, Product, ProductCreatedV1Payload, ProductDeletedV1Payload, ProductMedia, ProductPublishedV1Payload, ProductRestockedV1Payload, ProductSnapshot, ProductSortKey, ProductStatus, ProductType, ProductUnpublishedV1Payload, ProductUpdatedV1Payload, ProductVariantPriceChangedV1Payload, ProductViewedV1Payload, ProviderPackageModule, PutOptions, QueryParams, QueryResult, RateLimitAction, RateLimitDecision, RateLimitRule, RecentViewer, Recipe, RecipeValue, Refund, RefundOptions, RenderedEmail, RequestAuth, RequestContext, ResponseCookie, ResponseEnvelope, RetryPolicy, Review, ReviewModerationStatus, ReviewResult, ReviewSubmission, ReviewsAdapter, ReviewsResult, Role, RouteHandler, RouteOptions, SearchAdapter, SearchDocument, SearchParams, SearchResult, SecurityAdapter, SecurityConfigLike, SecurityState, SettingFieldView, SettingSourceName, SettingWriteResult, SettingsChangedV1Payload, SettingsListing, SettingsSlotView, ShippingAdapter, ShippingLabel, ShippingLabelParams, ShippingRate, ShippingRateParams, ShippingTracking, ShippingTrackingStatus, ShippingTrackingV1Payload, ShopperAddress, ShopperOrder, Slot, SortKeyCondition, SseEvent, StockAdjustment, StockAdjustmentReason, StockLevel, StockStatus, StoreCapabilities, StreamContext, StreamHandler, Subscription, SubscriptionConfig, TaxAdapter, TaxCalculationParams, TaxCalculationResult, TaxLine, TransactionalEmail, ValidationResult, Variant, VariantDimensions, VariantOption, VariantStatus, VisitorMergedV1Payload, WebhookDelivery, WebhookDeliveryProblem, WebhookEventDraft, WebhookHeaders, WebhookResult };
package/dist/index.js CHANGED
@@ -170,6 +170,7 @@ function assertOrderTransition(from, to) {
170
170
  }
171
171
  }
172
172
  var ORDER_KEY_HEADER = "x-nymbal-order-key";
173
+ var ORDER_EMAIL_HEADER = "x-nymbal-order-email";
173
174
  function paymentState(order) {
174
175
  if (order.capturedMinor <= 0) return "unpaid";
175
176
  if (order.refundedMinor <= 0) return "paid";
@@ -234,6 +235,7 @@ var EVT_PAYMENT_CAPTURED = "payment.captured.v1";
234
235
  var EVT_PAYMENT_FAILED = "payment.failed.v1";
235
236
  var EVT_PAYMENT_REFUNDED = "payment.refunded.v1";
236
237
  var EVT_ALERT_REQUESTED = "alert.requested.v1";
238
+ var EVT_ALERT_REPEATED = "alert.repeated.v1";
237
239
  var EVT_ALERT_CONFIRMED = "alert.confirmed.v1";
238
240
  var EVT_ALERT_UNSUBSCRIBED = "alert.unsubscribed.v1";
239
241
  var EVT_ALERT_SENT = "alert.sent.v1";
@@ -281,6 +283,7 @@ var STANDARD_EVENT_TYPES = [
281
283
  EVT_PAYMENT_FAILED,
282
284
  EVT_PAYMENT_REFUNDED,
283
285
  EVT_ALERT_REQUESTED,
286
+ EVT_ALERT_REPEATED,
284
287
  EVT_ALERT_CONFIRMED,
285
288
  EVT_ALERT_UNSUBSCRIBED,
286
289
  EVT_ALERT_SENT,
@@ -354,7 +357,7 @@ async function boundedHealthCheck(check, options = {}) {
354
357
  }
355
358
 
356
359
  // src/adapters/document-order.ts
357
- var collator = new Intl.Collator(void 0, { sensitivity: "base" });
360
+ var collator;
358
361
  function rankOf(value) {
359
362
  if (typeof value === "number") return 0;
360
363
  if (typeof value === "string") return 1;
@@ -381,6 +384,7 @@ function compareDocumentValues(a, b) {
381
384
  case 0:
382
385
  return sign(a - b);
383
386
  case 1:
387
+ collator ??= new Intl.Collator(void 0, { sensitivity: "base" });
384
388
  return sign(collator.compare(a, b));
385
389
  case 2:
386
390
  return sign(compareOther(a, b));
@@ -545,6 +549,82 @@ function webhookDeliveryProblem(providerName, producesEvents, delivery) {
545
549
  return null;
546
550
  }
547
551
 
552
+ // src/adapters/email-feedback.ts
553
+ function emailFeedbackType(providerName, what) {
554
+ return `${providerName}.${what}.v1`;
555
+ }
556
+ function payloadOf(event) {
557
+ const payload = event?.payload;
558
+ return typeof payload === "object" && payload !== null ? payload : {};
559
+ }
560
+ function emailFeedbackProblem(providerName, delivery) {
561
+ const bounced = emailFeedbackType(providerName, "bounced");
562
+ const complained = emailFeedbackType(providerName, "complained");
563
+ if (!Array.isArray(delivery.events)) return null;
564
+ for (const event of delivery.events) {
565
+ const type = event?.type;
566
+ if (type !== bounced && type !== complained) continue;
567
+ const payload = payloadOf(event);
568
+ const { recipient, messageId, occurredAt } = payload;
569
+ if (typeof recipient !== "string" || recipient.trim() === "") {
570
+ return { type, reason: "the recipient is missing or empty" };
571
+ }
572
+ if (type === bounced && payload["bounce"] !== "hard" && payload["bounce"] !== "soft") {
573
+ return { type, reason: "the bounce is neither 'hard' nor 'soft'" };
574
+ }
575
+ if (messageId !== null && typeof messageId !== "string") {
576
+ return { type, reason: "the messageId is neither a string nor null" };
577
+ }
578
+ if (typeof occurredAt !== "string" || Number.isNaN(Date.parse(occurredAt))) {
579
+ return { type, reason: "occurredAt is not a readable date" };
580
+ }
581
+ }
582
+ return null;
583
+ }
584
+
585
+ // src/adapters/shipping-tracking.ts
586
+ var SHIPPING_TRACKING_STATUSES = [
587
+ "pre_transit",
588
+ "in_transit",
589
+ "out_for_delivery",
590
+ "delivered",
591
+ "exception",
592
+ "returned",
593
+ "unknown"
594
+ ];
595
+ function shippingTrackingType(providerName) {
596
+ return `${providerName}.tracking.v1`;
597
+ }
598
+ function normaliseTrackingNumber(value) {
599
+ return value.replace(/\s+/g, "").toUpperCase();
600
+ }
601
+ function payloadOf2(event) {
602
+ const payload = event?.payload;
603
+ return typeof payload === "object" && payload !== null ? payload : {};
604
+ }
605
+ function shippingTrackingProblem(providerName, delivery) {
606
+ const tracking = shippingTrackingType(providerName);
607
+ if (!Array.isArray(delivery.events)) return null;
608
+ for (const event of delivery.events) {
609
+ const type = event?.type;
610
+ if (type !== tracking) continue;
611
+ const { trackingNumber, carrier, status, occurredAt } = payloadOf2(event);
612
+ if (typeof trackingNumber !== "string" || normaliseTrackingNumber(trackingNumber) === "") {
613
+ return { type, reason: "the trackingNumber is missing or empty" };
614
+ }
615
+ if (carrier !== null && typeof carrier !== "string") {
616
+ return { type, reason: "the carrier is neither a string nor null" };
617
+ }
618
+ if (!SHIPPING_TRACKING_STATUSES.includes(status)) {
619
+ return { type, reason: "the status is not one of the tracking statuses" };
620
+ }
621
+ if (typeof occurredAt !== "string" || Number.isNaN(Date.parse(occurredAt))) {
622
+ return { type, reason: "occurredAt is not a readable date" };
623
+ }
624
+ }
625
+ return null;
626
+ }
627
+
548
628
  // src/adapters/email-templates.ts
549
629
  function render(template, vars) {
550
630
  return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => {
@@ -680,6 +760,7 @@ export {
680
760
  DEFAULT_QUERY_LIMIT,
681
761
  EVT_ADAPTER_ERROR,
682
762
  EVT_ALERT_CONFIRMED,
763
+ EVT_ALERT_REPEATED,
683
764
  EVT_ALERT_REQUESTED,
684
765
  EVT_ALERT_SENT,
685
766
  EVT_ALERT_UNSUBSCRIBED,
@@ -731,6 +812,7 @@ export {
731
812
  IDEMPOTENT_REPLAYED_HEADER,
732
813
  NotFoundError,
733
814
  NymbalError,
815
+ ORDER_EMAIL_HEADER,
734
816
  ORDER_KEY_HEADER,
735
817
  ORDER_STATUS_TRANSITIONS,
736
818
  ORDER_TIMELINE_KINDS,
@@ -739,6 +821,7 @@ export {
739
821
  PRODUCT_SORT_LABELS,
740
822
  PaymentError,
741
823
  SECRET_MIN_LENGTH,
824
+ SHIPPING_TRACKING_STATUSES,
742
825
  STANDARD_EVENT_TYPES,
743
826
  STOCK_LABELS,
744
827
  StatusTransitionError,
@@ -754,6 +837,8 @@ export {
754
837
  connectionSecrets,
755
838
  displayUrl,
756
839
  documentValueAt,
840
+ emailFeedbackProblem,
841
+ emailFeedbackType,
757
842
  errorText,
758
843
  handlerFailedError,
759
844
  isValidCorrelationId,
@@ -761,10 +846,13 @@ export {
761
846
  matchesSortKey,
762
847
  money,
763
848
  mulBy,
849
+ normaliseTrackingNumber,
764
850
  paymentState,
765
851
  queryDocuments,
766
852
  refundableMinor,
767
853
  renderEmailTemplate,
854
+ shippingTrackingProblem,
855
+ shippingTrackingType,
768
856
  stockLevel,
769
857
  sub,
770
858
  sumMoney,
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/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"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/domain/money.ts","../src/domain/product.ts","../src/domain/order.ts","../src/domain/order-timeline.ts","../src/events/constants.ts","../src/adapters/adapter-base.ts","../src/adapters/document-order.ts","../src/adapters/event-bus.ts","../src/adapters/http.ts","../src/adapters/security.ts","../src/adapters/webhook-delivery.ts","../src/adapters/email-feedback.ts","../src/adapters/shipping-tracking.ts","../src/adapters/email-templates.ts","../src/stock-level.ts","../src/audit.ts"],"sourcesContent":["export type ErrorContext = Record<string, unknown>\n\nexport class NymbalError extends Error {\n readonly code: string\n readonly context: ErrorContext\n\n constructor(\n code: string,\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = this.constructor.name\n this.code = code\n this.context = options.context ?? {}\n }\n\n toJSON(): { name: string; code: string; message: string; context: ErrorContext } {\n return { name: this.name, code: this.code, message: this.message, context: this.context }\n }\n}\n\nexport class ConfigError extends NymbalError {\n constructor(\n message: string,\n options: {\n cause?: unknown\n context?: ErrorContext\n code?: `config.${string}` | `adapter.${string}`\n } = {},\n ) {\n super(options.code ?? 'config.invalid', message, options)\n }\n}\n\nexport class ValidationError extends NymbalError {\n constructor(message: string, options: { cause?: unknown; context?: ErrorContext } = {}) {\n super('validation.failed', message, options)\n }\n}\n\nexport class NotFoundError extends NymbalError {\n constructor(\n resource: string,\n identifier: unknown,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super('not_found', `${resource} not found: ${String(identifier)}`, {\n ...options,\n context: { resource, identifier, ...(options.context ?? {}) },\n })\n }\n}\n\nexport class AdapterError extends NymbalError {\n constructor(\n code: string,\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`adapter.${code}`, message, options)\n }\n}\n\nexport class HttpError extends NymbalError {\n readonly status: number\n\n constructor(\n status: number,\n message: string,\n options: { cause?: unknown; context?: ErrorContext; code?: string } = {},\n ) {\n super(options.code ?? `http.${status}`, message, options)\n this.status = status\n }\n}\n\nexport class AuthError extends HttpError {\n constructor(\n code: 'unauthorized' | 'forbidden' | 'invalid_credentials' | 'token_expired' | 'token_invalid',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n const status = code === 'forbidden' ? 403 : 401\n super(status, message, { ...options, code: `auth.${code}` })\n }\n}\n\nexport class PaymentError extends NymbalError {\n constructor(\n code:\n | 'intent_failed'\n | 'capture_failed'\n // A definite refusal: no money moved.\n | 'refund_rejected'\n // An outcome the shop cannot know (a timeout, a provider fault): money may have moved.\n | 'refund_unknown'\n | 'cancel_refused'\n | 'webhook_invalid'\n | 'not_configured',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`payment.${code}`, message, options)\n }\n}\n\n/** A command the order's money does not allow (confirm unpaid, cancel paid, refund beyond the capture). */\nexport class OrderConflictError extends NymbalError {\n constructor(\n code:\n | 'order.unpaid'\n | 'order.paid'\n | 'order.stock_unavailable'\n | 'order.payment_in_progress'\n | 'refund.not_captured'\n | 'refund.exceeds_captured'\n | 'refund.request_reused',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(code, message, options)\n }\n}\n\nexport class CheckoutError extends NymbalError {\n constructor(\n code:\n | 'cart_empty'\n | 'in_progress'\n | 'payment_in_progress'\n | 'variant_missing'\n | 'out_of_stock'\n | 'reservation_failed'\n | 'finalize_failed',\n message: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(`checkout.${code}`, message, options)\n }\n}\n\nexport class StatusTransitionError extends NymbalError {\n constructor(\n entity: string,\n fromStatus: string,\n toStatus: string,\n options: { cause?: unknown; context?: ErrorContext } = {},\n ) {\n super(\n 'status.invalid_transition',\n `Invalid ${entity} status transition: ${fromStatus} → ${toStatus}`,\n {\n ...options,\n context: { entity, fromStatus, toStatus, ...(options.context ?? {}) },\n },\n )\n }\n}\n","import { ValidationError } from '../errors.js'\n\nexport interface Money {\n amount: number\n currency: string\n}\n\nexport function money(amount: number, currency: string): Money {\n assertMinorUnits(amount)\n return { amount, currency }\n}\n\nexport function assertMinorUnits(amount: number): void {\n if (!Number.isInteger(amount)) {\n throw new ValidationError(`Monetary amount must be an integer (minor units), got ${amount}`)\n }\n}\n\nfunction assertSameCurrency(a: Money, b: Money): void {\n if (a.currency !== b.currency) {\n throw new ValidationError(\n `Currency mismatch: ${a.currency} vs ${b.currency}`,\n )\n }\n}\n\nexport function add(a: Money, b: Money): Money {\n assertSameCurrency(a, b)\n return { amount: a.amount + b.amount, currency: a.currency }\n}\n\nexport function sub(a: Money, b: Money): Money {\n assertSameCurrency(a, b)\n return { amount: a.amount - b.amount, currency: a.currency }\n}\n\nexport function mulBy(m: Money, scalar: number): Money {\n if (!Number.isFinite(scalar)) {\n throw new ValidationError(`Scalar must be finite, got ${scalar}`)\n }\n return { amount: Math.round(m.amount * scalar), currency: m.currency }\n}\n\nexport function sumMoney(values: Money[], currency: string): Money {\n const total = values.reduce((acc, m) => {\n assertSameCurrency(m, { amount: 0, currency })\n return acc + m.amount\n }, 0)\n return { amount: total, currency }\n}\n\nexport function allocate(m: Money, weights: number[]): Money[] {\n if (weights.length === 0) return []\n const totalWeight = weights.reduce((s, w) => s + w, 0)\n if (totalWeight <= 0) {\n throw new ValidationError('allocate: sum of weights must be > 0')\n }\n const portions = weights.map((w) => Math.floor((m.amount * w) / totalWeight))\n let remainder = m.amount - portions.reduce((s, p) => s + p, 0)\n const result = portions.slice()\n let idx = 0\n while (remainder !== 0 && idx < result.length) {\n const delta = remainder > 0 ? 1 : -1\n result[idx] = (result[idx] ?? 0) + delta\n remainder -= delta\n idx += 1\n }\n return result.map((amount) => ({ amount, currency: m.currency }))\n}\n","export type ProductStatus = 'draft' | 'active' | 'archived'\nexport type ProductType = 'simple' | 'variable'\nexport type VariantStatus = 'active' | 'inactive'\n\nexport interface ProductMedia {\n url: string\n altText: string\n position: number\n}\n\nexport interface VariantOption {\n name: string\n value: string\n}\n\nexport interface VariantDimensions {\n lengthMm?: number\n widthMm?: number\n heightMm?: number\n}\n\nexport interface Variant {\n id: string\n productId: string\n sku: string\n name: string\n priceMinor: number\n compareAtPriceMinor: number | null\n weightGrams: number | null\n dimensions: VariantDimensions | null\n stock: number\n lowStockThreshold: number\n options: VariantOption[]\n status: VariantStatus\n createdAt: string\n updatedAt: string\n}\n\nexport interface Product {\n id: string\n slug: string\n name: string\n description: string\n shortDescription: string\n status: ProductStatus\n type: ProductType\n seoTitle: string\n seoDescription: string\n media: ProductMedia[]\n metadata: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\n/** The orders a product listing can be requested in; ties break by slug, ascending. */\nexport const PRODUCT_SORT_KEYS = [\n 'newest',\n 'price-asc',\n 'price-desc',\n 'name-asc',\n 'name-desc',\n] as const\nexport type ProductSortKey = (typeof PRODUCT_SORT_KEYS)[number]\nexport const DEFAULT_PRODUCT_SORT: ProductSortKey = 'newest'\n/** What a storefront shows for each sort. */\nexport const PRODUCT_SORT_LABELS: Readonly<Record<ProductSortKey, string>> = {\n newest: 'Newest',\n 'price-asc': 'Price: Low to High',\n 'price-desc': 'Price: High to Low',\n 'name-asc': 'Name: A to Z',\n 'name-desc': 'Name: Z to A',\n}\n","import { StatusTransitionError } from '../errors.js'\n\nexport type OrderStatus =\n | 'pending'\n | 'confirmed'\n | 'processing'\n | 'shipped'\n | 'delivered'\n | 'cancelled'\n | 'refunded'\n | 'partially_refunded'\n\n// A captured order is refunded from any status between confirmed and delivered; a partial refund leaves room for more.\nexport const ORDER_STATUS_TRANSITIONS: Record<OrderStatus, readonly OrderStatus[]> = {\n pending: ['confirmed', 'cancelled'],\n confirmed: ['processing', 'cancelled', 'refunded', 'partially_refunded'],\n processing: ['shipped', 'cancelled', 'refunded', 'partially_refunded'],\n shipped: ['delivered', 'refunded', 'partially_refunded'],\n delivered: ['refunded', 'partially_refunded'],\n cancelled: [],\n refunded: [],\n partially_refunded: ['refunded'],\n}\n\nexport function canTransitionOrder(from: OrderStatus, to: OrderStatus): boolean {\n return ORDER_STATUS_TRANSITIONS[from].includes(to)\n}\n\nexport function assertOrderTransition(from: OrderStatus, to: OrderStatus): void {\n if (!canTransitionOrder(from, to)) {\n throw new StatusTransitionError('order', from, to)\n }\n}\n\nexport interface Address {\n firstName: string\n lastName: string\n company?: string\n addressLine1: string\n addressLine2?: string\n city: string\n region: string\n postalCode: string\n country: string\n phone?: string\n}\n\nexport interface OrderLineItem {\n variantId: string\n productId: string\n productName: string\n variantName: string\n sku: string\n qty: number\n unitPriceMinor: number\n lineSubtotalMinor: number\n lineTaxMinor: number\n lineTotalMinor: number\n imageUrl: string\n}\n\nexport interface Order {\n id: string\n orderNumber: string\n sequence: number\n customerId: string | null\n status: OrderStatus\n email: string\n billingAddress: Address\n shippingAddress: Address\n lineItems: OrderLineItem[]\n subtotalMinor: number\n taxTotalMinor: number\n shippingTotalMinor: number\n discountTotalMinor: number\n totalMinor: number\n currency: string\n notes: string\n metadata: Record<string, unknown>\n paymentIntentId: string | null\n /** Money taken for the order, in minor units of its currency; 0 while nothing is captured. */\n capturedMinor: number\n capturedAt: string | null\n /** Σ of the order's succeeded refunds (or, for an import or a pre-refund-table order, what was refunded then). */\n refundedMinor: number\n captureMethod: CaptureMethod | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * The request header a shopper's order key travels in to `GET /api/orders/:orderNumber`. Never a query parameter, so\n * the key stays out of access logs that record request URLs.\n */\nexport const ORDER_KEY_HEADER = 'x-nymbal-order-key'\n\n/** The request header that carries the order's email beside an expired order key. Never a query parameter either. */\nexport const ORDER_EMAIL_HEADER = 'x-nymbal-order-email'\n\n/** Who took the money: the payments provider, the merchant recording an offline payment, or an imported history. */\nexport type CaptureMethod = 'provider' | 'manual' | 'import'\n\nexport type OrderRefundStatus = 'pending' | 'succeeded' | 'failed'\n\n/** One refund of an order. `pending` until the provider's answer settles it, and counted against what is refundable. */\nexport interface OrderRefund {\n id: string\n orderId: string\n amountMinor: number\n currency: string\n providerRefundId: string | null\n status: OrderRefundStatus\n reason: string | null\n actor: string\n createdAt: string\n}\n\nexport type PaymentState = 'unpaid' | 'paid' | 'partially_refunded' | 'refunded'\n\n/** What the admin shows about an order's money: derived from the stored amounts and refunds alone. */\nexport interface OrderPayment {\n state: PaymentState\n capturedMinor: number\n refundedMinor: number\n refundableMinor: number\n currency: string\n refunds: OrderRefund[]\n}\n\nexport function paymentState(order: Pick<Order, 'capturedMinor' | 'refundedMinor'>): PaymentState {\n if (order.capturedMinor <= 0) return 'unpaid'\n if (order.refundedMinor <= 0) return 'paid'\n return order.refundedMinor < order.capturedMinor ? 'partially_refunded' : 'refunded'\n}\n\n/** What can still be refunded: the capture less what was refunded and what is still pending with the provider. */\nexport function refundableMinor(\n order: Pick<Order, 'capturedMinor' | 'refundedMinor'>,\n refunds: ReadonlyArray<Pick<OrderRefund, 'amountMinor' | 'status'>>,\n): number {\n const pending = refunds\n .filter((r) => r.status === 'pending')\n .reduce((sum, r) => sum + r.amountMinor, 0)\n return Math.max(0, order.capturedMinor - order.refundedMinor - pending)\n}\n\nexport interface OrderHistoryEntry {\n id: string\n orderId: string\n fromStatus: OrderStatus | null\n toStatus: OrderStatus\n actor: string\n note: string\n timestamp: string\n}\n","import type { Address, Order } from './order.js'\n\n/** The lifecycle events an order's timeline shows, each one from an `order_history` row. */\nexport const ORDER_TIMELINE_KINDS = [\n 'placed',\n 'paid',\n 'shipped',\n 'delivered',\n 'cancelled',\n 'partially_refunded',\n 'refunded',\n] as const\n\nexport type OrderTimelineKind = (typeof ORDER_TIMELINE_KINDS)[number]\n\nexport interface OrderTimelineEntry {\n kind: OrderTimelineKind\n /** ISO 8601, UTC. */\n at: string\n}\n\n/** A shipping address as an order's key holder reads it: no phone, no company. */\nexport type ShopperAddress = Omit<Address, 'phone' | 'company'>\n\n/** What only the order's signed-in owner reads, or the store's own records, beside what its key holder reads. */\ntype OwnerOrStoreField =\n | 'id'\n | 'email'\n | 'billingAddress'\n | 'customerId'\n | 'sequence'\n | 'capturedMinor'\n | 'capturedAt'\n | 'refundedMinor'\n | 'captureMethod'\n | 'notes'\n | 'metadata'\n | 'paymentIntentId'\n | 'updatedAt'\n\n/**\n * An order as a storefront reads it. A key holder reads the confirmation page's fields; its owner's session adds the\n * email, both full addresses and the order's id. Every other field is optional, so an adapter that returns a plain\n * `Order` still fits.\n */\nexport interface ShopperOrder\n extends\n Omit<Order, OwnerOrStoreField | 'shippingAddress'>,\n Partial<Pick<Order, OwnerOrStoreField>> {\n shippingAddress: ShopperAddress\n timeline?: OrderTimelineEntry[]\n}\n","// Product\nexport const EVT_PRODUCT_CREATED = 'product.created.v1'\nexport const EVT_PRODUCT_UPDATED = 'product.updated.v1'\nexport const EVT_PRODUCT_DELETED = 'product.deleted.v1'\nexport const EVT_PRODUCT_PUBLISHED = 'product.published.v1'\nexport const EVT_PRODUCT_UNPUBLISHED = 'product.unpublished.v1'\nexport const EVT_PRODUCT_VIEWED = 'product.viewed.v1'\n/** A variant's stock went from 0 to above 0, in the transaction of the inventory.changed.v1 it accompanies. */\nexport const EVT_PRODUCT_RESTOCKED = 'product.restocked.v1'\n/** A variant's price or compare-at price changed, in the transaction of the product.updated.v1 it accompanies. */\nexport const EVT_PRODUCT_VARIANT_PRICE_CHANGED = 'product.variant_price_changed.v1'\n\n// Category\nexport const EVT_CATEGORY_CREATED = 'category.created.v1'\nexport const EVT_CATEGORY_UPDATED = 'category.updated.v1'\nexport const EVT_CATEGORY_DELETED = 'category.deleted.v1'\n\n// Order\nexport const EVT_ORDER_PLACED = 'order.placed.v1'\nexport const EVT_ORDER_PAID = 'order.paid.v1'\nexport const EVT_ORDER_FULFILLED = 'order.fulfilled.v1'\nexport const EVT_ORDER_SHIPPED = 'order.shipped.v1'\nexport const EVT_ORDER_DELIVERED = 'order.delivered.v1'\nexport const EVT_ORDER_CANCELLED = 'order.cancelled.v1'\nexport const EVT_ORDER_REFUNDED = 'order.refunded.v1'\nexport const EVT_ORDER_PARTIALLY_REFUNDED = 'order.partially_refunded.v1'\nexport const EVT_ORDER_STATUS_CHANGED = 'order.status_changed.v1'\nexport const EVT_ORDER_CONFIRMATION_RESEND_REQUESTED = 'order.confirmation_resend_requested.v1'\n\n// Inventory\nexport const EVT_INVENTORY_CHANGED = 'inventory.changed.v1'\nexport const EVT_INVENTORY_RESERVED = 'inventory.reserved.v1'\nexport const EVT_INVENTORY_RELEASED = 'inventory.released.v1'\nexport const EVT_INVENTORY_LOW_STOCK = 'inventory.low_stock.v1'\nexport const EVT_INVENTORY_OUT_OF_STOCK = 'inventory.out_of_stock.v1'\n\n// Customer\nexport const EVT_CUSTOMER_CREATED = 'customer.created.v1'\nexport const EVT_CUSTOMER_UPDATED = 'customer.updated.v1'\nexport const EVT_CUSTOMER_DELETED = 'customer.deleted.v1'\n// Legacy alias (kept so Prompt 1 seed code continues to compile).\nexport const EVT_CUSTOMER_REGISTERED = EVT_CUSTOMER_CREATED\n\n// Visitor\nexport const EVT_VISITOR_MERGED = 'visitor.merged.v1'\n\n// Cart\nexport const EVT_CART_CREATED = 'cart.created.v1'\nexport const EVT_CART_ITEM_ADDED = 'cart.item_added.v1'\nexport const EVT_CART_ITEM_REMOVED = 'cart.item_removed.v1'\nexport const EVT_CART_UPDATED = 'cart.updated.v1'\nexport const EVT_CART_ABANDONED = 'cart.abandoned.v1'\nexport const EVT_CART_CONVERTED = 'cart.converted.v1'\n\n// Payment\nexport const EVT_PAYMENT_INTENT_CREATED = 'payment.intent_created.v1'\nexport const EVT_PAYMENT_CAPTURED = 'payment.captured.v1'\nexport const EVT_PAYMENT_FAILED = 'payment.failed.v1'\nexport const EVT_PAYMENT_REFUNDED = 'payment.refunded.v1'\n\n// Alerts\nexport const EVT_ALERT_REQUESTED = 'alert.requested.v1'\n/** A subscribe for an alert already active: the subscription stands and nothing is sent. */\nexport const EVT_ALERT_REPEATED = 'alert.repeated.v1'\nexport const EVT_ALERT_CONFIRMED = 'alert.confirmed.v1'\nexport const EVT_ALERT_UNSUBSCRIBED = 'alert.unsubscribed.v1'\nexport const EVT_ALERT_SENT = 'alert.sent.v1'\n\n// System\nexport const EVT_ADAPTER_ERROR = 'adapter.error.v1'\nexport const EVT_SETTINGS_CHANGED = 'settings.changed.v1'\n\nexport const STANDARD_EVENT_TYPES = [\n EVT_PRODUCT_CREATED,\n EVT_PRODUCT_UPDATED,\n EVT_PRODUCT_DELETED,\n EVT_PRODUCT_PUBLISHED,\n EVT_PRODUCT_UNPUBLISHED,\n EVT_PRODUCT_VIEWED,\n EVT_PRODUCT_RESTOCKED,\n EVT_PRODUCT_VARIANT_PRICE_CHANGED,\n EVT_CATEGORY_CREATED,\n EVT_CATEGORY_UPDATED,\n EVT_CATEGORY_DELETED,\n EVT_ORDER_PLACED,\n EVT_ORDER_PAID,\n EVT_ORDER_FULFILLED,\n EVT_ORDER_SHIPPED,\n EVT_ORDER_DELIVERED,\n EVT_ORDER_CANCELLED,\n EVT_ORDER_REFUNDED,\n EVT_ORDER_PARTIALLY_REFUNDED,\n EVT_ORDER_STATUS_CHANGED,\n EVT_ORDER_CONFIRMATION_RESEND_REQUESTED,\n EVT_INVENTORY_CHANGED,\n EVT_INVENTORY_RESERVED,\n EVT_INVENTORY_RELEASED,\n EVT_INVENTORY_LOW_STOCK,\n EVT_INVENTORY_OUT_OF_STOCK,\n EVT_CUSTOMER_CREATED,\n EVT_CUSTOMER_UPDATED,\n EVT_CUSTOMER_DELETED,\n EVT_VISITOR_MERGED,\n EVT_CART_CREATED,\n EVT_CART_ITEM_ADDED,\n EVT_CART_ITEM_REMOVED,\n EVT_CART_UPDATED,\n EVT_CART_ABANDONED,\n EVT_CART_CONVERTED,\n EVT_PAYMENT_INTENT_CREATED,\n EVT_PAYMENT_CAPTURED,\n EVT_PAYMENT_FAILED,\n EVT_PAYMENT_REFUNDED,\n EVT_ALERT_REQUESTED,\n EVT_ALERT_REPEATED,\n EVT_ALERT_CONFIRMED,\n EVT_ALERT_UNSUBSCRIBED,\n EVT_ALERT_SENT,\n EVT_ADAPTER_ERROR,\n EVT_SETTINGS_CHANGED,\n] as const\n","import type { Logger } from './http.js'\n\nexport type HealthStatus = 'healthy' | 'degraded' | 'down'\n\nexport interface HealthReport {\n status: HealthStatus\n detail?: string\n}\n\n/** How long a health check may run before it reports `down`. */\nexport const HEALTH_CHECK_TIMEOUT_MS = 5000\n\nexport interface BoundedHealthCheckOptions {\n /** `HEALTH_CHECK_TIMEOUT_MS` when not given. */\n timeoutMs?: number\n /** Values an error's message must never show (a URL's user, password or query values); each becomes `***`. */\n redact?: readonly string[]\n}\n\n/** An error's message for a report; an AggregateError (a refused connection to every address of a name) lists its own. */\nexport function errorText(error: unknown): string {\n if (!(error instanceof Error)) return String(error)\n if (error.message !== '') return error.message\n const inner = (error as { errors?: unknown }).errors\n if (Array.isArray(inner) && inner.length > 0) return inner.map(errorText).join('; ')\n const code = (error as { code?: unknown }).code\n return typeof code === 'string' ? code : error.name\n}\n\n/**\n * The shortest value worth removing from text as a secret. A shorter one is too likely to be an ordinary word (a local\n * password of `nymbal`), whose removal garbles the text; user info and query strings go with the URL rewrite anyway.\n */\nexport const SECRET_MIN_LENGTH = 8\n\n/**\n * The values in a connection URL that are credentials or may carry one: its password and its query values, and its\n * user unless `user` is false, each as written and decoded, if at least `SECRET_MIN_LENGTH` long. A user is worth\n * hiding in a driver's error, not in our own text.\n */\nexport function connectionSecrets(url: string, options: { user?: boolean } = {}): string[] {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return []\n }\n const raw = [\n ...(options.user === false ? [] : [parsed.username]),\n parsed.password,\n ...parsed.searchParams.values(),\n ]\n const decoded = raw.map((value) => {\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n })\n return [...new Set([...raw, ...decoded])].filter((value) => value.length >= SECRET_MIN_LENGTH)\n}\n\n/**\n * A connection URL as it may be printed: `scheme://host:port/path`, with no user, password, query or fragment. Text\n * that is not a URL comes back as `<unparsable url>`, since it may be a URL with a secret the parser could not read.\n */\nexport function displayUrl(url: string): string {\n try {\n const parsed = new URL(url)\n return `${parsed.protocol}//${parsed.host}${parsed.pathname === '/' ? '' : parsed.pathname}`\n } catch {\n return '<unparsable url>'\n }\n}\n\n/**\n * Runs `check` within the bound. A check that throws is `down` with the error's message, every `redact` value in it\n * replaced; one still running when the bound passes is `down` with `timed out after <n> ms`, and its signal aborted.\n */\nexport async function boundedHealthCheck(\n check: (signal: AbortSignal) => Promise<HealthReport> | HealthReport,\n options: BoundedHealthCheckOptions = {},\n): Promise<HealthReport> {\n const timeoutMs = options.timeoutMs ?? HEALTH_CHECK_TIMEOUT_MS\n const controller = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const timedOut = new Promise<HealthReport>((resolve) => {\n timer = setTimeout(() => {\n controller.abort()\n resolve({ status: 'down', detail: `timed out after ${timeoutMs} ms` })\n }, timeoutMs)\n })\n const failed = (error: unknown): HealthReport => {\n let detail = errorText(error)\n for (const secret of options.redact ?? []) {\n if (secret !== '') detail = detail.split(secret).join('***')\n }\n return { status: 'down', detail }\n }\n try {\n // Started inside a promise so a check that throws before returning one, or returns a plain report, is still\n // mapped rather than escaping the caller.\n const checked = Promise.resolve()\n .then(() => check(controller.signal))\n .catch(failed)\n return await Promise.race([checked, timedOut])\n } finally {\n clearTimeout(timer)\n }\n}\n\nexport interface AdapterBase {\n /** What this adapter offers, for the app's per-slot report; each adapter answers for itself. */\n capabilities(): readonly string[]\n readonly producesEvents: readonly string[]\n readonly consumesEvents: readonly string[]\n initialize(config: unknown): Promise<void>\n healthCheck(): Promise<HealthReport>\n}\n\n/** The commerce slots a store's config names a provider for. */\nexport type CommerceSlot =\n | 'payments'\n | 'email'\n | 'reviews'\n | 'search'\n | 'analytics'\n | 'shipping'\n | 'tax'\n | 'ai'\n\n/**\n * Every slot an adapter package can fill: the commerce slots, the infrastructure primitives and `security`, whose\n * choice is `security.adapter`.\n */\nexport type Slot = CommerceSlot | 'documentStore' | 'eventBus' | 'media' | 'security'\n\n/**\n * What the kernel hands an adapter package's `createAdapter`. A package that serves more than one slot switches on\n * `slot`; `config` is a commerce slot's `config` block from nymbal.config.ts, a primitive's connection block (or a\n * third-party primitive's `config`), or for `security` the block named after the adapter (`security.cloudflare`) or a\n * third-party adapter's `security.config`.\n */\nexport interface AdapterContext {\n slot: Slot\n config: Record<string, unknown>\n logger: Logger\n env: NodeJS.ProcessEnv\n}\n\n/**\n * One field of a slot's config block that a store owner may fill from the admin's Settings, as a provider package\n * declares it in its `settings` export. The config block wins, then `env`, then a saved value.\n */\nexport interface AdapterSetting {\n /** A dot path inside the slot's config block: `secretKey`, `smtp.pass`. */\n path: string\n label: string\n /** Stored encrypted and never shown again once saved. */\n secret: boolean\n /** The environment variable that sets the field when the config block does not. */\n env?: string\n /**\n * The adapter works without it (SMTP without authentication), so a secret left unset does not leave the slot\n * unconfigured.\n */\n optional?: boolean\n}\n\n/**\n * What a provider package exports: `createAdapter`, the slot and provider it serves (checked when it is loaded), and\n * the fields it lets Settings fill.\n */\nexport interface ProviderPackageModule {\n createAdapter(ctx: AdapterContext): unknown\n slot: Slot\n provider: string\n settings?: readonly AdapterSetting[]\n}\n\n/**\n * How `nymbal.config.ts` names an infrastructure slot's adapter (`documentStore`, `eventBus`, `media`): the provider,\n * the npm package it comes from when not `@nymbalhq/<slot>-<provider>`, and a third-party adapter's `ctx.config`.\n */\nexport interface InfrastructureBlock {\n provider: string\n package?: string\n config?: Record<string, unknown>\n}\n","import { ValidationError } from '../errors.js'\nimport type { QueryParams, QueryResult } from './document-store.js'\n\n// The one order and filter every document store is held to: the in-memory store runs it, and stores that cannot\n// order or filter the same way on their server fetch candidates and run it in the process.\n\n// Made on first use: a top-level `new` is a side effect bundlers keep, so every storefront bundle that imports\n// these types would carry it.\nlet collator: Intl.Collator | undefined\n\n/** Number, then string, then any other type, then null and missing: so values of different types never tie. */\nfunction rankOf(value: unknown): number {\n if (typeof value === 'number') return 0\n if (typeof value === 'string') return 1\n if (value === null || value === undefined) return 3\n return 2\n}\n\nfunction sign(n: number): number {\n return n > 0 ? 1 : n < 0 ? -1 : 0\n}\n\nfunction compareOther(a: unknown, b: unknown): number {\n const ta = typeof a\n const tb = typeof b\n if (ta !== tb) return ta < tb ? -1 : 1\n if (ta === 'boolean') return Number(a) - Number(b)\n const ja = JSON.stringify(a) ?? ''\n const jb = JSON.stringify(b) ?? ''\n return ja < jb ? -1 : ja > jb ? 1 : 0\n}\n\n/**\n * A total order over document values: numbers numerically, strings by `Intl.Collator` at base sensitivity (case and\n * accents tie), other types after strings, and null or missing last. Values of different types rank by type first.\n */\nexport function compareDocumentValues(a: unknown, b: unknown): number {\n const ra = rankOf(a)\n const rb = rankOf(b)\n if (ra !== rb) return ra - rb\n switch (ra) {\n case 0:\n return sign((a as number) - (b as number))\n case 1:\n collator ??= new Intl.Collator(undefined, { sensitivity: 'base' })\n return sign(collator.compare(a as string, b as string))\n case 2:\n return sign(compareOther(a, b))\n default:\n return 0\n }\n}\n\nexport type SortKeyCondition = Pick<NonNullable<QueryParams['sortKey']>, 'operator' | 'value'>\n\n/** `eq` is `===`; `gt`, `lt` and `between` (inclusive, `[low, high]`) are `compareDocumentValues`. */\nexport function matchesSortKey(value: unknown, condition: SortKeyCondition): boolean {\n const target = condition.value\n switch (condition.operator) {\n case 'eq':\n return value === target\n case 'gt':\n return compareDocumentValues(value, target) > 0\n case 'lt':\n return compareDocumentValues(value, target) < 0\n case 'between': {\n if (!Array.isArray(target) || target.length !== 2) return false\n const [low, high] = target as [unknown, unknown]\n return compareDocumentValues(value, low) >= 0 && compareDocumentValues(value, high) <= 0\n }\n default:\n return false\n }\n}\n\n/** The value at a dotted path (`priceRange.minMinor`); missing anywhere along it is `undefined`. */\nexport function documentValueAt(doc: unknown, path: string): unknown {\n let value: unknown = doc\n for (const part of path.split('.')) {\n if (value === null || typeof value !== 'object') return undefined\n value = (value as Record<string, unknown>)[part]\n }\n return value\n}\n\nexport const DEFAULT_QUERY_LIMIT = 50\n\ninterface Order {\n /** The field ordered by, or null for the document key alone. */\n field: string | null\n direction: 'asc' | 'desc'\n}\n\ninterface Position {\n key: string\n value: unknown\n}\n\nfunction orderOf(params: QueryParams): Order {\n if (params.orderBy !== undefined) {\n return { field: params.orderBy.field, direction: params.orderBy.direction }\n }\n const direction = (params.ascending ?? true) ? 'asc' : 'desc'\n return { field: params.sortKey?.field ?? null, direction }\n}\n\nconst isNullish = (value: unknown): boolean => value === null || value === undefined\n\n/** Nulls and missing values last in either direction; ties by key ascending, so no two documents tie. */\nfunction comparePositions(a: Position, b: Position, order: Order): number {\n if (order.field !== null) {\n const an = isNullish(a.value)\n const bn = isNullish(b.value)\n if (an !== bn) return an ? 1 : -1\n let c = an ? 0 : compareDocumentValues(a.value, b.value)\n if (order.direction === 'desc') c = -c\n if (c !== 0) return c\n return a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n }\n const c = a.key < b.key ? -1 : a.key > b.key ? 1 : 0\n return order.direction === 'desc' ? -c : c\n}\n\n/** What a cursor is bound to: the order and the partition, so a cursor reused under another is refused. */\nfunction cursorScope(order: Order, params: QueryParams): string {\n return JSON.stringify([order.field, order.direction, params.partitionKey])\n}\n\nfunction decodeCursor(cursor: string, scope: string): Position {\n let decoded: unknown\n try {\n decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'))\n } catch {\n decoded = null\n }\n const c = decoded as { o?: unknown; k?: unknown; v?: unknown } | null\n if (c === null || typeof c !== 'object' || typeof c.o !== 'string' || typeof c.k !== 'string') {\n throw new ValidationError('The cursor is not valid', { context: { cursor } })\n }\n if (c.o !== scope) {\n throw new ValidationError('The cursor was issued under another order', { context: { cursor } })\n }\n return { key: c.k, value: c.v ?? null }\n}\n\n/**\n * One page of `params` over the documents of a collection: filter by partition (`'*'` for all) and sort key, order,\n * then seek past the cursor by value, so a document deleted between pages neither restarts nor repeats the walk.\n */\nexport function queryDocuments<T>(\n documents: Iterable<readonly [string, unknown]>,\n params: QueryParams,\n): QueryResult<T> {\n const limit = params.limit ?? DEFAULT_QUERY_LIMIT\n const order = orderOf(params)\n const scope = cursorScope(order, params)\n const after = params.cursor ? decodeCursor(params.cursor, scope) : null\n const { partitionKey, sortKey } = params\n\n const matches: Array<{ position: Position; doc: unknown }> = []\n for (const [key, doc] of documents) {\n const record = (doc ?? {}) as Record<string, unknown>\n if (partitionKey.value !== '*' && record[partitionKey.field] !== partitionKey.value) continue\n if (sortKey !== undefined && !matchesSortKey(record[sortKey.field], sortKey)) continue\n const value = order.field === null ? null : (documentValueAt(record, order.field) ?? null)\n const position = { key, value }\n if (after !== null && comparePositions(position, after, order) <= 0) continue\n matches.push({ position, doc })\n }\n matches.sort((a, b) => comparePositions(a.position, b.position, order))\n\n const page = matches.slice(0, limit)\n const last = page[page.length - 1]\n const nextCursor =\n matches.length > limit && last !== undefined\n ? Buffer.from(\n JSON.stringify({ o: scope, k: last.position.key, v: last.position.value }),\n 'utf8',\n ).toString('base64url')\n : null\n return { items: page.map((m) => m.doc as T), nextCursor }\n}\n","import { AdapterError } from '../errors.js'\nimport type { NymbalEvent, EventHandler } from '../events/envelope.js'\nimport type { HealthReport } from './adapter-base.js'\n\nexport interface RetryPolicy {\n maxAttempts: number\n backoff: 'exponential' | 'linear' | 'fixed'\n initialDelayMs?: number\n}\n\nexport interface SubscriptionConfig {\n retryPolicy?: RetryPolicy\n deadLetterQueue?: string\n batchSize?: number\n /**\n * A named subscription is handled once per name across every bus instance (one consumer group); an unnamed one is\n * handled by every subscriber on every instance. On a single-process bus the two are the same.\n */\n name?: string\n /** The subscriber only maintains derived state (a projection, a search index): it also receives `replay`. */\n derived?: boolean\n}\n\nexport interface Subscription {\n id: string\n patterns: string[]\n unsubscribe(): Promise<void>\n}\n\n/** A named subscription whose handler threw on an event. */\nexport interface HandlerFailure {\n subscription: string\n message: string\n}\n\n/** What `publish` rejects with when a named subscription's handler threw: code `adapter.handler_failed`. */\nexport function handlerFailedError(\n event: NymbalEvent<unknown>,\n failed: HandlerFailure[],\n): AdapterError {\n return new AdapterError(\n 'handler_failed',\n `${event.type} (${event.id}) failed in ${failed.map((f) => f.subscription).join(', ')}`,\n { context: { eventId: event.id, eventType: event.type, failed } },\n )\n}\n\n/**\n * Delivery is at least once: a failed delivery is published again, to every matching subscription, so every handler\n * is idempotent (projections overwrite their documents, side effects use `withIdempotency`). The bus never retries a\n * failed handler by itself.\n */\nexport interface EventBusAdapter {\n capabilities(): readonly string[]\n /**\n * Resolves only after every matching subscription this instance holds has handled the event, so a read straight\n * after a command sees its projections. When a named subscription's handler throws, it rejects with\n * `handlerFailedError` naming each one that threw, once every matching handler has settled. An unnamed subscription's\n * throw is logged and does not reject.\n */\n publish<T>(event: NymbalEvent<T>): Promise<void>\n /** Hands every event out in order, then rejects as `publish` does for the first event a named handler failed. */\n publishBatch(events: NymbalEvent<unknown>[]): Promise<void>\n /**\n * Delivers to `derived` subscriptions only, so rebuilding derived state never triggers side effects. A handler's\n * throw is logged and does not reject.\n */\n replay(events: NymbalEvent<unknown>[]): Promise<void>\n subscribe(\n eventPattern: string | string[],\n handler: EventHandler,\n config?: SubscriptionConfig,\n ): Promise<Subscription>\n unsubscribe(subscription: Subscription): Promise<void>\n close(): Promise<void>\n /**\n * Whether the bus can deliver, within `HEALTH_CHECK_TIMEOUT_MS`. Optional, so a bus written before it existed still\n * type-checks; `nymbal doctor` reports one without it as `no healthCheck`.\n */\n healthCheck?(): Promise<HealthReport>\n}\n","export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'\n\n/** The request header a caller may name its own correlation id in, and the response header that echoes the one used. */\nexport const CORRELATION_ID_HEADER = 'x-correlation-id'\n\nconst CORRELATION_ID = /^[A-Za-z0-9._:-]{1,128}$/\n\n/**\n * Whether `s` may be used as a correlation id: it reaches logs and the event log, so an inbound value that fails is\n * replaced, never cleaned. The id is a label only; nothing reads it to decide access.\n */\nexport function isValidCorrelationId(s: string): boolean {\n return CORRELATION_ID.test(s)\n}\n\nexport interface RequestAuth {\n userId?: string\n roles?: string[]\n token?: string\n}\n\nexport interface Logger {\n trace(obj: unknown, msg?: string): void\n debug(obj: unknown, msg?: string): void\n info(obj: unknown, msg?: string): void\n warn(obj: unknown, msg?: string): void\n error(obj: unknown, msg?: string): void\n child(bindings: Record<string, unknown>): Logger\n}\n\nexport interface RequestContext<TBody = unknown, TQuery = Record<string, string | string[] | undefined>, TParams = Record<string, string>> {\n method: HttpMethod\n path: string\n url: string\n params: TParams\n query: TQuery\n body: TBody\n rawBody?: Buffer\n headers: Record<string, string | string[] | undefined>\n cookies: Record<string, string>\n auth?: RequestAuth\n requestId: string\n /** The inbound `x-correlation-id` when `isValidCorrelationId` accepts it, else the request id. */\n correlationId: string\n logger: Logger\n ip: string\n}\n\nexport interface ResponseCookie {\n name: string\n value: string\n path?: string\n domain?: string\n maxAge?: number\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n}\n\nexport interface ResponseEnvelope<T = unknown> {\n status: number\n headers?: Record<string, string>\n body?: T\n cookies?: ResponseCookie[]\n}\n\nexport type RouteHandler<TBody = unknown, TQuery = Record<string, string | string[] | undefined>, TParams = Record<string, string>, TResponse = unknown> = (\n ctx: RequestContext<TBody, TQuery, TParams>,\n) => Promise<ResponseEnvelope<TResponse>> | ResponseEnvelope<TResponse>\n\nexport interface RouteOptions {\n name?: string\n csrf?: boolean\n rateLimit?: string\n rawBody?: boolean\n}\n\nexport type MiddlewareHandler = (\n ctx: RequestContext,\n) => Promise<ResponseEnvelope | void> | ResponseEnvelope | void\n\nexport interface SseEvent {\n id?: string\n event?: string\n data: unknown\n}\n\nexport interface StreamContext {\n send(event: SseEvent): void\n close(): void\n onClose(callback: () => void): void\n}\n\nexport type StreamHandler = (\n ctx: RequestContext,\n stream: StreamContext,\n) => Promise<void> | void\n\nexport interface HttpAdapter {\n registerRoute(\n method: HttpMethod,\n path: string,\n handler: RouteHandler,\n options?: RouteOptions,\n ): void\n registerStreamRoute(\n method: 'GET',\n path: string,\n handler: StreamHandler,\n options?: RouteOptions,\n ): void\n registerMiddleware(middleware: MiddlewareHandler): void\n setDefaultHeaders(headers: Record<string, string>): void\n start(port: number, host?: string): Promise<void>\n stop(): Promise<void>\n}\n","import type { AdapterBase } from './adapter-base.js'\nimport type { HttpAdapter } from './http.js'\n\nexport type RateLimitAction = 'block' | 'challenge' | 'throttle'\n\nexport interface RateLimitRule {\n requests: number\n window: string\n action: RateLimitAction\n}\n\nexport interface RateLimitDecision {\n allowed: boolean\n retryAfterSeconds?: number\n action: RateLimitAction\n}\n\nexport interface SecurityConfigLike {\n rateLimit: Record<string, RateLimitRule>\n csrf: boolean\n headers: {\n hsts: boolean\n contentSecurityPolicy: string\n referrerPolicy: string\n xFrameOptions: string\n }\n [key: string]: unknown\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: Array<{ code: string; message: string; path?: string }>\n}\n\nexport interface DriftReport {\n drift: boolean\n differences: Array<{ path: string; configured: unknown; actual: unknown }>\n}\n\nexport interface SecurityState {\n rulesApplied: string[]\n lastValidatedAt: string\n}\n\nexport interface SecurityAdapter extends AdapterBase {\n applyMiddleware(app: HttpAdapter, config: SecurityConfigLike): Promise<void>\n validate(config: SecurityConfigLike): Promise<ValidationResult>\n drift?(config: SecurityConfigLike): Promise<DriftReport>\n inspect?(): Promise<SecurityState>\n}\n\n/** The response headers every security adapter sets in process, from the config's `headers` block. */\nexport function buildSecurityHeaders(security: SecurityConfigLike): Record<string, string> {\n const headers: Record<string, string> = {}\n if (security.headers.hsts) {\n headers['strict-transport-security'] = 'max-age=31536000; includeSubDomains'\n }\n const csp = security.headers.contentSecurityPolicy\n const cspValue =\n csp === 'strict'\n ? \"default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'\"\n : csp === 'relaxed'\n ? \"default-src 'self' https:; img-src * data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'\"\n : csp === 'off'\n ? null\n : csp\n if (cspValue) headers['content-security-policy'] = cspValue\n headers['referrer-policy'] = security.headers.referrerPolicy\n headers['x-frame-options'] = security.headers.xFrameOptions === 'deny' ? 'DENY' : 'SAMEORIGIN'\n headers['x-content-type-options'] = 'nosniff'\n return headers\n}\n","import { STANDARD_EVENT_TYPES } from '../events/constants.js'\nimport type { WebhookDelivery } from './commerce.js'\n\n/** Why a delivery's events may not be published; `type` is the offending event's, when one is to blame. */\nexport interface WebhookDeliveryProblem {\n type?: unknown\n reason: string\n}\n\nconst VERSIONED_EVENT = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*\\.v[1-9][0-9]*$/\n\n/** Plain JSON objects only: a class instance would not survive the event log's serialisation as itself. */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false\n const proto = Object.getPrototypeOf(value) as unknown\n return proto === Object.prototype || proto === null\n}\n\n/**\n * The first thing that makes a verified delivery from the adapter named `providerName` unpublishable, or null when\n * every event may be published: the rule of `receiveWebhook`, which the kernel refuses a delivery for and the\n * webhook contract suite certifies.\n */\nexport function webhookDeliveryProblem(\n providerName: string,\n producesEvents: readonly string[],\n delivery: WebhookDelivery,\n): WebhookDeliveryProblem | null {\n if (typeof delivery.deliveryId !== 'string' || delivery.deliveryId === '') {\n return { reason: 'the delivery id is empty' }\n }\n if (!Array.isArray(delivery.events)) return { reason: 'events is not an array' }\n for (const event of delivery.events as unknown[]) {\n const { type, payload } = (isPlainObject(event) ? event : {}) as {\n type?: unknown\n payload?: unknown\n }\n if (typeof type !== 'string') return { type, reason: 'an event has no type' }\n const dot = type.indexOf('.')\n // A provider name holding a dot never equals the text before the first dot, so it can publish nothing.\n if (dot === -1 || type.slice(0, dot) !== providerName) {\n return { type, reason: `the type is not in the ${providerName} namespace` }\n }\n // Computed here, not at module level: a top-level Set would keep every event type in each storefront bundle.\n if (STANDARD_EVENT_TYPES.some((standard) => standard.split('.')[0] === providerName)) {\n return { type, reason: 'the type is in a kernel namespace' }\n }\n if (!VERSIONED_EVENT.test(type.slice(dot + 1))) {\n return { type, reason: 'the type is not <provider>.<event>.v<n>' }\n }\n if (!producesEvents.includes(type)) {\n return { type, reason: 'the type is not listed in producesEvents' }\n }\n if (!isPlainObject(payload)) return { type, reason: 'the payload is not a JSON object' }\n }\n return null\n}\n","import type { WebhookDelivery } from './commerce.js'\nimport type { WebhookDeliveryProblem } from './webhook-delivery.js'\n\n/**\n * `<providerName>.bounced.v1`: a message to `recipient` bounced. `hard` is the provider's permanent failure, `soft`\n * its transient one; a classification the adapter cannot map is `soft`, so a doubtful bounce never suppresses.\n */\nexport interface EmailBouncedV1Payload {\n recipient: string\n bounce: 'hard' | 'soft'\n /** The `EmailSendResult.messageId` the bounce answers, when the provider gives one. */\n messageId: string | null\n /** ISO 8601. */\n occurredAt: string\n}\n\n/** `<providerName>.complained.v1`: `recipient` marked a message as spam. */\nexport interface EmailComplainedV1Payload {\n recipient: string\n messageId: string | null\n /** ISO 8601. */\n occurredAt: string\n}\n\nexport type EmailFeedbackKind = 'bounced' | 'complained'\n\n/** The event type an email adapter publishes a bounce or a complaint under. */\nexport function emailFeedbackType(providerName: string, what: EmailFeedbackKind): string {\n return `${providerName}.${what}.v1`\n}\n\nfunction payloadOf(event: unknown): Record<string, unknown> {\n const payload = (event as { payload?: unknown } | null)?.payload\n return typeof payload === 'object' && payload !== null ? (payload as Record<string, unknown>) : {}\n}\n\n/**\n * The first bounce or complaint in a verified delivery from the email adapter `providerName` that the kernel cannot\n * act on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's\n * retry after the adapter is fixed is still received; the email feedback contract suite certifies the same rule.\n */\nexport function emailFeedbackProblem(\n providerName: string,\n delivery: WebhookDelivery,\n): WebhookDeliveryProblem | null {\n const bounced = emailFeedbackType(providerName, 'bounced')\n const complained = emailFeedbackType(providerName, 'complained')\n if (!Array.isArray(delivery.events)) return null\n for (const event of delivery.events as unknown[]) {\n const type = (event as { type?: unknown } | null)?.type\n if (type !== bounced && type !== complained) continue\n const payload = payloadOf(event)\n const { recipient, messageId, occurredAt } = payload\n if (typeof recipient !== 'string' || recipient.trim() === '') {\n return { type, reason: 'the recipient is missing or empty' }\n }\n if (type === bounced && payload['bounce'] !== 'hard' && payload['bounce'] !== 'soft') {\n return { type, reason: \"the bounce is neither 'hard' nor 'soft'\" }\n }\n if (messageId !== null && typeof messageId !== 'string') {\n return { type, reason: 'the messageId is neither a string nor null' }\n }\n if (typeof occurredAt !== 'string' || Number.isNaN(Date.parse(occurredAt))) {\n return { type, reason: 'occurredAt is not a readable date' }\n }\n }\n return null\n}\n","import type { WebhookDelivery } from './commerce.js'\nimport type { WebhookDeliveryProblem } from './webhook-delivery.js'\n\n/** The statuses a shipping adapter maps its carrier's onto; one it cannot map is `unknown`. */\nexport const SHIPPING_TRACKING_STATUSES = [\n 'pre_transit',\n 'in_transit',\n 'out_for_delivery',\n 'delivered',\n 'exception',\n 'returned',\n 'unknown',\n] as const\n\nexport type ShippingTrackingStatus = (typeof SHIPPING_TRACKING_STATUSES)[number]\n\n/** `<providerName>.tracking.v1`: the carrier reported `status` for the parcel `trackingNumber`. */\nexport interface ShippingTrackingV1Payload {\n trackingNumber: string\n /** The carrier's code as the adapter's provider gives it, which need not match the merchant's own. */\n carrier: string | null\n status: ShippingTrackingStatus\n /** ISO 8601. */\n occurredAt: string\n}\n\n/** The event type a shipping adapter publishes a tracking update under. */\nexport function shippingTrackingType(providerName: string): string {\n return `${providerName}.tracking.v1`\n}\n\n/** A tracking number as stored and looked up: no whitespace, upper case, so a carrier's spelling finds the merchant's. */\nexport function normaliseTrackingNumber(value: string): string {\n return value.replace(/\\s+/g, '').toUpperCase()\n}\n\nfunction payloadOf(event: unknown): Record<string, unknown> {\n const payload = (event as { payload?: unknown } | null)?.payload\n return typeof payload === 'object' && payload !== null ? (payload as Record<string, unknown>) : {}\n}\n\n/**\n * The first tracking update in a verified delivery from the shipping adapter `providerName` that the kernel cannot act\n * on, or null when there is none. The route refuses such a delivery whole, before claiming it, so the provider's retry\n * after the adapter is fixed is still received; the shipping tracking contract suite certifies the same rule.\n */\nexport function shippingTrackingProblem(\n providerName: string,\n delivery: WebhookDelivery,\n): WebhookDeliveryProblem | null {\n const tracking = shippingTrackingType(providerName)\n if (!Array.isArray(delivery.events)) return null\n for (const event of delivery.events as unknown[]) {\n const type = (event as { type?: unknown } | null)?.type\n if (type !== tracking) continue\n const { trackingNumber, carrier, status, occurredAt } = payloadOf(event)\n if (typeof trackingNumber !== 'string' || normaliseTrackingNumber(trackingNumber) === '') {\n return { type, reason: 'the trackingNumber is missing or empty' }\n }\n if (carrier !== null && typeof carrier !== 'string') {\n return { type, reason: 'the carrier is neither a string nor null' }\n }\n if (!(SHIPPING_TRACKING_STATUSES as readonly unknown[]).includes(status)) {\n return { type, reason: 'the status is not one of the tracking statuses' }\n }\n if (typeof occurredAt !== 'string' || Number.isNaN(Date.parse(occurredAt))) {\n return { type, reason: 'occurredAt is not a readable date' }\n }\n }\n return null\n}\n","// The kernel's transactional email templates, shared by every email adapter (the console one and the SMTP package).\nexport interface RenderedEmail {\n subject: string\n text: string\n html: string\n}\n\nfunction render(template: string, vars: Record<string, unknown>): string {\n return template.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, (_, path: string) => {\n const parts = path.split('.')\n let cur: unknown = vars\n for (const p of parts) {\n if (cur && typeof cur === 'object' && p in (cur as Record<string, unknown>)) {\n cur = (cur as Record<string, unknown>)[p]\n } else {\n return ''\n }\n }\n return cur == null ? '' : String(cur)\n })\n}\n\nconst TEMPLATES: Record<string, { subject: string; body: string }> = {\n 'order-placed': {\n subject: 'Order {{orderNumber}} confirmed',\n // `orderLink` is the whole \"View your order\" paragraph, absent when the store has no URL to link from.\n body: `Hi {{customerName}},\\n\\nThank you for your order {{orderNumber}}. Total: {{totalFormatted}}.\\n\\nWe'll let you know when it ships.{{orderLink}}`,\n },\n 'order-shipped': {\n subject: 'Order {{orderNumber}} is on its way',\n body: `Hi {{customerName}},\\n\\nYour order {{orderNumber}} has shipped via {{carrier}}.\\nTracking: {{trackingNumber}}`,\n },\n welcome: {\n subject: 'Welcome to {{storeName}}',\n body: `Welcome {{customerName}}! Your account is ready.`,\n },\n 'cart-abandoned': {\n subject: \"Don't forget your cart at {{storeName}}\",\n body: `Hi,\\n\\nYou left items in your cart. Pick up where you left off: {{resumeUrl}}`,\n },\n // Each link on a line of its own, so a mail client never runs the next word into it.\n 'alert-confirm': {\n subject: 'Confirm your alert for {{productName}}',\n body: `Hi,\\n\\nYou asked {{storeName}} to email you when {{productName}} is back in stock. Confirm by following this link:\\n\\n{{confirmUrl}}\\n\\nIf you did not ask, ignore this email and you will hear nothing more.\\n\\nStop this alert:\\n{{unsubscribeUrl}}`,\n },\n 'alert-back-in-stock': {\n subject: '{{productName}} is back in stock',\n body: `Hi,\\n\\n{{productName}} is back in stock at {{storeName}}:\\n\\n{{productUrl}}\\n\\nThis was a one-time alert. Stop it:\\n{{unsubscribeUrl}}`,\n },\n 'alert-confirm-price-drop': {\n subject: 'Confirm your price alert for {{productName}}',\n body: `Hi,\\n\\nYou asked {{storeName}} to email you when the price of {{productName}} drops, each time it falls below the last price we told you. Confirm by following this link:\\n\\n{{confirmUrl}}\\n\\nIf you did not ask, ignore this email and you will hear nothing more.\\n\\nStop this alert:\\n{{unsubscribeUrl}}`,\n },\n 'alert-price-drop': {\n subject: 'Price drop: {{productName}} is now {{priceFormatted}}',\n body: `Hi,\\n\\nThe price of {{productName}} at {{storeName}} has dropped from {{previousPriceFormatted}} to {{priceFormatted}}:\\n\\n{{productUrl}}\\n\\nWe'll email you again if it drops below {{priceFormatted}}. Stop this alert:\\n{{unsubscribeUrl}}`,\n },\n}\n\nexport function renderEmailTemplate(\n template: string,\n vars: Record<string, unknown>,\n): RenderedEmail {\n const t = TEMPLATES[template]\n if (!t) {\n return {\n subject: `[nymbal] ${template}`,\n text: JSON.stringify(vars),\n html: `<pre>${JSON.stringify(vars, null, 2)}</pre>`,\n }\n }\n const subject = render(t.subject, vars)\n const text = render(t.body, vars)\n const html = `<pre>${text.replace(/</g, '&lt;')}</pre>`\n return { subject, text, html }\n}\n","export type StockLevel = 'out' | 'low' | 'in'\n\n/** Below this many units a variant is low on stock. */\nconst LOW_STOCK_BELOW = 5\n\n/** The one stock rule every storefront renders from. */\nexport function stockLevel(stock: number): StockLevel {\n if (stock <= 0) return 'out'\n return stock < LOW_STOCK_BELOW ? 'low' : 'in'\n}\n\nexport const STOCK_LABELS: Record<StockLevel, string> = {\n out: 'Out of Stock',\n low: 'Low Stock',\n in: 'In Stock',\n}\n","/** Who a command ran for. A guest is `guest:anonymous`: a cart's token is a bearer credential and is never an id. */\nexport interface AuditActor {\n kind: 'admin' | 'customer' | 'guest' | 'system' | 'import'\n id: string\n}\n\n/** `applied`: the command ran and committed. `refused`: its service refused it. `replayed`: a keyed retry answered. */\nexport type AuditOutcome = 'applied' | 'refused' | 'replayed'\n\n/** A record a command touched, by id only. */\nexport interface AuditSubject {\n type: string\n id: string\n}\n\n/**\n * One audit entry as `GET /api/admin/activity` lists it and the exports print it. A stored response is shown by its\n * status only; its body never leaves the store.\n */\nexport interface AuditEntry {\n id: string\n at: string\n actor: AuditActor\n command: string\n subjects: AuditSubject[]\n correlationId: string | null\n idempotencyKey: string | null\n outcome: AuditOutcome\n responseStatus: number | null\n}\n\n/** The request header an admin write carries to be applied once, and the response header a replay carries. */\nexport const IDEMPOTENCY_KEY_HEADER = 'idempotency-key'\nexport const IDEMPOTENT_REPLAYED_HEADER = 'idempotent-replayed'\n\n/** 1-128 characters of `[A-Za-z0-9._:-]`. */\nexport function isValidIdempotencyKey(value: string): boolean {\n return /^[A-Za-z0-9._:-]{1,128}$/.test(value)\n}\n"],"mappings":"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","names":["c","payloadOf"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nymbalhq/types",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Shared TypeScript types and adapter contracts for Nymbal",
5
5
  "license": "MIT",
6
6
  "repository": {