@asteby/storefront 0.6.0 → 0.7.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.cts CHANGED
@@ -75,6 +75,18 @@ type StorefrontClientOptions = {
75
75
  fetch?: FetchLike;
76
76
  /** Sent as `Accept-Language`. */
77
77
  locale?: string;
78
+ /**
79
+ * Public host of the storefront (`godsnutrition.com`), sent as `X-Storefront-Domain`. Platform (`pt_`)
80
+ * tokens need it to pick the organization; with an org-scoped token it only scopes the market.
81
+ */
82
+ domain?: string;
83
+ /**
84
+ * Market the request is for (`mx`, `us`), sent as `<domain>#<market>` in `X-Storefront-Domain`. It lets ONE
85
+ * public domain serve several markets (the storefront routes by path prefix, e.g. `/us`): OPS honors it
86
+ * when the organization has that market active and otherwise falls back to the domain's own market.
87
+ * Ignored without `domain`.
88
+ */
89
+ market?: string;
78
90
  /** Extra headers on every request. */
79
91
  headers?: Record<string, string>;
80
92
  /**
@@ -285,6 +297,8 @@ declare function createStorefrontClient(config: StorefrontClientOptions): {
285
297
  };
286
298
  };
287
299
  type StorefrontClient = ReturnType<typeof createStorefrontClient>;
300
+ /** `X-Storefront-Domain` value: the host, plus `#<market>` when a valid market code is given. */
301
+ declare function storefrontDomainHeader(domain: string, market?: string | null): string;
288
302
 
289
303
  type StorefrontErrorInit = {
290
304
  status: number;
@@ -573,4 +587,63 @@ type PromoProgressView = {
573
587
  */
574
588
  declare function promoProgressView(promotions: CartPromotions | null | undefined, locale?: string | null, overrides?: Partial<PromotionMessages> | null): PromoProgressView;
575
589
 
576
- export { Address, AddressInput, Article, type ArticleListParams, Availability, type AvailabilityLocale, type AvailabilityMessageKey, type AvailabilityMessages, AvailabilityStatus, type AvailabilityTone, type AvailabilityView, type AvailabilityViewOptions, CLIENT_IP_HEADER, Cart, CartBuyerInput, CartLineInput, CartLineProblem, type CartLineProblemView, CartLineUpdateInput, CartPromotions, CartRecommendations, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeclineCode, DeliveryInput, DeliveryOptions, type DiscountBarState, type DiscountBarView, type FetchLike, type FormatMoneyOptions, GeoPoint, InsufficientStockLine, InvoiceRequest, InvoiceResult, Location, LocationListParams, Menu, Money, NOTE_MAX_LENGTH, NearestLocation, Order, Page, type PaginationParams, PaymentAttemptResult, PaymentConfig, type PaymentErrorKind, type PaymentErrorView, type PaymentLocale, type PaymentMessageKey, type PaymentMessages, PaymentMethods, PaymentNextStep, type PaymentOutcome, type PaymentOutcomeKind, PaymentSubmitInput, PaymentVoucher, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, ProductVariant, type PromoProgressView, type PromotionLocale, type PromotionMessageKey, type PromotionMessages, PromotionReward, PromotionsList, type QueryParams, type QueryValue, RecommendationPlacement, type RequestOptions, type ResolveAvailabilityOptions, RestockSubscriptionInput, RestockSubscriptionResult, ReturnRequest, ReturnRequestInput, type ShippingBarState, type ShippingBarView, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, availabilityLocale, availabilityMessages, availabilityView, cartLineProblemView, clampPercent, createStorefrontClient, declineMessage, declineMessageKey, formatAvailabilityMessage, formatMoney, formatVoucherExpiry, generateIdempotencyKey, isStorefrontError, isTokenPublishable, normalizeClientIp, normalizeNote, parseMoney, parseProductFilters, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, productAvailability, productListQuery, promoProgressView, promotionMessages, resolveAvailability, rewardLabel, safeHttpsUrl, schemaAvailability, toMoney };
590
+ /**
591
+ * Detección de mercado por visita (país + idioma), pura y sin DOM: sirve en el servidor de cualquier tienda.
592
+ *
593
+ * Reglas (mejores prácticas de i18n para comercio):
594
+ * - El mercado canónico lo fija el DOMINIO (OPS liga cada dominio verificado a un mercado: org_domains.market_code).
595
+ * Esto no lo decide la geolocalización: así cada URL tiene un solo idioma/moneda (SEO, hreflang, caché de CDN).
596
+ * - La geolocalización solo SUGIERE (banner "¿Ver la tienda de Estados Unidos?"); nunca redirige sola, nunca a rastreadores
597
+ * y respeta la elección guardada del comprador.
598
+ * - Precios, impuestos y envío NO dependen de la IP: dependen del mercado del dominio y de la dirección de entrega.
599
+ * - El país sale de las cabeceras que pone el CDN/proxy (no se puede confiar en ellas si el servidor está expuesto
600
+ * directo a internet: solo se leen con `trustProxy`).
601
+ */
602
+ type MarketInfo = {
603
+ /** Código del mercado en OPS ("mx", "us"). */
604
+ code: string;
605
+ /** ISO 3166-1 alfa-2 a los que vende. */
606
+ countries: string[];
607
+ /** BCP 47 por defecto ("es-MX", "en-US"). */
608
+ locale: string;
609
+ /** ISO 4217 ("MXN", "USD"). */
610
+ currency: string;
611
+ /** Dominio público del mercado, si es distinto del actual ("godsnutrition.com"). */
612
+ host?: string;
613
+ };
614
+ /** Cabeceras de país de los CDN/proxies más comunes, en orden de preferencia. */
615
+ declare const COUNTRY_HEADERS: readonly ["cf-ipcountry", "cloudfront-viewer-country", "x-vercel-ip-country", "x-appengine-country", "x-country-code"];
616
+ /** País del visitante según el CDN. `null` si no viene, es desconocido (XX), Tor (T1) o no es ISO alfa-2. */
617
+ declare function visitorCountry(headers: Headers, { trustProxy }?: {
618
+ trustProxy?: boolean;
619
+ }): string | null;
620
+ /** Idiomas de Accept-Language ordenados por q (mayor primero), en minúsculas. */
621
+ declare function preferredLanguages(acceptLanguage: string | null | undefined): string[];
622
+ /** Mejor idioma entre los que ofrece la tienda: coincidencia exacta, luego por idioma base; si no, el de `fallback`. */
623
+ declare function negotiateLocale(acceptLanguage: string | null | undefined, available: string[], fallback: string): string;
624
+ type MarketSuggestion = {
625
+ market: MarketInfo;
626
+ /** Qué disparó la sugerencia. El país manda; el idioma solo si el CDN no dio país. */
627
+ reason: 'country' | 'language';
628
+ };
629
+ /**
630
+ * ¿Conviene sugerir otro mercado a esta visita? `null` si no (ya está en el correcto, rastreador, ya eligió, o no hay otro).
631
+ * `chosen` es el código que el comprador guardó al elegir a mano ("market" en cookie): si existe, no se vuelve a sugerir.
632
+ */
633
+ declare function suggestMarket({ headers, markets, current, chosen, trustProxy, }: {
634
+ headers: Headers;
635
+ markets: MarketInfo[];
636
+ current: string;
637
+ chosen?: string | null;
638
+ trustProxy?: boolean;
639
+ }): MarketSuggestion | null;
640
+ /**
641
+ * Enlaces `hreflang` para <head>: un alterno por mercado con dominio propio, más x-default (el mercado por defecto).
642
+ * `path` es la ruta con query actual ("/productos/x"). Es lo que permite a buscadores servir cada país en su idioma.
643
+ */
644
+ declare function hreflangLinks(markets: MarketInfo[], path: string, defaultMarket: string, protocol?: string): {
645
+ hrefLang: string;
646
+ href: string;
647
+ }[];
648
+
649
+ export { Address, AddressInput, Article, type ArticleListParams, Availability, type AvailabilityLocale, type AvailabilityMessageKey, type AvailabilityMessages, AvailabilityStatus, type AvailabilityTone, type AvailabilityView, type AvailabilityViewOptions, CLIENT_IP_HEADER, COUNTRY_HEADERS, Cart, CartBuyerInput, CartLineInput, CartLineProblem, type CartLineProblemView, CartLineUpdateInput, CartPromotions, CartRecommendations, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeclineCode, DeliveryInput, DeliveryOptions, type DiscountBarState, type DiscountBarView, type FetchLike, type FormatMoneyOptions, GeoPoint, InsufficientStockLine, InvoiceRequest, InvoiceResult, Location, LocationListParams, type MarketInfo, type MarketSuggestion, Menu, Money, NOTE_MAX_LENGTH, NearestLocation, Order, Page, type PaginationParams, PaymentAttemptResult, PaymentConfig, type PaymentErrorKind, type PaymentErrorView, type PaymentLocale, type PaymentMessageKey, type PaymentMessages, PaymentMethods, PaymentNextStep, type PaymentOutcome, type PaymentOutcomeKind, PaymentSubmitInput, PaymentVoucher, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, ProductVariant, type PromoProgressView, type PromotionLocale, type PromotionMessageKey, type PromotionMessages, PromotionReward, PromotionsList, type QueryParams, type QueryValue, RecommendationPlacement, type RequestOptions, type ResolveAvailabilityOptions, RestockSubscriptionInput, RestockSubscriptionResult, ReturnRequest, ReturnRequestInput, type ShippingBarState, type ShippingBarView, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, availabilityLocale, availabilityMessages, availabilityView, cartLineProblemView, clampPercent, createStorefrontClient, declineMessage, declineMessageKey, formatAvailabilityMessage, formatMoney, formatVoucherExpiry, generateIdempotencyKey, hreflangLinks, isStorefrontError, isTokenPublishable, negotiateLocale, normalizeClientIp, normalizeNote, parseMoney, parseProductFilters, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, preferredLanguages, productAvailability, productListQuery, promoProgressView, promotionMessages, resolveAvailability, rewardLabel, safeHttpsUrl, schemaAvailability, storefrontDomainHeader, suggestMarket, toMoney, visitorCountry };
package/dist/index.d.ts CHANGED
@@ -75,6 +75,18 @@ type StorefrontClientOptions = {
75
75
  fetch?: FetchLike;
76
76
  /** Sent as `Accept-Language`. */
77
77
  locale?: string;
78
+ /**
79
+ * Public host of the storefront (`godsnutrition.com`), sent as `X-Storefront-Domain`. Platform (`pt_`)
80
+ * tokens need it to pick the organization; with an org-scoped token it only scopes the market.
81
+ */
82
+ domain?: string;
83
+ /**
84
+ * Market the request is for (`mx`, `us`), sent as `<domain>#<market>` in `X-Storefront-Domain`. It lets ONE
85
+ * public domain serve several markets (the storefront routes by path prefix, e.g. `/us`): OPS honors it
86
+ * when the organization has that market active and otherwise falls back to the domain's own market.
87
+ * Ignored without `domain`.
88
+ */
89
+ market?: string;
78
90
  /** Extra headers on every request. */
79
91
  headers?: Record<string, string>;
80
92
  /**
@@ -285,6 +297,8 @@ declare function createStorefrontClient(config: StorefrontClientOptions): {
285
297
  };
286
298
  };
287
299
  type StorefrontClient = ReturnType<typeof createStorefrontClient>;
300
+ /** `X-Storefront-Domain` value: the host, plus `#<market>` when a valid market code is given. */
301
+ declare function storefrontDomainHeader(domain: string, market?: string | null): string;
288
302
 
289
303
  type StorefrontErrorInit = {
290
304
  status: number;
@@ -573,4 +587,63 @@ type PromoProgressView = {
573
587
  */
574
588
  declare function promoProgressView(promotions: CartPromotions | null | undefined, locale?: string | null, overrides?: Partial<PromotionMessages> | null): PromoProgressView;
575
589
 
576
- export { Address, AddressInput, Article, type ArticleListParams, Availability, type AvailabilityLocale, type AvailabilityMessageKey, type AvailabilityMessages, AvailabilityStatus, type AvailabilityTone, type AvailabilityView, type AvailabilityViewOptions, CLIENT_IP_HEADER, Cart, CartBuyerInput, CartLineInput, CartLineProblem, type CartLineProblemView, CartLineUpdateInput, CartPromotions, CartRecommendations, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeclineCode, DeliveryInput, DeliveryOptions, type DiscountBarState, type DiscountBarView, type FetchLike, type FormatMoneyOptions, GeoPoint, InsufficientStockLine, InvoiceRequest, InvoiceResult, Location, LocationListParams, Menu, Money, NOTE_MAX_LENGTH, NearestLocation, Order, Page, type PaginationParams, PaymentAttemptResult, PaymentConfig, type PaymentErrorKind, type PaymentErrorView, type PaymentLocale, type PaymentMessageKey, type PaymentMessages, PaymentMethods, PaymentNextStep, type PaymentOutcome, type PaymentOutcomeKind, PaymentSubmitInput, PaymentVoucher, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, ProductVariant, type PromoProgressView, type PromotionLocale, type PromotionMessageKey, type PromotionMessages, PromotionReward, PromotionsList, type QueryParams, type QueryValue, RecommendationPlacement, type RequestOptions, type ResolveAvailabilityOptions, RestockSubscriptionInput, RestockSubscriptionResult, ReturnRequest, ReturnRequestInput, type ShippingBarState, type ShippingBarView, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, availabilityLocale, availabilityMessages, availabilityView, cartLineProblemView, clampPercent, createStorefrontClient, declineMessage, declineMessageKey, formatAvailabilityMessage, formatMoney, formatVoucherExpiry, generateIdempotencyKey, isStorefrontError, isTokenPublishable, normalizeClientIp, normalizeNote, parseMoney, parseProductFilters, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, productAvailability, productListQuery, promoProgressView, promotionMessages, resolveAvailability, rewardLabel, safeHttpsUrl, schemaAvailability, toMoney };
590
+ /**
591
+ * Detección de mercado por visita (país + idioma), pura y sin DOM: sirve en el servidor de cualquier tienda.
592
+ *
593
+ * Reglas (mejores prácticas de i18n para comercio):
594
+ * - El mercado canónico lo fija el DOMINIO (OPS liga cada dominio verificado a un mercado: org_domains.market_code).
595
+ * Esto no lo decide la geolocalización: así cada URL tiene un solo idioma/moneda (SEO, hreflang, caché de CDN).
596
+ * - La geolocalización solo SUGIERE (banner "¿Ver la tienda de Estados Unidos?"); nunca redirige sola, nunca a rastreadores
597
+ * y respeta la elección guardada del comprador.
598
+ * - Precios, impuestos y envío NO dependen de la IP: dependen del mercado del dominio y de la dirección de entrega.
599
+ * - El país sale de las cabeceras que pone el CDN/proxy (no se puede confiar en ellas si el servidor está expuesto
600
+ * directo a internet: solo se leen con `trustProxy`).
601
+ */
602
+ type MarketInfo = {
603
+ /** Código del mercado en OPS ("mx", "us"). */
604
+ code: string;
605
+ /** ISO 3166-1 alfa-2 a los que vende. */
606
+ countries: string[];
607
+ /** BCP 47 por defecto ("es-MX", "en-US"). */
608
+ locale: string;
609
+ /** ISO 4217 ("MXN", "USD"). */
610
+ currency: string;
611
+ /** Dominio público del mercado, si es distinto del actual ("godsnutrition.com"). */
612
+ host?: string;
613
+ };
614
+ /** Cabeceras de país de los CDN/proxies más comunes, en orden de preferencia. */
615
+ declare const COUNTRY_HEADERS: readonly ["cf-ipcountry", "cloudfront-viewer-country", "x-vercel-ip-country", "x-appengine-country", "x-country-code"];
616
+ /** País del visitante según el CDN. `null` si no viene, es desconocido (XX), Tor (T1) o no es ISO alfa-2. */
617
+ declare function visitorCountry(headers: Headers, { trustProxy }?: {
618
+ trustProxy?: boolean;
619
+ }): string | null;
620
+ /** Idiomas de Accept-Language ordenados por q (mayor primero), en minúsculas. */
621
+ declare function preferredLanguages(acceptLanguage: string | null | undefined): string[];
622
+ /** Mejor idioma entre los que ofrece la tienda: coincidencia exacta, luego por idioma base; si no, el de `fallback`. */
623
+ declare function negotiateLocale(acceptLanguage: string | null | undefined, available: string[], fallback: string): string;
624
+ type MarketSuggestion = {
625
+ market: MarketInfo;
626
+ /** Qué disparó la sugerencia. El país manda; el idioma solo si el CDN no dio país. */
627
+ reason: 'country' | 'language';
628
+ };
629
+ /**
630
+ * ¿Conviene sugerir otro mercado a esta visita? `null` si no (ya está en el correcto, rastreador, ya eligió, o no hay otro).
631
+ * `chosen` es el código que el comprador guardó al elegir a mano ("market" en cookie): si existe, no se vuelve a sugerir.
632
+ */
633
+ declare function suggestMarket({ headers, markets, current, chosen, trustProxy, }: {
634
+ headers: Headers;
635
+ markets: MarketInfo[];
636
+ current: string;
637
+ chosen?: string | null;
638
+ trustProxy?: boolean;
639
+ }): MarketSuggestion | null;
640
+ /**
641
+ * Enlaces `hreflang` para <head>: un alterno por mercado con dominio propio, más x-default (el mercado por defecto).
642
+ * `path` es la ruta con query actual ("/productos/x"). Es lo que permite a buscadores servir cada país en su idioma.
643
+ */
644
+ declare function hreflangLinks(markets: MarketInfo[], path: string, defaultMarket: string, protocol?: string): {
645
+ hrefLang: string;
646
+ href: string;
647
+ }[];
648
+
649
+ export { Address, AddressInput, Article, type ArticleListParams, Availability, type AvailabilityLocale, type AvailabilityMessageKey, type AvailabilityMessages, AvailabilityStatus, type AvailabilityTone, type AvailabilityView, type AvailabilityViewOptions, CLIENT_IP_HEADER, COUNTRY_HEADERS, Cart, CartBuyerInput, CartLineInput, CartLineProblem, type CartLineProblemView, CartLineUpdateInput, CartPromotions, CartRecommendations, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeclineCode, DeliveryInput, DeliveryOptions, type DiscountBarState, type DiscountBarView, type FetchLike, type FormatMoneyOptions, GeoPoint, InsufficientStockLine, InvoiceRequest, InvoiceResult, Location, LocationListParams, type MarketInfo, type MarketSuggestion, Menu, Money, NOTE_MAX_LENGTH, NearestLocation, Order, Page, type PaginationParams, PaymentAttemptResult, PaymentConfig, type PaymentErrorKind, type PaymentErrorView, type PaymentLocale, type PaymentMessageKey, type PaymentMessages, PaymentMethods, PaymentNextStep, type PaymentOutcome, type PaymentOutcomeKind, PaymentSubmitInput, PaymentVoucher, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, ProductVariant, type PromoProgressView, type PromotionLocale, type PromotionMessageKey, type PromotionMessages, PromotionReward, PromotionsList, type QueryParams, type QueryValue, RecommendationPlacement, type RequestOptions, type ResolveAvailabilityOptions, RestockSubscriptionInput, RestockSubscriptionResult, ReturnRequest, ReturnRequestInput, type ShippingBarState, type ShippingBarView, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, availabilityLocale, availabilityMessages, availabilityView, cartLineProblemView, clampPercent, createStorefrontClient, declineMessage, declineMessageKey, formatAvailabilityMessage, formatMoney, formatVoucherExpiry, generateIdempotencyKey, hreflangLinks, isStorefrontError, isTokenPublishable, negotiateLocale, normalizeClientIp, normalizeNote, parseMoney, parseProductFilters, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, preferredLanguages, productAvailability, productListQuery, promoProgressView, promotionMessages, resolveAvailability, rewardLabel, safeHttpsUrl, schemaAvailability, storefrontDomainHeader, suggestMarket, toMoney, visitorCountry };
package/dist/index.js CHANGED
@@ -203,6 +203,7 @@ function createStorefrontClient(config) {
203
203
  headers.set("Accept", "application/json");
204
204
  headers.set("Authorization", `Bearer ${config.token}`);
205
205
  if (config.locale) headers.set("Accept-Language", config.locale);
206
+ if (config.domain) headers.set("X-Storefront-Domain", storefrontDomainHeader(config.domain, config.market));
206
207
  const customerToken = options.customerToken !== void 0 ? options.customerToken : config.customerToken;
207
208
  if (customerToken) headers.set("X-Customer-Token", customerToken);
208
209
  const clientIp = options.clientIp !== void 0 ? options.clientIp : config.clientIp;
@@ -405,6 +406,11 @@ function createStorefrontClient(config) {
405
406
  };
406
407
  return client;
407
408
  }
409
+ function storefrontDomainHeader(domain, market) {
410
+ const host = domain.trim();
411
+ const code = (market ?? "").trim().toLowerCase();
412
+ return code && /^[a-z0-9_-]{1,32}$/.test(code) ? `${host}#${code}` : host;
413
+ }
408
414
 
409
415
  // src/payments.ts
410
416
  var ES = {
@@ -826,6 +832,65 @@ function promoProgressView(promotions, locale, overrides) {
826
832
  return { discount, shipping };
827
833
  }
828
834
 
829
- export { CLIENT_IP_HEADER, NOTE_MAX_LENGTH, StorefrontError, appendParams, clampPercent, createStorefrontClient, declineMessage, declineMessageKey, formatMoney, formatVoucherExpiry, generateIdempotencyKey, isStorefrontError, isTokenPublishable, normalizeClientIp, normalizeNote, parseMoney, parseProductFilters, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, productListQuery, promoProgressView, promotionMessages, rewardLabel, safeHttpsUrl, toMoney };
835
+ // src/market.ts
836
+ var COUNTRY_HEADERS = ["cf-ipcountry", "cloudfront-viewer-country", "x-vercel-ip-country", "x-appengine-country", "x-country-code"];
837
+ var BOT = /bot|crawl|spider|slurp|bing|facebookexternalhit|embedly|preview|lighthouse|headless|curl|wget/i;
838
+ function visitorCountry(headers, { trustProxy = true } = {}) {
839
+ if (!trustProxy) return null;
840
+ for (const name of COUNTRY_HEADERS) {
841
+ const value = headers.get(name)?.trim().toUpperCase();
842
+ if (value && /^[A-Z]{2}$/.test(value) && value !== "XX" && value !== "T1") return value;
843
+ }
844
+ return null;
845
+ }
846
+ function preferredLanguages(acceptLanguage) {
847
+ if (!acceptLanguage) return [];
848
+ return acceptLanguage.split(",").map((part, index) => {
849
+ const [tag = "", ...params] = part.trim().split(";");
850
+ const q = Number(params.find((p) => p.trim().startsWith("q="))?.split("=")[1] ?? 1);
851
+ return { tag: tag.trim().toLowerCase(), q: Number.isFinite(q) ? q : 0, index };
852
+ }).filter((l) => l.tag && l.tag !== "*" && l.q > 0).sort((a, b) => b.q - a.q || a.index - b.index).map((l) => l.tag);
853
+ }
854
+ function negotiateLocale(acceptLanguage, available, fallback) {
855
+ const lowered = new Map(available.map((l) => [l.toLowerCase(), l]));
856
+ for (const wanted of preferredLanguages(acceptLanguage)) {
857
+ const exact = lowered.get(wanted);
858
+ if (exact) return exact;
859
+ const base = wanted.split("-")[0];
860
+ const sameBase = available.find((l) => l.toLowerCase().split("-")[0] === base);
861
+ if (sameBase) return sameBase;
862
+ }
863
+ return fallback;
864
+ }
865
+ function suggestMarket({
866
+ headers,
867
+ markets,
868
+ current,
869
+ chosen,
870
+ trustProxy = true
871
+ }) {
872
+ if (chosen || markets.length < 2) return null;
873
+ if (BOT.test(headers.get("user-agent") ?? "")) return null;
874
+ const country = visitorCountry(headers, { trustProxy });
875
+ if (country) {
876
+ const target = markets.find((m) => m.countries.includes(country));
877
+ return target && target.code !== current ? { market: target, reason: "country" } : null;
878
+ }
879
+ for (const tag of preferredLanguages(headers.get("accept-language"))) {
880
+ const region = tag.split("-")[1]?.toUpperCase();
881
+ if (!region) continue;
882
+ const target = markets.find((m) => m.countries.includes(region));
883
+ if (target) return target.code !== current ? { market: target, reason: "language" } : null;
884
+ }
885
+ return null;
886
+ }
887
+ function hreflangLinks(markets, path, defaultMarket, protocol = "https") {
888
+ const links = markets.filter((m) => m.host).map((m) => ({ hrefLang: m.locale, href: `${protocol}://${m.host}${path}` }));
889
+ const fallback = markets.find((m) => m.code === defaultMarket && m.host);
890
+ if (fallback) links.push({ hrefLang: "x-default", href: `${protocol}://${fallback.host}${path}` });
891
+ return links;
892
+ }
893
+
894
+ export { CLIENT_IP_HEADER, COUNTRY_HEADERS, NOTE_MAX_LENGTH, StorefrontError, appendParams, clampPercent, createStorefrontClient, declineMessage, declineMessageKey, formatMoney, formatVoucherExpiry, generateIdempotencyKey, hreflangLinks, isStorefrontError, isTokenPublishable, negotiateLocale, normalizeClientIp, normalizeNote, parseMoney, parseProductFilters, paymentErrorView, paymentIdempotencyKey, paymentMessages, paymentOutcome, preferredLanguages, productListQuery, promoProgressView, promotionMessages, rewardLabel, safeHttpsUrl, storefrontDomainHeader, suggestMarket, toMoney, visitorCountry };
830
895
  //# sourceMappingURL=index.js.map
831
896
  //# sourceMappingURL=index.js.map