@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.js CHANGED
@@ -606,6 +606,32 @@ function listingGroupsQuery(params) {
606
606
  function listingGroupsCatalogQuery(params) {
607
607
  return `/api/products/listing-groups/catalog?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
608
608
  }
609
+ function appendListingGroupsQuerySearchParams(search, options) {
610
+ if (options?.page != null) search.set("page", String(options.page));
611
+ if (options?.limit != null) search.set("limit", String(options.limit));
612
+ if (options?.sort != null) {
613
+ const sort = options.sort;
614
+ search.set(
615
+ "sort",
616
+ Array.isArray(sort) ? sort.join(",") : sort
617
+ );
618
+ }
619
+ if (options?.where != null) {
620
+ search.set("whereJson", JSON.stringify(options.where));
621
+ }
622
+ }
623
+ function listingGroupsQueryUrl(options) {
624
+ const search = new URLSearchParams();
625
+ appendListingGroupsQuerySearchParams(search, options);
626
+ const query = search.toString();
627
+ return `/api/products/listing-groups/query${query ? `?${query}` : ""}`;
628
+ }
629
+ function listingGroupsQueryCatalogUrl(options) {
630
+ const search = new URLSearchParams();
631
+ appendListingGroupsQuerySearchParams(search, options);
632
+ const query = search.toString();
633
+ return `/api/products/listing-groups/query/catalog${query ? `?${query}` : ""}`;
634
+ }
609
635
  function stockSnapshotQuery(params) {
610
636
  return `/api/products/stock?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
611
637
  }
@@ -1221,7 +1247,8 @@ var CommunityClient = class extends CustomerScopedApi {
1221
1247
  const urlParams = new URLSearchParams();
1222
1248
  const sort = params?.sort ?? DEFAULT_POST_LIST_SORT;
1223
1249
  urlParams.set("sort", sort);
1224
- if (params?.limit !== void 0) urlParams.set("limit", String(params.limit));
1250
+ if (params?.limit !== void 0)
1251
+ urlParams.set("limit", String(params.limit));
1225
1252
  if (params?.page !== void 0) urlParams.set("page", String(params.page));
1226
1253
  if (params?.categoryId !== void 0) {
1227
1254
  urlParams.set("where[categories][in]", params.categoryId);
@@ -1229,7 +1256,7 @@ var CommunityClient = class extends CustomerScopedApi {
1229
1256
  if (params?.tagId !== void 0) {
1230
1257
  urlParams.set("where[tags][in]", params.tagId);
1231
1258
  }
1232
- return `/api/posts?${urlParams.toString()}`;
1259
+ return `/api/posts/public?${urlParams.toString()}`;
1233
1260
  }
1234
1261
  buildCommentsListQuery(params) {
1235
1262
  const urlParams = new URLSearchParams();
@@ -1249,7 +1276,7 @@ var CommunityClient = class extends CustomerScopedApi {
1249
1276
  }
1250
1277
  if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
1251
1278
  if (params.page !== void 0) urlParams.set("page", String(params.page));
1252
- return `/api/comments?${urlParams.toString()}`;
1279
+ return `/api/comments/public?${urlParams.toString()}`;
1253
1280
  }
1254
1281
  async execute(endpoint, method, body) {
1255
1282
  return this.request(endpoint, { method, body });
@@ -1259,7 +1286,11 @@ var CommunityClient = class extends CustomerScopedApi {
1259
1286
  return unwrapPayloadDoc(response);
1260
1287
  }
1261
1288
  createPost(params) {
1262
- return this.executeDoc("/api/posts", "POST", params);
1289
+ return this.executeDoc(
1290
+ "/api/posts",
1291
+ "POST",
1292
+ params
1293
+ );
1263
1294
  }
1264
1295
  /**
1265
1296
  * Public post feed. Server applies the same visibility contract as
@@ -1316,7 +1347,11 @@ var CommunityClient = class extends CustomerScopedApi {
1316
1347
  if (parentId !== void 0) {
1317
1348
  body.parent = parentId;
1318
1349
  }
1319
- return this.executeDoc("/api/comments", "POST", body);
1350
+ return this.executeDoc(
1351
+ "/api/comments",
1352
+ "POST",
1353
+ body
1354
+ );
1320
1355
  }
1321
1356
  /**
1322
1357
  * List comments for a post.
@@ -1385,10 +1420,14 @@ var CommunityClient = class extends CustomerScopedApi {
1385
1420
  400
1386
1421
  );
1387
1422
  }
1388
- return this.executeDoc("/api/reactions", "POST", {
1389
- post: postId,
1390
- type: reactionType
1391
- });
1423
+ return this.executeDoc(
1424
+ "/api/reactions",
1425
+ "POST",
1426
+ {
1427
+ post: postId,
1428
+ type: reactionType
1429
+ }
1430
+ );
1392
1431
  }
1393
1432
  removeReaction(params) {
1394
1433
  const { postId, type } = params;
@@ -1407,10 +1446,14 @@ var CommunityClient = class extends CustomerScopedApi {
1407
1446
  400
1408
1447
  );
1409
1448
  }
1410
- return this.executeDoc("/api/reactions", "POST", {
1411
- comment: commentId,
1412
- type: reactionType
1413
- });
1449
+ return this.executeDoc(
1450
+ "/api/reactions",
1451
+ "POST",
1452
+ {
1453
+ comment: commentId,
1454
+ type: reactionType
1455
+ }
1456
+ );
1414
1457
  }
1415
1458
  removeCommentReaction(params) {
1416
1459
  const { commentId, type } = params;
@@ -1439,9 +1482,13 @@ var CommunityClient = class extends CustomerScopedApi {
1439
1482
  }
1440
1483
  // Bookmarks
1441
1484
  addBookmark(params) {
1442
- return this.executeDoc("/api/bookmarks", "POST", {
1443
- post: params.postId
1444
- });
1485
+ return this.executeDoc(
1486
+ "/api/bookmarks",
1487
+ "POST",
1488
+ {
1489
+ post: params.postId
1490
+ }
1491
+ );
1445
1492
  }
1446
1493
  removeBookmark(params) {
1447
1494
  return this.execute(
@@ -1457,10 +1504,7 @@ var CommunityClient = class extends CustomerScopedApi {
1457
1504
  }
1458
1505
  // Profiles
1459
1506
  listProfileLists(params) {
1460
- return this.execute(
1461
- `/api/customer-profile-lists${this.buildQuery(params)}`,
1462
- "GET"
1463
- );
1507
+ return this.execute(`/api/customer-profile-lists${this.buildQuery(params)}`, "GET");
1464
1508
  }
1465
1509
  async getProfileList(params) {
1466
1510
  const query = "slug" in params ? `?where[slug][equals]=${encodeURIComponent(params.slug)}&limit=1` : `?where[id][equals]=${encodeURIComponent(params.id)}&limit=1`;
@@ -1542,7 +1586,10 @@ var CartApi = class extends CustomerScopedApi {
1542
1586
  return this.request(endpoint, { method, body });
1543
1587
  }
1544
1588
  getCart(cartId) {
1545
- return this.execute(`/api/carts/${cartId}`, "GET");
1589
+ return this.execute(
1590
+ `/api/carts/${cartId}?depth=0&joins=false`,
1591
+ "GET"
1592
+ );
1546
1593
  }
1547
1594
  addItem(params) {
1548
1595
  return this.execute("/api/carts/add-item", "POST", params);
@@ -1572,6 +1619,1061 @@ var CartApi = class extends CustomerScopedApi {
1572
1619
  }
1573
1620
  };
1574
1621
 
1622
+ // src/utils/product-selection-media.ts
1623
+ function selectedSwatchMediaItemId(swatch) {
1624
+ if (!swatch || swatch.type !== "media") return null;
1625
+ const id = swatch.mediaItemId;
1626
+ if (id == null || id === "") return null;
1627
+ return String(id);
1628
+ }
1629
+ function getMediaId(value) {
1630
+ if (typeof value === "string" || typeof value === "number") {
1631
+ return String(value);
1632
+ }
1633
+ if (typeof value === "object" && value !== null && "id" in value) {
1634
+ const id = value.id;
1635
+ if (typeof id === "string" || typeof id === "number") return String(id);
1636
+ }
1637
+ return null;
1638
+ }
1639
+ function toPointerId(value) {
1640
+ return getMediaId(value);
1641
+ }
1642
+ function mediaArray(value) {
1643
+ if (!Array.isArray(value)) return [];
1644
+ return value.filter((entry) => entry != null);
1645
+ }
1646
+ function uniqueWithPrimaryFirst(primary, items) {
1647
+ const unique = /* @__PURE__ */ new Map();
1648
+ for (const item of items) {
1649
+ const id = getMediaId(item);
1650
+ const key = id ?? `inline:${unique.size}`;
1651
+ if (!unique.has(key)) unique.set(key, item);
1652
+ }
1653
+ if (primary) {
1654
+ const primaryId = getMediaId(primary);
1655
+ const prefixed = /* @__PURE__ */ new Map();
1656
+ const primaryKey = primaryId ?? "inline:primary";
1657
+ prefixed.set(primaryKey, primary);
1658
+ for (const [key, value] of unique.entries()) {
1659
+ if (!prefixed.has(key)) prefixed.set(key, value);
1660
+ }
1661
+ return Array.from(prefixed.values());
1662
+ }
1663
+ return Array.from(unique.values());
1664
+ }
1665
+ function buildPoolById(pool) {
1666
+ const poolById = /* @__PURE__ */ new Map();
1667
+ for (const item of pool) {
1668
+ const id = getMediaId(item);
1669
+ if (id) poolById.set(id, item);
1670
+ }
1671
+ return poolById;
1672
+ }
1673
+ function resolveVariantImageItems(variant, poolById) {
1674
+ if (!variant || !Array.isArray(variant.images)) return [];
1675
+ const resolved = [];
1676
+ for (const entry of variant.images) {
1677
+ if (entry == null) continue;
1678
+ if (typeof entry === "string" || typeof entry === "number") {
1679
+ const pooled = poolById.get(String(entry));
1680
+ if (pooled) resolved.push(pooled);
1681
+ continue;
1682
+ }
1683
+ const mediaId = getMediaId(entry);
1684
+ if (mediaId) {
1685
+ const pooled = poolById.get(mediaId);
1686
+ if (pooled) {
1687
+ resolved.push(pooled);
1688
+ }
1689
+ continue;
1690
+ }
1691
+ }
1692
+ return resolved;
1693
+ }
1694
+ function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
1695
+ if (!selectedOptionValues?.length) return null;
1696
+ for (const optionValue of selectedOptionValues) {
1697
+ const swatch = optionValue?.swatch;
1698
+ const swatchPointer = selectedSwatchMediaItemId(swatch);
1699
+ if (swatchPointer) {
1700
+ const pooled = poolById.get(swatchPointer);
1701
+ if (pooled) return pooled;
1702
+ }
1703
+ const inline = swatch?.inlineMedia;
1704
+ if (inline != null && typeof inline === "object") {
1705
+ return inline;
1706
+ }
1707
+ }
1708
+ return null;
1709
+ }
1710
+ function resolveProductSelectionMedia(input) {
1711
+ const pool = mediaArray(input.productMediaPool);
1712
+ const poolById = buildPoolById(pool);
1713
+ const selectedVariantImages = resolveVariantImageItems(
1714
+ input.selectedVariant,
1715
+ poolById
1716
+ );
1717
+ if (selectedVariantImages.length > 0) {
1718
+ const primaryImage = selectedVariantImages[0] ?? null;
1719
+ return {
1720
+ primaryImage,
1721
+ images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
1722
+ source: "variant_media_selected"
1723
+ };
1724
+ }
1725
+ if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
1726
+ const mergedMatchingImages = [];
1727
+ for (const matchingVariant of input.matchingVariants ?? []) {
1728
+ mergedMatchingImages.push(
1729
+ ...resolveVariantImageItems(matchingVariant, poolById)
1730
+ );
1731
+ }
1732
+ if (mergedMatchingImages.length > 0) {
1733
+ const primaryImage = mergedMatchingImages[0] ?? null;
1734
+ return {
1735
+ primaryImage,
1736
+ images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
1737
+ source: "variant_media_matching"
1738
+ };
1739
+ }
1740
+ }
1741
+ const optionSwatchPrimary = resolveOptionSwatchPrimary(
1742
+ input.selectedOptionValues,
1743
+ poolById
1744
+ );
1745
+ if (optionSwatchPrimary) {
1746
+ return {
1747
+ primaryImage: optionSwatchPrimary,
1748
+ images: [optionSwatchPrimary],
1749
+ source: "option_swatch"
1750
+ };
1751
+ }
1752
+ return {
1753
+ primaryImage: null,
1754
+ images: [],
1755
+ source: "none"
1756
+ };
1757
+ }
1758
+ function resolveListingPrimaryImagePointer(input) {
1759
+ const pool = mediaArray(input.productMediaPool);
1760
+ const resolvedPointer = getMediaId(input.resolvedPrimary);
1761
+ if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
1762
+ return resolvedPointer;
1763
+ }
1764
+ const poolById = buildPoolById(pool);
1765
+ const listingPointer = getMediaId(input.listingPrimaryImage);
1766
+ if (listingPointer && poolById.has(listingPointer)) {
1767
+ return listingPointer;
1768
+ }
1769
+ const primaryPointer = toPointerId(input.productPrimaryMediaItemId);
1770
+ if (primaryPointer && poolById.has(primaryPointer)) return primaryPointer;
1771
+ const thumbnailPointer = getMediaId(input.productThumbnail);
1772
+ if (thumbnailPointer && poolById.has(thumbnailPointer)) {
1773
+ return thumbnailPointer;
1774
+ }
1775
+ if (pool.length > 0) {
1776
+ const firstPoolId = getMediaId(pool[0]);
1777
+ if (firstPoolId) return firstPoolId;
1778
+ }
1779
+ return null;
1780
+ }
1781
+
1782
+ // src/utils/ecommerce.ts
1783
+ var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
1784
+ function resolveProductSelectionUrlEmit(emit) {
1785
+ return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
1786
+ }
1787
+ function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
1788
+ const option = matrix.optionById.get(optionId);
1789
+ const value = matrix.valueById.get(valueId);
1790
+ if (!option?.slug || !value?.slug) return false;
1791
+ const slugMatches = option.values.filter(
1792
+ (candidate) => candidate.slug === value.slug
1793
+ );
1794
+ if (slugMatches.length !== 1) return false;
1795
+ params.append(`opt.${option.slug}`, value.slug);
1796
+ return true;
1797
+ }
1798
+ function appendCanonicalSelectionParam(params, optionId, valueId) {
1799
+ params.append(`opt.${optionId}`, valueId);
1800
+ }
1801
+ var ProductSelectionCodecError = class extends Error {
1802
+ constructor(message) {
1803
+ super(message);
1804
+ this.code = "ambiguous_product_selection_query";
1805
+ this.name = "ProductSelectionCodecError";
1806
+ }
1807
+ };
1808
+ function getRelationID(value) {
1809
+ if (typeof value === "string") return value;
1810
+ if (typeof value === "number") return String(value);
1811
+ if (value && typeof value === "object" && "id" in value) {
1812
+ const id = value.id;
1813
+ if (typeof id === "string") return id;
1814
+ if (typeof id === "number") return String(id);
1815
+ }
1816
+ return void 0;
1817
+ }
1818
+ function extractEntityId(value) {
1819
+ if (typeof value === "string") return value;
1820
+ if (typeof value === "number") return String(value);
1821
+ if (value && typeof value === "object" && "id" in value) {
1822
+ if (typeof value.id === "string") return value.id;
1823
+ if (typeof value.id === "number") return String(value.id);
1824
+ }
1825
+ return null;
1826
+ }
1827
+ function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
1828
+ return resolveListingPrimaryImagePointer({
1829
+ productMediaPool: product?.images ?? [],
1830
+ productPrimaryMediaItemId: getRelationID(
1831
+ product?.primaryMediaItemId ?? null
1832
+ ),
1833
+ productThumbnail: product?.thumbnail ?? null,
1834
+ listingPrimaryImage: product?.listing?.primaryImage ?? null,
1835
+ resolvedPrimary,
1836
+ resolvedSource
1837
+ });
1838
+ }
1839
+ function normalizeProductOptionValueSwatch(swatch) {
1840
+ if (swatch == null) return null;
1841
+ if (typeof swatch !== "object") return null;
1842
+ const raw = swatch;
1843
+ const mediaItemId = extractEntityId(raw.mediaItemId);
1844
+ const color = typeof raw.color === "string" ? raw.color.trim() : "";
1845
+ const hasColor = color.length > 0;
1846
+ if (raw.type === "media" || mediaItemId && raw.type !== "color") {
1847
+ if (!mediaItemId || hasColor) return null;
1848
+ return {
1849
+ type: "media",
1850
+ mediaItemId,
1851
+ color: null
1852
+ };
1853
+ }
1854
+ if (raw.type === "color" || hasColor) {
1855
+ if (mediaItemId || !hasColor) return null;
1856
+ return { type: "color", color, mediaItemId: null };
1857
+ }
1858
+ return null;
1859
+ }
1860
+ function matrixOrder(index) {
1861
+ return String(index).padStart(6, "0");
1862
+ }
1863
+ function buildProductOptionMatrixFromDetail(detail) {
1864
+ const normalizedOptions = detail.options.map((option, optionIndex) => ({
1865
+ id: String(option.id),
1866
+ title: option.title || String(option.id),
1867
+ slug: option.slug,
1868
+ order: matrixOrder(optionIndex),
1869
+ values: option.values.map((value, valueIndex) => ({
1870
+ id: String(value.id),
1871
+ optionId: String(option.id),
1872
+ optionSlug: option.slug,
1873
+ label: value.value || value.slug || String(value.id),
1874
+ slug: value.slug,
1875
+ swatch: normalizeProductOptionValueSwatch(value.swatch),
1876
+ order: matrixOrder(valueIndex)
1877
+ }))
1878
+ }));
1879
+ const optionById = new Map(
1880
+ normalizedOptions.map((option) => [option.id, option])
1881
+ );
1882
+ const optionBySlug = new Map(
1883
+ normalizedOptions.map((option) => [option.slug, option])
1884
+ );
1885
+ const valueById = /* @__PURE__ */ new Map();
1886
+ const valueToOptionId = /* @__PURE__ */ new Map();
1887
+ const valueToOptionSlug = /* @__PURE__ */ new Map();
1888
+ for (const option of normalizedOptions) {
1889
+ for (const value of option.values) {
1890
+ valueById.set(value.id, value);
1891
+ valueToOptionId.set(value.id, option.id);
1892
+ valueToOptionSlug.set(value.id, option.slug);
1893
+ }
1894
+ }
1895
+ const optionIds = normalizedOptions.map((option) => option.id);
1896
+ const optionSlugs = normalizedOptions.map((option) => option.slug);
1897
+ const normalizedVariants = detail.variants.map((variant) => {
1898
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
1899
+ const optionValueByOptionSlug = /* @__PURE__ */ new Map();
1900
+ for (const rawValue of variant.optionValues) {
1901
+ const optionId = String(rawValue.optionId);
1902
+ const valueId = String(rawValue.valueId);
1903
+ const optionSlug = rawValue.optionSlug;
1904
+ if (!optionById.has(optionId)) continue;
1905
+ if (valueToOptionId.get(valueId) !== optionId) continue;
1906
+ if (optionValueByOptionId.has(optionId)) continue;
1907
+ optionValueByOptionId.set(optionId, valueId);
1908
+ if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
1909
+ optionValueByOptionSlug.set(optionSlug, valueId);
1910
+ }
1911
+ }
1912
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
1913
+ return {
1914
+ id: String(variant.id),
1915
+ optionValueIds,
1916
+ optionValueByOptionId,
1917
+ optionValueByOptionSlug,
1918
+ source: variant
1919
+ };
1920
+ });
1921
+ return {
1922
+ options: normalizedOptions,
1923
+ optionIds,
1924
+ optionSlugs,
1925
+ optionById,
1926
+ optionBySlug,
1927
+ valueById,
1928
+ valueToOptionId,
1929
+ valueToOptionSlug,
1930
+ variants: normalizedVariants
1931
+ };
1932
+ }
1933
+ function getVariantSelection(matrix, variantId) {
1934
+ if (variantId == null) return void 0;
1935
+ const id = String(variantId);
1936
+ return matrix.variants.find((variant) => variant.id === id);
1937
+ }
1938
+ function hasExplicitSelection(selection) {
1939
+ return Boolean(
1940
+ selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
1941
+ );
1942
+ }
1943
+ function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
1944
+ if (valueId == null) return false;
1945
+ const normalizedValueId = String(valueId);
1946
+ if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
1947
+ selectedByOptionId.set(optionId, normalizedValueId);
1948
+ return true;
1949
+ }
1950
+ function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
1951
+ if (!valueSlug) return false;
1952
+ const option = matrix.optionById.get(optionId);
1953
+ if (!option) return false;
1954
+ const values = option.values.filter(
1955
+ (candidate) => candidate.slug === valueSlug
1956
+ );
1957
+ if (values.length > 1) {
1958
+ throw new ProductSelectionCodecError(
1959
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
1960
+ );
1961
+ }
1962
+ const value = values[0];
1963
+ if (!value) return false;
1964
+ selectedByOptionId.set(optionId, value.id);
1965
+ return true;
1966
+ }
1967
+ function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
1968
+ if (!valueSlug) return false;
1969
+ const option = matrix.optionBySlug.get(optionSlug);
1970
+ if (!option) return false;
1971
+ const values = option.values.filter(
1972
+ (candidate) => candidate.slug === valueSlug
1973
+ );
1974
+ if (values.length > 1) {
1975
+ throw new ProductSelectionCodecError(
1976
+ `Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
1977
+ );
1978
+ }
1979
+ const value = values[0];
1980
+ if (!value) return false;
1981
+ selectedByOptionId.set(option.id, value.id);
1982
+ return true;
1983
+ }
1984
+ function toSearchParams(search) {
1985
+ if (!search) return new URLSearchParams();
1986
+ if (search instanceof URLSearchParams) return new URLSearchParams(search);
1987
+ if (search instanceof URL) return new URLSearchParams(search.searchParams);
1988
+ const trimmed = search.trim();
1989
+ if (!trimmed) return new URLSearchParams();
1990
+ try {
1991
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
1992
+ return new URL(trimmed).searchParams;
1993
+ }
1994
+ } catch {
1995
+ return new URLSearchParams();
1996
+ }
1997
+ return new URLSearchParams(
1998
+ trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
1999
+ );
2000
+ }
2001
+ function slugLike(value) {
2002
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2003
+ }
2004
+ function assertNoAmbiguousSelectionParams(matrix, params) {
2005
+ const knownSelectionKeys = /* @__PURE__ */ new Set();
2006
+ const hasVariantParam = params.has("variant");
2007
+ const hasOptionParams = Array.from(params.keys()).some(
2008
+ (key) => key.startsWith("opt.")
2009
+ );
2010
+ if (hasVariantParam && hasOptionParams) {
2011
+ throw new ProductSelectionCodecError(
2012
+ "Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
2013
+ );
2014
+ }
2015
+ for (const option of matrix.options) {
2016
+ knownSelectionKeys.add(slugLike(option.slug));
2017
+ knownSelectionKeys.add(slugLike(option.title));
2018
+ for (const value of option.values) {
2019
+ if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
2020
+ }
2021
+ }
2022
+ for (const [key, value] of params.entries()) {
2023
+ if (key.startsWith("opt.")) {
2024
+ const optionToken = key.slice(4);
2025
+ if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
2026
+ throw new ProductSelectionCodecError(
2027
+ `Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2028
+ );
2029
+ }
2030
+ if (!value) {
2031
+ throw new ProductSelectionCodecError(
2032
+ `Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
2033
+ );
2034
+ }
2035
+ continue;
2036
+ }
2037
+ if (key === "variant") {
2038
+ if (!value) {
2039
+ throw new ProductSelectionCodecError(
2040
+ 'Product selection query parameter "variant" requires a variant ID.'
2041
+ );
2042
+ }
2043
+ if (!getVariantSelection(matrix, value)) {
2044
+ throw new ProductSelectionCodecError(
2045
+ `Unknown product selection variant "${value}".`
2046
+ );
2047
+ }
2048
+ continue;
2049
+ }
2050
+ const keyToken = slugLike(key);
2051
+ if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
2052
+ throw new ProductSelectionCodecError(
2053
+ `Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
2054
+ );
2055
+ }
2056
+ }
2057
+ }
2058
+ function emitCompatibilityOptionIdParam(options, event) {
2059
+ try {
2060
+ if (options?.onCompatibilityOptionIdParam) {
2061
+ options.onCompatibilityOptionIdParam(event);
2062
+ return;
2063
+ }
2064
+ options?.onLegacyOptionIdParam?.(event);
2065
+ } catch {
2066
+ }
2067
+ }
2068
+ function assignSearchSelection(matrix, selectedByOptionId, search, options) {
2069
+ if (!search) return null;
2070
+ const params = toSearchParams(search);
2071
+ assertNoAmbiguousSelectionParams(matrix, params);
2072
+ const variantParam = params.get("variant");
2073
+ if (variantParam != null) {
2074
+ const variantSelection = getVariantSelection(matrix, variantParam);
2075
+ if (!variantSelection) {
2076
+ throw new ProductSelectionCodecError(
2077
+ `Unknown product selection variant "${variantParam}".`
2078
+ );
2079
+ }
2080
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2081
+ selectedByOptionId.set(optionId, valueId);
2082
+ }
2083
+ return variantSelection.id;
2084
+ }
2085
+ for (const [key, valueToken] of params.entries()) {
2086
+ if (!key.startsWith("opt.")) continue;
2087
+ const optionToken = key.slice(4);
2088
+ const optionById = matrix.optionById.get(optionToken);
2089
+ if (optionById) {
2090
+ if (assignSelectedValue(
2091
+ matrix,
2092
+ selectedByOptionId,
2093
+ optionById.id,
2094
+ valueToken
2095
+ )) {
2096
+ continue;
2097
+ }
2098
+ const before = selectedByOptionId.get(optionById.id);
2099
+ if (assignSelectedValueSlugByOptionId(
2100
+ matrix,
2101
+ selectedByOptionId,
2102
+ optionById.id,
2103
+ valueToken
2104
+ ) && selectedByOptionId.get(optionById.id) !== before) {
2105
+ emitCompatibilityOptionIdParam(options, {
2106
+ optionId: optionById.id,
2107
+ optionSlug: optionById.slug,
2108
+ valueSlug: valueToken,
2109
+ searchParam: key
2110
+ });
2111
+ continue;
2112
+ }
2113
+ throw new ProductSelectionCodecError(
2114
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2115
+ );
2116
+ }
2117
+ const optionBySlug = matrix.optionBySlug.get(optionToken);
2118
+ if (optionBySlug) {
2119
+ if (assignSelectedValueSlugByOptionSlug(
2120
+ matrix,
2121
+ selectedByOptionId,
2122
+ optionBySlug.slug,
2123
+ valueToken
2124
+ )) {
2125
+ continue;
2126
+ }
2127
+ if (matrix.valueById.has(valueToken)) {
2128
+ throw new ProductSelectionCodecError(
2129
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
2130
+ );
2131
+ }
2132
+ throw new ProductSelectionCodecError(
2133
+ `Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
2134
+ );
2135
+ }
2136
+ }
2137
+ return null;
2138
+ }
2139
+ function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
2140
+ const selectedByOptionId = /* @__PURE__ */ new Map();
2141
+ const variantSelection = getVariantSelection(matrix, selection.variantId);
2142
+ const variantId = variantSelection?.id ?? null;
2143
+ if (variantSelection) {
2144
+ for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
2145
+ selectedByOptionId.set(optionId, valueId);
2146
+ }
2147
+ }
2148
+ for (const rawValueId of selection.valueIds ?? []) {
2149
+ const valueId = getRelationID(rawValueId);
2150
+ if (!valueId) continue;
2151
+ const optionId = matrix.valueToOptionId.get(valueId);
2152
+ if (!optionId) continue;
2153
+ selectedByOptionId.set(optionId, valueId);
2154
+ }
2155
+ const searchVariantId = assignSearchSelection(
2156
+ matrix,
2157
+ selectedByOptionId,
2158
+ selection.search,
2159
+ options
2160
+ );
2161
+ for (const [rawOptionId, rawSelection] of Object.entries(
2162
+ selection.byOptionId ?? {}
2163
+ )) {
2164
+ const optionId = String(rawOptionId);
2165
+ if (!matrix.optionById.has(optionId)) continue;
2166
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2167
+ assignSelectedValue(
2168
+ matrix,
2169
+ selectedByOptionId,
2170
+ optionId,
2171
+ rawSelection.valueId
2172
+ );
2173
+ continue;
2174
+ }
2175
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2176
+ assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
2177
+ continue;
2178
+ }
2179
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2180
+ assignSelectedValueSlugByOptionId(
2181
+ matrix,
2182
+ selectedByOptionId,
2183
+ optionId,
2184
+ rawSelection.valueSlug
2185
+ );
2186
+ }
2187
+ }
2188
+ for (const [rawOptionSlug, rawSelection] of Object.entries(
2189
+ selection.byOptionSlug ?? {}
2190
+ )) {
2191
+ const optionSlug = String(rawOptionSlug);
2192
+ if (!matrix.optionBySlug.has(optionSlug)) continue;
2193
+ if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
2194
+ const option = matrix.optionBySlug.get(optionSlug);
2195
+ if (option) {
2196
+ assignSelectedValue(
2197
+ matrix,
2198
+ selectedByOptionId,
2199
+ option.id,
2200
+ rawSelection.valueId
2201
+ );
2202
+ }
2203
+ continue;
2204
+ }
2205
+ if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
2206
+ assignSelectedValueSlugByOptionSlug(
2207
+ matrix,
2208
+ selectedByOptionId,
2209
+ optionSlug,
2210
+ rawSelection.valueSlug
2211
+ );
2212
+ continue;
2213
+ }
2214
+ if (typeof rawSelection === "string" || typeof rawSelection === "number") {
2215
+ assignSelectedValueSlugByOptionSlug(
2216
+ matrix,
2217
+ selectedByOptionId,
2218
+ optionSlug,
2219
+ String(rawSelection)
2220
+ );
2221
+ }
2222
+ }
2223
+ const byOptionId = Object.fromEntries(
2224
+ matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
2225
+ );
2226
+ const byOptionSlug = Object.fromEntries(
2227
+ matrix.options.map((option) => {
2228
+ const valueId = selectedByOptionId.get(option.id);
2229
+ const value = valueId ? matrix.valueById.get(valueId) : void 0;
2230
+ return [option.slug, value?.slug ?? void 0];
2231
+ }).filter((entry) => Boolean(entry[1]))
2232
+ );
2233
+ return {
2234
+ byOptionSlug,
2235
+ byOptionId,
2236
+ valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
2237
+ variantId: searchVariantId ?? variantId
2238
+ };
2239
+ }
2240
+ function stringifyProductSelection(detail, selection = {}, options) {
2241
+ const matrix = buildProductOptionMatrixFromDetail(detail);
2242
+ const normalized = normalizeProductSelectionFromMatrix(
2243
+ matrix,
2244
+ selection,
2245
+ options
2246
+ );
2247
+ const params = new URLSearchParams();
2248
+ if (hasExplicitSelection(selection)) {
2249
+ const matchingVariants = getMatchingVariantEntries(matrix, normalized);
2250
+ const exactVariant = getExactSelectedVariantEntry(
2251
+ matrix,
2252
+ normalized,
2253
+ matchingVariants
2254
+ );
2255
+ if (exactVariant) {
2256
+ params.set("variant", exactVariant.id);
2257
+ return params.toString();
2258
+ }
2259
+ }
2260
+ const emit = resolveProductSelectionUrlEmit(options?.emit);
2261
+ for (const optionId of matrix.optionIds) {
2262
+ const valueId = normalized.byOptionId[optionId];
2263
+ if (!valueId) continue;
2264
+ if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
2265
+ continue;
2266
+ }
2267
+ if (emit === "slug-compat") {
2268
+ if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
2269
+ continue;
2270
+ }
2271
+ }
2272
+ appendCanonicalSelectionParam(params, optionId, valueId);
2273
+ }
2274
+ return params.toString();
2275
+ }
2276
+ function selectedEntries(selection) {
2277
+ return Object.entries(selection.byOptionId);
2278
+ }
2279
+ function getMatchingVariantEntries(matrix, selection) {
2280
+ const entries = selectedEntries(selection);
2281
+ if (entries.length === 0) return matrix.variants;
2282
+ return matrix.variants.filter(
2283
+ (variant) => entries.every(
2284
+ ([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
2285
+ )
2286
+ );
2287
+ }
2288
+ function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
2289
+ if (matrix.optionIds.length === 0) {
2290
+ return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
2291
+ }
2292
+ const allOptionsSelected = matrix.optionIds.every(
2293
+ (optionId) => Boolean(selection.byOptionId[optionId])
2294
+ );
2295
+ if (!allOptionsSelected) return null;
2296
+ return matchingVariants.find(
2297
+ (variant) => matrix.optionIds.every(
2298
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
2299
+ )
2300
+ ) ?? null;
2301
+ }
2302
+ function isPresentMedia(value) {
2303
+ return value != null;
2304
+ }
2305
+ function mediaArray2(values) {
2306
+ if (!Array.isArray(values)) return [];
2307
+ return values.filter(isPresentMedia);
2308
+ }
2309
+ function getProductHrefSlug(product) {
2310
+ if ("product" in product && product.product?.slug) {
2311
+ return product.product.slug;
2312
+ }
2313
+ if ("slug" in product && product.slug) return product.slug;
2314
+ throw new ProductSelectionCodecError(
2315
+ "Product slug is required to build a product href."
2316
+ );
2317
+ }
2318
+ function joinProductPath(basePath, slug, trailingSlash) {
2319
+ const base = basePath.replace(/\/+$/, "");
2320
+ const encodedSlug = encodeURIComponent(slug);
2321
+ return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
2322
+ }
2323
+ function getProductHrefGroupSelection(group, matrix) {
2324
+ if (!group) return null;
2325
+ if (group.variantId != null) return { variantId: group.variantId };
2326
+ const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
2327
+ if (!optionId) return null;
2328
+ const option = matrix?.optionById.get(optionId);
2329
+ const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
2330
+ if (!optionValueId) return null;
2331
+ return { byOptionId: { [optionId]: optionValueId } };
2332
+ }
2333
+ function getProductHrefCodecOptions(options) {
2334
+ return options.emit != null ? { emit: options.emit } : void 0;
2335
+ }
2336
+ function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
2337
+ const emit = resolveProductSelectionUrlEmit(options.emit);
2338
+ const byOptionId = groupSelection.byOptionId ?? {};
2339
+ const entries = Object.entries(byOptionId);
2340
+ if (entries.length === 0) return false;
2341
+ for (const [optionId, valueId] of entries) {
2342
+ const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
2343
+ const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
2344
+ if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
2345
+ params.set(`opt.${optionSlug}`, valueSlug);
2346
+ return true;
2347
+ }
2348
+ if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
2349
+ params,
2350
+ options.matrix,
2351
+ optionId,
2352
+ String(valueId)
2353
+ )) {
2354
+ continue;
2355
+ }
2356
+ appendCanonicalSelectionParam(params, optionId, String(valueId));
2357
+ }
2358
+ return params.size > 0;
2359
+ }
2360
+ function getPreferCompleteVariantFromHintSelection(group, options) {
2361
+ if (!options.preferCompleteVariantFromHint) return null;
2362
+ if (group?.optionValueId != null || group?.optionValueSlug) return null;
2363
+ const hintVariantId = group?.listing?.selectionHintVariant;
2364
+ if (hintVariantId == null) return null;
2365
+ return { variantId: hintVariantId };
2366
+ }
2367
+ function buildProductHref(product, group, options = {}) {
2368
+ const path = joinProductPath(
2369
+ options.basePath ?? "/products",
2370
+ getProductHrefSlug(product),
2371
+ options.trailingSlash ?? false
2372
+ );
2373
+ const params = new URLSearchParams();
2374
+ if (options.detail && options.selection) {
2375
+ const selection = stringifyProductSelection(
2376
+ options.detail,
2377
+ options.selection,
2378
+ getProductHrefCodecOptions(options)
2379
+ );
2380
+ return selection ? `${path}?${selection}` : path;
2381
+ }
2382
+ const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
2383
+ group,
2384
+ options
2385
+ );
2386
+ if (preferVariantSelection) {
2387
+ if (options.detail) {
2388
+ const selection = stringifyProductSelection(
2389
+ options.detail,
2390
+ preferVariantSelection,
2391
+ getProductHrefCodecOptions(options)
2392
+ );
2393
+ return selection ? `${path}?${selection}` : path;
2394
+ }
2395
+ if (preferVariantSelection.variantId != null) {
2396
+ params.set("variant", String(preferVariantSelection.variantId));
2397
+ return `${path}?${params.toString()}`;
2398
+ }
2399
+ }
2400
+ const groupSelection = getProductHrefGroupSelection(group, options.matrix);
2401
+ if (groupSelection) {
2402
+ if (options.detail) {
2403
+ const selection = stringifyProductSelection(
2404
+ options.detail,
2405
+ groupSelection,
2406
+ getProductHrefCodecOptions(options)
2407
+ );
2408
+ return selection ? `${path}?${selection}` : path;
2409
+ }
2410
+ if (groupSelection.variantId != null) {
2411
+ params.set("variant", String(groupSelection.variantId));
2412
+ return `${path}?${params.toString()}`;
2413
+ }
2414
+ if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
2415
+ return `${path}?${params.toString()}`;
2416
+ }
2417
+ }
2418
+ if (group?.optionValueSlug) {
2419
+ const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
2420
+ if (optionSlug) {
2421
+ params.set(`opt.${optionSlug}`, group.optionValueSlug);
2422
+ }
2423
+ }
2424
+ return params.size > 0 ? `${path}?${params.toString()}` : path;
2425
+ }
2426
+ function compareVariantOrder(a, b) {
2427
+ const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
2428
+ const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
2429
+ if (Number.isFinite(aOrder) && Number.isFinite(bOrder) && aOrder !== bOrder) {
2430
+ return aOrder - bOrder;
2431
+ }
2432
+ const aId = String(getRelationID(a.id) ?? "");
2433
+ const bId = String(getRelationID(b.id) ?? "");
2434
+ return aId.localeCompare(bId);
2435
+ }
2436
+ function isVariantAvailableForSale(variant) {
2437
+ if (variant.isActive === false) return false;
2438
+ if (variant.isUnlimited) return true;
2439
+ return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
2440
+ }
2441
+ function sortVariantsForMediaSelection(variants) {
2442
+ const orderedVariants = [...variants].sort(compareVariantOrder);
2443
+ const activeVariants = orderedVariants.filter(
2444
+ (variant) => variant.isActive !== false
2445
+ );
2446
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
2447
+ const unavailableActiveVariants = activeVariants.filter(
2448
+ (variant) => !isVariantAvailableForSale(variant)
2449
+ );
2450
+ return [...availableVariants, ...unavailableActiveVariants];
2451
+ }
2452
+ function variantHasPoolMedia(variant, productMediaPool) {
2453
+ if (!variant?.images?.length) return false;
2454
+ return variant.images.some((image) => {
2455
+ const imageId = getRelationID(image);
2456
+ return Boolean(
2457
+ imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
2458
+ );
2459
+ });
2460
+ }
2461
+ function getMinMax(values) {
2462
+ const numbers = values.filter(
2463
+ (value) => typeof value === "number"
2464
+ );
2465
+ if (numbers.length === 0) {
2466
+ return { min: null, max: null };
2467
+ }
2468
+ return {
2469
+ min: Math.min(...numbers),
2470
+ max: Math.max(...numbers)
2471
+ };
2472
+ }
2473
+ function buildProductListingProjection(product, variants) {
2474
+ const orderedVariants = [...variants].sort(compareVariantOrder);
2475
+ const activeVariants = orderedVariants.filter(
2476
+ (variant) => variant.isActive !== false
2477
+ );
2478
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
2479
+ const selectionHintVariant = availableVariants[0] ?? activeVariants[0] ?? null;
2480
+ const { min: minPrice, max: maxPrice } = getMinMax(
2481
+ activeVariants.map((variant) => variant.price)
2482
+ );
2483
+ const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
2484
+ activeVariants.map((variant) => variant.compareAtPrice)
2485
+ );
2486
+ const productMediaPool = product?.images ?? [];
2487
+ const selectionHintHasPoolMedia = variantHasPoolMedia(
2488
+ selectionHintVariant,
2489
+ productMediaPool
2490
+ );
2491
+ const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
2492
+ const resolvedProductMedia = resolveProductSelectionMedia({
2493
+ productMediaPool,
2494
+ productPrimaryMediaItemId: getRelationID(product?.primaryMediaItemId),
2495
+ selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
2496
+ id: selectionHintVariant.id,
2497
+ images: selectionHintVariant.images
2498
+ } : null,
2499
+ matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
2500
+ });
2501
+ return {
2502
+ selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
2503
+ primaryImage: resolveGenericListingPrimaryImage(
2504
+ product,
2505
+ resolvedProductMedia.primaryImage,
2506
+ resolvedProductMedia.source
2507
+ ),
2508
+ minPrice,
2509
+ maxPrice,
2510
+ minCompareAtPrice,
2511
+ maxCompareAtPrice,
2512
+ isPriceRange: minPrice !== null && maxPrice !== null ? minPrice !== maxPrice : false,
2513
+ availableForSale: availableVariants.length > 0
2514
+ };
2515
+ }
2516
+ function buildProductListingCard(item, options = {}) {
2517
+ const product = item.product;
2518
+ const groups = item.groups;
2519
+ const variants = getProductListingCardVariants(item);
2520
+ const projectedListing = buildProductListingProjection(product, variants);
2521
+ const selectionHintVariant = getRelationID(
2522
+ product.listing && "selectionHintVariant" in product.listing ? product.listing.selectionHintVariant : null
2523
+ ) ?? projectedListing.selectionHintVariant;
2524
+ const representativeVariant = findListingCardRepresentativeVariant(
2525
+ variants,
2526
+ selectionHintVariant
2527
+ );
2528
+ const productMediaPool = mediaArray2(product.images);
2529
+ const representativeHasPoolMedia = Boolean(
2530
+ representativeVariant?.images?.some(
2531
+ (image) => productMediaPool.some(
2532
+ (poolItem) => getRelationID(poolItem) === getRelationID(image)
2533
+ )
2534
+ )
2535
+ );
2536
+ const resolvedPrimaryMedia = resolveProductSelectionMedia(
2537
+ {
2538
+ productMediaPool,
2539
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
2540
+ selectedVariant: representativeHasPoolMedia ? representativeVariant : null,
2541
+ matchingVariants: representativeHasPoolMedia ? [] : variants
2542
+ }
2543
+ );
2544
+ const listingPrimaryPointer = resolveListingPrimaryImagePointer({
2545
+ productMediaPool,
2546
+ productPrimaryMediaItemId: getRelationID(product.primaryMediaItemId),
2547
+ productThumbnail: product.thumbnail ?? null,
2548
+ listingPrimaryImage: product.listing?.primaryImage ?? null,
2549
+ resolvedPrimary: resolvedPrimaryMedia.primaryImage,
2550
+ resolvedSource: resolvedPrimaryMedia.source
2551
+ });
2552
+ const listingPrimaryMedia = listingPrimaryPointer ? productMediaPool.find(
2553
+ (item2) => getRelationID(item2) === listingPrimaryPointer
2554
+ ) ?? resolvedPrimaryMedia.primaryImage ?? null : null;
2555
+ const priceRange = resolveListingCardPriceRange(
2556
+ product,
2557
+ projectedListing,
2558
+ groups
2559
+ );
2560
+ const availableForSale = product.listing?.availableForSale != null ? product.listing.availableForSale : groups.length > 0 ? groups.some((group) => group.listing.availableForSale) : projectedListing.availableForSale;
2561
+ const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
2562
+ return {
2563
+ id: String(product.id),
2564
+ href: buildProductHref(
2565
+ { slug: product.slug },
2566
+ { listing: { selectionHintVariant } },
2567
+ options
2568
+ ),
2569
+ title: product.title,
2570
+ representativeVariant,
2571
+ primaryImage: listingPrimaryMedia,
2572
+ priceRange,
2573
+ availableForSale,
2574
+ swatches
2575
+ };
2576
+ }
2577
+ function getProductListingCardVariants(item) {
2578
+ const productVariants = item.product.variants?.docs;
2579
+ if (Array.isArray(productVariants) && productVariants.length > 0) {
2580
+ return productVariants;
2581
+ }
2582
+ const variants = [];
2583
+ const seen = /* @__PURE__ */ new Set();
2584
+ for (const group of item.groups) {
2585
+ for (const variant of group.variants) {
2586
+ const id = getRelationID(variant.id);
2587
+ if (id && seen.has(id)) continue;
2588
+ if (id) seen.add(id);
2589
+ variants.push(variant);
2590
+ }
2591
+ }
2592
+ return variants;
2593
+ }
2594
+ function findListingCardRepresentativeVariant(variants, representativeVariantId) {
2595
+ if (representativeVariantId == null) return null;
2596
+ return variants.find(
2597
+ (variant) => getRelationID(variant.id) === representativeVariantId
2598
+ ) ?? null;
2599
+ }
2600
+ function hasCompleteListingPriceProjection(listing) {
2601
+ return listing?.minPrice != null && listing?.maxPrice != null;
2602
+ }
2603
+ function listingDefinesCompareAtProjection(listing) {
2604
+ return "minCompareAtPrice" in listing || "maxCompareAtPrice" in listing;
2605
+ }
2606
+ function resolveListingCardPriceRange(product, projectedListing, groups) {
2607
+ const listing = product.listing;
2608
+ if (hasCompleteListingPriceProjection(listing)) {
2609
+ const groupRange = groups.length > 0 ? aggregateListingPriceRange(groups) : null;
2610
+ const definesCompareAt = listingDefinesCompareAtProjection(listing);
2611
+ return {
2612
+ minPrice: listing.minPrice,
2613
+ maxPrice: listing.maxPrice,
2614
+ minCompareAtPrice: definesCompareAt ? listing.minCompareAtPrice ?? null : groupRange?.minCompareAtPrice ?? projectedListing.minCompareAtPrice,
2615
+ maxCompareAtPrice: definesCompareAt ? listing.maxCompareAtPrice ?? null : groupRange?.maxCompareAtPrice ?? projectedListing.maxCompareAtPrice,
2616
+ isPriceRange: listing.isPriceRange ?? listing.minPrice !== listing.maxPrice
2617
+ };
2618
+ }
2619
+ if (groups.length > 0) {
2620
+ return aggregateListingPriceRange(groups);
2621
+ }
2622
+ return {
2623
+ minPrice: projectedListing.minPrice,
2624
+ maxPrice: projectedListing.maxPrice,
2625
+ minCompareAtPrice: projectedListing.minCompareAtPrice,
2626
+ maxCompareAtPrice: projectedListing.maxCompareAtPrice,
2627
+ isPriceRange: projectedListing.isPriceRange
2628
+ };
2629
+ }
2630
+ function aggregateListingPriceRange(groups) {
2631
+ const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
2632
+ const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
2633
+ const minCompareAtPrice = minOfNullable(
2634
+ groups.map((g) => g.listing.minCompareAtPrice)
2635
+ );
2636
+ const maxCompareAtPrice = maxOfNullable(
2637
+ groups.map((g) => g.listing.maxCompareAtPrice)
2638
+ );
2639
+ const isPriceRange = minPrice !== null && maxPrice !== null && minPrice !== maxPrice;
2640
+ return {
2641
+ minPrice,
2642
+ maxPrice,
2643
+ minCompareAtPrice,
2644
+ maxCompareAtPrice,
2645
+ isPriceRange
2646
+ };
2647
+ }
2648
+ function buildListingSwatch(product, group, options) {
2649
+ return {
2650
+ optionId: group.optionId,
2651
+ optionValueId: group.optionValueId,
2652
+ label: group.optionValueLabel,
2653
+ swatch: group.optionValueSwatch ?? null,
2654
+ href: buildProductHref(
2655
+ { slug: product.slug },
2656
+ {
2657
+ optionId: group.optionId,
2658
+ optionSlug: group.optionSlug,
2659
+ optionValueId: group.optionValueId,
2660
+ optionValueSlug: group.optionValueSlug,
2661
+ listing: group.listing
2662
+ },
2663
+ options
2664
+ ),
2665
+ availableForSale: group.listing.availableForSale
2666
+ };
2667
+ }
2668
+ function minOfNullable(values) {
2669
+ const numbers = values.filter((v) => v !== null);
2670
+ return numbers.length === 0 ? null : Math.min(...numbers);
2671
+ }
2672
+ function maxOfNullable(values) {
2673
+ const numbers = values.filter((v) => v !== null);
2674
+ return numbers.length === 0 ? null : Math.max(...numbers);
2675
+ }
2676
+
1575
2677
  // src/core/api/product-api.ts
1576
2678
  var PRODUCT_DETAIL_UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
1577
2679
  "not_found",
@@ -1595,13 +2697,71 @@ function productDetailResultFromError(error) {
1595
2697
  if (!reason) return void 0;
1596
2698
  return { found: false, reason };
1597
2699
  }
2700
+ function nonEmpty(values) {
2701
+ return values?.length ? values : void 0;
2702
+ }
2703
+ function buildProductListingPageWhere(params) {
2704
+ const clauses = [];
2705
+ const search = params.search?.trim();
2706
+ if (search) {
2707
+ clauses.push({
2708
+ or: [
2709
+ { title: { like: search } },
2710
+ { slug: { like: search } },
2711
+ { handle: { like: search } }
2712
+ ]
2713
+ });
2714
+ }
2715
+ const filters = params.filters;
2716
+ const ids = nonEmpty(filters?.ids);
2717
+ if (ids) clauses.push({ id: { in: ids } });
2718
+ const slugs = nonEmpty(filters?.slugs);
2719
+ if (slugs) clauses.push({ slug: { in: slugs } });
2720
+ const handles = nonEmpty(filters?.handles);
2721
+ if (handles) clauses.push({ handle: { in: handles } });
2722
+ const categoryIds = nonEmpty(filters?.categoryIds);
2723
+ if (categoryIds) clauses.push({ categories: { in: categoryIds } });
2724
+ const tagIds = nonEmpty(filters?.tagIds);
2725
+ if (tagIds) clauses.push({ tags: { in: tagIds } });
2726
+ const minPrice = filters?.price?.min;
2727
+ if (minPrice != null) {
2728
+ clauses.push({ "listing.maxPrice": { greater_than_equal: minPrice } });
2729
+ }
2730
+ const maxPrice = filters?.price?.max;
2731
+ if (maxPrice != null) {
2732
+ clauses.push({ "listing.minPrice": { less_than_equal: maxPrice } });
2733
+ }
2734
+ if (filters?.availableForSale != null) {
2735
+ clauses.push({
2736
+ "listing.availableForSale": { equals: filters.availableForSale }
2737
+ });
2738
+ }
2739
+ if (clauses.length === 0) return void 0;
2740
+ if (clauses.length === 1) return clauses[0];
2741
+ return { and: clauses };
2742
+ }
2743
+ function buildProductListingPageUrl(params) {
2744
+ const options = {
2745
+ page: params.page,
2746
+ limit: params.limit,
2747
+ sort: params.sort,
2748
+ where: buildProductListingPageWhere(params)
2749
+ };
2750
+ return params.mode === "full" ? listingGroupsQueryUrl(options) : listingGroupsQueryCatalogUrl(options);
2751
+ }
2752
+ function withProductListingCards(response, options) {
2753
+ return {
2754
+ ...response,
2755
+ cards: response.docs.map((item) => buildProductListingCard(item, options))
2756
+ };
2757
+ }
1598
2758
  function rejectLegacyOptionValueSwatchColor(value) {
1599
2759
  if (Object.prototype.hasOwnProperty.call(
1600
2760
  value,
1601
2761
  "swatchColor"
1602
2762
  )) {
1603
2763
  throw new TypeError(
1604
- 'Product upsert option values no longer accept legacy flat "swatchColor"; use nested "swatch" instead.'
2764
+ 'Product upsert option values no longer accept removed flat "swatchColor"; use nested "swatch" instead.'
1605
2765
  );
1606
2766
  }
1607
2767
  }
@@ -1659,6 +2819,14 @@ var ProductApi = class extends BaseApi {
1659
2819
  { method: "GET" }
1660
2820
  );
1661
2821
  }
2822
+ async listingPage(params = {}) {
2823
+ if (params.mode === "full") {
2824
+ const response2 = await this.request(buildProductListingPageUrl(params), void 0, { method: "GET" });
2825
+ return withProductListingCards(response2, params);
2826
+ }
2827
+ const response = await this.request(buildProductListingPageUrl(params), void 0, { method: "GET" });
2828
+ return withProductListingCards(response, params);
2829
+ }
1662
2830
  /**
1663
2831
  * Fetch full product detail by slug or id.
1664
2832
  * Returns a discriminated result so storefronts can distinguish missing,
@@ -1930,6 +3098,7 @@ var ServerCommerceClient = class {
1930
3098
  this.product = {
1931
3099
  stockCheck: productApi.stockCheck.bind(productApi),
1932
3100
  listingGroups: productApi.listingGroups.bind(productApi),
3101
+ listingPage: productApi.listingPage.bind(productApi),
1933
3102
  detail: productApi.detail.bind(productApi),
1934
3103
  previewDetail: productApi.previewDetail.bind(productApi),
1935
3104
  upsert: productApi.upsert.bind(productApi)