@01.software/sdk 0.37.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 (74) hide show
  1. package/README.md +189 -84
  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 +1286 -109
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +8 -7
  9. package/dist/client.d.ts +8 -7
  10. package/dist/client.js +1286 -109
  11. package/dist/client.js.map +1 -1
  12. package/dist/{collection-client-DyELGUcL.d.ts → collection-client-CaMgs5KE.d.ts} +18 -12
  13. package/dist/{collection-client-zOmnxwdA.d.cts → collection-client-DVfB0Em1.d.cts} +18 -12
  14. package/dist/const-6XHz_jej.d.ts +32 -0
  15. package/dist/const-B5KT72c7.d.cts +32 -0
  16. package/dist/errors.cjs +4 -1
  17. package/dist/errors.cjs.map +1 -1
  18. package/dist/errors.js +4 -1
  19. package/dist/errors.js.map +1 -1
  20. package/dist/{index-DRJs7QIh.d.cts → index-BOLQxveo.d.cts} +3 -3
  21. package/dist/{index-DTqoUZk_.d.ts → index-CSwR2HSg.d.ts} +3 -3
  22. package/dist/index.cjs +2861 -2714
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +9 -9
  25. package/dist/index.d.ts +9 -9
  26. package/dist/index.js +2861 -2714
  27. package/dist/index.js.map +1 -1
  28. package/dist/{payload-types-CREOjFNT.d.cts → payload-types-m3jjhxk9.d.cts} +418 -106
  29. package/dist/{payload-types-CREOjFNT.d.ts → payload-types-m3jjhxk9.d.ts} +418 -106
  30. package/dist/query.cjs +244 -1093
  31. package/dist/query.cjs.map +1 -1
  32. package/dist/query.d.cts +159 -34
  33. package/dist/query.d.ts +159 -34
  34. package/dist/query.js +244 -1093
  35. package/dist/query.js.map +1 -1
  36. package/dist/realtime.cjs +5 -1
  37. package/dist/realtime.cjs.map +1 -1
  38. package/dist/realtime.d.cts +2 -2
  39. package/dist/realtime.d.ts +2 -2
  40. package/dist/realtime.js +5 -1
  41. package/dist/realtime.js.map +1 -1
  42. package/dist/server.cjs +1191 -22
  43. package/dist/server.cjs.map +1 -1
  44. package/dist/server.d.cts +7 -7
  45. package/dist/server.d.ts +7 -7
  46. package/dist/server.js +1191 -22
  47. package/dist/server.js.map +1 -1
  48. package/dist/storefront-cache.cjs +144 -0
  49. package/dist/storefront-cache.cjs.map +1 -0
  50. package/dist/storefront-cache.d.cts +24 -0
  51. package/dist/storefront-cache.d.ts +24 -0
  52. package/dist/storefront-cache.js +121 -0
  53. package/dist/storefront-cache.js.map +1 -0
  54. package/dist/{types-DMvVHdb1.d.ts → types-BQo7UdI9.d.cts} +1608 -1215
  55. package/dist/{types-BWMUr3Zw.d.cts → types-CVf8sCZ-.d.ts} +1608 -1215
  56. package/dist/{types-CxzWHspI.d.ts → types-Cmrd1ezc.d.ts} +1 -15
  57. package/dist/{types-BkZNhuBh.d.cts → types-D0ubzQw0.d.cts} +1 -15
  58. package/dist/ui/canvas/server.cjs +5 -1
  59. package/dist/ui/canvas/server.cjs.map +1 -1
  60. package/dist/ui/canvas/server.js +5 -1
  61. package/dist/ui/canvas/server.js.map +1 -1
  62. package/dist/ui/canvas.cjs +5 -1
  63. package/dist/ui/canvas.cjs.map +1 -1
  64. package/dist/ui/canvas.js +5 -1
  65. package/dist/ui/canvas.js.map +1 -1
  66. package/dist/ui/form.d.cts +1 -1
  67. package/dist/ui/form.d.ts +1 -1
  68. package/dist/ui/video.d.cts +1 -1
  69. package/dist/ui/video.d.ts +1 -1
  70. package/dist/webhook.d.cts +4 -4
  71. package/dist/webhook.d.ts +4 -4
  72. package/package.json +11 -1
  73. package/dist/const-CK_FPaIn.d.cts +0 -32
  74. package/dist/const-Dqz05oaG.d.ts +0 -32
package/dist/server.cjs CHANGED
@@ -641,6 +641,32 @@ function listingGroupsQuery(params) {
641
641
  function listingGroupsCatalogQuery(params) {
642
642
  return `/api/products/listing-groups/catalog?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
643
643
  }
644
+ function appendListingGroupsQuerySearchParams(search, options) {
645
+ if (options?.page != null) search.set("page", String(options.page));
646
+ if (options?.limit != null) search.set("limit", String(options.limit));
647
+ if (options?.sort != null) {
648
+ const sort = options.sort;
649
+ search.set(
650
+ "sort",
651
+ Array.isArray(sort) ? sort.join(",") : sort
652
+ );
653
+ }
654
+ if (options?.where != null) {
655
+ search.set("whereJson", JSON.stringify(options.where));
656
+ }
657
+ }
658
+ function listingGroupsQueryUrl(options) {
659
+ const search = new URLSearchParams();
660
+ appendListingGroupsQuerySearchParams(search, options);
661
+ const query = search.toString();
662
+ return `/api/products/listing-groups/query${query ? `?${query}` : ""}`;
663
+ }
664
+ function listingGroupsQueryCatalogUrl(options) {
665
+ const search = new URLSearchParams();
666
+ appendListingGroupsQuerySearchParams(search, options);
667
+ const query = search.toString();
668
+ return `/api/products/listing-groups/query/catalog${query ? `?${query}` : ""}`;
669
+ }
644
670
  function stockSnapshotQuery(params) {
645
671
  return `/api/products/stock?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
646
672
  }
@@ -1256,7 +1282,8 @@ var CommunityClient = class extends CustomerScopedApi {
1256
1282
  const urlParams = new URLSearchParams();
1257
1283
  const sort = params?.sort ?? DEFAULT_POST_LIST_SORT;
1258
1284
  urlParams.set("sort", sort);
1259
- if (params?.limit !== void 0) urlParams.set("limit", String(params.limit));
1285
+ if (params?.limit !== void 0)
1286
+ urlParams.set("limit", String(params.limit));
1260
1287
  if (params?.page !== void 0) urlParams.set("page", String(params.page));
1261
1288
  if (params?.categoryId !== void 0) {
1262
1289
  urlParams.set("where[categories][in]", params.categoryId);
@@ -1264,7 +1291,7 @@ var CommunityClient = class extends CustomerScopedApi {
1264
1291
  if (params?.tagId !== void 0) {
1265
1292
  urlParams.set("where[tags][in]", params.tagId);
1266
1293
  }
1267
- return `/api/posts?${urlParams.toString()}`;
1294
+ return `/api/posts/public?${urlParams.toString()}`;
1268
1295
  }
1269
1296
  buildCommentsListQuery(params) {
1270
1297
  const urlParams = new URLSearchParams();
@@ -1284,7 +1311,7 @@ var CommunityClient = class extends CustomerScopedApi {
1284
1311
  }
1285
1312
  if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
1286
1313
  if (params.page !== void 0) urlParams.set("page", String(params.page));
1287
- return `/api/comments?${urlParams.toString()}`;
1314
+ return `/api/comments/public?${urlParams.toString()}`;
1288
1315
  }
1289
1316
  async execute(endpoint, method, body) {
1290
1317
  return this.request(endpoint, { method, body });
@@ -1294,7 +1321,11 @@ var CommunityClient = class extends CustomerScopedApi {
1294
1321
  return unwrapPayloadDoc(response);
1295
1322
  }
1296
1323
  createPost(params) {
1297
- return this.executeDoc("/api/posts", "POST", params);
1324
+ return this.executeDoc(
1325
+ "/api/posts",
1326
+ "POST",
1327
+ params
1328
+ );
1298
1329
  }
1299
1330
  /**
1300
1331
  * Public post feed. Server applies the same visibility contract as
@@ -1351,7 +1382,11 @@ var CommunityClient = class extends CustomerScopedApi {
1351
1382
  if (parentId !== void 0) {
1352
1383
  body.parent = parentId;
1353
1384
  }
1354
- return this.executeDoc("/api/comments", "POST", body);
1385
+ return this.executeDoc(
1386
+ "/api/comments",
1387
+ "POST",
1388
+ body
1389
+ );
1355
1390
  }
1356
1391
  /**
1357
1392
  * List comments for a post.
@@ -1420,10 +1455,14 @@ var CommunityClient = class extends CustomerScopedApi {
1420
1455
  400
1421
1456
  );
1422
1457
  }
1423
- return this.executeDoc("/api/reactions", "POST", {
1424
- post: postId,
1425
- type: reactionType
1426
- });
1458
+ return this.executeDoc(
1459
+ "/api/reactions",
1460
+ "POST",
1461
+ {
1462
+ post: postId,
1463
+ type: reactionType
1464
+ }
1465
+ );
1427
1466
  }
1428
1467
  removeReaction(params) {
1429
1468
  const { postId, type } = params;
@@ -1442,10 +1481,14 @@ var CommunityClient = class extends CustomerScopedApi {
1442
1481
  400
1443
1482
  );
1444
1483
  }
1445
- return this.executeDoc("/api/reactions", "POST", {
1446
- comment: commentId,
1447
- type: reactionType
1448
- });
1484
+ return this.executeDoc(
1485
+ "/api/reactions",
1486
+ "POST",
1487
+ {
1488
+ comment: commentId,
1489
+ type: reactionType
1490
+ }
1491
+ );
1449
1492
  }
1450
1493
  removeCommentReaction(params) {
1451
1494
  const { commentId, type } = params;
@@ -1474,9 +1517,13 @@ var CommunityClient = class extends CustomerScopedApi {
1474
1517
  }
1475
1518
  // Bookmarks
1476
1519
  addBookmark(params) {
1477
- return this.executeDoc("/api/bookmarks", "POST", {
1478
- post: params.postId
1479
- });
1520
+ return this.executeDoc(
1521
+ "/api/bookmarks",
1522
+ "POST",
1523
+ {
1524
+ post: params.postId
1525
+ }
1526
+ );
1480
1527
  }
1481
1528
  removeBookmark(params) {
1482
1529
  return this.execute(
@@ -1492,10 +1539,7 @@ var CommunityClient = class extends CustomerScopedApi {
1492
1539
  }
1493
1540
  // Profiles
1494
1541
  listProfileLists(params) {
1495
- return this.execute(
1496
- `/api/customer-profile-lists${this.buildQuery(params)}`,
1497
- "GET"
1498
- );
1542
+ return this.execute(`/api/customer-profile-lists${this.buildQuery(params)}`, "GET");
1499
1543
  }
1500
1544
  async getProfileList(params) {
1501
1545
  const query = "slug" in params ? `?where[slug][equals]=${encodeURIComponent(params.slug)}&limit=1` : `?where[id][equals]=${encodeURIComponent(params.id)}&limit=1`;
@@ -1577,7 +1621,10 @@ var CartApi = class extends CustomerScopedApi {
1577
1621
  return this.request(endpoint, { method, body });
1578
1622
  }
1579
1623
  getCart(cartId) {
1580
- return this.execute(`/api/carts/${cartId}`, "GET");
1624
+ return this.execute(
1625
+ `/api/carts/${cartId}?depth=0&joins=false`,
1626
+ "GET"
1627
+ );
1581
1628
  }
1582
1629
  addItem(params) {
1583
1630
  return this.execute("/api/carts/add-item", "POST", params);
@@ -1607,6 +1654,1061 @@ var CartApi = class extends CustomerScopedApi {
1607
1654
  }
1608
1655
  };
1609
1656
 
1657
+ // src/utils/product-selection-media.ts
1658
+ function selectedSwatchMediaItemId(swatch) {
1659
+ if (!swatch || swatch.type !== "media") return null;
1660
+ const id = swatch.mediaItemId;
1661
+ if (id == null || id === "") return null;
1662
+ return String(id);
1663
+ }
1664
+ function getMediaId(value) {
1665
+ if (typeof value === "string" || typeof value === "number") {
1666
+ return String(value);
1667
+ }
1668
+ if (typeof value === "object" && value !== null && "id" in value) {
1669
+ const id = value.id;
1670
+ if (typeof id === "string" || typeof id === "number") return String(id);
1671
+ }
1672
+ return null;
1673
+ }
1674
+ function toPointerId(value) {
1675
+ return getMediaId(value);
1676
+ }
1677
+ function mediaArray(value) {
1678
+ if (!Array.isArray(value)) return [];
1679
+ return value.filter((entry) => entry != null);
1680
+ }
1681
+ function uniqueWithPrimaryFirst(primary, items) {
1682
+ const unique = /* @__PURE__ */ new Map();
1683
+ for (const item of items) {
1684
+ const id = getMediaId(item);
1685
+ const key = id ?? `inline:${unique.size}`;
1686
+ if (!unique.has(key)) unique.set(key, item);
1687
+ }
1688
+ if (primary) {
1689
+ const primaryId = getMediaId(primary);
1690
+ const prefixed = /* @__PURE__ */ new Map();
1691
+ const primaryKey = primaryId ?? "inline:primary";
1692
+ prefixed.set(primaryKey, primary);
1693
+ for (const [key, value] of unique.entries()) {
1694
+ if (!prefixed.has(key)) prefixed.set(key, value);
1695
+ }
1696
+ return Array.from(prefixed.values());
1697
+ }
1698
+ return Array.from(unique.values());
1699
+ }
1700
+ function buildPoolById(pool) {
1701
+ const poolById = /* @__PURE__ */ new Map();
1702
+ for (const item of pool) {
1703
+ const id = getMediaId(item);
1704
+ if (id) poolById.set(id, item);
1705
+ }
1706
+ return poolById;
1707
+ }
1708
+ function resolveVariantImageItems(variant, poolById) {
1709
+ if (!variant || !Array.isArray(variant.images)) return [];
1710
+ const resolved = [];
1711
+ for (const entry of variant.images) {
1712
+ if (entry == null) continue;
1713
+ if (typeof entry === "string" || typeof entry === "number") {
1714
+ const pooled = poolById.get(String(entry));
1715
+ if (pooled) resolved.push(pooled);
1716
+ continue;
1717
+ }
1718
+ const mediaId = getMediaId(entry);
1719
+ if (mediaId) {
1720
+ const pooled = poolById.get(mediaId);
1721
+ if (pooled) {
1722
+ resolved.push(pooled);
1723
+ }
1724
+ continue;
1725
+ }
1726
+ }
1727
+ return resolved;
1728
+ }
1729
+ function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
1730
+ if (!selectedOptionValues?.length) return null;
1731
+ for (const optionValue of selectedOptionValues) {
1732
+ const swatch = optionValue?.swatch;
1733
+ const swatchPointer = selectedSwatchMediaItemId(swatch);
1734
+ if (swatchPointer) {
1735
+ const pooled = poolById.get(swatchPointer);
1736
+ if (pooled) return pooled;
1737
+ }
1738
+ const inline = swatch?.inlineMedia;
1739
+ if (inline != null && typeof inline === "object") {
1740
+ return inline;
1741
+ }
1742
+ }
1743
+ return null;
1744
+ }
1745
+ function resolveProductSelectionMedia(input) {
1746
+ const pool = mediaArray(input.productMediaPool);
1747
+ const poolById = buildPoolById(pool);
1748
+ const selectedVariantImages = resolveVariantImageItems(
1749
+ input.selectedVariant,
1750
+ poolById
1751
+ );
1752
+ if (selectedVariantImages.length > 0) {
1753
+ const primaryImage = selectedVariantImages[0] ?? null;
1754
+ return {
1755
+ primaryImage,
1756
+ images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
1757
+ source: "variant_media_selected"
1758
+ };
1759
+ }
1760
+ if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
1761
+ const mergedMatchingImages = [];
1762
+ for (const matchingVariant of input.matchingVariants ?? []) {
1763
+ mergedMatchingImages.push(
1764
+ ...resolveVariantImageItems(matchingVariant, poolById)
1765
+ );
1766
+ }
1767
+ if (mergedMatchingImages.length > 0) {
1768
+ const primaryImage = mergedMatchingImages[0] ?? null;
1769
+ return {
1770
+ primaryImage,
1771
+ images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
1772
+ source: "variant_media_matching"
1773
+ };
1774
+ }
1775
+ }
1776
+ const optionSwatchPrimary = resolveOptionSwatchPrimary(
1777
+ input.selectedOptionValues,
1778
+ poolById
1779
+ );
1780
+ if (optionSwatchPrimary) {
1781
+ return {
1782
+ primaryImage: optionSwatchPrimary,
1783
+ images: [optionSwatchPrimary],
1784
+ source: "option_swatch"
1785
+ };
1786
+ }
1787
+ return {
1788
+ primaryImage: null,
1789
+ images: [],
1790
+ source: "none"
1791
+ };
1792
+ }
1793
+ function resolveListingPrimaryImagePointer(input) {
1794
+ const pool = mediaArray(input.productMediaPool);
1795
+ const resolvedPointer = getMediaId(input.resolvedPrimary);
1796
+ if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
1797
+ return resolvedPointer;
1798
+ }
1799
+ const poolById = buildPoolById(pool);
1800
+ const listingPointer = getMediaId(input.listingPrimaryImage);
1801
+ if (listingPointer && poolById.has(listingPointer)) {
1802
+ return listingPointer;
1803
+ }
1804
+ const primaryPointer = toPointerId(input.productPrimaryMediaItemId);
1805
+ if (primaryPointer && poolById.has(primaryPointer)) return primaryPointer;
1806
+ const thumbnailPointer = getMediaId(input.productThumbnail);
1807
+ if (thumbnailPointer && poolById.has(thumbnailPointer)) {
1808
+ return thumbnailPointer;
1809
+ }
1810
+ if (pool.length > 0) {
1811
+ const firstPoolId = getMediaId(pool[0]);
1812
+ if (firstPoolId) return firstPoolId;
1813
+ }
1814
+ return null;
1815
+ }
1816
+
1817
+ // src/utils/ecommerce.ts
1818
+ var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
1819
+ function resolveProductSelectionUrlEmit(emit) {
1820
+ return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
1821
+ }
1822
+ function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
1823
+ const option = matrix.optionById.get(optionId);
1824
+ const value = matrix.valueById.get(valueId);
1825
+ if (!option?.slug || !value?.slug) return false;
1826
+ const slugMatches = option.values.filter(
1827
+ (candidate) => candidate.slug === value.slug
1828
+ );
1829
+ if (slugMatches.length !== 1) return false;
1830
+ params.append(`opt.${option.slug}`, value.slug);
1831
+ return true;
1832
+ }
1833
+ function appendCanonicalSelectionParam(params, optionId, valueId) {
1834
+ params.append(`opt.${optionId}`, valueId);
1835
+ }
1836
+ var ProductSelectionCodecError = class extends Error {
1837
+ constructor(message) {
1838
+ super(message);
1839
+ this.code = "ambiguous_product_selection_query";
1840
+ this.name = "ProductSelectionCodecError";
1841
+ }
1842
+ };
1843
+ function getRelationID(value) {
1844
+ if (typeof value === "string") return value;
1845
+ if (typeof value === "number") return String(value);
1846
+ if (value && typeof value === "object" && "id" in value) {
1847
+ const id = value.id;
1848
+ if (typeof id === "string") return id;
1849
+ if (typeof id === "number") return String(id);
1850
+ }
1851
+ return void 0;
1852
+ }
1853
+ function extractEntityId(value) {
1854
+ if (typeof value === "string") return value;
1855
+ if (typeof value === "number") return String(value);
1856
+ if (value && typeof value === "object" && "id" in value) {
1857
+ if (typeof value.id === "string") return value.id;
1858
+ if (typeof value.id === "number") return String(value.id);
1859
+ }
1860
+ return null;
1861
+ }
1862
+ function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
1863
+ return resolveListingPrimaryImagePointer({
1864
+ productMediaPool: product?.images ?? [],
1865
+ productPrimaryMediaItemId: getRelationID(
1866
+ product?.primaryMediaItemId ?? null
1867
+ ),
1868
+ productThumbnail: product?.thumbnail ?? null,
1869
+ listingPrimaryImage: product?.listing?.primaryImage ?? null,
1870
+ resolvedPrimary,
1871
+ resolvedSource
1872
+ });
1873
+ }
1874
+ function normalizeProductOptionValueSwatch(swatch) {
1875
+ if (swatch == null) return null;
1876
+ if (typeof swatch !== "object") return null;
1877
+ const raw = swatch;
1878
+ const mediaItemId = extractEntityId(raw.mediaItemId);
1879
+ const color = typeof raw.color === "string" ? raw.color.trim() : "";
1880
+ const hasColor = color.length > 0;
1881
+ if (raw.type === "media" || mediaItemId && raw.type !== "color") {
1882
+ if (!mediaItemId || hasColor) return null;
1883
+ return {
1884
+ type: "media",
1885
+ mediaItemId,
1886
+ color: null
1887
+ };
1888
+ }
1889
+ if (raw.type === "color" || hasColor) {
1890
+ if (mediaItemId || !hasColor) return null;
1891
+ return { type: "color", color, mediaItemId: null };
1892
+ }
1893
+ return null;
1894
+ }
1895
+ function matrixOrder(index) {
1896
+ return String(index).padStart(6, "0");
1897
+ }
1898
+ function buildProductOptionMatrixFromDetail(detail) {
1899
+ const normalizedOptions = detail.options.map((option, optionIndex) => ({
1900
+ id: String(option.id),
1901
+ title: option.title || String(option.id),
1902
+ slug: option.slug,
1903
+ order: matrixOrder(optionIndex),
1904
+ values: option.values.map((value, valueIndex) => ({
1905
+ id: String(value.id),
1906
+ optionId: String(option.id),
1907
+ optionSlug: option.slug,
1908
+ label: value.value || value.slug || String(value.id),
1909
+ slug: value.slug,
1910
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
1911
+ order: matrixOrder(valueIndex)
1912
+ }))
1913
+ }));
1914
+ const optionById = new Map(
1915
+ normalizedOptions.map((option) => [option.id, option])
1916
+ );
1917
+ const optionBySlug = new Map(
1918
+ normalizedOptions.map((option) => [option.slug, option])
1919
+ );
1920
+ const valueById = /* @__PURE__ */ new Map();
1921
+ const valueToOptionId = /* @__PURE__ */ new Map();
1922
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
1923
+ for (const option of normalizedOptions) {
1924
+ for (const value of option.values) {
1925
+ valueById.set(value.id, value);
1926
+ valueToOptionId.set(value.id, option.id);
1927
+ valueToOptionSlug.set(value.id, option.slug);
1928
+ }
1929
+ }
1930
+ const optionIds = normalizedOptions.map((option) => option.id);
1931
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
1932
+ const normalizedVariants = detail.variants.map((variant) => {
1933
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
1934
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
1935
+ for (const rawValue of variant.optionValues) {
1936
+ const optionId = String(rawValue.optionId);
1937
+ const valueId = String(rawValue.valueId);
1938
+ const optionSlug = rawValue.optionSlug;
1939
+ if (!optionById.has(optionId)) continue;
1940
+ if (valueToOptionId.get(valueId) !== optionId) continue;
1941
+ if (optionValueByOptionId.has(optionId)) continue;
1942
+ optionValueByOptionId.set(optionId, valueId);
1943
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
1944
+ optionValueByOptionSlug.set(optionSlug, valueId);
1945
+ }
1946
+ }
1947
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
1948
+ return {
1949
+ id: String(variant.id),
1950
+ optionValueIds,
1951
+ optionValueByOptionId,
1952
+ optionValueByOptionSlug,
1953
+ source: variant
1954
+ };
1955
+ });
1956
+ return {
1957
+ options: normalizedOptions,
1958
+ optionIds,
1959
+ optionSlugs,
1960
+ optionById,
1961
+ optionBySlug,
1962
+ valueById,
1963
+ valueToOptionId,
1964
+ valueToOptionSlug,
1965
+ variants: normalizedVariants
1966
+ };
1967
+ }
1968
+ function getVariantSelection(matrix, variantId) {
1969
+ if (variantId == null) return void 0;
1970
+ const id = String(variantId);
1971
+ return matrix.variants.find((variant) => variant.id === id);
1972
+ }
1973
+ function hasExplicitSelection(selection) {
1974
+ return Boolean(
1975
+ selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
1976
+ );
1977
+ }
1978
+ function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
1979
+ if (valueId == null) return false;
1980
+ const normalizedValueId = String(valueId);
1981
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
1982
+ selectedByOptionId.set(optionId, normalizedValueId);
1983
+ return true;
1984
+ }
1985
+ function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
1986
+ if (!valueSlug) return false;
1987
+ const option = matrix.optionById.get(optionId);
1988
+ if (!option) return false;
1989
+ const values = option.values.filter(
1990
+ (candidate) => candidate.slug === valueSlug
1991
+ );
1992
+ if (values.length > 1) {
1993
+ throw new ProductSelectionCodecError(
1994
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
1995
+ );
1996
+ }
1997
+ const value = values[0];
1998
+ if (!value) return false;
1999
+ selectedByOptionId.set(optionId, value.id);
2000
+ return true;
2001
+ }
2002
+ function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
2003
+ if (!valueSlug) return false;
2004
+ const option = matrix.optionBySlug.get(optionSlug);
2005
+ if (!option) return false;
2006
+ const values = option.values.filter(
2007
+ (candidate) => candidate.slug === valueSlug
2008
+ );
2009
+ if (values.length > 1) {
2010
+ throw new ProductSelectionCodecError(
2011
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
2012
+ );
2013
+ }
2014
+ const value = values[0];
2015
+ if (!value) return false;
2016
+ selectedByOptionId.set(option.id, value.id);
2017
+ return true;
2018
+ }
2019
+ function toSearchParams(search) {
2020
+ if (!search) return new URLSearchParams();
2021
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
2022
+ if (search instanceof URL) return new URLSearchParams(search.searchParams);
2023
+ const trimmed = search.trim();
2024
+ if (!trimmed) return new URLSearchParams();
2025
+ try {
2026
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
2027
+ return new URL(trimmed).searchParams;
2028
+ }
2029
+ } catch {
2030
+ return new URLSearchParams();
2031
+ }
2032
+ return new URLSearchParams(
2033
+ trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
2034
+ );
2035
+ }
2036
+ function slugLike(value) {
2037
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2038
+ }
2039
+ function assertNoAmbiguousSelectionParams(matrix, params) {
2040
+ const knownSelectionKeys = /* @__PURE__ */ new Set();
2041
+ const hasVariantParam = params.has("variant");
2042
+ const hasOptionParams = Array.from(params.keys()).some(
2043
+ (key) => key.startsWith("opt.")
2044
+ );
2045
+ if (hasVariantParam && hasOptionParams) {
2046
+ throw new ProductSelectionCodecError(
2047
+ "Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
2048
+ );
2049
+ }
2050
+ for (const option of matrix.options) {
2051
+ knownSelectionKeys.add(slugLike(option.slug));
2052
+ knownSelectionKeys.add(slugLike(option.title));
2053
+ for (const value of option.values) {
2054
+ if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
2055
+ }
2056
+ }
2057
+ for (const [key, value] of params.entries()) {
2058
+ if (key.startsWith("opt.")) {
2059
+ const optionToken = key.slice(4);
2060
+ if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
2061
+ throw new ProductSelectionCodecError(
2062
+ `Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2063
+ );
2064
+ }
2065
+ if (!value) {
2066
+ throw new ProductSelectionCodecError(
2067
+ `Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
2068
+ );
2069
+ }
2070
+ continue;
2071
+ }
2072
+ if (key === "variant") {
2073
+ if (!value) {
2074
+ throw new ProductSelectionCodecError(
2075
+ 'Product selection query parameter "variant" requires a variant ID.'
2076
+ );
2077
+ }
2078
+ if (!getVariantSelection(matrix, value)) {
2079
+ throw new ProductSelectionCodecError(
2080
+ `Unknown product selection variant "${value}".`
2081
+ );
2082
+ }
2083
+ continue;
2084
+ }
2085
+ const keyToken = slugLike(key);
2086
+ if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
2087
+ throw new ProductSelectionCodecError(
2088
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2089
+ );
2090
+ }
2091
+ }
2092
+ }
2093
+ function emitCompatibilityOptionIdParam(options, event) {
2094
+ try {
2095
+ if (options?.onCompatibilityOptionIdParam) {
2096
+ options.onCompatibilityOptionIdParam(event);
2097
+ return;
2098
+ }
2099
+ options?.onLegacyOptionIdParam?.(event);
2100
+ } catch {
2101
+ }
2102
+ }
2103
+ function assignSearchSelection(matrix, selectedByOptionId, search, options) {
2104
+ if (!search) return null;
2105
+ const params = toSearchParams(search);
2106
+ assertNoAmbiguousSelectionParams(matrix, params);
2107
+ const variantParam = params.get("variant");
2108
+ if (variantParam != null) {
2109
+ const variantSelection = getVariantSelection(matrix, variantParam);
2110
+ if (!variantSelection) {
2111
+ throw new ProductSelectionCodecError(
2112
+ `Unknown product selection variant "${variantParam}".`
2113
+ );
2114
+ }
2115
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2116
+ selectedByOptionId.set(optionId, valueId);
2117
+ }
2118
+ return variantSelection.id;
2119
+ }
2120
+ for (const [key, valueToken] of params.entries()) {
2121
+ if (!key.startsWith("opt.")) continue;
2122
+ const optionToken = key.slice(4);
2123
+ const optionById = matrix.optionById.get(optionToken);
2124
+ if (optionById) {
2125
+ if (assignSelectedValue(
2126
+ matrix,
2127
+ selectedByOptionId,
2128
+ optionById.id,
2129
+ valueToken
2130
+ )) {
2131
+ continue;
2132
+ }
2133
+ const before = selectedByOptionId.get(optionById.id);
2134
+ if (assignSelectedValueSlugByOptionId(
2135
+ matrix,
2136
+ selectedByOptionId,
2137
+ optionById.id,
2138
+ valueToken
2139
+ ) && selectedByOptionId.get(optionById.id) !== before) {
2140
+ emitCompatibilityOptionIdParam(options, {
2141
+ optionId: optionById.id,
2142
+ optionSlug: optionById.slug,
2143
+ valueSlug: valueToken,
2144
+ searchParam: key
2145
+ });
2146
+ continue;
2147
+ }
2148
+ throw new ProductSelectionCodecError(
2149
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2150
+ );
2151
+ }
2152
+ const optionBySlug = matrix.optionBySlug.get(optionToken);
2153
+ if (optionBySlug) {
2154
+ if (assignSelectedValueSlugByOptionSlug(
2155
+ matrix,
2156
+ selectedByOptionId,
2157
+ optionBySlug.slug,
2158
+ valueToken
2159
+ )) {
2160
+ continue;
2161
+ }
2162
+ if (matrix.valueById.has(valueToken)) {
2163
+ throw new ProductSelectionCodecError(
2164
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2165
+ );
2166
+ }
2167
+ throw new ProductSelectionCodecError(
2168
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
2169
+ );
2170
+ }
2171
+ }
2172
+ return null;
2173
+ }
2174
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
2175
+ const selectedByOptionId = /* @__PURE__ */ new Map();
2176
+ const variantSelection = getVariantSelection(matrix, selection.variantId);
2177
+ const variantId = variantSelection?.id ?? null;
2178
+ if (variantSelection) {
2179
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2180
+ selectedByOptionId.set(optionId, valueId);
2181
+ }
2182
+ }
2183
+ for (const rawValueId of selection.valueIds ?? []) {
2184
+ const valueId = getRelationID(rawValueId);
2185
+ if (!valueId) continue;
2186
+ const optionId = matrix.valueToOptionId.get(valueId);
2187
+ if (!optionId) continue;
2188
+ selectedByOptionId.set(optionId, valueId);
2189
+ }
2190
+ const searchVariantId = assignSearchSelection(
2191
+ matrix,
2192
+ selectedByOptionId,
2193
+ selection.search,
2194
+ options
2195
+ );
2196
+ for (const [rawOptionId, rawSelection] of Object.entries(
2197
+ selection.byOptionId ?? {}
2198
+ )) {
2199
+ const optionId = String(rawOptionId);
2200
+ if (!matrix.optionById.has(optionId)) continue;
2201
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2202
+ assignSelectedValue(
2203
+ matrix,
2204
+ selectedByOptionId,
2205
+ optionId,
2206
+ rawSelection.valueId
2207
+ );
2208
+ continue;
2209
+ }
2210
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2211
+ assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
2212
+ continue;
2213
+ }
2214
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2215
+ assignSelectedValueSlugByOptionId(
2216
+ matrix,
2217
+ selectedByOptionId,
2218
+ optionId,
2219
+ rawSelection.valueSlug
2220
+ );
2221
+ }
2222
+ }
2223
+ for (const [rawOptionSlug, rawSelection] of Object.entries(
2224
+ selection.byOptionSlug ?? {}
2225
+ )) {
2226
+ const optionSlug = String(rawOptionSlug);
2227
+ if (!matrix.optionBySlug.has(optionSlug)) continue;
2228
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2229
+ const option = matrix.optionBySlug.get(optionSlug);
2230
+ if (option) {
2231
+ assignSelectedValue(
2232
+ matrix,
2233
+ selectedByOptionId,
2234
+ option.id,
2235
+ rawSelection.valueId
2236
+ );
2237
+ }
2238
+ continue;
2239
+ }
2240
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2241
+ assignSelectedValueSlugByOptionSlug(
2242
+ matrix,
2243
+ selectedByOptionId,
2244
+ optionSlug,
2245
+ rawSelection.valueSlug
2246
+ );
2247
+ continue;
2248
+ }
2249
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2250
+ assignSelectedValueSlugByOptionSlug(
2251
+ matrix,
2252
+ selectedByOptionId,
2253
+ optionSlug,
2254
+ String(rawSelection)
2255
+ );
2256
+ }
2257
+ }
2258
+ const byOptionId = Object.fromEntries(
2259
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
2260
+ );
2261
+ const byOptionSlug = Object.fromEntries(
2262
+ matrix.options.map((option) => {
2263
+ const valueId = selectedByOptionId.get(option.id);
2264
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
2265
+ return [option.slug, value?.slug ?? void 0];
2266
+ }).filter((entry) => Boolean(entry[1]))
2267
+ );
2268
+ return {
2269
+ byOptionSlug,
2270
+ byOptionId,
2271
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
2272
+ variantId: searchVariantId ?? variantId
2273
+ };
2274
+ }
2275
+ function stringifyProductSelection(detail, selection = {}, options) {
2276
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2277
+ const normalized = normalizeProductSelectionFromMatrix(
2278
+ matrix,
2279
+ selection,
2280
+ options
2281
+ );
2282
+ const params = new URLSearchParams();
2283
+ if (hasExplicitSelection(selection)) {
2284
+ const matchingVariants = getMatchingVariantEntries(matrix, normalized);
2285
+ const exactVariant = getExactSelectedVariantEntry(
2286
+ matrix,
2287
+ normalized,
2288
+ matchingVariants
2289
+ );
2290
+ if (exactVariant) {
2291
+ params.set("variant", exactVariant.id);
2292
+ return params.toString();
2293
+ }
2294
+ }
2295
+ const emit = resolveProductSelectionUrlEmit(options?.emit);
2296
+ for (const optionId of matrix.optionIds) {
2297
+ const valueId = normalized.byOptionId[optionId];
2298
+ if (!valueId) continue;
2299
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2300
+ continue;
2301
+ }
2302
+ if (emit === "slug-compat") {
2303
+ if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
2304
+ continue;
2305
+ }
2306
+ }
2307
+ appendCanonicalSelectionParam(params, optionId, valueId);
2308
+ }
2309
+ return params.toString();
2310
+ }
2311
+ function selectedEntries(selection) {
2312
+ return Object.entries(selection.byOptionId);
2313
+ }
2314
+ function getMatchingVariantEntries(matrix, selection) {
2315
+ const entries = selectedEntries(selection);
2316
+ if (entries.length === 0) return matrix.variants;
2317
+ return matrix.variants.filter(
2318
+ (variant) => entries.every(
2319
+ ([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
2320
+ )
2321
+ );
2322
+ }
2323
+ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
2324
+ if (matrix.optionIds.length === 0) {
2325
+ return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
2326
+ }
2327
+ const allOptionsSelected = matrix.optionIds.every(
2328
+ (optionId) => Boolean(selection.byOptionId[optionId])
2329
+ );
2330
+ if (!allOptionsSelected) return null;
2331
+ return matchingVariants.find(
2332
+ (variant) => matrix.optionIds.every(
2333
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
2334
+ )
2335
+ ) ?? null;
2336
+ }
2337
+ function isPresentMedia(value) {
2338
+ return value != null;
2339
+ }
2340
+ function mediaArray2(values) {
2341
+ if (!Array.isArray(values)) return [];
2342
+ return values.filter(isPresentMedia);
2343
+ }
2344
+ function getProductHrefSlug(product) {
2345
+ if ("product" in product && product.product?.slug) {
2346
+ return product.product.slug;
2347
+ }
2348
+ if ("slug" in product && product.slug) return product.slug;
2349
+ throw new ProductSelectionCodecError(
2350
+ "Product slug is required to build a product href."
2351
+ );
2352
+ }
2353
+ function joinProductPath(basePath, slug, trailingSlash) {
2354
+ const base = basePath.replace(/\/+$/, "");
2355
+ const encodedSlug = encodeURIComponent(slug);
2356
+ return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
2357
+ }
2358
+ function getProductHrefGroupSelection(group, matrix) {
2359
+ if (!group) return null;
2360
+ if (group.variantId != null) return { variantId: group.variantId };
2361
+ const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
2362
+ if (!optionId) return null;
2363
+ const option = matrix?.optionById.get(optionId);
2364
+ const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
2365
+ if (!optionValueId) return null;
2366
+ return { byOptionId: { [optionId]: optionValueId } };
2367
+ }
2368
+ function getProductHrefCodecOptions(options) {
2369
+ return options.emit != null ? { emit: options.emit } : void 0;
2370
+ }
2371
+ function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
2372
+ const emit = resolveProductSelectionUrlEmit(options.emit);
2373
+ const byOptionId = groupSelection.byOptionId ?? {};
2374
+ const entries = Object.entries(byOptionId);
2375
+ if (entries.length === 0) return false;
2376
+ for (const [optionId, valueId] of entries) {
2377
+ const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
2378
+ const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
2379
+ if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
2380
+ params.set(`opt.${optionSlug}`, valueSlug);
2381
+ return true;
2382
+ }
2383
+ if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
2384
+ params,
2385
+ options.matrix,
2386
+ optionId,
2387
+ String(valueId)
2388
+ )) {
2389
+ continue;
2390
+ }
2391
+ appendCanonicalSelectionParam(params, optionId, String(valueId));
2392
+ }
2393
+ return params.size > 0;
2394
+ }
2395
+ function getPreferCompleteVariantFromHintSelection(group, options) {
2396
+ if (!options.preferCompleteVariantFromHint) return null;
2397
+ if (group?.optionValueId != null || group?.optionValueSlug) return null;
2398
+ const hintVariantId = group?.listing?.selectionHintVariant;
2399
+ if (hintVariantId == null) return null;
2400
+ return { variantId: hintVariantId };
2401
+ }
2402
+ function buildProductHref(product, group, options = {}) {
2403
+ const path = joinProductPath(
2404
+ options.basePath ?? "/products",
2405
+ getProductHrefSlug(product),
2406
+ options.trailingSlash ?? false
2407
+ );
2408
+ const params = new URLSearchParams();
2409
+ if (options.detail && options.selection) {
2410
+ const selection = stringifyProductSelection(
2411
+ options.detail,
2412
+ options.selection,
2413
+ getProductHrefCodecOptions(options)
2414
+ );
2415
+ return selection ? `${path}?${selection}` : path;
2416
+ }
2417
+ const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
2418
+ group,
2419
+ options
2420
+ );
2421
+ if (preferVariantSelection) {
2422
+ if (options.detail) {
2423
+ const selection = stringifyProductSelection(
2424
+ options.detail,
2425
+ preferVariantSelection,
2426
+ getProductHrefCodecOptions(options)
2427
+ );
2428
+ return selection ? `${path}?${selection}` : path;
2429
+ }
2430
+ if (preferVariantSelection.variantId != null) {
2431
+ params.set("variant", String(preferVariantSelection.variantId));
2432
+ return `${path}?${params.toString()}`;
2433
+ }
2434
+ }
2435
+ const groupSelection = getProductHrefGroupSelection(group, options.matrix);
2436
+ if (groupSelection) {
2437
+ if (options.detail) {
2438
+ const selection = stringifyProductSelection(
2439
+ options.detail,
2440
+ groupSelection,
2441
+ getProductHrefCodecOptions(options)
2442
+ );
2443
+ return selection ? `${path}?${selection}` : path;
2444
+ }
2445
+ if (groupSelection.variantId != null) {
2446
+ params.set("variant", String(groupSelection.variantId));
2447
+ return `${path}?${params.toString()}`;
2448
+ }
2449
+ if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
2450
+ return `${path}?${params.toString()}`;
2451
+ }
2452
+ }
2453
+ if (group?.optionValueSlug) {
2454
+ const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
2455
+ if (optionSlug) {
2456
+ params.set(`opt.${optionSlug}`, group.optionValueSlug);
2457
+ }
2458
+ }
2459
+ return params.size > 0 ? `${path}?${params.toString()}` : path;
2460
+ }
2461
+ function compareVariantOrder(a, b) {
2462
+ const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
2463
+ const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
2464
+ if (Number.isFinite(aOrder) && Number.isFinite(bOrder) && aOrder !== bOrder) {
2465
+ return aOrder - bOrder;
2466
+ }
2467
+ const aId = String(getRelationID(a.id) ?? "");
2468
+ const bId = String(getRelationID(b.id) ?? "");
2469
+ return aId.localeCompare(bId);
2470
+ }
2471
+ function isVariantAvailableForSale(variant) {
2472
+ if (variant.isActive === false) return false;
2473
+ if (variant.isUnlimited) return true;
2474
+ return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
2475
+ }
2476
+ function sortVariantsForMediaSelection(variants) {
2477
+ const orderedVariants = [...variants].sort(compareVariantOrder);
2478
+ const activeVariants = orderedVariants.filter(
2479
+ (variant) => variant.isActive !== false
2480
+ );
2481
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
2482
+ const unavailableActiveVariants = activeVariants.filter(
2483
+ (variant) => !isVariantAvailableForSale(variant)
2484
+ );
2485
+ return [...availableVariants, ...unavailableActiveVariants];
2486
+ }
2487
+ function variantHasPoolMedia(variant, productMediaPool) {
2488
+ if (!variant?.images?.length) return false;
2489
+ return variant.images.some((image) => {
2490
+ const imageId = getRelationID(image);
2491
+ return Boolean(
2492
+ imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
2493
+ );
2494
+ });
2495
+ }
2496
+ function getMinMax(values) {
2497
+ const numbers = values.filter(
2498
+ (value) => typeof value === "number"
2499
+ );
2500
+ if (numbers.length === 0) {
2501
+ return { min: null, max: null };
2502
+ }
2503
+ return {
2504
+ min: Math.min(...numbers),
2505
+ max: Math.max(...numbers)
2506
+ };
2507
+ }
2508
+ function buildProductListingProjection(product, variants) {
2509
+ const orderedVariants = [...variants].sort(compareVariantOrder);
2510
+ const activeVariants = orderedVariants.filter(
2511
+ (variant) => variant.isActive !== false
2512
+ );
2513
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
2514
+ const selectionHintVariant = availableVariants[0] ?? activeVariants[0] ?? null;
2515
+ const { min: minPrice, max: maxPrice } = getMinMax(
2516
+ activeVariants.map((variant) => variant.price)
2517
+ );
2518
+ const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
2519
+ activeVariants.map((variant) => variant.compareAtPrice)
2520
+ );
2521
+ const productMediaPool = product?.images ?? [];
2522
+ const selectionHintHasPoolMedia = variantHasPoolMedia(
2523
+ selectionHintVariant,
2524
+ productMediaPool
2525
+ );
2526
+ const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
2527
+ const resolvedProductMedia = resolveProductSelectionMedia({
2528
+ productMediaPool,
2529
+ productPrimaryMediaItemId: getRelationID(product?.primaryMediaItemId),
2530
+ selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
2531
+ id: selectionHintVariant.id,
2532
+ images: selectionHintVariant.images
2533
+ } : null,
2534
+ matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
2535
+ });
2536
+ return {
2537
+ selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
2538
+ primaryImage: resolveGenericListingPrimaryImage(
2539
+ product,
2540
+ resolvedProductMedia.primaryImage,
2541
+ resolvedProductMedia.source
2542
+ ),
2543
+ minPrice,
2544
+ maxPrice,
2545
+ minCompareAtPrice,
2546
+ maxCompareAtPrice,
2547
+ isPriceRange: minPrice !== null && maxPrice !== null ? minPrice !== maxPrice : false,
2548
+ availableForSale: availableVariants.length > 0
2549
+ };
2550
+ }
2551
+ function buildProductListingCard(item, options = {}) {
2552
+ const product = item.product;
2553
+ const groups = item.groups;
2554
+ const variants = getProductListingCardVariants(item);
2555
+ const projectedListing = buildProductListingProjection(product, variants);
2556
+ const selectionHintVariant = getRelationID(
2557
+ product.listing && "selectionHintVariant" in product.listing ? product.listing.selectionHintVariant : null
2558
+ ) ?? projectedListing.selectionHintVariant;
2559
+ const representativeVariant = findListingCardRepresentativeVariant(
2560
+ variants,
2561
+ selectionHintVariant
2562
+ );
2563
+ const productMediaPool = mediaArray2(product.images);
2564
+ const representativeHasPoolMedia = Boolean(
2565
+ representativeVariant?.images?.some(
2566
+ (image) => productMediaPool.some(
2567
+ (poolItem) => getRelationID(poolItem) === getRelationID(image)
2568
+ )
2569
+ )
2570
+ );
2571
+ const resolvedPrimaryMedia = resolveProductSelectionMedia(
2572
+ {
2573
+ productMediaPool,
2574
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
2575
+ selectedVariant: representativeHasPoolMedia ? representativeVariant : null,
2576
+ matchingVariants: representativeHasPoolMedia ? [] : variants
2577
+ }
2578
+ );
2579
+ const listingPrimaryPointer = resolveListingPrimaryImagePointer({
2580
+ productMediaPool,
2581
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
2582
+ productThumbnail: product.thumbnail ?? null,
2583
+ listingPrimaryImage: product.listing?.primaryImage ?? null,
2584
+ resolvedPrimary: resolvedPrimaryMedia.primaryImage,
2585
+ resolvedSource: resolvedPrimaryMedia.source
2586
+ });
2587
+ const listingPrimaryMedia = listingPrimaryPointer ? productMediaPool.find(
2588
+ (item2) => getRelationID(item2) === listingPrimaryPointer
2589
+ ) ?? resolvedPrimaryMedia.primaryImage ?? null : null;
2590
+ const priceRange = resolveListingCardPriceRange(
2591
+ product,
2592
+ projectedListing,
2593
+ groups
2594
+ );
2595
+ const availableForSale = product.listing?.availableForSale != null ? product.listing.availableForSale : groups.length > 0 ? groups.some((group) => group.listing.availableForSale) : projectedListing.availableForSale;
2596
+ const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
2597
+ return {
2598
+ id: String(product.id),
2599
+ href: buildProductHref(
2600
+ { slug: product.slug },
2601
+ { listing: { selectionHintVariant } },
2602
+ options
2603
+ ),
2604
+ title: product.title,
2605
+ representativeVariant,
2606
+ primaryImage: listingPrimaryMedia,
2607
+ priceRange,
2608
+ availableForSale,
2609
+ swatches
2610
+ };
2611
+ }
2612
+ function getProductListingCardVariants(item) {
2613
+ const productVariants = item.product.variants?.docs;
2614
+ if (Array.isArray(productVariants) && productVariants.length > 0) {
2615
+ return productVariants;
2616
+ }
2617
+ const variants = [];
2618
+ const seen = /* @__PURE__ */ new Set();
2619
+ for (const group of item.groups) {
2620
+ for (const variant of group.variants) {
2621
+ const id = getRelationID(variant.id);
2622
+ if (id && seen.has(id)) continue;
2623
+ if (id) seen.add(id);
2624
+ variants.push(variant);
2625
+ }
2626
+ }
2627
+ return variants;
2628
+ }
2629
+ function findListingCardRepresentativeVariant(variants, representativeVariantId) {
2630
+ if (representativeVariantId == null) return null;
2631
+ return variants.find(
2632
+ (variant) => getRelationID(variant.id) === representativeVariantId
2633
+ ) ?? null;
2634
+ }
2635
+ function hasCompleteListingPriceProjection(listing) {
2636
+ return listing?.minPrice != null && listing?.maxPrice != null;
2637
+ }
2638
+ function listingDefinesCompareAtProjection(listing) {
2639
+ return "minCompareAtPrice" in listing || "maxCompareAtPrice" in listing;
2640
+ }
2641
+ function resolveListingCardPriceRange(product, projectedListing, groups) {
2642
+ const listing = product.listing;
2643
+ if (hasCompleteListingPriceProjection(listing)) {
2644
+ const groupRange = groups.length > 0 ? aggregateListingPriceRange(groups) : null;
2645
+ const definesCompareAt = listingDefinesCompareAtProjection(listing);
2646
+ return {
2647
+ minPrice: listing.minPrice,
2648
+ maxPrice: listing.maxPrice,
2649
+ minCompareAtPrice: definesCompareAt ? listing.minCompareAtPrice ?? null : groupRange?.minCompareAtPrice ?? projectedListing.minCompareAtPrice,
2650
+ maxCompareAtPrice: definesCompareAt ? listing.maxCompareAtPrice ?? null : groupRange?.maxCompareAtPrice ?? projectedListing.maxCompareAtPrice,
2651
+ isPriceRange: listing.isPriceRange ?? listing.minPrice !== listing.maxPrice
2652
+ };
2653
+ }
2654
+ if (groups.length > 0) {
2655
+ return aggregateListingPriceRange(groups);
2656
+ }
2657
+ return {
2658
+ minPrice: projectedListing.minPrice,
2659
+ maxPrice: projectedListing.maxPrice,
2660
+ minCompareAtPrice: projectedListing.minCompareAtPrice,
2661
+ maxCompareAtPrice: projectedListing.maxCompareAtPrice,
2662
+ isPriceRange: projectedListing.isPriceRange
2663
+ };
2664
+ }
2665
+ function aggregateListingPriceRange(groups) {
2666
+ const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
2667
+ const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
2668
+ const minCompareAtPrice = minOfNullable(
2669
+ groups.map((g) => g.listing.minCompareAtPrice)
2670
+ );
2671
+ const maxCompareAtPrice = maxOfNullable(
2672
+ groups.map((g) => g.listing.maxCompareAtPrice)
2673
+ );
2674
+ const isPriceRange = minPrice !== null && maxPrice !== null && minPrice !== maxPrice;
2675
+ return {
2676
+ minPrice,
2677
+ maxPrice,
2678
+ minCompareAtPrice,
2679
+ maxCompareAtPrice,
2680
+ isPriceRange
2681
+ };
2682
+ }
2683
+ function buildListingSwatch(product, group, options) {
2684
+ return {
2685
+ optionId: group.optionId,
2686
+ optionValueId: group.optionValueId,
2687
+ label: group.optionValueLabel,
2688
+ swatch: group.optionValueSwatch ?? null,
2689
+ href: buildProductHref(
2690
+ { slug: product.slug },
2691
+ {
2692
+ optionId: group.optionId,
2693
+ optionSlug: group.optionSlug,
2694
+ optionValueId: group.optionValueId,
2695
+ optionValueSlug: group.optionValueSlug,
2696
+ listing: group.listing
2697
+ },
2698
+ options
2699
+ ),
2700
+ availableForSale: group.listing.availableForSale
2701
+ };
2702
+ }
2703
+ function minOfNullable(values) {
2704
+ const numbers = values.filter((v) => v !== null);
2705
+ return numbers.length === 0 ? null : Math.min(...numbers);
2706
+ }
2707
+ function maxOfNullable(values) {
2708
+ const numbers = values.filter((v) => v !== null);
2709
+ return numbers.length === 0 ? null : Math.max(...numbers);
2710
+ }
2711
+
1610
2712
  // src/core/api/product-api.ts
1611
2713
  var PRODUCT_DETAIL_UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
1612
2714
  "not_found",
@@ -1630,13 +2732,71 @@ function productDetailResultFromError(error) {
1630
2732
  if (!reason) return void 0;
1631
2733
  return { found: false, reason };
1632
2734
  }
2735
+ function nonEmpty(values) {
2736
+ return values?.length ? values : void 0;
2737
+ }
2738
+ function buildProductListingPageWhere(params) {
2739
+ const clauses = [];
2740
+ const search = params.search?.trim();
2741
+ if (search) {
2742
+ clauses.push({
2743
+ or: [
2744
+ { title: { like: search } },
2745
+ { slug: { like: search } },
2746
+ { handle: { like: search } }
2747
+ ]
2748
+ });
2749
+ }
2750
+ const filters = params.filters;
2751
+ const ids = nonEmpty(filters?.ids);
2752
+ if (ids) clauses.push({ id: { in: ids } });
2753
+ const slugs = nonEmpty(filters?.slugs);
2754
+ if (slugs) clauses.push({ slug: { in: slugs } });
2755
+ const handles = nonEmpty(filters?.handles);
2756
+ if (handles) clauses.push({ handle: { in: handles } });
2757
+ const categoryIds = nonEmpty(filters?.categoryIds);
2758
+ if (categoryIds) clauses.push({ categories: { in: categoryIds } });
2759
+ const tagIds = nonEmpty(filters?.tagIds);
2760
+ if (tagIds) clauses.push({ tags: { in: tagIds } });
2761
+ const minPrice = filters?.price?.min;
2762
+ if (minPrice != null) {
2763
+ clauses.push({ "listing.maxPrice": { greater_than_equal: minPrice } });
2764
+ }
2765
+ const maxPrice = filters?.price?.max;
2766
+ if (maxPrice != null) {
2767
+ clauses.push({ "listing.minPrice": { less_than_equal: maxPrice } });
2768
+ }
2769
+ if (filters?.availableForSale != null) {
2770
+ clauses.push({
2771
+ "listing.availableForSale": { equals: filters.availableForSale }
2772
+ });
2773
+ }
2774
+ if (clauses.length === 0) return void 0;
2775
+ if (clauses.length === 1) return clauses[0];
2776
+ return { and: clauses };
2777
+ }
2778
+ function buildProductListingPageUrl(params) {
2779
+ const options = {
2780
+ page: params.page,
2781
+ limit: params.limit,
2782
+ sort: params.sort,
2783
+ where: buildProductListingPageWhere(params)
2784
+ };
2785
+ return params.mode === "full" ? listingGroupsQueryUrl(options) : listingGroupsQueryCatalogUrl(options);
2786
+ }
2787
+ function withProductListingCards(response, options) {
2788
+ return {
2789
+ ...response,
2790
+ cards: response.docs.map((item) => buildProductListingCard(item, options))
2791
+ };
2792
+ }
1633
2793
  function rejectLegacyOptionValueSwatchColor(value) {
1634
2794
  if (Object.prototype.hasOwnProperty.call(
1635
2795
  value,
1636
2796
  "swatchColor"
1637
2797
  )) {
1638
2798
  throw new TypeError(
1639
- 'Product upsert option values no longer accept legacy flat "swatchColor"; use nested "swatch" instead.'
2799
+ 'Product upsert option values no longer accept removed flat "swatchColor"; use nested "swatch" instead.'
1640
2800
  );
1641
2801
  }
1642
2802
  }
@@ -1694,6 +2854,14 @@ var ProductApi = class extends BaseApi {
1694
2854
  { method: "GET" }
1695
2855
  );
1696
2856
  }
2857
+ async listingPage(params = {}) {
2858
+ if (params.mode === "full") {
2859
+ const response2 = await this.request(buildProductListingPageUrl(params), void 0, { method: "GET" });
2860
+ return withProductListingCards(response2, params);
2861
+ }
2862
+ const response = await this.request(buildProductListingPageUrl(params), void 0, { method: "GET" });
2863
+ return withProductListingCards(response, params);
2864
+ }
1697
2865
  /**
1698
2866
  * Fetch full product detail by slug or id.
1699
2867
  * Returns a discriminated result so storefronts can distinguish missing,
@@ -1965,6 +3133,7 @@ var ServerCommerceClient = class {
1965
3133
  this.product = {
1966
3134
  stockCheck: productApi.stockCheck.bind(productApi),
1967
3135
  listingGroups: productApi.listingGroups.bind(productApi),
3136
+ listingPage: productApi.listingPage.bind(productApi),
1968
3137
  detail: productApi.detail.bind(productApi),
1969
3138
  previewDetail: productApi.previewDetail.bind(productApi),
1970
3139
  upsert: productApi.upsert.bind(productApi)