@01.software/sdk 0.38.0 → 0.39.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.
Files changed (56) hide show
  1. package/README.md +117 -34
  2. package/dist/analytics/react.cjs.map +1 -1
  3. package/dist/analytics/react.js.map +1 -1
  4. package/dist/analytics.cjs.map +1 -1
  5. package/dist/analytics.js.map +1 -1
  6. package/dist/client.cjs +1219 -5
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +5 -4
  9. package/dist/client.d.ts +5 -4
  10. package/dist/client.js +1219 -5
  11. package/dist/client.js.map +1 -1
  12. package/dist/{collection-client-BroIWHY1.d.ts → collection-client-CaMgs5KE.d.ts} +14 -8
  13. package/dist/{collection-client-B0J9wMNE.d.cts → collection-client-DVfB0Em1.d.cts} +14 -8
  14. package/dist/errors.cjs +4 -1
  15. package/dist/errors.cjs.map +1 -1
  16. package/dist/errors.js +4 -1
  17. package/dist/errors.js.map +1 -1
  18. package/dist/index.cjs +2787 -2612
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +2787 -2612
  23. package/dist/index.js.map +1 -1
  24. package/dist/query.cjs +241 -58
  25. package/dist/query.cjs.map +1 -1
  26. package/dist/query.d.cts +148 -23
  27. package/dist/query.d.ts +148 -23
  28. package/dist/query.js +241 -58
  29. package/dist/query.js.map +1 -1
  30. package/dist/realtime.cjs +5 -1
  31. package/dist/realtime.cjs.map +1 -1
  32. package/dist/realtime.js +5 -1
  33. package/dist/realtime.js.map +1 -1
  34. package/dist/server.cjs +1149 -1
  35. package/dist/server.cjs.map +1 -1
  36. package/dist/server.d.cts +4 -4
  37. package/dist/server.d.ts +4 -4
  38. package/dist/server.js +1149 -1
  39. package/dist/server.js.map +1 -1
  40. package/dist/storefront-cache.cjs +144 -0
  41. package/dist/storefront-cache.cjs.map +1 -0
  42. package/dist/storefront-cache.d.cts +24 -0
  43. package/dist/storefront-cache.d.ts +24 -0
  44. package/dist/storefront-cache.js +121 -0
  45. package/dist/storefront-cache.js.map +1 -0
  46. package/dist/{types-CIGscmus.d.cts → types-BQo7UdI9.d.cts} +181 -35
  47. package/dist/{types-D2xYdz4P.d.ts → types-CVf8sCZ-.d.ts} +181 -35
  48. package/dist/ui/canvas/server.cjs +5 -1
  49. package/dist/ui/canvas/server.cjs.map +1 -1
  50. package/dist/ui/canvas/server.js +5 -1
  51. package/dist/ui/canvas/server.js.map +1 -1
  52. package/dist/ui/canvas.cjs +5 -1
  53. package/dist/ui/canvas.cjs.map +1 -1
  54. package/dist/ui/canvas.js +5 -1
  55. package/dist/ui/canvas.js.map +1 -1
  56. package/package.json +13 -3
package/dist/client.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var client_exports = {};
22
22
  __export(client_exports, {
23
23
  Client: () => Client,
24
+ ContentClient: () => ContentClient,
24
25
  createClient: () => createClient
25
26
  });
26
27
  module.exports = __toCommonJS(client_exports);
@@ -627,12 +628,35 @@ function productDetailCatalogQuery(params) {
627
628
  }
628
629
  return `/api/products/detail/catalog?${search}`;
629
630
  }
630
- function listingGroupsQuery(params) {
631
- return `/api/products/listing-groups?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
632
- }
633
631
  function listingGroupsCatalogQuery(params) {
634
632
  return `/api/products/listing-groups/catalog?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
635
633
  }
634
+ function appendListingGroupsQuerySearchParams(search, options) {
635
+ if (options?.page != null) search.set("page", String(options.page));
636
+ if (options?.limit != null) search.set("limit", String(options.limit));
637
+ if (options?.sort != null) {
638
+ const sort = options.sort;
639
+ search.set(
640
+ "sort",
641
+ Array.isArray(sort) ? sort.join(",") : sort
642
+ );
643
+ }
644
+ if (options?.where != null) {
645
+ search.set("whereJson", JSON.stringify(options.where));
646
+ }
647
+ }
648
+ function listingGroupsQueryUrl(options) {
649
+ const search = new URLSearchParams();
650
+ appendListingGroupsQuerySearchParams(search, options);
651
+ const query = search.toString();
652
+ return `/api/products/listing-groups/query${query ? `?${query}` : ""}`;
653
+ }
654
+ function listingGroupsQueryCatalogUrl(options) {
655
+ const search = new URLSearchParams();
656
+ appendListingGroupsQuerySearchParams(search, options);
657
+ const query = search.toString();
658
+ return `/api/products/listing-groups/query/catalog${query ? `?${query}` : ""}`;
659
+ }
636
660
  function stockSnapshotQuery(params) {
637
661
  return `/api/products/stock?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
638
662
  }
@@ -772,7 +796,20 @@ var HttpClient = class {
772
796
  };
773
797
 
774
798
  // src/core/collection/query-builder.ts
799
+ var PUBLIC_READ_DEPTH_ERROR = "Publishable collection reads require depth: 0. Use a shaped commerce endpoint or server client for populated data.";
800
+ var PUBLIC_READ_JOINS_ERROR = "Publishable collection reads require joins: false. Use a shaped commerce endpoint or server client for joined data.";
801
+ var PUBLIC_READ_POPULATE_ERROR = "Publishable collection reads do not support populate. Use a shaped commerce endpoint or server client for populated data.";
775
802
  function withDefaultPublicReadOptions(options) {
803
+ const rawOptions = options;
804
+ if (rawOptions?.depth !== void 0 && rawOptions.depth !== 0) {
805
+ throw createValidationError(PUBLIC_READ_DEPTH_ERROR);
806
+ }
807
+ if (rawOptions?.joins !== void 0 && rawOptions.joins !== false) {
808
+ throw createValidationError(PUBLIC_READ_JOINS_ERROR);
809
+ }
810
+ if (rawOptions?.populate !== void 0) {
811
+ throw createValidationError(PUBLIC_READ_POPULATE_ERROR);
812
+ }
776
813
  return {
777
814
  ...options,
778
815
  depth: options?.depth ?? 0,
@@ -799,7 +836,7 @@ var ReadOnlyCollectionQueryBuilder = class {
799
836
  async count(options) {
800
837
  return this.api.requestCount(
801
838
  `/api/${String(this.collection)}/count`,
802
- options
839
+ withDefaultPublicReadOptions(options)
803
840
  );
804
841
  }
805
842
  };
@@ -1235,6 +1272,55 @@ var CommunityClient = class extends CustomerScopedApi {
1235
1272
  }
1236
1273
  };
1237
1274
 
1275
+ // src/core/content/content-client.ts
1276
+ var PublicContentApi = class extends CustomerScopedApi {
1277
+ get(endpoint) {
1278
+ return this.request(endpoint, { method: "GET" });
1279
+ }
1280
+ };
1281
+ function appendCommonParams(search, options, defaultSort) {
1282
+ search.set("sort", options?.sort ?? defaultSort);
1283
+ if (options?.page != null) search.set("page", String(options.page));
1284
+ if (options?.limit != null) search.set("limit", String(options.limit));
1285
+ }
1286
+ function linksListPath(options) {
1287
+ const search = new URLSearchParams();
1288
+ appendCommonParams(search, options, "-publishedAt,-createdAt");
1289
+ if (options?.categoryId) search.set("categoryId", options.categoryId);
1290
+ if (options?.categorySlug) {
1291
+ search.set("categorySlug", options.categorySlug);
1292
+ }
1293
+ if (options?.tagId) search.set("tagId", options.tagId);
1294
+ if (options?.tagSlug) search.set("tagSlug", options.tagSlug);
1295
+ if (options?.featured != null) search.set("featured", String(options.featured));
1296
+ return `/api/links/storefront?${search}`;
1297
+ }
1298
+ function galleryItemsListPath(options) {
1299
+ const search = new URLSearchParams();
1300
+ appendCommonParams(search, options, "manual");
1301
+ if (options?.galleryId) search.set("galleryId", options.galleryId);
1302
+ if (options?.gallerySlug) search.set("gallerySlug", options.gallerySlug);
1303
+ return `/api/gallery-items/storefront?${search}`;
1304
+ }
1305
+ var ContentClient = class {
1306
+ constructor(options) {
1307
+ const api = new PublicContentApi("ContentClient", {
1308
+ publishableKey: options.publishableKey,
1309
+ apiUrl: options.apiUrl,
1310
+ onRequestId: options.onRequestId,
1311
+ requiresCredential: false
1312
+ });
1313
+ this.links = {
1314
+ list: (params) => api.get(linksListPath(params))
1315
+ };
1316
+ this.galleryItems = {
1317
+ list: (params) => api.get(
1318
+ galleryItemsListPath(params)
1319
+ )
1320
+ };
1321
+ }
1322
+ };
1323
+
1238
1324
  // src/core/customer/customer-auth.ts
1239
1325
  var DEFAULT_TIMEOUT2 = 15e3;
1240
1326
  function safeGetItem(key) {
@@ -1490,6 +1576,1061 @@ var CartApi = class extends CustomerScopedApi {
1490
1576
  }
1491
1577
  };
1492
1578
 
1579
+ // src/utils/product-selection-media.ts
1580
+ function selectedSwatchMediaItemId(swatch) {
1581
+ if (!swatch || swatch.type !== "media") return null;
1582
+ const id = swatch.mediaItemId;
1583
+ if (id == null || id === "") return null;
1584
+ return String(id);
1585
+ }
1586
+ function getMediaId(value) {
1587
+ if (typeof value === "string" || typeof value === "number") {
1588
+ return String(value);
1589
+ }
1590
+ if (typeof value === "object" && value !== null && "id" in value) {
1591
+ const id = value.id;
1592
+ if (typeof id === "string" || typeof id === "number") return String(id);
1593
+ }
1594
+ return null;
1595
+ }
1596
+ function toPointerId(value) {
1597
+ return getMediaId(value);
1598
+ }
1599
+ function mediaArray(value) {
1600
+ if (!Array.isArray(value)) return [];
1601
+ return value.filter((entry) => entry != null);
1602
+ }
1603
+ function uniqueWithPrimaryFirst(primary, items) {
1604
+ const unique = /* @__PURE__ */ new Map();
1605
+ for (const item of items) {
1606
+ const id = getMediaId(item);
1607
+ const key = id ?? `inline:${unique.size}`;
1608
+ if (!unique.has(key)) unique.set(key, item);
1609
+ }
1610
+ if (primary) {
1611
+ const primaryId = getMediaId(primary);
1612
+ const prefixed = /* @__PURE__ */ new Map();
1613
+ const primaryKey = primaryId ?? "inline:primary";
1614
+ prefixed.set(primaryKey, primary);
1615
+ for (const [key, value] of unique.entries()) {
1616
+ if (!prefixed.has(key)) prefixed.set(key, value);
1617
+ }
1618
+ return Array.from(prefixed.values());
1619
+ }
1620
+ return Array.from(unique.values());
1621
+ }
1622
+ function buildPoolById(pool) {
1623
+ const poolById = /* @__PURE__ */ new Map();
1624
+ for (const item of pool) {
1625
+ const id = getMediaId(item);
1626
+ if (id) poolById.set(id, item);
1627
+ }
1628
+ return poolById;
1629
+ }
1630
+ function resolveVariantImageItems(variant, poolById) {
1631
+ if (!variant || !Array.isArray(variant.images)) return [];
1632
+ const resolved = [];
1633
+ for (const entry of variant.images) {
1634
+ if (entry == null) continue;
1635
+ if (typeof entry === "string" || typeof entry === "number") {
1636
+ const pooled = poolById.get(String(entry));
1637
+ if (pooled) resolved.push(pooled);
1638
+ continue;
1639
+ }
1640
+ const mediaId = getMediaId(entry);
1641
+ if (mediaId) {
1642
+ const pooled = poolById.get(mediaId);
1643
+ if (pooled) {
1644
+ resolved.push(pooled);
1645
+ }
1646
+ continue;
1647
+ }
1648
+ }
1649
+ return resolved;
1650
+ }
1651
+ function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
1652
+ if (!selectedOptionValues?.length) return null;
1653
+ for (const optionValue of selectedOptionValues) {
1654
+ const swatch = optionValue?.swatch;
1655
+ const swatchPointer = selectedSwatchMediaItemId(swatch);
1656
+ if (swatchPointer) {
1657
+ const pooled = poolById.get(swatchPointer);
1658
+ if (pooled) return pooled;
1659
+ }
1660
+ const inline = swatch?.inlineMedia;
1661
+ if (inline != null && typeof inline === "object") {
1662
+ return inline;
1663
+ }
1664
+ }
1665
+ return null;
1666
+ }
1667
+ function resolveProductSelectionMedia(input) {
1668
+ const pool = mediaArray(input.productMediaPool);
1669
+ const poolById = buildPoolById(pool);
1670
+ const selectedVariantImages = resolveVariantImageItems(
1671
+ input.selectedVariant,
1672
+ poolById
1673
+ );
1674
+ if (selectedVariantImages.length > 0) {
1675
+ const primaryImage = selectedVariantImages[0] ?? null;
1676
+ return {
1677
+ primaryImage,
1678
+ images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
1679
+ source: "variant_media_selected"
1680
+ };
1681
+ }
1682
+ if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
1683
+ const mergedMatchingImages = [];
1684
+ for (const matchingVariant of input.matchingVariants ?? []) {
1685
+ mergedMatchingImages.push(
1686
+ ...resolveVariantImageItems(matchingVariant, poolById)
1687
+ );
1688
+ }
1689
+ if (mergedMatchingImages.length > 0) {
1690
+ const primaryImage = mergedMatchingImages[0] ?? null;
1691
+ return {
1692
+ primaryImage,
1693
+ images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
1694
+ source: "variant_media_matching"
1695
+ };
1696
+ }
1697
+ }
1698
+ const optionSwatchPrimary = resolveOptionSwatchPrimary(
1699
+ input.selectedOptionValues,
1700
+ poolById
1701
+ );
1702
+ if (optionSwatchPrimary) {
1703
+ return {
1704
+ primaryImage: optionSwatchPrimary,
1705
+ images: [optionSwatchPrimary],
1706
+ source: "option_swatch"
1707
+ };
1708
+ }
1709
+ return {
1710
+ primaryImage: null,
1711
+ images: [],
1712
+ source: "none"
1713
+ };
1714
+ }
1715
+ function resolveListingPrimaryImagePointer(input) {
1716
+ const pool = mediaArray(input.productMediaPool);
1717
+ const resolvedPointer = getMediaId(input.resolvedPrimary);
1718
+ if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
1719
+ return resolvedPointer;
1720
+ }
1721
+ const poolById = buildPoolById(pool);
1722
+ const listingPointer = getMediaId(input.listingPrimaryImage);
1723
+ if (listingPointer && poolById.has(listingPointer)) {
1724
+ return listingPointer;
1725
+ }
1726
+ const primaryPointer = toPointerId(input.productPrimaryMediaItemId);
1727
+ if (primaryPointer && poolById.has(primaryPointer)) return primaryPointer;
1728
+ const thumbnailPointer = getMediaId(input.productThumbnail);
1729
+ if (thumbnailPointer && poolById.has(thumbnailPointer)) {
1730
+ return thumbnailPointer;
1731
+ }
1732
+ if (pool.length > 0) {
1733
+ const firstPoolId = getMediaId(pool[0]);
1734
+ if (firstPoolId) return firstPoolId;
1735
+ }
1736
+ return null;
1737
+ }
1738
+
1739
+ // src/utils/ecommerce.ts
1740
+ var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
1741
+ function resolveProductSelectionUrlEmit(emit) {
1742
+ return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
1743
+ }
1744
+ function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
1745
+ const option = matrix.optionById.get(optionId);
1746
+ const value = matrix.valueById.get(valueId);
1747
+ if (!option?.slug || !value?.slug) return false;
1748
+ const slugMatches = option.values.filter(
1749
+ (candidate) => candidate.slug === value.slug
1750
+ );
1751
+ if (slugMatches.length !== 1) return false;
1752
+ params.append(`opt.${option.slug}`, value.slug);
1753
+ return true;
1754
+ }
1755
+ function appendCanonicalSelectionParam(params, optionId, valueId) {
1756
+ params.append(`opt.${optionId}`, valueId);
1757
+ }
1758
+ var ProductSelectionCodecError = class extends Error {
1759
+ constructor(message) {
1760
+ super(message);
1761
+ this.code = "ambiguous_product_selection_query";
1762
+ this.name = "ProductSelectionCodecError";
1763
+ }
1764
+ };
1765
+ function getRelationID(value) {
1766
+ if (typeof value === "string") return value;
1767
+ if (typeof value === "number") return String(value);
1768
+ if (value && typeof value === "object" && "id" in value) {
1769
+ const id = value.id;
1770
+ if (typeof id === "string") return id;
1771
+ if (typeof id === "number") return String(id);
1772
+ }
1773
+ return void 0;
1774
+ }
1775
+ function extractEntityId(value) {
1776
+ if (typeof value === "string") return value;
1777
+ if (typeof value === "number") return String(value);
1778
+ if (value && typeof value === "object" && "id" in value) {
1779
+ if (typeof value.id === "string") return value.id;
1780
+ if (typeof value.id === "number") return String(value.id);
1781
+ }
1782
+ return null;
1783
+ }
1784
+ function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
1785
+ return resolveListingPrimaryImagePointer({
1786
+ productMediaPool: product?.images ?? [],
1787
+ productPrimaryMediaItemId: getRelationID(
1788
+ product?.primaryMediaItemId ?? null
1789
+ ),
1790
+ productThumbnail: product?.thumbnail ?? null,
1791
+ listingPrimaryImage: product?.listing?.primaryImage ?? null,
1792
+ resolvedPrimary,
1793
+ resolvedSource
1794
+ });
1795
+ }
1796
+ function normalizeProductOptionValueSwatch(swatch) {
1797
+ if (swatch == null) return null;
1798
+ if (typeof swatch !== "object") return null;
1799
+ const raw = swatch;
1800
+ const mediaItemId = extractEntityId(raw.mediaItemId);
1801
+ const color = typeof raw.color === "string" ? raw.color.trim() : "";
1802
+ const hasColor = color.length > 0;
1803
+ if (raw.type === "media" || mediaItemId && raw.type !== "color") {
1804
+ if (!mediaItemId || hasColor) return null;
1805
+ return {
1806
+ type: "media",
1807
+ mediaItemId,
1808
+ color: null
1809
+ };
1810
+ }
1811
+ if (raw.type === "color" || hasColor) {
1812
+ if (mediaItemId || !hasColor) return null;
1813
+ return { type: "color", color, mediaItemId: null };
1814
+ }
1815
+ return null;
1816
+ }
1817
+ function matrixOrder(index) {
1818
+ return String(index).padStart(6, "0");
1819
+ }
1820
+ function buildProductOptionMatrixFromDetail(detail) {
1821
+ const normalizedOptions = detail.options.map((option, optionIndex) => ({
1822
+ id: String(option.id),
1823
+ title: option.title || String(option.id),
1824
+ slug: option.slug,
1825
+ order: matrixOrder(optionIndex),
1826
+ values: option.values.map((value, valueIndex) => ({
1827
+ id: String(value.id),
1828
+ optionId: String(option.id),
1829
+ optionSlug: option.slug,
1830
+ label: value.value || value.slug || String(value.id),
1831
+ slug: value.slug,
1832
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
1833
+ order: matrixOrder(valueIndex)
1834
+ }))
1835
+ }));
1836
+ const optionById = new Map(
1837
+ normalizedOptions.map((option) => [option.id, option])
1838
+ );
1839
+ const optionBySlug = new Map(
1840
+ normalizedOptions.map((option) => [option.slug, option])
1841
+ );
1842
+ const valueById = /* @__PURE__ */ new Map();
1843
+ const valueToOptionId = /* @__PURE__ */ new Map();
1844
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
1845
+ for (const option of normalizedOptions) {
1846
+ for (const value of option.values) {
1847
+ valueById.set(value.id, value);
1848
+ valueToOptionId.set(value.id, option.id);
1849
+ valueToOptionSlug.set(value.id, option.slug);
1850
+ }
1851
+ }
1852
+ const optionIds = normalizedOptions.map((option) => option.id);
1853
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
1854
+ const normalizedVariants = detail.variants.map((variant) => {
1855
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
1856
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
1857
+ for (const rawValue of variant.optionValues) {
1858
+ const optionId = String(rawValue.optionId);
1859
+ const valueId = String(rawValue.valueId);
1860
+ const optionSlug = rawValue.optionSlug;
1861
+ if (!optionById.has(optionId)) continue;
1862
+ if (valueToOptionId.get(valueId) !== optionId) continue;
1863
+ if (optionValueByOptionId.has(optionId)) continue;
1864
+ optionValueByOptionId.set(optionId, valueId);
1865
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
1866
+ optionValueByOptionSlug.set(optionSlug, valueId);
1867
+ }
1868
+ }
1869
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
1870
+ return {
1871
+ id: String(variant.id),
1872
+ optionValueIds,
1873
+ optionValueByOptionId,
1874
+ optionValueByOptionSlug,
1875
+ source: variant
1876
+ };
1877
+ });
1878
+ return {
1879
+ options: normalizedOptions,
1880
+ optionIds,
1881
+ optionSlugs,
1882
+ optionById,
1883
+ optionBySlug,
1884
+ valueById,
1885
+ valueToOptionId,
1886
+ valueToOptionSlug,
1887
+ variants: normalizedVariants
1888
+ };
1889
+ }
1890
+ function getVariantSelection(matrix, variantId) {
1891
+ if (variantId == null) return void 0;
1892
+ const id = String(variantId);
1893
+ return matrix.variants.find((variant) => variant.id === id);
1894
+ }
1895
+ function hasExplicitSelection(selection) {
1896
+ return Boolean(
1897
+ selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
1898
+ );
1899
+ }
1900
+ function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
1901
+ if (valueId == null) return false;
1902
+ const normalizedValueId = String(valueId);
1903
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
1904
+ selectedByOptionId.set(optionId, normalizedValueId);
1905
+ return true;
1906
+ }
1907
+ function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
1908
+ if (!valueSlug) return false;
1909
+ const option = matrix.optionById.get(optionId);
1910
+ if (!option) return false;
1911
+ const values = option.values.filter(
1912
+ (candidate) => candidate.slug === valueSlug
1913
+ );
1914
+ if (values.length > 1) {
1915
+ throw new ProductSelectionCodecError(
1916
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
1917
+ );
1918
+ }
1919
+ const value = values[0];
1920
+ if (!value) return false;
1921
+ selectedByOptionId.set(optionId, value.id);
1922
+ return true;
1923
+ }
1924
+ function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
1925
+ if (!valueSlug) return false;
1926
+ const option = matrix.optionBySlug.get(optionSlug);
1927
+ if (!option) return false;
1928
+ const values = option.values.filter(
1929
+ (candidate) => candidate.slug === valueSlug
1930
+ );
1931
+ if (values.length > 1) {
1932
+ throw new ProductSelectionCodecError(
1933
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
1934
+ );
1935
+ }
1936
+ const value = values[0];
1937
+ if (!value) return false;
1938
+ selectedByOptionId.set(option.id, value.id);
1939
+ return true;
1940
+ }
1941
+ function toSearchParams(search) {
1942
+ if (!search) return new URLSearchParams();
1943
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
1944
+ if (search instanceof URL) return new URLSearchParams(search.searchParams);
1945
+ const trimmed = search.trim();
1946
+ if (!trimmed) return new URLSearchParams();
1947
+ try {
1948
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
1949
+ return new URL(trimmed).searchParams;
1950
+ }
1951
+ } catch {
1952
+ return new URLSearchParams();
1953
+ }
1954
+ return new URLSearchParams(
1955
+ trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
1956
+ );
1957
+ }
1958
+ function slugLike(value) {
1959
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1960
+ }
1961
+ function assertNoAmbiguousSelectionParams(matrix, params) {
1962
+ const knownSelectionKeys = /* @__PURE__ */ new Set();
1963
+ const hasVariantParam = params.has("variant");
1964
+ const hasOptionParams = Array.from(params.keys()).some(
1965
+ (key) => key.startsWith("opt.")
1966
+ );
1967
+ if (hasVariantParam && hasOptionParams) {
1968
+ throw new ProductSelectionCodecError(
1969
+ "Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
1970
+ );
1971
+ }
1972
+ for (const option of matrix.options) {
1973
+ knownSelectionKeys.add(slugLike(option.slug));
1974
+ knownSelectionKeys.add(slugLike(option.title));
1975
+ for (const value of option.values) {
1976
+ if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
1977
+ }
1978
+ }
1979
+ for (const [key, value] of params.entries()) {
1980
+ if (key.startsWith("opt.")) {
1981
+ const optionToken = key.slice(4);
1982
+ if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
1983
+ throw new ProductSelectionCodecError(
1984
+ `Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
1985
+ );
1986
+ }
1987
+ if (!value) {
1988
+ throw new ProductSelectionCodecError(
1989
+ `Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
1990
+ );
1991
+ }
1992
+ continue;
1993
+ }
1994
+ if (key === "variant") {
1995
+ if (!value) {
1996
+ throw new ProductSelectionCodecError(
1997
+ 'Product selection query parameter "variant" requires a variant ID.'
1998
+ );
1999
+ }
2000
+ if (!getVariantSelection(matrix, value)) {
2001
+ throw new ProductSelectionCodecError(
2002
+ `Unknown product selection variant "${value}".`
2003
+ );
2004
+ }
2005
+ continue;
2006
+ }
2007
+ const keyToken = slugLike(key);
2008
+ if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
2009
+ throw new ProductSelectionCodecError(
2010
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2011
+ );
2012
+ }
2013
+ }
2014
+ }
2015
+ function emitCompatibilityOptionIdParam(options, event) {
2016
+ try {
2017
+ if (options?.onCompatibilityOptionIdParam) {
2018
+ options.onCompatibilityOptionIdParam(event);
2019
+ return;
2020
+ }
2021
+ options?.onLegacyOptionIdParam?.(event);
2022
+ } catch {
2023
+ }
2024
+ }
2025
+ function assignSearchSelection(matrix, selectedByOptionId, search, options) {
2026
+ if (!search) return null;
2027
+ const params = toSearchParams(search);
2028
+ assertNoAmbiguousSelectionParams(matrix, params);
2029
+ const variantParam = params.get("variant");
2030
+ if (variantParam != null) {
2031
+ const variantSelection = getVariantSelection(matrix, variantParam);
2032
+ if (!variantSelection) {
2033
+ throw new ProductSelectionCodecError(
2034
+ `Unknown product selection variant "${variantParam}".`
2035
+ );
2036
+ }
2037
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2038
+ selectedByOptionId.set(optionId, valueId);
2039
+ }
2040
+ return variantSelection.id;
2041
+ }
2042
+ for (const [key, valueToken] of params.entries()) {
2043
+ if (!key.startsWith("opt.")) continue;
2044
+ const optionToken = key.slice(4);
2045
+ const optionById = matrix.optionById.get(optionToken);
2046
+ if (optionById) {
2047
+ if (assignSelectedValue(
2048
+ matrix,
2049
+ selectedByOptionId,
2050
+ optionById.id,
2051
+ valueToken
2052
+ )) {
2053
+ continue;
2054
+ }
2055
+ const before = selectedByOptionId.get(optionById.id);
2056
+ if (assignSelectedValueSlugByOptionId(
2057
+ matrix,
2058
+ selectedByOptionId,
2059
+ optionById.id,
2060
+ valueToken
2061
+ ) && selectedByOptionId.get(optionById.id) !== before) {
2062
+ emitCompatibilityOptionIdParam(options, {
2063
+ optionId: optionById.id,
2064
+ optionSlug: optionById.slug,
2065
+ valueSlug: valueToken,
2066
+ searchParam: key
2067
+ });
2068
+ continue;
2069
+ }
2070
+ throw new ProductSelectionCodecError(
2071
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2072
+ );
2073
+ }
2074
+ const optionBySlug = matrix.optionBySlug.get(optionToken);
2075
+ if (optionBySlug) {
2076
+ if (assignSelectedValueSlugByOptionSlug(
2077
+ matrix,
2078
+ selectedByOptionId,
2079
+ optionBySlug.slug,
2080
+ valueToken
2081
+ )) {
2082
+ continue;
2083
+ }
2084
+ if (matrix.valueById.has(valueToken)) {
2085
+ throw new ProductSelectionCodecError(
2086
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2087
+ );
2088
+ }
2089
+ throw new ProductSelectionCodecError(
2090
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
2091
+ );
2092
+ }
2093
+ }
2094
+ return null;
2095
+ }
2096
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
2097
+ const selectedByOptionId = /* @__PURE__ */ new Map();
2098
+ const variantSelection = getVariantSelection(matrix, selection.variantId);
2099
+ const variantId = variantSelection?.id ?? null;
2100
+ if (variantSelection) {
2101
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2102
+ selectedByOptionId.set(optionId, valueId);
2103
+ }
2104
+ }
2105
+ for (const rawValueId of selection.valueIds ?? []) {
2106
+ const valueId = getRelationID(rawValueId);
2107
+ if (!valueId) continue;
2108
+ const optionId = matrix.valueToOptionId.get(valueId);
2109
+ if (!optionId) continue;
2110
+ selectedByOptionId.set(optionId, valueId);
2111
+ }
2112
+ const searchVariantId = assignSearchSelection(
2113
+ matrix,
2114
+ selectedByOptionId,
2115
+ selection.search,
2116
+ options
2117
+ );
2118
+ for (const [rawOptionId, rawSelection] of Object.entries(
2119
+ selection.byOptionId ?? {}
2120
+ )) {
2121
+ const optionId = String(rawOptionId);
2122
+ if (!matrix.optionById.has(optionId)) continue;
2123
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2124
+ assignSelectedValue(
2125
+ matrix,
2126
+ selectedByOptionId,
2127
+ optionId,
2128
+ rawSelection.valueId
2129
+ );
2130
+ continue;
2131
+ }
2132
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2133
+ assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
2134
+ continue;
2135
+ }
2136
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2137
+ assignSelectedValueSlugByOptionId(
2138
+ matrix,
2139
+ selectedByOptionId,
2140
+ optionId,
2141
+ rawSelection.valueSlug
2142
+ );
2143
+ }
2144
+ }
2145
+ for (const [rawOptionSlug, rawSelection] of Object.entries(
2146
+ selection.byOptionSlug ?? {}
2147
+ )) {
2148
+ const optionSlug = String(rawOptionSlug);
2149
+ if (!matrix.optionBySlug.has(optionSlug)) continue;
2150
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2151
+ const option = matrix.optionBySlug.get(optionSlug);
2152
+ if (option) {
2153
+ assignSelectedValue(
2154
+ matrix,
2155
+ selectedByOptionId,
2156
+ option.id,
2157
+ rawSelection.valueId
2158
+ );
2159
+ }
2160
+ continue;
2161
+ }
2162
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2163
+ assignSelectedValueSlugByOptionSlug(
2164
+ matrix,
2165
+ selectedByOptionId,
2166
+ optionSlug,
2167
+ rawSelection.valueSlug
2168
+ );
2169
+ continue;
2170
+ }
2171
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2172
+ assignSelectedValueSlugByOptionSlug(
2173
+ matrix,
2174
+ selectedByOptionId,
2175
+ optionSlug,
2176
+ String(rawSelection)
2177
+ );
2178
+ }
2179
+ }
2180
+ const byOptionId = Object.fromEntries(
2181
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
2182
+ );
2183
+ const byOptionSlug = Object.fromEntries(
2184
+ matrix.options.map((option) => {
2185
+ const valueId = selectedByOptionId.get(option.id);
2186
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
2187
+ return [option.slug, value?.slug ?? void 0];
2188
+ }).filter((entry) => Boolean(entry[1]))
2189
+ );
2190
+ return {
2191
+ byOptionSlug,
2192
+ byOptionId,
2193
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
2194
+ variantId: searchVariantId ?? variantId
2195
+ };
2196
+ }
2197
+ function stringifyProductSelection(detail, selection = {}, options) {
2198
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2199
+ const normalized = normalizeProductSelectionFromMatrix(
2200
+ matrix,
2201
+ selection,
2202
+ options
2203
+ );
2204
+ const params = new URLSearchParams();
2205
+ if (hasExplicitSelection(selection)) {
2206
+ const matchingVariants = getMatchingVariantEntries(matrix, normalized);
2207
+ const exactVariant = getExactSelectedVariantEntry(
2208
+ matrix,
2209
+ normalized,
2210
+ matchingVariants
2211
+ );
2212
+ if (exactVariant) {
2213
+ params.set("variant", exactVariant.id);
2214
+ return params.toString();
2215
+ }
2216
+ }
2217
+ const emit = resolveProductSelectionUrlEmit(options?.emit);
2218
+ for (const optionId of matrix.optionIds) {
2219
+ const valueId = normalized.byOptionId[optionId];
2220
+ if (!valueId) continue;
2221
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2222
+ continue;
2223
+ }
2224
+ if (emit === "slug-compat") {
2225
+ if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
2226
+ continue;
2227
+ }
2228
+ }
2229
+ appendCanonicalSelectionParam(params, optionId, valueId);
2230
+ }
2231
+ return params.toString();
2232
+ }
2233
+ function selectedEntries(selection) {
2234
+ return Object.entries(selection.byOptionId);
2235
+ }
2236
+ function getMatchingVariantEntries(matrix, selection) {
2237
+ const entries = selectedEntries(selection);
2238
+ if (entries.length === 0) return matrix.variants;
2239
+ return matrix.variants.filter(
2240
+ (variant) => entries.every(
2241
+ ([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
2242
+ )
2243
+ );
2244
+ }
2245
+ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
2246
+ if (matrix.optionIds.length === 0) {
2247
+ return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
2248
+ }
2249
+ const allOptionsSelected = matrix.optionIds.every(
2250
+ (optionId) => Boolean(selection.byOptionId[optionId])
2251
+ );
2252
+ if (!allOptionsSelected) return null;
2253
+ return matchingVariants.find(
2254
+ (variant) => matrix.optionIds.every(
2255
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
2256
+ )
2257
+ ) ?? null;
2258
+ }
2259
+ function isPresentMedia(value) {
2260
+ return value != null;
2261
+ }
2262
+ function mediaArray2(values) {
2263
+ if (!Array.isArray(values)) return [];
2264
+ return values.filter(isPresentMedia);
2265
+ }
2266
+ function getProductHrefSlug(product) {
2267
+ if ("product" in product && product.product?.slug) {
2268
+ return product.product.slug;
2269
+ }
2270
+ if ("slug" in product && product.slug) return product.slug;
2271
+ throw new ProductSelectionCodecError(
2272
+ "Product slug is required to build a product href."
2273
+ );
2274
+ }
2275
+ function joinProductPath(basePath, slug, trailingSlash) {
2276
+ const base = basePath.replace(/\/+$/, "");
2277
+ const encodedSlug = encodeURIComponent(slug);
2278
+ return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
2279
+ }
2280
+ function getProductHrefGroupSelection(group, matrix) {
2281
+ if (!group) return null;
2282
+ if (group.variantId != null) return { variantId: group.variantId };
2283
+ const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
2284
+ if (!optionId) return null;
2285
+ const option = matrix?.optionById.get(optionId);
2286
+ const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
2287
+ if (!optionValueId) return null;
2288
+ return { byOptionId: { [optionId]: optionValueId } };
2289
+ }
2290
+ function getProductHrefCodecOptions(options) {
2291
+ return options.emit != null ? { emit: options.emit } : void 0;
2292
+ }
2293
+ function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
2294
+ const emit = resolveProductSelectionUrlEmit(options.emit);
2295
+ const byOptionId = groupSelection.byOptionId ?? {};
2296
+ const entries = Object.entries(byOptionId);
2297
+ if (entries.length === 0) return false;
2298
+ for (const [optionId, valueId] of entries) {
2299
+ const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
2300
+ const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
2301
+ if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
2302
+ params.set(`opt.${optionSlug}`, valueSlug);
2303
+ return true;
2304
+ }
2305
+ if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
2306
+ params,
2307
+ options.matrix,
2308
+ optionId,
2309
+ String(valueId)
2310
+ )) {
2311
+ continue;
2312
+ }
2313
+ appendCanonicalSelectionParam(params, optionId, String(valueId));
2314
+ }
2315
+ return params.size > 0;
2316
+ }
2317
+ function getPreferCompleteVariantFromHintSelection(group, options) {
2318
+ if (!options.preferCompleteVariantFromHint) return null;
2319
+ if (group?.optionValueId != null || group?.optionValueSlug) return null;
2320
+ const hintVariantId = group?.listing?.selectionHintVariant;
2321
+ if (hintVariantId == null) return null;
2322
+ return { variantId: hintVariantId };
2323
+ }
2324
+ function buildProductHref(product, group, options = {}) {
2325
+ const path = joinProductPath(
2326
+ options.basePath ?? "/products",
2327
+ getProductHrefSlug(product),
2328
+ options.trailingSlash ?? false
2329
+ );
2330
+ const params = new URLSearchParams();
2331
+ if (options.detail && options.selection) {
2332
+ const selection = stringifyProductSelection(
2333
+ options.detail,
2334
+ options.selection,
2335
+ getProductHrefCodecOptions(options)
2336
+ );
2337
+ return selection ? `${path}?${selection}` : path;
2338
+ }
2339
+ const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
2340
+ group,
2341
+ options
2342
+ );
2343
+ if (preferVariantSelection) {
2344
+ if (options.detail) {
2345
+ const selection = stringifyProductSelection(
2346
+ options.detail,
2347
+ preferVariantSelection,
2348
+ getProductHrefCodecOptions(options)
2349
+ );
2350
+ return selection ? `${path}?${selection}` : path;
2351
+ }
2352
+ if (preferVariantSelection.variantId != null) {
2353
+ params.set("variant", String(preferVariantSelection.variantId));
2354
+ return `${path}?${params.toString()}`;
2355
+ }
2356
+ }
2357
+ const groupSelection = getProductHrefGroupSelection(group, options.matrix);
2358
+ if (groupSelection) {
2359
+ if (options.detail) {
2360
+ const selection = stringifyProductSelection(
2361
+ options.detail,
2362
+ groupSelection,
2363
+ getProductHrefCodecOptions(options)
2364
+ );
2365
+ return selection ? `${path}?${selection}` : path;
2366
+ }
2367
+ if (groupSelection.variantId != null) {
2368
+ params.set("variant", String(groupSelection.variantId));
2369
+ return `${path}?${params.toString()}`;
2370
+ }
2371
+ if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
2372
+ return `${path}?${params.toString()}`;
2373
+ }
2374
+ }
2375
+ if (group?.optionValueSlug) {
2376
+ const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
2377
+ if (optionSlug) {
2378
+ params.set(`opt.${optionSlug}`, group.optionValueSlug);
2379
+ }
2380
+ }
2381
+ return params.size > 0 ? `${path}?${params.toString()}` : path;
2382
+ }
2383
+ function compareVariantOrder(a, b) {
2384
+ const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
2385
+ const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
2386
+ if (Number.isFinite(aOrder) && Number.isFinite(bOrder) && aOrder !== bOrder) {
2387
+ return aOrder - bOrder;
2388
+ }
2389
+ const aId = String(getRelationID(a.id) ?? "");
2390
+ const bId = String(getRelationID(b.id) ?? "");
2391
+ return aId.localeCompare(bId);
2392
+ }
2393
+ function isVariantAvailableForSale(variant) {
2394
+ if (variant.isActive === false) return false;
2395
+ if (variant.isUnlimited) return true;
2396
+ return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
2397
+ }
2398
+ function sortVariantsForMediaSelection(variants) {
2399
+ const orderedVariants = [...variants].sort(compareVariantOrder);
2400
+ const activeVariants = orderedVariants.filter(
2401
+ (variant) => variant.isActive !== false
2402
+ );
2403
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
2404
+ const unavailableActiveVariants = activeVariants.filter(
2405
+ (variant) => !isVariantAvailableForSale(variant)
2406
+ );
2407
+ return [...availableVariants, ...unavailableActiveVariants];
2408
+ }
2409
+ function variantHasPoolMedia(variant, productMediaPool) {
2410
+ if (!variant?.images?.length) return false;
2411
+ return variant.images.some((image) => {
2412
+ const imageId = getRelationID(image);
2413
+ return Boolean(
2414
+ imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
2415
+ );
2416
+ });
2417
+ }
2418
+ function getMinMax(values) {
2419
+ const numbers = values.filter(
2420
+ (value) => typeof value === "number"
2421
+ );
2422
+ if (numbers.length === 0) {
2423
+ return { min: null, max: null };
2424
+ }
2425
+ return {
2426
+ min: Math.min(...numbers),
2427
+ max: Math.max(...numbers)
2428
+ };
2429
+ }
2430
+ function buildProductListingProjection(product, variants) {
2431
+ const orderedVariants = [...variants].sort(compareVariantOrder);
2432
+ const activeVariants = orderedVariants.filter(
2433
+ (variant) => variant.isActive !== false
2434
+ );
2435
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
2436
+ const selectionHintVariant = availableVariants[0] ?? activeVariants[0] ?? null;
2437
+ const { min: minPrice, max: maxPrice } = getMinMax(
2438
+ activeVariants.map((variant) => variant.price)
2439
+ );
2440
+ const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
2441
+ activeVariants.map((variant) => variant.compareAtPrice)
2442
+ );
2443
+ const productMediaPool = product?.images ?? [];
2444
+ const selectionHintHasPoolMedia = variantHasPoolMedia(
2445
+ selectionHintVariant,
2446
+ productMediaPool
2447
+ );
2448
+ const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
2449
+ const resolvedProductMedia = resolveProductSelectionMedia({
2450
+ productMediaPool,
2451
+ productPrimaryMediaItemId: getRelationID(product?.primaryMediaItemId),
2452
+ selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
2453
+ id: selectionHintVariant.id,
2454
+ images: selectionHintVariant.images
2455
+ } : null,
2456
+ matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
2457
+ });
2458
+ return {
2459
+ selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
2460
+ primaryImage: resolveGenericListingPrimaryImage(
2461
+ product,
2462
+ resolvedProductMedia.primaryImage,
2463
+ resolvedProductMedia.source
2464
+ ),
2465
+ minPrice,
2466
+ maxPrice,
2467
+ minCompareAtPrice,
2468
+ maxCompareAtPrice,
2469
+ isPriceRange: minPrice !== null && maxPrice !== null ? minPrice !== maxPrice : false,
2470
+ availableForSale: availableVariants.length > 0
2471
+ };
2472
+ }
2473
+ function buildProductListingCard(item, options = {}) {
2474
+ const product = item.product;
2475
+ const groups = item.groups;
2476
+ const variants = getProductListingCardVariants(item);
2477
+ const projectedListing = buildProductListingProjection(product, variants);
2478
+ const selectionHintVariant = getRelationID(
2479
+ product.listing && "selectionHintVariant" in product.listing ? product.listing.selectionHintVariant : null
2480
+ ) ?? projectedListing.selectionHintVariant;
2481
+ const representativeVariant = findListingCardRepresentativeVariant(
2482
+ variants,
2483
+ selectionHintVariant
2484
+ );
2485
+ const productMediaPool = mediaArray2(product.images);
2486
+ const representativeHasPoolMedia = Boolean(
2487
+ representativeVariant?.images?.some(
2488
+ (image) => productMediaPool.some(
2489
+ (poolItem) => getRelationID(poolItem) === getRelationID(image)
2490
+ )
2491
+ )
2492
+ );
2493
+ const resolvedPrimaryMedia = resolveProductSelectionMedia(
2494
+ {
2495
+ productMediaPool,
2496
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
2497
+ selectedVariant: representativeHasPoolMedia ? representativeVariant : null,
2498
+ matchingVariants: representativeHasPoolMedia ? [] : variants
2499
+ }
2500
+ );
2501
+ const listingPrimaryPointer = resolveListingPrimaryImagePointer({
2502
+ productMediaPool,
2503
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
2504
+ productThumbnail: product.thumbnail ?? null,
2505
+ listingPrimaryImage: product.listing?.primaryImage ?? null,
2506
+ resolvedPrimary: resolvedPrimaryMedia.primaryImage,
2507
+ resolvedSource: resolvedPrimaryMedia.source
2508
+ });
2509
+ const listingPrimaryMedia = listingPrimaryPointer ? productMediaPool.find(
2510
+ (item2) => getRelationID(item2) === listingPrimaryPointer
2511
+ ) ?? resolvedPrimaryMedia.primaryImage ?? null : null;
2512
+ const priceRange = resolveListingCardPriceRange(
2513
+ product,
2514
+ projectedListing,
2515
+ groups
2516
+ );
2517
+ const availableForSale = product.listing?.availableForSale != null ? product.listing.availableForSale : groups.length > 0 ? groups.some((group) => group.listing.availableForSale) : projectedListing.availableForSale;
2518
+ const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
2519
+ return {
2520
+ id: String(product.id),
2521
+ href: buildProductHref(
2522
+ { slug: product.slug },
2523
+ { listing: { selectionHintVariant } },
2524
+ options
2525
+ ),
2526
+ title: product.title,
2527
+ representativeVariant,
2528
+ primaryImage: listingPrimaryMedia,
2529
+ priceRange,
2530
+ availableForSale,
2531
+ swatches
2532
+ };
2533
+ }
2534
+ function getProductListingCardVariants(item) {
2535
+ const productVariants = item.product.variants?.docs;
2536
+ if (Array.isArray(productVariants) && productVariants.length > 0) {
2537
+ return productVariants;
2538
+ }
2539
+ const variants = [];
2540
+ const seen = /* @__PURE__ */ new Set();
2541
+ for (const group of item.groups) {
2542
+ for (const variant of group.variants) {
2543
+ const id = getRelationID(variant.id);
2544
+ if (id && seen.has(id)) continue;
2545
+ if (id) seen.add(id);
2546
+ variants.push(variant);
2547
+ }
2548
+ }
2549
+ return variants;
2550
+ }
2551
+ function findListingCardRepresentativeVariant(variants, representativeVariantId) {
2552
+ if (representativeVariantId == null) return null;
2553
+ return variants.find(
2554
+ (variant) => getRelationID(variant.id) === representativeVariantId
2555
+ ) ?? null;
2556
+ }
2557
+ function hasCompleteListingPriceProjection(listing) {
2558
+ return listing?.minPrice != null && listing?.maxPrice != null;
2559
+ }
2560
+ function listingDefinesCompareAtProjection(listing) {
2561
+ return "minCompareAtPrice" in listing || "maxCompareAtPrice" in listing;
2562
+ }
2563
+ function resolveListingCardPriceRange(product, projectedListing, groups) {
2564
+ const listing = product.listing;
2565
+ if (hasCompleteListingPriceProjection(listing)) {
2566
+ const groupRange = groups.length > 0 ? aggregateListingPriceRange(groups) : null;
2567
+ const definesCompareAt = listingDefinesCompareAtProjection(listing);
2568
+ return {
2569
+ minPrice: listing.minPrice,
2570
+ maxPrice: listing.maxPrice,
2571
+ minCompareAtPrice: definesCompareAt ? listing.minCompareAtPrice ?? null : groupRange?.minCompareAtPrice ?? projectedListing.minCompareAtPrice,
2572
+ maxCompareAtPrice: definesCompareAt ? listing.maxCompareAtPrice ?? null : groupRange?.maxCompareAtPrice ?? projectedListing.maxCompareAtPrice,
2573
+ isPriceRange: listing.isPriceRange ?? listing.minPrice !== listing.maxPrice
2574
+ };
2575
+ }
2576
+ if (groups.length > 0) {
2577
+ return aggregateListingPriceRange(groups);
2578
+ }
2579
+ return {
2580
+ minPrice: projectedListing.minPrice,
2581
+ maxPrice: projectedListing.maxPrice,
2582
+ minCompareAtPrice: projectedListing.minCompareAtPrice,
2583
+ maxCompareAtPrice: projectedListing.maxCompareAtPrice,
2584
+ isPriceRange: projectedListing.isPriceRange
2585
+ };
2586
+ }
2587
+ function aggregateListingPriceRange(groups) {
2588
+ const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
2589
+ const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
2590
+ const minCompareAtPrice = minOfNullable(
2591
+ groups.map((g) => g.listing.minCompareAtPrice)
2592
+ );
2593
+ const maxCompareAtPrice = maxOfNullable(
2594
+ groups.map((g) => g.listing.maxCompareAtPrice)
2595
+ );
2596
+ const isPriceRange = minPrice !== null && maxPrice !== null && minPrice !== maxPrice;
2597
+ return {
2598
+ minPrice,
2599
+ maxPrice,
2600
+ minCompareAtPrice,
2601
+ maxCompareAtPrice,
2602
+ isPriceRange
2603
+ };
2604
+ }
2605
+ function buildListingSwatch(product, group, options) {
2606
+ return {
2607
+ optionId: group.optionId,
2608
+ optionValueId: group.optionValueId,
2609
+ label: group.optionValueLabel,
2610
+ swatch: group.optionValueSwatch ?? null,
2611
+ href: buildProductHref(
2612
+ { slug: product.slug },
2613
+ {
2614
+ optionId: group.optionId,
2615
+ optionSlug: group.optionSlug,
2616
+ optionValueId: group.optionValueId,
2617
+ optionValueSlug: group.optionValueSlug,
2618
+ listing: group.listing
2619
+ },
2620
+ options
2621
+ ),
2622
+ availableForSale: group.listing.availableForSale
2623
+ };
2624
+ }
2625
+ function minOfNullable(values) {
2626
+ const numbers = values.filter((v) => v !== null);
2627
+ return numbers.length === 0 ? null : Math.min(...numbers);
2628
+ }
2629
+ function maxOfNullable(values) {
2630
+ const numbers = values.filter((v) => v !== null);
2631
+ return numbers.length === 0 ? null : Math.max(...numbers);
2632
+ }
2633
+
1493
2634
  // src/core/api/product-api.ts
1494
2635
  var PRODUCT_DETAIL_UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
1495
2636
  "not_found",
@@ -1513,6 +2654,64 @@ function productDetailResultFromError(error) {
1513
2654
  if (!reason) return void 0;
1514
2655
  return { found: false, reason };
1515
2656
  }
2657
+ function nonEmpty(values) {
2658
+ return values?.length ? values : void 0;
2659
+ }
2660
+ function buildProductListingPageWhere(params) {
2661
+ const clauses = [];
2662
+ const search = params.search?.trim();
2663
+ if (search) {
2664
+ clauses.push({
2665
+ or: [
2666
+ { title: { like: search } },
2667
+ { slug: { like: search } },
2668
+ { handle: { like: search } }
2669
+ ]
2670
+ });
2671
+ }
2672
+ const filters = params.filters;
2673
+ const ids = nonEmpty(filters?.ids);
2674
+ if (ids) clauses.push({ id: { in: ids } });
2675
+ const slugs = nonEmpty(filters?.slugs);
2676
+ if (slugs) clauses.push({ slug: { in: slugs } });
2677
+ const handles = nonEmpty(filters?.handles);
2678
+ if (handles) clauses.push({ handle: { in: handles } });
2679
+ const categoryIds = nonEmpty(filters?.categoryIds);
2680
+ if (categoryIds) clauses.push({ categories: { in: categoryIds } });
2681
+ const tagIds = nonEmpty(filters?.tagIds);
2682
+ if (tagIds) clauses.push({ tags: { in: tagIds } });
2683
+ const minPrice = filters?.price?.min;
2684
+ if (minPrice != null) {
2685
+ clauses.push({ "listing.maxPrice": { greater_than_equal: minPrice } });
2686
+ }
2687
+ const maxPrice = filters?.price?.max;
2688
+ if (maxPrice != null) {
2689
+ clauses.push({ "listing.minPrice": { less_than_equal: maxPrice } });
2690
+ }
2691
+ if (filters?.availableForSale != null) {
2692
+ clauses.push({
2693
+ "listing.availableForSale": { equals: filters.availableForSale }
2694
+ });
2695
+ }
2696
+ if (clauses.length === 0) return void 0;
2697
+ if (clauses.length === 1) return clauses[0];
2698
+ return { and: clauses };
2699
+ }
2700
+ function buildProductListingPageUrl(params) {
2701
+ const options = {
2702
+ page: params.page,
2703
+ limit: params.limit,
2704
+ sort: params.sort,
2705
+ where: buildProductListingPageWhere(params)
2706
+ };
2707
+ return params.mode === "full" ? listingGroupsQueryUrl(options) : listingGroupsQueryCatalogUrl(options);
2708
+ }
2709
+ function withProductListingCards(response, options) {
2710
+ return {
2711
+ ...response,
2712
+ cards: response.docs.map((item) => buildProductListingCard(item, options))
2713
+ };
2714
+ }
1516
2715
 
1517
2716
  // src/core/api/order-api.ts
1518
2717
  function idempotencyRequestOptions(idempotencyKey) {
@@ -1555,10 +2754,20 @@ var CommerceClient = class {
1555
2754
  this.product = {
1556
2755
  stockCheck: (params) => api.post("/api/products/stock-check", params),
1557
2756
  stockSnapshot: (params) => api.get(stockSnapshotQuery(params)),
1558
- listingGroups: (params) => api.get(listingGroupsQuery(params)),
2757
+ listingGroups: (params) => api.get(
2758
+ listingGroupsCatalogQuery(params)
2759
+ ),
1559
2760
  listingGroupsCatalog: (params) => api.get(
1560
2761
  listingGroupsCatalogQuery(params)
1561
2762
  ),
2763
+ listingPage: async (params = {}) => {
2764
+ const catalogParams = {
2765
+ ...params,
2766
+ mode: "catalog"
2767
+ };
2768
+ const response = await api.get(buildProductListingPageUrl(catalogParams));
2769
+ return withProductListingCards(response, catalogParams);
2770
+ },
1562
2771
  detail: async (params) => {
1563
2772
  try {
1564
2773
  const product = await api.get(productDetailQuery(params));
@@ -1763,6 +2972,11 @@ var Client = class {
1763
2972
  onUnauthorized,
1764
2973
  onRequestId
1765
2974
  });
2975
+ this.content = new ContentClient({
2976
+ publishableKey: this.config.publishableKey,
2977
+ apiUrl: this.config.apiUrl,
2978
+ onRequestId
2979
+ });
1766
2980
  this.events = new EventsClient({
1767
2981
  publishableKey: this.config.publishableKey,
1768
2982
  apiUrl: this.config.apiUrl,