@asteby/storefront 0.17.0 → 0.18.1

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/README.md CHANGED
@@ -408,6 +408,30 @@ a rule that produces a `discard_fields` field. `qr_canonical` (top level) is a t
408
408
  (`fiscal_regime`, `invoice_use`). A text that does not match yields `missing` (required fields); only a spec that cannot
409
409
  run throws `IdentityRulesError`.
410
410
 
411
+ ## Buyer items: «My items» / «My garage» (Storefront API 1.9)
412
+
413
+ A **subject** is something the buyer shops for: a vehicle, a printer, a phone, a pet, a size. WHICH kinds a store has is data the organization publishes (`content.subjectKinds()`); the SDK names none. A store that declares none (`customer.capabilities().subjects` absent, `subjectKinds()` empty) has no such section: show nothing.
414
+
415
+ ```ts
416
+ const kinds = await sf.content.subjectKinds() // [] when the store declares none (or the server is older)
417
+ const me = sf.withCustomerToken(token)
418
+ const item = await me.customer.subjects.create({ kind: 'vehicle', attributes: { make: 'nissan', model: 'np300', year: 2012, engine: '2.5', plate: 'ABC-123' } })
419
+ item.label // 'Nissan NP300 Frontier 2012 2.5 L' (built by the server from the catalog and the PUBLIC values)
420
+ item.fit_key // 'nissan/np300/2012/2.5' (the `fit:<key>` tag of the products; '' if it does not resolve completely)
421
+ item.attributes.plate // '••23' (a `private` field is returned masked, always)
422
+
423
+ // Only the parts that fit: its own tag plus the universal one.
424
+ await sf.products.list({ tags_any: fitTagsAny(item.fit_key) }) // ?tags_any=fit:nissan/np300/2012/2.5,fit:universal
425
+ ```
426
+
427
+ - `customer.subjects.list({ kind? })`, `create`, `update(id, patch)` (attributes merge; `null` clears a field; a private value sent back as its mask is kept), `delete(id)`. Errors: `422` with `field: 'attributes.<field>'` (never the value), `409` repeated or past the limit, `429` too many saves, `404` for an item that is not the buyer's.
428
+ - **Privacy**: a `private` field (a licence plate) is stored for the buyer and never enters the label, the key, a filter, a cache key, a cookie or an order. Never prefill it from the server (it only comes masked, `isMasked`), never log it, never put it in a URL.
429
+ - Pure helpers (`@asteby/storefront`), no UI: `subjectOptions(kind, field, values)` (the cascade), `reconcileSubjectValues`, `resolveSubject(kind, values)` (the same validation as the server, so the form answers without a round trip; the server stays the authority), `fitTagsAny`, `fitTag`, `isMasked`.
430
+ - `splitSubjectTree(tree, maxBytes?)`: a catalog larger than one page (a page is one 64 KiB write with the draft and the published copy, ~28 KB of tree) is published as several `subject_catalog` pages, split by its first level; the kind names them all (`catalog_ref: ["page:x-1", "page:x-2"]`) and the server joins them.
431
+ - Catalog: the levels of the tree follow the kind's `catalog` fields that are not years; an option may declare `from`/`to`/`ranges` (model years) and a `year` field must satisfy every option on the chosen path. A year chosen before an engine narrows the engines; an engine never narrows the year.
432
+ - `products.list({ tags_any })`: at most 5 tags of 120 characters, comma-separated on the wire (a tag with a comma is refused by the client); a hard filter, so facets count inside it.
433
+ - Mock: `createMockStorefront({ subjectKinds: MOCK_SUBJECT_KINDS })` serves the kinds and the whole CRUD with the same rules (default: none, like a store that declares nothing).
434
+
411
435
  ## Buyer account: orders, shipments, invoices (Storefront API 1.8)
412
436
 
413
437
  `customer.order(id)` carries `progress` (unified timeline), `next_action`, `shipments[]` (events, carrier, `https`
@@ -375,6 +375,10 @@ type DeliveryOptions = {
375
375
  /** `true` when some provider failed: `rates` holds only what arrived. */
376
376
  partial?: boolean;
377
377
  unavailable?: UnavailableRateProvider[];
378
+ /** The shop's own fixed price: no carrier was asked (addon setting `shipping_quote_mode`). */
379
+ fixed?: boolean;
380
+ /** Every shipping rate costs 0 (free-shipping minimum reached): nothing to choose, show "Free shipping" and go on. */
381
+ free?: boolean;
378
382
  };
379
383
  /** One way to pay (`GET /storefront/carts/:id/payment-methods`). `key` is sent as `payment_method` at checkout. */
380
384
  type PaymentMethod = {
@@ -463,6 +467,17 @@ type CheckoutPayment = {
463
467
  client?: PaymentClient;
464
468
  /** Pending action of the last attempt (voucher, 3DS challenge). */
465
469
  action?: PaymentAction | null;
470
+ /**
471
+ * Only on `GET checkouts/:id` when the LATEST attempt was rejected (e.g. the bank failed the 3DS challenge): the checkout
472
+ * stays `pending` (a new attempt is allowed) but `outcome` is `rejected`, with the typed reason and next step.
473
+ */
474
+ outcome?: string | null;
475
+ decline_code?: DeclineCode | null;
476
+ next_step?: PaymentNextStep | null;
477
+ retryable?: boolean | null;
478
+ message?: string | null;
479
+ message_en?: string | null;
480
+ attempts_left?: number | null;
466
481
  };
467
482
  type CheckoutInfo = {
468
483
  /** Checkout session id; use it with `payment.instructions`. */
@@ -535,6 +550,14 @@ type CustomerCapabilities = {
535
550
  fiscal_profiles?: {
536
551
  max: number;
537
552
  };
553
+ /**
554
+ * The kinds of saved item the store declares («My items»; contract 1.9). Absent when the store declares none (or on
555
+ * older servers): there is no such section, show nothing.
556
+ */
557
+ subjects?: {
558
+ kinds: string[];
559
+ share_with_workshop?: boolean;
560
+ };
538
561
  /** Channels the notification preferences can use (connector installed). Absent on older servers: no preferences section. */
539
562
  notifications?: {
540
563
  channels: string[];
@@ -1391,6 +1414,91 @@ type FiscalProfileInput = Partial<FiscalIdentityInput> & {
1391
1414
  label?: string;
1392
1415
  is_default?: boolean;
1393
1416
  };
1417
+ /**
1418
+ * A subject is something the buyer shops for: a vehicle, a printer, a phone, a pet, a size. WHICH kinds exist is
1419
+ * data the organization publishes (`content.subjectKinds()`); nothing in the SDK or the contract names one. A store
1420
+ * with no kinds has no such section.
1421
+ */
1422
+ type SubjectFieldType = 'text' | 'year' | 'select';
1423
+ type SubjectField = {
1424
+ key: string;
1425
+ label: string;
1426
+ type: SubjectFieldType;
1427
+ /** `catalog`: the value is an option of the kind's catalog (a `year` is checked against the options' years). Default `free`. */
1428
+ source?: 'catalog' | 'free';
1429
+ /** The field whose choice narrows this one (the cascade order). */
1430
+ depends_on?: string | null;
1431
+ required?: boolean;
1432
+ /**
1433
+ * Stored for the buyer and NEVER part of the label, the compatibility key, a filter, a cache key, a cookie or an
1434
+ * order (a licence plate). The server returns it only masked (`••34`): never prefill it, never log it.
1435
+ */
1436
+ private?: boolean;
1437
+ max?: number;
1438
+ };
1439
+ /**
1440
+ * One option of a catalog level (make, model, engine…). `from`/`to`/`ranges` bound the model years it covers: a
1441
+ * `year` field must satisfy every option on the chosen path that declares them.
1442
+ */
1443
+ type SubjectCatalogNode = {
1444
+ label: string;
1445
+ from?: number;
1446
+ to?: number;
1447
+ ranges?: [number, number][];
1448
+ children?: Record<string, SubjectCatalogNode>;
1449
+ };
1450
+ /** The cascade of options of a kind: levels follow the kind's `catalog` fields that are not years. */
1451
+ type SubjectCatalog = {
1452
+ version?: string | null;
1453
+ tree: Record<string, SubjectCatalogNode>;
1454
+ };
1455
+ type SubjectKind = {
1456
+ key: string;
1457
+ /** Singular («Vehicle»). */
1458
+ label: string;
1459
+ /** The section's name («My garage»). */
1460
+ label_plural: string;
1461
+ max_per_account: number;
1462
+ fields: SubjectField[];
1463
+ /** Template of the compatibility key with the fields in braces (public ones only). */
1464
+ fit_key_template: string;
1465
+ /** The checkout may ask «which one is this order for?». */
1466
+ attach_to_order?: boolean;
1467
+ /** `null` for kinds with free fields only. */
1468
+ catalog?: SubjectCatalog | null;
1469
+ };
1470
+ /** A saved item. `attributes` holds the public values; private ones come masked (`••34`). */
1471
+ type Subject = {
1472
+ id: string;
1473
+ kind: string;
1474
+ /** Public label built by the server from the catalog and the public values. */
1475
+ label: string;
1476
+ nickname?: string | null;
1477
+ /** Compatibility key (the `fit:<key>` tag of the products); `''` when the item does not resolve completely. */
1478
+ fit_key: string;
1479
+ attributes: Record<string, unknown>;
1480
+ /** The active item of its kind. */
1481
+ is_default: boolean;
1482
+ last_used_at?: string | null;
1483
+ created_at: string;
1484
+ };
1485
+ type SubjectInput = {
1486
+ kind: string;
1487
+ attributes: Record<string, unknown>;
1488
+ nickname?: string | null;
1489
+ is_default?: boolean;
1490
+ };
1491
+ /** Merged over the stored item: `null` clears a field; a private value sent back as its mask is kept. */
1492
+ type SubjectPatch = {
1493
+ attributes?: Record<string, unknown>;
1494
+ nickname?: string | null;
1495
+ is_default?: boolean;
1496
+ };
1497
+ /** The item an order is for (kinds with `attach_to_order`). */
1498
+ type SubjectRef = {
1499
+ kind: string;
1500
+ id: string;
1501
+ };
1394
1502
  /** `PATCH /customer`. `fiscal: null` deletes the saved fiscal data (ARCO). */
1395
1503
  type CustomerUpdateInput = {
1396
1504
  first_name?: string | null;
@@ -1591,4 +1699,4 @@ declare function dataExportBytes(file: Pick<DataExportFile, 'content_base64'>):
1591
1699
  /** A download filename that is safe in a `Content-Disposition` header: no path, quotes, control characters or spaces. */
1592
1700
  declare function safeExportFilename(name: unknown, fallback?: string): string;
1593
1701
 
1594
- export { type FiscalProfile as $, type AccountDeleteInput as A, type Customer as B, type Cart as C, type CustomerCapabilities as D, type CustomerIdentifier as E, type CustomerPasswordLoginInput as F, type CustomerPreferences as G, type CustomerRegisterInput as H, type CustomerRegisterResult as I, type CustomerResetPasswordInput as J, type CustomerSession as K, type CustomerSessionInfo as L, type CustomerSessionInput as M, type CustomerSetPasswordInput as N, type CustomerUpdateInput as O, type CustomerVerifyRegistrationInput as P, type DataExportFile as Q, type DeclineCode as R, type DeliveryInput as S, type DeliveryMethod as T, type DeliveryOptions as U, type Facet as V, type FacetValue as W, type FiscalDocumentEvidence as X, type FiscalDocumentSource as Y, type FiscalIdentity as Z, type FiscalIdentityInput as _, type Address as a, type Product as a$, type FiscalProfileInput as a0, type FiscalVerification as a1, type GeoHint as a2, type IdentityCapture as a3, type IdentityDocumentSpec as a4, type IdentityFieldRule as a5, type IdentityRulesV1 as a6, type IdentityStrategy as a7, type Image as a8, type InsufficientStockDetails as a9, type OrderProgress as aA, type OrderProgressStep as aB, type OrderSection as aC, type OrderStage as aD, type OrderStepKey as aE, type OrderStepState as aF, type Page as aG, type PageInfo as aH, type PaymentAction as aI, type PaymentAttemptResult as aJ, type PaymentAttemptStatus as aK, type PaymentClient as aL, type PaymentClientCsp as aM, type PaymentConfig as aN, type PaymentFlow as aO, type PaymentInfo as aP, type PaymentMethod as aQ, type PaymentMethods as aR, type PaymentNextStep as aS, type PaymentOption as aT, type PaymentOptionKey as aU, type PaymentPoint as aV, type PaymentSubmitInput as aW, type PaymentVoucher as aX, type PendingPayment as aY, type PendingPaymentVoucher as aZ, type PickupOption as a_, type InsufficientStockLine as aa, type InvoiceAction as ab, type InvoiceDoc as ac, type InvoiceFile as ad, type InvoiceFileKind as ae, type InvoiceRequest as af, type InvoiceResult as ag, type InvoiceStage as ah, type InvoiceSummary as ai, type InvoicingCapabilities as aj, type Location as ak, type LocationContacts as al, type LocationKind as am, type LocationListParams as an, type LocationPickup as ao, type Menu as ap, type MenuItem as aq, type Money as ar, type MoneyRange as as, type NearestLocation as at, type NextAction as au, type NextActionKind as av, type Order as aw, type OrderLine as ax, type OrderPickup as ay, type OrderProblem as az, type AddressInput as b, type ProductAttributeValue as b0, type ProductCategory as b1, type ProductConnection as b2, type ProductOption as b3, type ProductSort as b4, type ProductVariant as b5, type PromotionAnnouncement as b6, type PromotionReward as b7, type PromotionTier as b8, type PromotionsList as b9, type ShopPolicies as bA, type StockLevel as bB, StorefrontError as bC, type StorefrontErrorBody as bD, type StorefrontErrorCode as bE, type StorefrontErrorInit as bF, type Theme as bG, type TokenIntrospection as bH, type UnavailableRateProvider as bI, dataExportBytes as bJ, isAccountDeleteBlocked as bK, isReauthRequired as bL, isStorefrontError as bM, safeExportFilename as bN, type ReauthInput as ba, type ReauthResult as bb, type RecommendationPlacement as bc, type Refund as bd, type Region as be, type RegionFiscalEntry as bf, type RegionFiscalId as bg, type RegionPaymentMethod as bh, type RegionPostalCode as bi, type RestockSubscriptionInput as bj, type RestockSubscriptionResult as bk, type ReturnRequest as bl, type ReturnRequestInput as bm, type SEO as bn, type Section as bo, type SelectedOption as bp, type Shipment as bq, type ShipmentEvent as br, type ShipmentKind as bs, type ShipmentMap as bt, type ShipmentStatus as bu, type ShipmentsSummary as bv, type ShippingRate as bw, type Shop as bx, type ShopBranch as by, type ShopContact as bz, type Article as c, type Availability as d, type AvailabilityStatus as e, type CartBuyer as f, type CartBuyerInput as g, type CartCost as h, type CartDelivery as i, type CartDiscountCode as j, type CartDiscountLine as k, type CartInvoice as l, type CartLine as m, type CartLineInput as n, type CartLineMerchandise as o, type CartLineProblem as p, type CartLineUpdateInput as q, type CartPromotions as r, type CartRecommendation as s, type CartRecommendations as t, type CheckoutInfo as u, type CheckoutInput as v, type CheckoutPayment as w, type CheckoutResult as x, type Collection as y, type Connection as z };
1702
+ export { type FiscalProfile as $, type AccountDeleteInput as A, type Customer as B, type Cart as C, type CustomerCapabilities as D, type CustomerIdentifier as E, type CustomerPasswordLoginInput as F, type CustomerPreferences as G, type CustomerRegisterInput as H, type CustomerRegisterResult as I, type CustomerResetPasswordInput as J, type CustomerSession as K, type CustomerSessionInfo as L, type CustomerSessionInput as M, type CustomerSetPasswordInput as N, type CustomerUpdateInput as O, type CustomerVerifyRegistrationInput as P, type DataExportFile as Q, type DeclineCode as R, type DeliveryInput as S, type DeliveryMethod as T, type DeliveryOptions as U, type Facet as V, type FacetValue as W, type FiscalDocumentEvidence as X, type FiscalDocumentSource as Y, type FiscalIdentity as Z, type FiscalIdentityInput as _, type Address as a, type Product as a$, type FiscalProfileInput as a0, type FiscalVerification as a1, type GeoHint as a2, type IdentityCapture as a3, type IdentityDocumentSpec as a4, type IdentityFieldRule as a5, type IdentityRulesV1 as a6, type IdentityStrategy as a7, type Image as a8, type InsufficientStockDetails as a9, type OrderProgress as aA, type OrderProgressStep as aB, type OrderSection as aC, type OrderStage as aD, type OrderStepKey as aE, type OrderStepState as aF, type Page as aG, type PageInfo as aH, type PaymentAction as aI, type PaymentAttemptResult as aJ, type PaymentAttemptStatus as aK, type PaymentClient as aL, type PaymentClientCsp as aM, type PaymentConfig as aN, type PaymentFlow as aO, type PaymentInfo as aP, type PaymentMethod as aQ, type PaymentMethods as aR, type PaymentNextStep as aS, type PaymentOption as aT, type PaymentOptionKey as aU, type PaymentPoint as aV, type PaymentSubmitInput as aW, type PaymentVoucher as aX, type PendingPayment as aY, type PendingPaymentVoucher as aZ, type PickupOption as a_, type InsufficientStockLine as aa, type InvoiceAction as ab, type InvoiceDoc as ac, type InvoiceFile as ad, type InvoiceFileKind as ae, type InvoiceRequest as af, type InvoiceResult as ag, type InvoiceStage as ah, type InvoiceSummary as ai, type InvoicingCapabilities as aj, type Location as ak, type LocationContacts as al, type LocationKind as am, type LocationListParams as an, type LocationPickup as ao, type Menu as ap, type MenuItem as aq, type Money as ar, type MoneyRange as as, type NearestLocation as at, type NextAction as au, type NextActionKind as av, type Order as aw, type OrderLine as ax, type OrderPickup as ay, type OrderProblem as az, type AddressInput as b, type ProductAttributeValue as b0, type ProductCategory as b1, type ProductConnection as b2, type ProductOption as b3, type ProductSort as b4, type ProductVariant as b5, type PromotionAnnouncement as b6, type PromotionReward as b7, type PromotionTier as b8, type PromotionsList as b9, type ShopPolicies as bA, type StockLevel as bB, StorefrontError as bC, type StorefrontErrorBody as bD, type StorefrontErrorCode as bE, type StorefrontErrorInit as bF, type Subject as bG, type SubjectCatalog as bH, type SubjectCatalogNode as bI, type SubjectField as bJ, type SubjectFieldType as bK, type SubjectInput as bL, type SubjectKind as bM, type SubjectPatch as bN, type SubjectRef as bO, type Theme as bP, type TokenIntrospection as bQ, type UnavailableRateProvider as bR, dataExportBytes as bS, isAccountDeleteBlocked as bT, isReauthRequired as bU, isStorefrontError as bV, safeExportFilename as bW, type ReauthInput as ba, type ReauthResult as bb, type RecommendationPlacement as bc, type Refund as bd, type Region as be, type RegionFiscalEntry as bf, type RegionFiscalId as bg, type RegionPaymentMethod as bh, type RegionPostalCode as bi, type RestockSubscriptionInput as bj, type RestockSubscriptionResult as bk, type ReturnRequest as bl, type ReturnRequestInput as bm, type SEO as bn, type Section as bo, type SelectedOption as bp, type Shipment as bq, type ShipmentEvent as br, type ShipmentKind as bs, type ShipmentMap as bt, type ShipmentStatus as bu, type ShipmentsSummary as bv, type ShippingRate as bw, type Shop as bx, type ShopBranch as by, type ShopContact as bz, type Article as c, type Availability as d, type AvailabilityStatus as e, type CartBuyer as f, type CartBuyerInput as g, type CartCost as h, type CartDelivery as i, type CartDiscountCode as j, type CartDiscountLine as k, type CartInvoice as l, type CartLine as m, type CartLineInput as n, type CartLineMerchandise as o, type CartLineProblem as p, type CartLineUpdateInput as q, type CartPromotions as r, type CartRecommendation as s, type CartRecommendations as t, type CheckoutInfo as u, type CheckoutInput as v, type CheckoutPayment as w, type CheckoutResult as x, type Collection as y, type Connection as z };
@@ -375,6 +375,10 @@ type DeliveryOptions = {
375
375
  /** `true` when some provider failed: `rates` holds only what arrived. */
376
376
  partial?: boolean;
377
377
  unavailable?: UnavailableRateProvider[];
378
+ /** The shop's own fixed price: no carrier was asked (addon setting `shipping_quote_mode`). */
379
+ fixed?: boolean;
380
+ /** Every shipping rate costs 0 (free-shipping minimum reached): nothing to choose, show "Free shipping" and go on. */
381
+ free?: boolean;
378
382
  };
379
383
  /** One way to pay (`GET /storefront/carts/:id/payment-methods`). `key` is sent as `payment_method` at checkout. */
380
384
  type PaymentMethod = {
@@ -463,6 +467,17 @@ type CheckoutPayment = {
463
467
  client?: PaymentClient;
464
468
  /** Pending action of the last attempt (voucher, 3DS challenge). */
465
469
  action?: PaymentAction | null;
470
+ /**
471
+ * Only on `GET checkouts/:id` when the LATEST attempt was rejected (e.g. the bank failed the 3DS challenge): the checkout
472
+ * stays `pending` (a new attempt is allowed) but `outcome` is `rejected`, with the typed reason and next step.
473
+ */
474
+ outcome?: string | null;
475
+ decline_code?: DeclineCode | null;
476
+ next_step?: PaymentNextStep | null;
477
+ retryable?: boolean | null;
478
+ message?: string | null;
479
+ message_en?: string | null;
480
+ attempts_left?: number | null;
466
481
  };
467
482
  type CheckoutInfo = {
468
483
  /** Checkout session id; use it with `payment.instructions`. */
@@ -535,6 +550,14 @@ type CustomerCapabilities = {
535
550
  fiscal_profiles?: {
536
551
  max: number;
537
552
  };
553
+ /**
554
+ * The kinds of saved item the store declares («My items»; contract 1.9). Absent when the store declares none (or on
555
+ * older servers): there is no such section, show nothing.
556
+ */
557
+ subjects?: {
558
+ kinds: string[];
559
+ share_with_workshop?: boolean;
560
+ };
538
561
  /** Channels the notification preferences can use (connector installed). Absent on older servers: no preferences section. */
539
562
  notifications?: {
540
563
  channels: string[];
@@ -1391,6 +1414,91 @@ type FiscalProfileInput = Partial<FiscalIdentityInput> & {
1391
1414
  label?: string;
1392
1415
  is_default?: boolean;
1393
1416
  };
1417
+ /**
1418
+ * A subject is something the buyer shops for: a vehicle, a printer, a phone, a pet, a size. WHICH kinds exist is
1419
+ * data the organization publishes (`content.subjectKinds()`); nothing in the SDK or the contract names one. A store
1420
+ * with no kinds has no such section.
1421
+ */
1422
+ type SubjectFieldType = 'text' | 'year' | 'select';
1423
+ type SubjectField = {
1424
+ key: string;
1425
+ label: string;
1426
+ type: SubjectFieldType;
1427
+ /** `catalog`: the value is an option of the kind's catalog (a `year` is checked against the options' years). Default `free`. */
1428
+ source?: 'catalog' | 'free';
1429
+ /** The field whose choice narrows this one (the cascade order). */
1430
+ depends_on?: string | null;
1431
+ required?: boolean;
1432
+ /**
1433
+ * Stored for the buyer and NEVER part of the label, the compatibility key, a filter, a cache key, a cookie or an
1434
+ * order (a licence plate). The server returns it only masked (`••34`): never prefill it, never log it.
1435
+ */
1436
+ private?: boolean;
1437
+ max?: number;
1438
+ };
1439
+ /**
1440
+ * One option of a catalog level (make, model, engine…). `from`/`to`/`ranges` bound the model years it covers: a
1441
+ * `year` field must satisfy every option on the chosen path that declares them.
1442
+ */
1443
+ type SubjectCatalogNode = {
1444
+ label: string;
1445
+ from?: number;
1446
+ to?: number;
1447
+ ranges?: [number, number][];
1448
+ children?: Record<string, SubjectCatalogNode>;
1449
+ };
1450
+ /** The cascade of options of a kind: levels follow the kind's `catalog` fields that are not years. */
1451
+ type SubjectCatalog = {
1452
+ version?: string | null;
1453
+ tree: Record<string, SubjectCatalogNode>;
1454
+ };
1455
+ type SubjectKind = {
1456
+ key: string;
1457
+ /** Singular («Vehicle»). */
1458
+ label: string;
1459
+ /** The section's name («My garage»). */
1460
+ label_plural: string;
1461
+ max_per_account: number;
1462
+ fields: SubjectField[];
1463
+ /** Template of the compatibility key with the fields in braces (public ones only). */
1464
+ fit_key_template: string;
1465
+ /** The checkout may ask «which one is this order for?». */
1466
+ attach_to_order?: boolean;
1467
+ /** `null` for kinds with free fields only. */
1468
+ catalog?: SubjectCatalog | null;
1469
+ };
1470
+ /** A saved item. `attributes` holds the public values; private ones come masked (`••34`). */
1471
+ type Subject = {
1472
+ id: string;
1473
+ kind: string;
1474
+ /** Public label built by the server from the catalog and the public values. */
1475
+ label: string;
1476
+ nickname?: string | null;
1477
+ /** Compatibility key (the `fit:<key>` tag of the products); `''` when the item does not resolve completely. */
1478
+ fit_key: string;
1479
+ attributes: Record<string, unknown>;
1480
+ /** The active item of its kind. */
1481
+ is_default: boolean;
1482
+ last_used_at?: string | null;
1483
+ created_at: string;
1484
+ };
1485
+ type SubjectInput = {
1486
+ kind: string;
1487
+ attributes: Record<string, unknown>;
1488
+ nickname?: string | null;
1489
+ is_default?: boolean;
1490
+ };
1491
+ /** Merged over the stored item: `null` clears a field; a private value sent back as its mask is kept. */
1492
+ type SubjectPatch = {
1493
+ attributes?: Record<string, unknown>;
1494
+ nickname?: string | null;
1495
+ is_default?: boolean;
1496
+ };
1497
+ /** The item an order is for (kinds with `attach_to_order`). */
1498
+ type SubjectRef = {
1499
+ kind: string;
1500
+ id: string;
1501
+ };
1394
1502
  /** `PATCH /customer`. `fiscal: null` deletes the saved fiscal data (ARCO). */
1395
1503
  type CustomerUpdateInput = {
1396
1504
  first_name?: string | null;
@@ -1591,4 +1699,4 @@ declare function dataExportBytes(file: Pick<DataExportFile, 'content_base64'>):
1591
1699
  /** A download filename that is safe in a `Content-Disposition` header: no path, quotes, control characters or spaces. */
1592
1700
  declare function safeExportFilename(name: unknown, fallback?: string): string;
1593
1701
 
1594
- export { type FiscalProfile as $, type AccountDeleteInput as A, type Customer as B, type Cart as C, type CustomerCapabilities as D, type CustomerIdentifier as E, type CustomerPasswordLoginInput as F, type CustomerPreferences as G, type CustomerRegisterInput as H, type CustomerRegisterResult as I, type CustomerResetPasswordInput as J, type CustomerSession as K, type CustomerSessionInfo as L, type CustomerSessionInput as M, type CustomerSetPasswordInput as N, type CustomerUpdateInput as O, type CustomerVerifyRegistrationInput as P, type DataExportFile as Q, type DeclineCode as R, type DeliveryInput as S, type DeliveryMethod as T, type DeliveryOptions as U, type Facet as V, type FacetValue as W, type FiscalDocumentEvidence as X, type FiscalDocumentSource as Y, type FiscalIdentity as Z, type FiscalIdentityInput as _, type Address as a, type Product as a$, type FiscalProfileInput as a0, type FiscalVerification as a1, type GeoHint as a2, type IdentityCapture as a3, type IdentityDocumentSpec as a4, type IdentityFieldRule as a5, type IdentityRulesV1 as a6, type IdentityStrategy as a7, type Image as a8, type InsufficientStockDetails as a9, type OrderProgress as aA, type OrderProgressStep as aB, type OrderSection as aC, type OrderStage as aD, type OrderStepKey as aE, type OrderStepState as aF, type Page as aG, type PageInfo as aH, type PaymentAction as aI, type PaymentAttemptResult as aJ, type PaymentAttemptStatus as aK, type PaymentClient as aL, type PaymentClientCsp as aM, type PaymentConfig as aN, type PaymentFlow as aO, type PaymentInfo as aP, type PaymentMethod as aQ, type PaymentMethods as aR, type PaymentNextStep as aS, type PaymentOption as aT, type PaymentOptionKey as aU, type PaymentPoint as aV, type PaymentSubmitInput as aW, type PaymentVoucher as aX, type PendingPayment as aY, type PendingPaymentVoucher as aZ, type PickupOption as a_, type InsufficientStockLine as aa, type InvoiceAction as ab, type InvoiceDoc as ac, type InvoiceFile as ad, type InvoiceFileKind as ae, type InvoiceRequest as af, type InvoiceResult as ag, type InvoiceStage as ah, type InvoiceSummary as ai, type InvoicingCapabilities as aj, type Location as ak, type LocationContacts as al, type LocationKind as am, type LocationListParams as an, type LocationPickup as ao, type Menu as ap, type MenuItem as aq, type Money as ar, type MoneyRange as as, type NearestLocation as at, type NextAction as au, type NextActionKind as av, type Order as aw, type OrderLine as ax, type OrderPickup as ay, type OrderProblem as az, type AddressInput as b, type ProductAttributeValue as b0, type ProductCategory as b1, type ProductConnection as b2, type ProductOption as b3, type ProductSort as b4, type ProductVariant as b5, type PromotionAnnouncement as b6, type PromotionReward as b7, type PromotionTier as b8, type PromotionsList as b9, type ShopPolicies as bA, type StockLevel as bB, StorefrontError as bC, type StorefrontErrorBody as bD, type StorefrontErrorCode as bE, type StorefrontErrorInit as bF, type Theme as bG, type TokenIntrospection as bH, type UnavailableRateProvider as bI, dataExportBytes as bJ, isAccountDeleteBlocked as bK, isReauthRequired as bL, isStorefrontError as bM, safeExportFilename as bN, type ReauthInput as ba, type ReauthResult as bb, type RecommendationPlacement as bc, type Refund as bd, type Region as be, type RegionFiscalEntry as bf, type RegionFiscalId as bg, type RegionPaymentMethod as bh, type RegionPostalCode as bi, type RestockSubscriptionInput as bj, type RestockSubscriptionResult as bk, type ReturnRequest as bl, type ReturnRequestInput as bm, type SEO as bn, type Section as bo, type SelectedOption as bp, type Shipment as bq, type ShipmentEvent as br, type ShipmentKind as bs, type ShipmentMap as bt, type ShipmentStatus as bu, type ShipmentsSummary as bv, type ShippingRate as bw, type Shop as bx, type ShopBranch as by, type ShopContact as bz, type Article as c, type Availability as d, type AvailabilityStatus as e, type CartBuyer as f, type CartBuyerInput as g, type CartCost as h, type CartDelivery as i, type CartDiscountCode as j, type CartDiscountLine as k, type CartInvoice as l, type CartLine as m, type CartLineInput as n, type CartLineMerchandise as o, type CartLineProblem as p, type CartLineUpdateInput as q, type CartPromotions as r, type CartRecommendation as s, type CartRecommendations as t, type CheckoutInfo as u, type CheckoutInput as v, type CheckoutPayment as w, type CheckoutResult as x, type Collection as y, type Connection as z };
1702
+ export { type FiscalProfile as $, type AccountDeleteInput as A, type Customer as B, type Cart as C, type CustomerCapabilities as D, type CustomerIdentifier as E, type CustomerPasswordLoginInput as F, type CustomerPreferences as G, type CustomerRegisterInput as H, type CustomerRegisterResult as I, type CustomerResetPasswordInput as J, type CustomerSession as K, type CustomerSessionInfo as L, type CustomerSessionInput as M, type CustomerSetPasswordInput as N, type CustomerUpdateInput as O, type CustomerVerifyRegistrationInput as P, type DataExportFile as Q, type DeclineCode as R, type DeliveryInput as S, type DeliveryMethod as T, type DeliveryOptions as U, type Facet as V, type FacetValue as W, type FiscalDocumentEvidence as X, type FiscalDocumentSource as Y, type FiscalIdentity as Z, type FiscalIdentityInput as _, type Address as a, type Product as a$, type FiscalProfileInput as a0, type FiscalVerification as a1, type GeoHint as a2, type IdentityCapture as a3, type IdentityDocumentSpec as a4, type IdentityFieldRule as a5, type IdentityRulesV1 as a6, type IdentityStrategy as a7, type Image as a8, type InsufficientStockDetails as a9, type OrderProgress as aA, type OrderProgressStep as aB, type OrderSection as aC, type OrderStage as aD, type OrderStepKey as aE, type OrderStepState as aF, type Page as aG, type PageInfo as aH, type PaymentAction as aI, type PaymentAttemptResult as aJ, type PaymentAttemptStatus as aK, type PaymentClient as aL, type PaymentClientCsp as aM, type PaymentConfig as aN, type PaymentFlow as aO, type PaymentInfo as aP, type PaymentMethod as aQ, type PaymentMethods as aR, type PaymentNextStep as aS, type PaymentOption as aT, type PaymentOptionKey as aU, type PaymentPoint as aV, type PaymentSubmitInput as aW, type PaymentVoucher as aX, type PendingPayment as aY, type PendingPaymentVoucher as aZ, type PickupOption as a_, type InsufficientStockLine as aa, type InvoiceAction as ab, type InvoiceDoc as ac, type InvoiceFile as ad, type InvoiceFileKind as ae, type InvoiceRequest as af, type InvoiceResult as ag, type InvoiceStage as ah, type InvoiceSummary as ai, type InvoicingCapabilities as aj, type Location as ak, type LocationContacts as al, type LocationKind as am, type LocationListParams as an, type LocationPickup as ao, type Menu as ap, type MenuItem as aq, type Money as ar, type MoneyRange as as, type NearestLocation as at, type NextAction as au, type NextActionKind as av, type Order as aw, type OrderLine as ax, type OrderPickup as ay, type OrderProblem as az, type AddressInput as b, type ProductAttributeValue as b0, type ProductCategory as b1, type ProductConnection as b2, type ProductOption as b3, type ProductSort as b4, type ProductVariant as b5, type PromotionAnnouncement as b6, type PromotionReward as b7, type PromotionTier as b8, type PromotionsList as b9, type ShopPolicies as bA, type StockLevel as bB, StorefrontError as bC, type StorefrontErrorBody as bD, type StorefrontErrorCode as bE, type StorefrontErrorInit as bF, type Subject as bG, type SubjectCatalog as bH, type SubjectCatalogNode as bI, type SubjectField as bJ, type SubjectFieldType as bK, type SubjectInput as bL, type SubjectKind as bM, type SubjectPatch as bN, type SubjectRef as bO, type Theme as bP, type TokenIntrospection as bQ, type UnavailableRateProvider as bR, dataExportBytes as bS, isAccountDeleteBlocked as bT, isReauthRequired as bU, isStorefrontError as bV, safeExportFilename as bW, type ReauthInput as ba, type ReauthResult as bb, type RecommendationPlacement as bc, type Refund as bd, type Region as be, type RegionFiscalEntry as bf, type RegionFiscalId as bg, type RegionPaymentMethod as bh, type RegionPostalCode as bi, type RestockSubscriptionInput as bj, type RestockSubscriptionResult as bk, type ReturnRequest as bl, type ReturnRequestInput as bm, type SEO as bn, type Section as bo, type SelectedOption as bp, type Shipment as bq, type ShipmentEvent as br, type ShipmentKind as bs, type ShipmentMap as bt, type ShipmentStatus as bu, type ShipmentsSummary as bv, type ShippingRate as bw, type Shop as bx, type ShopBranch as by, type ShopContact as bz, type Article as c, type Availability as d, type AvailabilityStatus as e, type CartBuyer as f, type CartBuyerInput as g, type CartCost as h, type CartDelivery as i, type CartDiscountCode as j, type CartDiscountLine as k, type CartInvoice as l, type CartLine as m, type CartLineInput as n, type CartLineMerchandise as o, type CartLineProblem as p, type CartLineUpdateInput as q, type CartPromotions as r, type CartRecommendation as s, type CartRecommendations as t, type CheckoutInfo as u, type CheckoutInput as v, type CheckoutPayment as w, type CheckoutResult as x, type Collection as y, type Connection as z };
@@ -641,6 +641,199 @@ function shipmentsStatus(order) {
641
641
  return live.length ? live[0].status : null;
642
642
  }
643
643
 
644
- export { PICKUP_STEPS, SHIPPING_STEPS, StorefrontError, availabilityLocale, availabilityMessages, availabilityView, cartLineProblemView, declineMessage, declineMessageKey, defaultCodeForStatus, formatAvailabilityMessage, formatVoucherExpiry, isStorefrontError, isTestInvoice, isTestShipment, nextAction, orderProgress, orderShipments, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, productAvailability, resolveAvailability, safeHttpsUrl, safeTrackingUrl, schemaAvailability, shipmentsStatus, sortTrackingEvents };
645
- //# sourceMappingURL=chunk-IPUAZ3LQ.js.map
646
- //# sourceMappingURL=chunk-IPUAZ3LQ.js.map
644
+ // src/subjects.ts
645
+ var MIN_SUBJECT_YEAR = 1900;
646
+ var MAX_SUBJECT_YEAR = 2100;
647
+ var MAX_VALUE_LENGTH = 80;
648
+ var MAX_LABEL_LENGTH = 120;
649
+ var MAX_FIT_KEY_LENGTH = 100;
650
+ var isPresent = (v) => v !== null && v !== void 0 && v !== "";
651
+ var asYear = (v) => {
652
+ const n = typeof v === "number" ? v : typeof v === "string" && /^\s*\d+\s*$/.test(v) ? Number(v) : NaN;
653
+ return Number.isInteger(n) && n >= MIN_SUBJECT_YEAR && n <= MAX_SUBJECT_YEAR ? n : null;
654
+ };
655
+ var cleanText = (v, max) => {
656
+ if (typeof v !== "string") return null;
657
+ const s = v.split(/\s+/).filter(Boolean).join(" ");
658
+ return [...s].length > max || /[\u0000-\u001f\u007f-\u009f]/.test(s) ? null : s;
659
+ };
660
+ var slug = (s) => s.toLowerCase().replace(/[^a-z0-9._+-]+/g, "-").replace(/^-+|-+$/g, "");
661
+ function coversYear(node, year) {
662
+ if (node.ranges && node.ranges.length > 0) return node.ranges.some(([a, b]) => year >= a && year <= b);
663
+ if (node.from !== void 0 && year < node.from) return false;
664
+ if (node.to !== void 0 && year > node.to) return false;
665
+ return true;
666
+ }
667
+ var declaresYears = (n) => n.from !== void 0 || n.to !== void 0 || (n.ranges?.length ?? 0) > 0;
668
+ function subjectLevels(kind) {
669
+ return kind.fields.filter((f) => f.source === "catalog" && f.type !== "year");
670
+ }
671
+ function subjectFields(kind) {
672
+ return kind.fields;
673
+ }
674
+ function walk(kind, values) {
675
+ const path = [];
676
+ const ids = {};
677
+ let tree = kind.catalog?.tree;
678
+ for (const f of subjectLevels(kind)) {
679
+ const raw = values[f.key];
680
+ const s = isPresent(raw) ? cleanText(raw, MAX_VALUE_LENGTH) : null;
681
+ const node = s && tree ? tree[s] ?? tree[s.toLowerCase()] : void 0;
682
+ if (!s || !node) return { path, ids, next: tree };
683
+ ids[f.key] = tree && tree[s] ? s : s.toLowerCase();
684
+ path.push(node);
685
+ tree = node.children;
686
+ if (!tree) return { path, ids, next: void 0 };
687
+ }
688
+ return { path, ids, next: tree };
689
+ }
690
+ var byLabel = (a, b) => a.label.localeCompare(b.label, void 0, { sensitivity: "base", numeric: true });
691
+ function subjectOptions(kind, fieldKey, values) {
692
+ const field = kind.fields.find((f) => f.key === fieldKey);
693
+ if (!field || field.source !== "catalog" || !kind.catalog) return [];
694
+ if (field.type === "year") {
695
+ const upto = kind.fields.slice(0, kind.fields.indexOf(field));
696
+ const { path: path2 } = walk({ ...kind, fields: upto }, values);
697
+ const declared = path2.filter(declaresYears);
698
+ if (declared.length === 0) return [];
699
+ let lo = Infinity;
700
+ let hi = -Infinity;
701
+ for (const n of declared) {
702
+ for (const [a, b] of n.ranges?.length ? n.ranges : [[n.from ?? MIN_SUBJECT_YEAR, n.to ?? MAX_SUBJECT_YEAR]]) {
703
+ lo = Math.min(lo, a);
704
+ hi = Math.max(hi, b);
705
+ }
706
+ }
707
+ const out = [];
708
+ for (let y = Math.min(hi, MAX_SUBJECT_YEAR); y >= Math.max(lo, MIN_SUBJECT_YEAR); y--) if (declared.every((n) => coversYear(n, y))) out.push({ value: String(y), label: String(y) });
709
+ return out;
710
+ }
711
+ const levels = subjectLevels(kind);
712
+ const at = levels.findIndex((f) => f.key === fieldKey);
713
+ if (at < 0) return [];
714
+ const before = Object.fromEntries(levels.slice(0, at).map((f) => [f.key, values[f.key]]));
715
+ const { path, next } = walk({ ...kind, fields: kind.fields.filter((f) => levels.slice(0, at).includes(f) || f.type === "year") }, before);
716
+ if (path.length < at || !next) return [];
717
+ const year = asYear(values[kind.fields.find((f) => f.type === "year" && f.source === "catalog")?.key ?? ""]);
718
+ return Object.entries(next).filter(([, n]) => year === null || coversYear(n, year)).map(([value, n]) => ({ value, label: n.label })).sort(byLabel);
719
+ }
720
+ function reconcileSubjectValues(kind, values) {
721
+ const out = { ...values };
722
+ for (const f of kind.fields) {
723
+ if (f.source !== "catalog" || !isPresent(out[f.key])) continue;
724
+ const options = subjectOptions(kind, f.key, out);
725
+ const v = String(out[f.key]);
726
+ const ok = f.type === "year" ? options.length === 0 || options.some((o) => o.value === v) : options.some((o) => o.value === v || o.value === v.toLowerCase());
727
+ if (!ok) out[f.key] = "";
728
+ }
729
+ return out;
730
+ }
731
+ function resolveSubject(kind, values) {
732
+ const problems = {};
733
+ const known = new Set(kind.fields.map((f) => f.key));
734
+ for (const key2 of Object.keys(values)) if (isPresent(values[key2]) && !known.has(key2)) problems[key2] = "unknown";
735
+ const attributes = {};
736
+ const priv = {};
737
+ const display = {};
738
+ const ids = {};
739
+ const path = [];
740
+ let tree = kind.catalog?.tree;
741
+ const years = [];
742
+ const put = (f, v) => {
743
+ if (f.private) priv[f.key] = String(v);
744
+ else attributes[f.key] = v;
745
+ };
746
+ for (const f of kind.fields) {
747
+ const raw = values[f.key];
748
+ if (!isPresent(raw)) {
749
+ if (f.required) problems[f.key] = "required";
750
+ if (f.source === "catalog" && f.type !== "year") tree = void 0;
751
+ continue;
752
+ }
753
+ const max = f.max ?? MAX_VALUE_LENGTH;
754
+ if (f.type === "year") {
755
+ const y = asYear(raw);
756
+ if (y === null) {
757
+ problems[f.key] = "invalid";
758
+ continue;
759
+ }
760
+ put(f, y);
761
+ display[f.key] = ids[f.key] = String(y);
762
+ if (f.source === "catalog") years.push(f.key);
763
+ } else if (f.source === "catalog") {
764
+ if (!kind.catalog) {
765
+ problems[f.key] = "catalog_unavailable";
766
+ continue;
767
+ }
768
+ const s = cleanText(raw, max);
769
+ if (s === null) {
770
+ problems[f.key] = "invalid";
771
+ continue;
772
+ }
773
+ const node = tree?.[s] ?? tree?.[s.toLowerCase()];
774
+ if (!node) {
775
+ problems[f.key] = "not_in_catalog";
776
+ continue;
777
+ }
778
+ const id = tree?.[s] ? s : s.toLowerCase();
779
+ path.push(node);
780
+ tree = node.children;
781
+ put(f, id);
782
+ display[f.key] = node.label;
783
+ ids[f.key] = id;
784
+ } else {
785
+ const s = cleanText(raw, max);
786
+ if (s === null) {
787
+ problems[f.key] = "invalid";
788
+ continue;
789
+ }
790
+ put(f, s);
791
+ display[f.key] = s;
792
+ ids[f.key] = slug(s);
793
+ }
794
+ }
795
+ for (const key2 of years) {
796
+ const y = attributes[key2];
797
+ if (y !== void 0 && !problems[key2] && !path.every((n) => coversYear(n, y))) problems[key2] = "year_range";
798
+ }
799
+ if (Object.keys(problems).length > 0) return { ok: false, problems };
800
+ const label = [...kind.fields.filter((f) => !f.private).map((f) => display[f.key]).filter(Boolean).join(" ")].slice(0, MAX_LABEL_LENGTH).join("").trim();
801
+ if (!label) return { ok: false, problems: { _: "required" } };
802
+ let complete = true;
803
+ const key = kind.fit_key_template.replace(/\{([a-z][a-z0-9_]*)\}/g, (_m, name) => {
804
+ const id = ids[name];
805
+ if (!id) complete = false;
806
+ return id ?? "";
807
+ });
808
+ return { ok: true, subject: { label, fit_key: complete && key.length <= MAX_FIT_KEY_LENGTH ? key.toLowerCase() : "", attributes, private: priv } };
809
+ }
810
+ var PRIVATE_MASK = "\u2022\u2022";
811
+ var isMasked = (v) => typeof v === "string" && v.startsWith(PRIVATE_MASK);
812
+ var DEFAULT_FIT_TAG_PREFIX = "fit:";
813
+ var fitTag = (fitKey, prefix = DEFAULT_FIT_TAG_PREFIX) => `${prefix}${fitKey}`;
814
+ function fitTagsAny(fitKey, opts = {}) {
815
+ if (!fitKey) return [];
816
+ const prefix = opts.prefix ?? DEFAULT_FIT_TAG_PREFIX;
817
+ return opts.universal === false ? [fitTag(fitKey, prefix)] : [fitTag(fitKey, prefix), `${prefix}universal`];
818
+ }
819
+ var SUBJECT_CATALOG_PAGE_BYTES = 28 * 1024;
820
+ var utf8Bytes = (s) => new TextEncoder().encode(s).length;
821
+ function splitSubjectTree(tree, maxBytes = SUBJECT_CATALOG_PAGE_BYTES) {
822
+ const size = (part) => utf8Bytes(JSON.stringify(JSON.stringify(part)));
823
+ const parts = [];
824
+ let current = {};
825
+ for (const [id, node] of Object.entries(tree)) {
826
+ if (size({ [id]: node }) > maxBytes) throw new RangeError(`splitSubjectTree: option ${id} alone exceeds ${maxBytes} bytes`);
827
+ const next = { ...current, [id]: node };
828
+ if (Object.keys(current).length > 0 && size(next) > maxBytes) {
829
+ parts.push(current);
830
+ current = { [id]: node };
831
+ } else current = next;
832
+ }
833
+ if (Object.keys(current).length > 0) parts.push(current);
834
+ return parts;
835
+ }
836
+
837
+ export { DEFAULT_FIT_TAG_PREFIX, MAX_SUBJECT_YEAR, MIN_SUBJECT_YEAR, PICKUP_STEPS, PRIVATE_MASK, SHIPPING_STEPS, SUBJECT_CATALOG_PAGE_BYTES, StorefrontError, availabilityLocale, availabilityMessages, availabilityView, cartLineProblemView, coversYear, declineMessage, declineMessageKey, defaultCodeForStatus, fitTag, fitTagsAny, formatAvailabilityMessage, formatVoucherExpiry, isMasked, isStorefrontError, isTestInvoice, isTestShipment, nextAction, orderProgress, orderShipments, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, productAvailability, reconcileSubjectValues, resolveAvailability, resolveSubject, safeHttpsUrl, safeTrackingUrl, schemaAvailability, shipmentsStatus, sortTrackingEvents, splitSubjectTree, subjectFields, subjectLevels, subjectOptions };
838
+ //# sourceMappingURL=chunk-5BJRZIS3.js.map
839
+ //# sourceMappingURL=chunk-5BJRZIS3.js.map