@01.software/sdk 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -54
- package/dist/analytics/react.cjs.map +1 -1
- package/dist/analytics/react.js.map +1 -1
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/client.cjs +1194 -5
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +8 -7
- package/dist/client.d.ts +8 -7
- package/dist/client.js +1194 -5
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-BroIWHY1.d.ts → collection-client-CKFa99C6.d.ts} +16 -10
- package/dist/{collection-client-B0J9wMNE.d.cts → collection-client-VYwYi6u6.d.cts} +16 -10
- package/dist/const-4BUtUdGU.d.cts +32 -0
- package/dist/const-ymprfiPf.d.ts +32 -0
- package/dist/errors.cjs +4 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +4 -1
- package/dist/errors.js.map +1 -1
- package/dist/{index-BOLQxveo.d.cts → index-Dquv4xTL.d.cts} +3 -3
- package/dist/{index-CSwR2HSg.d.ts → index-w36lpSkU.d.ts} +3 -3
- package/dist/index.cjs +2796 -2651
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +2796 -2651
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-m3jjhxk9.d.cts → payload-types-DC0xzR9i.d.cts} +470 -265
- package/dist/{payload-types-m3jjhxk9.d.ts → payload-types-DC0xzR9i.d.ts} +470 -265
- package/dist/query.cjs +241 -58
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +151 -26
- package/dist/query.d.ts +151 -26
- package/dist/query.js +241 -58
- package/dist/query.js.map +1 -1
- package/dist/realtime.cjs +5 -1
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js +5 -1
- package/dist/realtime.js.map +1 -1
- package/dist/server.cjs +1117 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -7
- package/dist/server.d.ts +7 -7
- package/dist/server.js +1117 -1
- package/dist/server.js.map +1 -1
- package/dist/storefront-cache.cjs +144 -0
- package/dist/storefront-cache.cjs.map +1 -0
- package/dist/storefront-cache.d.cts +24 -0
- package/dist/storefront-cache.d.ts +24 -0
- package/dist/storefront-cache.js +121 -0
- package/dist/storefront-cache.js.map +1 -0
- package/dist/{types-Cmrd1ezc.d.ts → types-Bh2p-EMc.d.ts} +5 -1
- package/dist/{types-D0ubzQw0.d.cts → types-BvpjooU-.d.cts} +5 -1
- package/dist/{types-D2xYdz4P.d.ts → types-D5uHrPLr.d.cts} +264 -96
- package/dist/{types-CIGscmus.d.cts → types-Umd-YTjM.d.ts} +264 -96
- package/dist/ui/canvas/server.cjs +5 -1
- package/dist/ui/canvas/server.cjs.map +1 -1
- package/dist/ui/canvas/server.js +5 -1
- package/dist/ui/canvas/server.js.map +1 -1
- package/dist/ui/canvas.cjs +5 -1
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.js +5 -1
- package/dist/ui/canvas.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.d.cts +4 -4
- package/dist/webhook.d.ts +4 -4
- package/package.json +13 -3
- package/dist/const-6XHz_jej.d.ts +0 -32
- package/dist/const-B5KT72c7.d.cts +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
|
}
|
|
@@ -1628,6 +1654,1019 @@ var CartApi = class extends CustomerScopedApi {
|
|
|
1628
1654
|
}
|
|
1629
1655
|
};
|
|
1630
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 mediaArray(value) {
|
|
1675
|
+
if (!Array.isArray(value)) return [];
|
|
1676
|
+
return value.filter((entry) => entry != null);
|
|
1677
|
+
}
|
|
1678
|
+
function uniqueWithPrimaryFirst(primary, items) {
|
|
1679
|
+
const unique = /* @__PURE__ */ new Map();
|
|
1680
|
+
for (const item of items) {
|
|
1681
|
+
const id = getMediaId(item);
|
|
1682
|
+
const key = id ?? `inline:${unique.size}`;
|
|
1683
|
+
if (!unique.has(key)) unique.set(key, item);
|
|
1684
|
+
}
|
|
1685
|
+
if (primary) {
|
|
1686
|
+
const primaryId = getMediaId(primary);
|
|
1687
|
+
const prefixed = /* @__PURE__ */ new Map();
|
|
1688
|
+
const primaryKey = primaryId ?? "inline:primary";
|
|
1689
|
+
prefixed.set(primaryKey, primary);
|
|
1690
|
+
for (const [key, value] of unique.entries()) {
|
|
1691
|
+
if (!prefixed.has(key)) prefixed.set(key, value);
|
|
1692
|
+
}
|
|
1693
|
+
return Array.from(prefixed.values());
|
|
1694
|
+
}
|
|
1695
|
+
return Array.from(unique.values());
|
|
1696
|
+
}
|
|
1697
|
+
function buildPoolById(pool) {
|
|
1698
|
+
const poolById = /* @__PURE__ */ new Map();
|
|
1699
|
+
for (const item of pool) {
|
|
1700
|
+
const id = getMediaId(item);
|
|
1701
|
+
if (id) poolById.set(id, item);
|
|
1702
|
+
}
|
|
1703
|
+
return poolById;
|
|
1704
|
+
}
|
|
1705
|
+
function resolveVariantImageItems(variant, poolById) {
|
|
1706
|
+
if (!variant || !Array.isArray(variant.images)) return [];
|
|
1707
|
+
const resolved = [];
|
|
1708
|
+
for (const entry of variant.images) {
|
|
1709
|
+
if (entry == null) continue;
|
|
1710
|
+
if (typeof entry === "string" || typeof entry === "number") {
|
|
1711
|
+
const pooled = poolById.get(String(entry));
|
|
1712
|
+
if (pooled) resolved.push(pooled);
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
const mediaId = getMediaId(entry);
|
|
1716
|
+
if (mediaId) {
|
|
1717
|
+
const pooled = poolById.get(mediaId);
|
|
1718
|
+
if (pooled) {
|
|
1719
|
+
resolved.push(pooled);
|
|
1720
|
+
}
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
return resolved;
|
|
1725
|
+
}
|
|
1726
|
+
function resolveOptionSwatchPrimary(selectedOptionValues, poolById) {
|
|
1727
|
+
if (!selectedOptionValues?.length) return null;
|
|
1728
|
+
for (const optionValue of selectedOptionValues) {
|
|
1729
|
+
const swatch = optionValue?.swatch;
|
|
1730
|
+
const swatchPointer = selectedSwatchMediaItemId(swatch);
|
|
1731
|
+
if (swatchPointer) {
|
|
1732
|
+
const pooled = poolById.get(swatchPointer);
|
|
1733
|
+
if (pooled) return pooled;
|
|
1734
|
+
}
|
|
1735
|
+
const inline = swatch?.inlineMedia;
|
|
1736
|
+
if (inline != null && typeof inline === "object") {
|
|
1737
|
+
return inline;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
function resolveProductSelectionMedia(input) {
|
|
1743
|
+
const pool = mediaArray(input.productMediaPool);
|
|
1744
|
+
const poolById = buildPoolById(pool);
|
|
1745
|
+
const selectedVariantImages = resolveVariantImageItems(
|
|
1746
|
+
input.selectedVariant,
|
|
1747
|
+
poolById
|
|
1748
|
+
);
|
|
1749
|
+
if (selectedVariantImages.length > 0) {
|
|
1750
|
+
const primaryImage = selectedVariantImages[0] ?? null;
|
|
1751
|
+
return {
|
|
1752
|
+
primaryImage,
|
|
1753
|
+
images: uniqueWithPrimaryFirst(primaryImage, selectedVariantImages),
|
|
1754
|
+
source: "variant_media_selected"
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
if (input.selectedVariant == null && (input.matchingVariants?.length ?? 0) > 0) {
|
|
1758
|
+
const mergedMatchingImages = [];
|
|
1759
|
+
for (const matchingVariant of input.matchingVariants ?? []) {
|
|
1760
|
+
mergedMatchingImages.push(
|
|
1761
|
+
...resolveVariantImageItems(matchingVariant, poolById)
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
if (mergedMatchingImages.length > 0) {
|
|
1765
|
+
const primaryImage = mergedMatchingImages[0] ?? null;
|
|
1766
|
+
return {
|
|
1767
|
+
primaryImage,
|
|
1768
|
+
images: uniqueWithPrimaryFirst(primaryImage, mergedMatchingImages),
|
|
1769
|
+
source: "variant_media_matching"
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
const optionSwatchPrimary = resolveOptionSwatchPrimary(
|
|
1774
|
+
input.selectedOptionValues,
|
|
1775
|
+
poolById
|
|
1776
|
+
);
|
|
1777
|
+
if (optionSwatchPrimary) {
|
|
1778
|
+
return {
|
|
1779
|
+
primaryImage: optionSwatchPrimary,
|
|
1780
|
+
images: [optionSwatchPrimary],
|
|
1781
|
+
source: "option_swatch"
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
return {
|
|
1785
|
+
primaryImage: null,
|
|
1786
|
+
images: [],
|
|
1787
|
+
source: "none"
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
function resolveFeaturedImagePointer(input) {
|
|
1791
|
+
const pool = mediaArray(input.productMediaPool);
|
|
1792
|
+
const resolvedPointer = getMediaId(input.resolvedPrimary);
|
|
1793
|
+
if (resolvedPointer && input.resolvedSource !== "product_pool" && input.resolvedSource !== "none") {
|
|
1794
|
+
return resolvedPointer;
|
|
1795
|
+
}
|
|
1796
|
+
const poolById = buildPoolById(pool);
|
|
1797
|
+
const featuredImagePointer = getMediaId(input.featuredImage);
|
|
1798
|
+
if (featuredImagePointer && poolById.has(featuredImagePointer)) {
|
|
1799
|
+
return featuredImagePointer;
|
|
1800
|
+
}
|
|
1801
|
+
if (pool.length > 0) {
|
|
1802
|
+
const firstPoolId = getMediaId(pool[0]);
|
|
1803
|
+
if (firstPoolId) return firstPoolId;
|
|
1804
|
+
}
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// src/utils/ecommerce.ts
|
|
1809
|
+
var DEFAULT_PRODUCT_SELECTION_URL_EMIT = "slug-compat";
|
|
1810
|
+
function resolveProductSelectionUrlEmit(emit) {
|
|
1811
|
+
return emit ?? DEFAULT_PRODUCT_SELECTION_URL_EMIT;
|
|
1812
|
+
}
|
|
1813
|
+
function appendSlugCompatSelectionParam(params, matrix, optionId, valueId) {
|
|
1814
|
+
const option = matrix.optionById.get(optionId);
|
|
1815
|
+
const value = matrix.valueById.get(valueId);
|
|
1816
|
+
if (!option?.slug || !value?.slug) return false;
|
|
1817
|
+
const slugMatches = option.values.filter(
|
|
1818
|
+
(candidate) => candidate.slug === value.slug
|
|
1819
|
+
);
|
|
1820
|
+
if (slugMatches.length !== 1) return false;
|
|
1821
|
+
params.append(`opt.${option.slug}`, value.slug);
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1824
|
+
function appendCanonicalSelectionParam(params, optionId, valueId) {
|
|
1825
|
+
params.append(`opt.${optionId}`, valueId);
|
|
1826
|
+
}
|
|
1827
|
+
var ProductSelectionCodecError = class extends Error {
|
|
1828
|
+
constructor(message) {
|
|
1829
|
+
super(message);
|
|
1830
|
+
this.code = "ambiguous_product_selection_query";
|
|
1831
|
+
this.name = "ProductSelectionCodecError";
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
function getRelationID(value) {
|
|
1835
|
+
if (typeof value === "string") return value;
|
|
1836
|
+
if (typeof value === "number") return String(value);
|
|
1837
|
+
if (value && typeof value === "object" && "id" in value) {
|
|
1838
|
+
const id = value.id;
|
|
1839
|
+
if (typeof id === "string") return id;
|
|
1840
|
+
if (typeof id === "number") return String(id);
|
|
1841
|
+
}
|
|
1842
|
+
return void 0;
|
|
1843
|
+
}
|
|
1844
|
+
function extractEntityId(value) {
|
|
1845
|
+
if (typeof value === "string") return value;
|
|
1846
|
+
if (typeof value === "number") return String(value);
|
|
1847
|
+
if (value && typeof value === "object" && "id" in value) {
|
|
1848
|
+
if (typeof value.id === "string") return value.id;
|
|
1849
|
+
if (typeof value.id === "number") return String(value.id);
|
|
1850
|
+
}
|
|
1851
|
+
return null;
|
|
1852
|
+
}
|
|
1853
|
+
function resolveGenericListingPrimaryImage(product, resolvedPrimary, resolvedSource) {
|
|
1854
|
+
return resolveFeaturedImagePointer({
|
|
1855
|
+
productMediaPool: product?.images ?? [],
|
|
1856
|
+
featuredImage: product?.featuredImage ?? null,
|
|
1857
|
+
resolvedPrimary,
|
|
1858
|
+
resolvedSource
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
function normalizeProductOptionValueSwatch(swatch) {
|
|
1862
|
+
if (swatch == null) return null;
|
|
1863
|
+
if (typeof swatch !== "object") return null;
|
|
1864
|
+
const raw = swatch;
|
|
1865
|
+
const mediaItemId = extractEntityId(raw.mediaItemId);
|
|
1866
|
+
const color = typeof raw.color === "string" ? raw.color.trim() : "";
|
|
1867
|
+
const hasColor = color.length > 0;
|
|
1868
|
+
if (raw.type === "media" || mediaItemId && raw.type !== "color") {
|
|
1869
|
+
if (!mediaItemId || hasColor) return null;
|
|
1870
|
+
return {
|
|
1871
|
+
type: "media",
|
|
1872
|
+
mediaItemId,
|
|
1873
|
+
color: null
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
if (raw.type === "color" || hasColor) {
|
|
1877
|
+
if (mediaItemId || !hasColor) return null;
|
|
1878
|
+
return { type: "color", color, mediaItemId: null };
|
|
1879
|
+
}
|
|
1880
|
+
return null;
|
|
1881
|
+
}
|
|
1882
|
+
function matrixOrder(index) {
|
|
1883
|
+
return String(index).padStart(6, "0");
|
|
1884
|
+
}
|
|
1885
|
+
function buildProductOptionMatrixFromDetail(detail) {
|
|
1886
|
+
const normalizedOptions = detail.options.map((option, optionIndex) => ({
|
|
1887
|
+
id: String(option.id),
|
|
1888
|
+
title: option.title || String(option.id),
|
|
1889
|
+
slug: option.slug,
|
|
1890
|
+
order: matrixOrder(optionIndex),
|
|
1891
|
+
values: option.values.map((value, valueIndex) => ({
|
|
1892
|
+
id: String(value.id),
|
|
1893
|
+
optionId: String(option.id),
|
|
1894
|
+
optionSlug: option.slug,
|
|
1895
|
+
label: value.value || value.slug || String(value.id),
|
|
1896
|
+
slug: value.slug,
|
|
1897
|
+
swatch: normalizeProductOptionValueSwatch(value.swatch),
|
|
1898
|
+
order: matrixOrder(valueIndex)
|
|
1899
|
+
}))
|
|
1900
|
+
}));
|
|
1901
|
+
const optionById = new Map(
|
|
1902
|
+
normalizedOptions.map((option) => [option.id, option])
|
|
1903
|
+
);
|
|
1904
|
+
const optionBySlug = new Map(
|
|
1905
|
+
normalizedOptions.map((option) => [option.slug, option])
|
|
1906
|
+
);
|
|
1907
|
+
const valueById = /* @__PURE__ */ new Map();
|
|
1908
|
+
const valueToOptionId = /* @__PURE__ */ new Map();
|
|
1909
|
+
const valueToOptionSlug = /* @__PURE__ */ new Map();
|
|
1910
|
+
for (const option of normalizedOptions) {
|
|
1911
|
+
for (const value of option.values) {
|
|
1912
|
+
valueById.set(value.id, value);
|
|
1913
|
+
valueToOptionId.set(value.id, option.id);
|
|
1914
|
+
valueToOptionSlug.set(value.id, option.slug);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
const optionIds = normalizedOptions.map((option) => option.id);
|
|
1918
|
+
const optionSlugs = normalizedOptions.map((option) => option.slug);
|
|
1919
|
+
const normalizedVariants = detail.variants.map((variant) => {
|
|
1920
|
+
const optionValueByOptionId = /* @__PURE__ */ new Map();
|
|
1921
|
+
const optionValueByOptionSlug = /* @__PURE__ */ new Map();
|
|
1922
|
+
for (const rawValue of variant.optionValues) {
|
|
1923
|
+
const optionId = String(rawValue.optionId);
|
|
1924
|
+
const valueId = String(rawValue.valueId);
|
|
1925
|
+
const optionSlug = rawValue.optionSlug;
|
|
1926
|
+
if (!optionById.has(optionId)) continue;
|
|
1927
|
+
if (valueToOptionId.get(valueId) !== optionId) continue;
|
|
1928
|
+
if (optionValueByOptionId.has(optionId)) continue;
|
|
1929
|
+
optionValueByOptionId.set(optionId, valueId);
|
|
1930
|
+
if (optionSlug && !optionValueByOptionSlug.has(optionSlug)) {
|
|
1931
|
+
optionValueByOptionSlug.set(optionSlug, valueId);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
|
|
1935
|
+
return {
|
|
1936
|
+
id: String(variant.id),
|
|
1937
|
+
optionValueIds,
|
|
1938
|
+
optionValueByOptionId,
|
|
1939
|
+
optionValueByOptionSlug,
|
|
1940
|
+
source: variant
|
|
1941
|
+
};
|
|
1942
|
+
});
|
|
1943
|
+
return {
|
|
1944
|
+
options: normalizedOptions,
|
|
1945
|
+
optionIds,
|
|
1946
|
+
optionSlugs,
|
|
1947
|
+
optionById,
|
|
1948
|
+
optionBySlug,
|
|
1949
|
+
valueById,
|
|
1950
|
+
valueToOptionId,
|
|
1951
|
+
valueToOptionSlug,
|
|
1952
|
+
variants: normalizedVariants
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
function getVariantSelection(matrix, variantId) {
|
|
1956
|
+
if (variantId == null) return void 0;
|
|
1957
|
+
const id = String(variantId);
|
|
1958
|
+
return matrix.variants.find((variant) => variant.id === id);
|
|
1959
|
+
}
|
|
1960
|
+
function hasExplicitSelection(selection) {
|
|
1961
|
+
return Boolean(
|
|
1962
|
+
selection.variantId != null || selection.search || selection.valueIds || Object.keys(selection.byOptionId ?? {}).length > 0 || Object.keys(selection.byOptionSlug ?? {}).length > 0
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
|
|
1966
|
+
if (valueId == null) return false;
|
|
1967
|
+
const normalizedValueId = String(valueId);
|
|
1968
|
+
if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
|
|
1969
|
+
selectedByOptionId.set(optionId, normalizedValueId);
|
|
1970
|
+
return true;
|
|
1971
|
+
}
|
|
1972
|
+
function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
|
|
1973
|
+
if (!valueSlug) return false;
|
|
1974
|
+
const option = matrix.optionById.get(optionId);
|
|
1975
|
+
if (!option) return false;
|
|
1976
|
+
const values = option.values.filter(
|
|
1977
|
+
(candidate) => candidate.slug === valueSlug
|
|
1978
|
+
);
|
|
1979
|
+
if (values.length > 1) {
|
|
1980
|
+
throw new ProductSelectionCodecError(
|
|
1981
|
+
`Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
const value = values[0];
|
|
1985
|
+
if (!value) return false;
|
|
1986
|
+
selectedByOptionId.set(optionId, value.id);
|
|
1987
|
+
return true;
|
|
1988
|
+
}
|
|
1989
|
+
function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
|
|
1990
|
+
if (!valueSlug) return false;
|
|
1991
|
+
const option = matrix.optionBySlug.get(optionSlug);
|
|
1992
|
+
if (!option) return false;
|
|
1993
|
+
const values = option.values.filter(
|
|
1994
|
+
(candidate) => candidate.slug === valueSlug
|
|
1995
|
+
);
|
|
1996
|
+
if (values.length > 1) {
|
|
1997
|
+
throw new ProductSelectionCodecError(
|
|
1998
|
+
`Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
const value = values[0];
|
|
2002
|
+
if (!value) return false;
|
|
2003
|
+
selectedByOptionId.set(option.id, value.id);
|
|
2004
|
+
return true;
|
|
2005
|
+
}
|
|
2006
|
+
function toSearchParams(search) {
|
|
2007
|
+
if (!search) return new URLSearchParams();
|
|
2008
|
+
if (search instanceof URLSearchParams) return new URLSearchParams(search);
|
|
2009
|
+
if (search instanceof URL) return new URLSearchParams(search.searchParams);
|
|
2010
|
+
const trimmed = search.trim();
|
|
2011
|
+
if (!trimmed) return new URLSearchParams();
|
|
2012
|
+
try {
|
|
2013
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
|
|
2014
|
+
return new URL(trimmed).searchParams;
|
|
2015
|
+
}
|
|
2016
|
+
} catch {
|
|
2017
|
+
return new URLSearchParams();
|
|
2018
|
+
}
|
|
2019
|
+
return new URLSearchParams(
|
|
2020
|
+
trimmed.startsWith("?") ? trimmed.slice(1) : trimmed
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
function slugLike(value) {
|
|
2024
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2025
|
+
}
|
|
2026
|
+
function assertNoAmbiguousSelectionParams(matrix, params) {
|
|
2027
|
+
const knownSelectionKeys = /* @__PURE__ */ new Set();
|
|
2028
|
+
const hasVariantParam = params.has("variant");
|
|
2029
|
+
const hasOptionParams = Array.from(params.keys()).some(
|
|
2030
|
+
(key) => key.startsWith("opt.")
|
|
2031
|
+
);
|
|
2032
|
+
if (hasVariantParam && hasOptionParams) {
|
|
2033
|
+
throw new ProductSelectionCodecError(
|
|
2034
|
+
"Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
for (const option of matrix.options) {
|
|
2038
|
+
knownSelectionKeys.add(slugLike(option.slug));
|
|
2039
|
+
knownSelectionKeys.add(slugLike(option.title));
|
|
2040
|
+
for (const value of option.values) {
|
|
2041
|
+
if (value.slug) knownSelectionKeys.add(slugLike(value.slug));
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
for (const [key, value] of params.entries()) {
|
|
2045
|
+
if (key.startsWith("opt.")) {
|
|
2046
|
+
const optionToken = key.slice(4);
|
|
2047
|
+
if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
|
|
2048
|
+
throw new ProductSelectionCodecError(
|
|
2049
|
+
`Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
if (!value) {
|
|
2053
|
+
throw new ProductSelectionCodecError(
|
|
2054
|
+
`Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
continue;
|
|
2058
|
+
}
|
|
2059
|
+
if (key === "variant") {
|
|
2060
|
+
if (!value) {
|
|
2061
|
+
throw new ProductSelectionCodecError(
|
|
2062
|
+
'Product selection query parameter "variant" requires a variant ID.'
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
if (!getVariantSelection(matrix, value)) {
|
|
2066
|
+
throw new ProductSelectionCodecError(
|
|
2067
|
+
`Unknown product selection variant "${value}".`
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
continue;
|
|
2071
|
+
}
|
|
2072
|
+
const keyToken = slugLike(key);
|
|
2073
|
+
if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
|
|
2074
|
+
throw new ProductSelectionCodecError(
|
|
2075
|
+
`Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
function emitCompatibilityOptionIdParam(options, event) {
|
|
2081
|
+
try {
|
|
2082
|
+
if (options?.onCompatibilityOptionIdParam) {
|
|
2083
|
+
options.onCompatibilityOptionIdParam(event);
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
options?.onLegacyOptionIdParam?.(event);
|
|
2087
|
+
} catch {
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
function assignSearchSelection(matrix, selectedByOptionId, search, options) {
|
|
2091
|
+
if (!search) return null;
|
|
2092
|
+
const params = toSearchParams(search);
|
|
2093
|
+
assertNoAmbiguousSelectionParams(matrix, params);
|
|
2094
|
+
const variantParam = params.get("variant");
|
|
2095
|
+
if (variantParam != null) {
|
|
2096
|
+
const variantSelection = getVariantSelection(matrix, variantParam);
|
|
2097
|
+
if (!variantSelection) {
|
|
2098
|
+
throw new ProductSelectionCodecError(
|
|
2099
|
+
`Unknown product selection variant "${variantParam}".`
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
|
|
2103
|
+
selectedByOptionId.set(optionId, valueId);
|
|
2104
|
+
}
|
|
2105
|
+
return variantSelection.id;
|
|
2106
|
+
}
|
|
2107
|
+
for (const [key, valueToken] of params.entries()) {
|
|
2108
|
+
if (!key.startsWith("opt.")) continue;
|
|
2109
|
+
const optionToken = key.slice(4);
|
|
2110
|
+
const optionById = matrix.optionById.get(optionToken);
|
|
2111
|
+
if (optionById) {
|
|
2112
|
+
if (assignSelectedValue(
|
|
2113
|
+
matrix,
|
|
2114
|
+
selectedByOptionId,
|
|
2115
|
+
optionById.id,
|
|
2116
|
+
valueToken
|
|
2117
|
+
)) {
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
const before = selectedByOptionId.get(optionById.id);
|
|
2121
|
+
if (assignSelectedValueSlugByOptionId(
|
|
2122
|
+
matrix,
|
|
2123
|
+
selectedByOptionId,
|
|
2124
|
+
optionById.id,
|
|
2125
|
+
valueToken
|
|
2126
|
+
) && selectedByOptionId.get(optionById.id) !== before) {
|
|
2127
|
+
emitCompatibilityOptionIdParam(options, {
|
|
2128
|
+
optionId: optionById.id,
|
|
2129
|
+
optionSlug: optionById.slug,
|
|
2130
|
+
valueSlug: valueToken,
|
|
2131
|
+
searchParam: key
|
|
2132
|
+
});
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
throw new ProductSelectionCodecError(
|
|
2136
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
const optionBySlug = matrix.optionBySlug.get(optionToken);
|
|
2140
|
+
if (optionBySlug) {
|
|
2141
|
+
if (assignSelectedValueSlugByOptionSlug(
|
|
2142
|
+
matrix,
|
|
2143
|
+
selectedByOptionId,
|
|
2144
|
+
optionBySlug.slug,
|
|
2145
|
+
valueToken
|
|
2146
|
+
)) {
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
if (matrix.valueById.has(valueToken)) {
|
|
2150
|
+
throw new ProductSelectionCodecError(
|
|
2151
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
throw new ProductSelectionCodecError(
|
|
2155
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
return null;
|
|
2160
|
+
}
|
|
2161
|
+
function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
|
|
2162
|
+
const selectedByOptionId = /* @__PURE__ */ new Map();
|
|
2163
|
+
const variantSelection = getVariantSelection(matrix, selection.variantId);
|
|
2164
|
+
const variantId = variantSelection?.id ?? null;
|
|
2165
|
+
if (variantSelection) {
|
|
2166
|
+
for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
|
|
2167
|
+
selectedByOptionId.set(optionId, valueId);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
for (const rawValueId of selection.valueIds ?? []) {
|
|
2171
|
+
const valueId = getRelationID(rawValueId);
|
|
2172
|
+
if (!valueId) continue;
|
|
2173
|
+
const optionId = matrix.valueToOptionId.get(valueId);
|
|
2174
|
+
if (!optionId) continue;
|
|
2175
|
+
selectedByOptionId.set(optionId, valueId);
|
|
2176
|
+
}
|
|
2177
|
+
const searchVariantId = assignSearchSelection(
|
|
2178
|
+
matrix,
|
|
2179
|
+
selectedByOptionId,
|
|
2180
|
+
selection.search,
|
|
2181
|
+
options
|
|
2182
|
+
);
|
|
2183
|
+
for (const [rawOptionId, rawSelection] of Object.entries(
|
|
2184
|
+
selection.byOptionId ?? {}
|
|
2185
|
+
)) {
|
|
2186
|
+
const optionId = String(rawOptionId);
|
|
2187
|
+
if (!matrix.optionById.has(optionId)) continue;
|
|
2188
|
+
if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
|
|
2189
|
+
assignSelectedValue(
|
|
2190
|
+
matrix,
|
|
2191
|
+
selectedByOptionId,
|
|
2192
|
+
optionId,
|
|
2193
|
+
rawSelection.valueId
|
|
2194
|
+
);
|
|
2195
|
+
continue;
|
|
2196
|
+
}
|
|
2197
|
+
if (typeof rawSelection === "string" || typeof rawSelection === "number") {
|
|
2198
|
+
assignSelectedValue(matrix, selectedByOptionId, optionId, rawSelection);
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
|
|
2202
|
+
assignSelectedValueSlugByOptionId(
|
|
2203
|
+
matrix,
|
|
2204
|
+
selectedByOptionId,
|
|
2205
|
+
optionId,
|
|
2206
|
+
rawSelection.valueSlug
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
for (const [rawOptionSlug, rawSelection] of Object.entries(
|
|
2211
|
+
selection.byOptionSlug ?? {}
|
|
2212
|
+
)) {
|
|
2213
|
+
const optionSlug = String(rawOptionSlug);
|
|
2214
|
+
if (!matrix.optionBySlug.has(optionSlug)) continue;
|
|
2215
|
+
if (rawSelection && typeof rawSelection === "object" && "valueId" in rawSelection && rawSelection.valueId != null) {
|
|
2216
|
+
const option = matrix.optionBySlug.get(optionSlug);
|
|
2217
|
+
if (option) {
|
|
2218
|
+
assignSelectedValue(
|
|
2219
|
+
matrix,
|
|
2220
|
+
selectedByOptionId,
|
|
2221
|
+
option.id,
|
|
2222
|
+
rawSelection.valueId
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
continue;
|
|
2226
|
+
}
|
|
2227
|
+
if (rawSelection && typeof rawSelection === "object" && "valueSlug" in rawSelection) {
|
|
2228
|
+
assignSelectedValueSlugByOptionSlug(
|
|
2229
|
+
matrix,
|
|
2230
|
+
selectedByOptionId,
|
|
2231
|
+
optionSlug,
|
|
2232
|
+
rawSelection.valueSlug
|
|
2233
|
+
);
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
if (typeof rawSelection === "string" || typeof rawSelection === "number") {
|
|
2237
|
+
assignSelectedValueSlugByOptionSlug(
|
|
2238
|
+
matrix,
|
|
2239
|
+
selectedByOptionId,
|
|
2240
|
+
optionSlug,
|
|
2241
|
+
String(rawSelection)
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
const byOptionId = Object.fromEntries(
|
|
2246
|
+
matrix.optionIds.map((optionId) => [optionId, selectedByOptionId.get(optionId)]).filter((entry) => Boolean(entry[1]))
|
|
2247
|
+
);
|
|
2248
|
+
const byOptionSlug = Object.fromEntries(
|
|
2249
|
+
matrix.options.map((option) => {
|
|
2250
|
+
const valueId = selectedByOptionId.get(option.id);
|
|
2251
|
+
const value = valueId ? matrix.valueById.get(valueId) : void 0;
|
|
2252
|
+
return [option.slug, value?.slug ?? void 0];
|
|
2253
|
+
}).filter((entry) => Boolean(entry[1]))
|
|
2254
|
+
);
|
|
2255
|
+
return {
|
|
2256
|
+
byOptionSlug,
|
|
2257
|
+
byOptionId,
|
|
2258
|
+
valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
|
|
2259
|
+
variantId: searchVariantId ?? variantId
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
function stringifyProductSelection(detail, selection = {}, options) {
|
|
2263
|
+
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
2264
|
+
const normalized = normalizeProductSelectionFromMatrix(
|
|
2265
|
+
matrix,
|
|
2266
|
+
selection,
|
|
2267
|
+
options
|
|
2268
|
+
);
|
|
2269
|
+
const params = new URLSearchParams();
|
|
2270
|
+
if (hasExplicitSelection(selection)) {
|
|
2271
|
+
const matchingVariants = getMatchingVariantEntries(matrix, normalized);
|
|
2272
|
+
const exactVariant = getExactSelectedVariantEntry(
|
|
2273
|
+
matrix,
|
|
2274
|
+
normalized,
|
|
2275
|
+
matchingVariants
|
|
2276
|
+
);
|
|
2277
|
+
if (exactVariant) {
|
|
2278
|
+
params.set("variant", exactVariant.id);
|
|
2279
|
+
return params.toString();
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
const emit = resolveProductSelectionUrlEmit(options?.emit);
|
|
2283
|
+
for (const optionId of matrix.optionIds) {
|
|
2284
|
+
const valueId = normalized.byOptionId[optionId];
|
|
2285
|
+
if (!valueId) continue;
|
|
2286
|
+
if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
if (emit === "slug-compat") {
|
|
2290
|
+
if (appendSlugCompatSelectionParam(params, matrix, optionId, valueId)) {
|
|
2291
|
+
continue;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
appendCanonicalSelectionParam(params, optionId, valueId);
|
|
2295
|
+
}
|
|
2296
|
+
return params.toString();
|
|
2297
|
+
}
|
|
2298
|
+
function selectedEntries(selection) {
|
|
2299
|
+
return Object.entries(selection.byOptionId);
|
|
2300
|
+
}
|
|
2301
|
+
function getMatchingVariantEntries(matrix, selection) {
|
|
2302
|
+
const entries = selectedEntries(selection);
|
|
2303
|
+
if (entries.length === 0) return matrix.variants;
|
|
2304
|
+
return matrix.variants.filter(
|
|
2305
|
+
(variant) => entries.every(
|
|
2306
|
+
([optionId, valueId]) => variant.optionValueByOptionId.get(optionId) === valueId
|
|
2307
|
+
)
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
function getExactSelectedVariantEntry(matrix, selection, matchingVariants) {
|
|
2311
|
+
if (matrix.optionIds.length === 0) {
|
|
2312
|
+
return getVariantSelection(matrix, selection.variantId) ?? (matchingVariants.length === 1 ? matchingVariants[0] ?? null : null);
|
|
2313
|
+
}
|
|
2314
|
+
const allOptionsSelected = matrix.optionIds.every(
|
|
2315
|
+
(optionId) => Boolean(selection.byOptionId[optionId])
|
|
2316
|
+
);
|
|
2317
|
+
if (!allOptionsSelected) return null;
|
|
2318
|
+
return matchingVariants.find(
|
|
2319
|
+
(variant) => matrix.optionIds.every(
|
|
2320
|
+
(optionId) => variant.optionValueByOptionId.get(optionId) === selection.byOptionId[optionId]
|
|
2321
|
+
)
|
|
2322
|
+
) ?? null;
|
|
2323
|
+
}
|
|
2324
|
+
function isPresentMedia(value) {
|
|
2325
|
+
return value != null;
|
|
2326
|
+
}
|
|
2327
|
+
function mediaArray2(values) {
|
|
2328
|
+
if (!Array.isArray(values)) return [];
|
|
2329
|
+
return values.filter(isPresentMedia);
|
|
2330
|
+
}
|
|
2331
|
+
function getProductHrefSlug(product) {
|
|
2332
|
+
if ("product" in product && product.product?.slug) {
|
|
2333
|
+
return product.product.slug;
|
|
2334
|
+
}
|
|
2335
|
+
if ("slug" in product && product.slug) return product.slug;
|
|
2336
|
+
throw new ProductSelectionCodecError(
|
|
2337
|
+
"Product slug is required to build a product href."
|
|
2338
|
+
);
|
|
2339
|
+
}
|
|
2340
|
+
function joinProductPath(basePath, slug, trailingSlash) {
|
|
2341
|
+
const base = basePath.replace(/\/+$/, "");
|
|
2342
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
2343
|
+
return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
|
|
2344
|
+
}
|
|
2345
|
+
function getProductHrefGroupSelection(group, matrix) {
|
|
2346
|
+
if (!group) return null;
|
|
2347
|
+
if (group.variantId != null) return { variantId: group.variantId };
|
|
2348
|
+
const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
|
|
2349
|
+
if (!optionId) return null;
|
|
2350
|
+
const option = matrix?.optionById.get(optionId);
|
|
2351
|
+
const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
|
|
2352
|
+
if (!optionValueId) return null;
|
|
2353
|
+
return { byOptionId: { [optionId]: optionValueId } };
|
|
2354
|
+
}
|
|
2355
|
+
function getProductHrefCodecOptions(options) {
|
|
2356
|
+
return options.emit != null ? { emit: options.emit } : void 0;
|
|
2357
|
+
}
|
|
2358
|
+
function appendGroupByOptionIdHrefParams(params, group, groupSelection, options) {
|
|
2359
|
+
const emit = resolveProductSelectionUrlEmit(options.emit);
|
|
2360
|
+
const byOptionId = groupSelection.byOptionId ?? {};
|
|
2361
|
+
const entries = Object.entries(byOptionId);
|
|
2362
|
+
if (entries.length === 0) return false;
|
|
2363
|
+
for (const [optionId, valueId] of entries) {
|
|
2364
|
+
const optionSlug = group?.optionSlug ?? options.matrix?.optionById.get(optionId)?.slug ?? null;
|
|
2365
|
+
const valueSlug = group?.optionValueSlug ?? options.matrix?.valueById.get(String(valueId))?.slug ?? null;
|
|
2366
|
+
if (emit === "slug-compat" && optionSlug && valueSlug && entries.length === 1) {
|
|
2367
|
+
params.set(`opt.${optionSlug}`, valueSlug);
|
|
2368
|
+
return true;
|
|
2369
|
+
}
|
|
2370
|
+
if (emit === "slug-compat" && options.matrix && appendSlugCompatSelectionParam(
|
|
2371
|
+
params,
|
|
2372
|
+
options.matrix,
|
|
2373
|
+
optionId,
|
|
2374
|
+
String(valueId)
|
|
2375
|
+
)) {
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
appendCanonicalSelectionParam(params, optionId, String(valueId));
|
|
2379
|
+
}
|
|
2380
|
+
return params.size > 0;
|
|
2381
|
+
}
|
|
2382
|
+
function getPreferCompleteVariantFromHintSelection(group, options) {
|
|
2383
|
+
if (!options.preferCompleteVariantFromHint) return null;
|
|
2384
|
+
if (group?.optionValueId != null || group?.optionValueSlug) return null;
|
|
2385
|
+
const hintVariantId = group?.listing?.selectionHintVariant;
|
|
2386
|
+
if (hintVariantId == null) return null;
|
|
2387
|
+
return { variantId: hintVariantId };
|
|
2388
|
+
}
|
|
2389
|
+
function buildProductHref(product, group, options = {}) {
|
|
2390
|
+
const path = joinProductPath(
|
|
2391
|
+
options.basePath ?? "/products",
|
|
2392
|
+
getProductHrefSlug(product),
|
|
2393
|
+
options.trailingSlash ?? false
|
|
2394
|
+
);
|
|
2395
|
+
const params = new URLSearchParams();
|
|
2396
|
+
if (options.detail && options.selection) {
|
|
2397
|
+
const selection = stringifyProductSelection(
|
|
2398
|
+
options.detail,
|
|
2399
|
+
options.selection,
|
|
2400
|
+
getProductHrefCodecOptions(options)
|
|
2401
|
+
);
|
|
2402
|
+
return selection ? `${path}?${selection}` : path;
|
|
2403
|
+
}
|
|
2404
|
+
const preferVariantSelection = getPreferCompleteVariantFromHintSelection(
|
|
2405
|
+
group,
|
|
2406
|
+
options
|
|
2407
|
+
);
|
|
2408
|
+
if (preferVariantSelection) {
|
|
2409
|
+
if (options.detail) {
|
|
2410
|
+
const selection = stringifyProductSelection(
|
|
2411
|
+
options.detail,
|
|
2412
|
+
preferVariantSelection,
|
|
2413
|
+
getProductHrefCodecOptions(options)
|
|
2414
|
+
);
|
|
2415
|
+
return selection ? `${path}?${selection}` : path;
|
|
2416
|
+
}
|
|
2417
|
+
if (preferVariantSelection.variantId != null) {
|
|
2418
|
+
params.set("variant", String(preferVariantSelection.variantId));
|
|
2419
|
+
return `${path}?${params.toString()}`;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
const groupSelection = getProductHrefGroupSelection(group, options.matrix);
|
|
2423
|
+
if (groupSelection) {
|
|
2424
|
+
if (options.detail) {
|
|
2425
|
+
const selection = stringifyProductSelection(
|
|
2426
|
+
options.detail,
|
|
2427
|
+
groupSelection,
|
|
2428
|
+
getProductHrefCodecOptions(options)
|
|
2429
|
+
);
|
|
2430
|
+
return selection ? `${path}?${selection}` : path;
|
|
2431
|
+
}
|
|
2432
|
+
if (groupSelection.variantId != null) {
|
|
2433
|
+
params.set("variant", String(groupSelection.variantId));
|
|
2434
|
+
return `${path}?${params.toString()}`;
|
|
2435
|
+
}
|
|
2436
|
+
if (appendGroupByOptionIdHrefParams(params, group, groupSelection, options)) {
|
|
2437
|
+
return `${path}?${params.toString()}`;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
if (group?.optionValueSlug) {
|
|
2441
|
+
const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
|
|
2442
|
+
if (optionSlug) {
|
|
2443
|
+
params.set(`opt.${optionSlug}`, group.optionValueSlug);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
return params.size > 0 ? `${path}?${params.toString()}` : path;
|
|
2447
|
+
}
|
|
2448
|
+
function compareVariantOrder(a, b) {
|
|
2449
|
+
const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
|
|
2450
|
+
const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
|
|
2451
|
+
if (Number.isFinite(aOrder) && Number.isFinite(bOrder) && aOrder !== bOrder) {
|
|
2452
|
+
return aOrder - bOrder;
|
|
2453
|
+
}
|
|
2454
|
+
const aId = String(getRelationID(a.id) ?? "");
|
|
2455
|
+
const bId = String(getRelationID(b.id) ?? "");
|
|
2456
|
+
return aId.localeCompare(bId);
|
|
2457
|
+
}
|
|
2458
|
+
function isVariantAvailableForSale(variant) {
|
|
2459
|
+
if (variant.isActive === false) return false;
|
|
2460
|
+
if (variant.isUnlimited) return true;
|
|
2461
|
+
return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
|
|
2462
|
+
}
|
|
2463
|
+
function sortVariantsForMediaSelection(variants) {
|
|
2464
|
+
const orderedVariants = [...variants].sort(compareVariantOrder);
|
|
2465
|
+
const activeVariants = orderedVariants.filter(
|
|
2466
|
+
(variant) => variant.isActive !== false
|
|
2467
|
+
);
|
|
2468
|
+
const availableVariants = activeVariants.filter(isVariantAvailableForSale);
|
|
2469
|
+
const unavailableActiveVariants = activeVariants.filter(
|
|
2470
|
+
(variant) => !isVariantAvailableForSale(variant)
|
|
2471
|
+
);
|
|
2472
|
+
return [...availableVariants, ...unavailableActiveVariants];
|
|
2473
|
+
}
|
|
2474
|
+
function variantHasPoolMedia(variant, productMediaPool) {
|
|
2475
|
+
if (!variant?.images?.length) return false;
|
|
2476
|
+
return variant.images.some((image) => {
|
|
2477
|
+
const imageId = getRelationID(image);
|
|
2478
|
+
return Boolean(
|
|
2479
|
+
imageId && productMediaPool.some((poolItem) => getRelationID(poolItem) === imageId)
|
|
2480
|
+
);
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
function getMinMax(values) {
|
|
2484
|
+
const numbers = values.filter(
|
|
2485
|
+
(value) => typeof value === "number"
|
|
2486
|
+
);
|
|
2487
|
+
if (numbers.length === 0) {
|
|
2488
|
+
return { min: null, max: null };
|
|
2489
|
+
}
|
|
2490
|
+
return {
|
|
2491
|
+
min: Math.min(...numbers),
|
|
2492
|
+
max: Math.max(...numbers)
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
function buildProductListingGroupProjection(product, variants) {
|
|
2496
|
+
const orderedVariants = [...variants].sort(compareVariantOrder);
|
|
2497
|
+
const activeVariants = orderedVariants.filter(
|
|
2498
|
+
(variant) => variant.isActive !== false
|
|
2499
|
+
);
|
|
2500
|
+
const availableVariants = activeVariants.filter(isVariantAvailableForSale);
|
|
2501
|
+
const selectionHintVariant = availableVariants[0] ?? activeVariants[0] ?? null;
|
|
2502
|
+
const { min: minPrice, max: maxPrice } = getMinMax(
|
|
2503
|
+
activeVariants.map((variant) => variant.price)
|
|
2504
|
+
);
|
|
2505
|
+
const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
|
|
2506
|
+
activeVariants.map((variant) => variant.compareAtPrice)
|
|
2507
|
+
);
|
|
2508
|
+
const productMediaPool = product?.images ?? [];
|
|
2509
|
+
const selectionHintHasPoolMedia = variantHasPoolMedia(
|
|
2510
|
+
selectionHintVariant,
|
|
2511
|
+
productMediaPool
|
|
2512
|
+
);
|
|
2513
|
+
const mediaSelectionVariants = sortVariantsForMediaSelection(variants);
|
|
2514
|
+
const resolvedProductMedia = resolveProductSelectionMedia({
|
|
2515
|
+
productMediaPool,
|
|
2516
|
+
selectedVariant: selectionHintVariant && selectionHintHasPoolMedia ? {
|
|
2517
|
+
id: selectionHintVariant.id,
|
|
2518
|
+
images: selectionHintVariant.images
|
|
2519
|
+
} : null,
|
|
2520
|
+
matchingVariants: selectionHintHasPoolMedia ? [] : mediaSelectionVariants
|
|
2521
|
+
});
|
|
2522
|
+
return {
|
|
2523
|
+
selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
|
|
2524
|
+
primaryImage: resolveGenericListingPrimaryImage(
|
|
2525
|
+
product,
|
|
2526
|
+
resolvedProductMedia.primaryImage,
|
|
2527
|
+
resolvedProductMedia.source
|
|
2528
|
+
),
|
|
2529
|
+
minPrice,
|
|
2530
|
+
maxPrice,
|
|
2531
|
+
minCompareAtPrice,
|
|
2532
|
+
maxCompareAtPrice,
|
|
2533
|
+
isPriceRange: minPrice !== null && maxPrice !== null ? minPrice !== maxPrice : false,
|
|
2534
|
+
availableForSale: availableVariants.length > 0
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
function buildProductListingCard(item, options = {}) {
|
|
2538
|
+
const product = item.product;
|
|
2539
|
+
const groups = item.groups;
|
|
2540
|
+
const variants = getProductListingCardVariants(item);
|
|
2541
|
+
const projectedListing = buildProductListingGroupProjection(product, variants);
|
|
2542
|
+
const selectionHintVariant = getRelationID(product.selectedOrFirstAvailableVariant) ?? projectedListing.selectionHintVariant;
|
|
2543
|
+
const representativeVariant = findListingCardRepresentativeVariant(
|
|
2544
|
+
variants,
|
|
2545
|
+
selectionHintVariant
|
|
2546
|
+
);
|
|
2547
|
+
const productMediaPool = mediaArray2(product.images);
|
|
2548
|
+
const featuredImageId = getRelationID(product.featuredImage);
|
|
2549
|
+
const featuredImageFromPool = featuredImageId ? productMediaPool.find((item2) => getRelationID(item2) === featuredImageId) : void 0;
|
|
2550
|
+
const listingPrimaryMedia = featuredImageFromPool ?? product.featuredImage ?? productMediaPool[0] ?? null;
|
|
2551
|
+
const priceRange = resolveListingCardPriceRange(
|
|
2552
|
+
product,
|
|
2553
|
+
projectedListing,
|
|
2554
|
+
groups
|
|
2555
|
+
);
|
|
2556
|
+
const availableForSale = product.availableForSale != null ? product.availableForSale : groups.length > 0 ? groups.some((group) => group.listing.availableForSale) : projectedListing.availableForSale;
|
|
2557
|
+
const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
|
|
2558
|
+
return {
|
|
2559
|
+
id: String(product.id),
|
|
2560
|
+
href: buildProductHref(
|
|
2561
|
+
{ slug: product.slug },
|
|
2562
|
+
{ listing: { selectionHintVariant } },
|
|
2563
|
+
options
|
|
2564
|
+
),
|
|
2565
|
+
title: product.title,
|
|
2566
|
+
representativeVariant,
|
|
2567
|
+
primaryImage: listingPrimaryMedia,
|
|
2568
|
+
priceRange,
|
|
2569
|
+
availableForSale,
|
|
2570
|
+
swatches
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
function getProductListingCardVariants(item) {
|
|
2574
|
+
const productVariants = item.product.variants?.docs;
|
|
2575
|
+
if (Array.isArray(productVariants) && productVariants.length > 0) {
|
|
2576
|
+
return productVariants;
|
|
2577
|
+
}
|
|
2578
|
+
const variants = [];
|
|
2579
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2580
|
+
for (const group of item.groups) {
|
|
2581
|
+
for (const variant of group.variants) {
|
|
2582
|
+
const id = getRelationID(variant.id);
|
|
2583
|
+
if (id && seen.has(id)) continue;
|
|
2584
|
+
if (id) seen.add(id);
|
|
2585
|
+
variants.push(variant);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
return variants;
|
|
2589
|
+
}
|
|
2590
|
+
function findListingCardRepresentativeVariant(variants, representativeVariantId) {
|
|
2591
|
+
if (representativeVariantId == null) return null;
|
|
2592
|
+
return variants.find(
|
|
2593
|
+
(variant) => getRelationID(variant.id) === representativeVariantId
|
|
2594
|
+
) ?? null;
|
|
2595
|
+
}
|
|
2596
|
+
function hasCompleteProductPriceRange(priceRange) {
|
|
2597
|
+
return priceRange?.minVariantPrice?.amount != null && priceRange?.maxVariantPrice?.amount != null;
|
|
2598
|
+
}
|
|
2599
|
+
function resolveListingCardPriceRange(product, projectedListing, groups) {
|
|
2600
|
+
if (hasCompleteProductPriceRange(product.priceRange)) {
|
|
2601
|
+
const groupRange = groups.length > 0 ? aggregateListingPriceRange(groups) : null;
|
|
2602
|
+
const minCompareAtPrice = product.compareAtPriceRange?.minVariantPrice?.amount ?? groupRange?.minCompareAtPrice ?? null;
|
|
2603
|
+
const maxCompareAtPrice = product.compareAtPriceRange?.maxVariantPrice?.amount ?? groupRange?.maxCompareAtPrice ?? null;
|
|
2604
|
+
return {
|
|
2605
|
+
minPrice: product.priceRange.minVariantPrice.amount,
|
|
2606
|
+
maxPrice: product.priceRange.maxVariantPrice.amount,
|
|
2607
|
+
minCompareAtPrice,
|
|
2608
|
+
maxCompareAtPrice,
|
|
2609
|
+
isPriceRange: product.priceRange.isPriceRange ?? product.priceRange.minVariantPrice.amount !== product.priceRange.maxVariantPrice.amount
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
if (groups.length > 0) {
|
|
2613
|
+
return aggregateListingPriceRange(groups);
|
|
2614
|
+
}
|
|
2615
|
+
return {
|
|
2616
|
+
minPrice: projectedListing.minPrice,
|
|
2617
|
+
maxPrice: projectedListing.maxPrice,
|
|
2618
|
+
minCompareAtPrice: projectedListing.minCompareAtPrice,
|
|
2619
|
+
maxCompareAtPrice: projectedListing.maxCompareAtPrice,
|
|
2620
|
+
isPriceRange: projectedListing.isPriceRange
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
function aggregateListingPriceRange(groups) {
|
|
2624
|
+
const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
|
|
2625
|
+
const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
|
|
2626
|
+
const minCompareAtPrice = minOfNullable(
|
|
2627
|
+
groups.map((g) => g.listing.minCompareAtPrice)
|
|
2628
|
+
);
|
|
2629
|
+
const maxCompareAtPrice = maxOfNullable(
|
|
2630
|
+
groups.map((g) => g.listing.maxCompareAtPrice)
|
|
2631
|
+
);
|
|
2632
|
+
const isPriceRange = minPrice !== null && maxPrice !== null && minPrice !== maxPrice;
|
|
2633
|
+
return {
|
|
2634
|
+
minPrice,
|
|
2635
|
+
maxPrice,
|
|
2636
|
+
minCompareAtPrice,
|
|
2637
|
+
maxCompareAtPrice,
|
|
2638
|
+
isPriceRange
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
function buildListingSwatch(product, group, options) {
|
|
2642
|
+
return {
|
|
2643
|
+
optionId: group.optionId,
|
|
2644
|
+
optionValueId: group.optionValueId,
|
|
2645
|
+
label: group.optionValueLabel,
|
|
2646
|
+
swatch: group.optionValueSwatch ?? null,
|
|
2647
|
+
href: buildProductHref(
|
|
2648
|
+
{ slug: product.slug },
|
|
2649
|
+
{
|
|
2650
|
+
optionId: group.optionId,
|
|
2651
|
+
optionSlug: group.optionSlug,
|
|
2652
|
+
optionValueId: group.optionValueId,
|
|
2653
|
+
optionValueSlug: group.optionValueSlug,
|
|
2654
|
+
listing: group.listing
|
|
2655
|
+
},
|
|
2656
|
+
options
|
|
2657
|
+
),
|
|
2658
|
+
availableForSale: group.listing.availableForSale
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
function minOfNullable(values) {
|
|
2662
|
+
const numbers = values.filter((v) => v !== null);
|
|
2663
|
+
return numbers.length === 0 ? null : Math.min(...numbers);
|
|
2664
|
+
}
|
|
2665
|
+
function maxOfNullable(values) {
|
|
2666
|
+
const numbers = values.filter((v) => v !== null);
|
|
2667
|
+
return numbers.length === 0 ? null : Math.max(...numbers);
|
|
2668
|
+
}
|
|
2669
|
+
|
|
1631
2670
|
// src/core/api/product-api.ts
|
|
1632
2671
|
var PRODUCT_DETAIL_UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
|
|
1633
2672
|
"not_found",
|
|
@@ -1651,13 +2690,75 @@ function productDetailResultFromError(error) {
|
|
|
1651
2690
|
if (!reason) return void 0;
|
|
1652
2691
|
return { found: false, reason };
|
|
1653
2692
|
}
|
|
2693
|
+
function nonEmpty(values) {
|
|
2694
|
+
return values?.length ? values : void 0;
|
|
2695
|
+
}
|
|
2696
|
+
function buildProductListingPageWhere(params) {
|
|
2697
|
+
const clauses = [];
|
|
2698
|
+
const search = params.search?.trim();
|
|
2699
|
+
if (search) {
|
|
2700
|
+
clauses.push({
|
|
2701
|
+
or: [
|
|
2702
|
+
{ title: { like: search } },
|
|
2703
|
+
{ slug: { like: search } },
|
|
2704
|
+
{ handle: { like: search } }
|
|
2705
|
+
]
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
const filters = params.filters;
|
|
2709
|
+
const ids = nonEmpty(filters?.ids);
|
|
2710
|
+
if (ids) clauses.push({ id: { in: ids } });
|
|
2711
|
+
const slugs = nonEmpty(filters?.slugs);
|
|
2712
|
+
if (slugs) clauses.push({ slug: { in: slugs } });
|
|
2713
|
+
const handles = nonEmpty(filters?.handles);
|
|
2714
|
+
if (handles) clauses.push({ handle: { in: handles } });
|
|
2715
|
+
const categoryIds = nonEmpty(filters?.categoryIds);
|
|
2716
|
+
if (categoryIds) clauses.push({ categories: { in: categoryIds } });
|
|
2717
|
+
const tagIds = nonEmpty(filters?.tagIds);
|
|
2718
|
+
if (tagIds) clauses.push({ tags: { in: tagIds } });
|
|
2719
|
+
const minPrice = filters?.price?.min;
|
|
2720
|
+
if (minPrice != null) {
|
|
2721
|
+
clauses.push({
|
|
2722
|
+
"priceRange.maxVariantPrice.amount": { greater_than_equal: minPrice }
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
const maxPrice = filters?.price?.max;
|
|
2726
|
+
if (maxPrice != null) {
|
|
2727
|
+
clauses.push({
|
|
2728
|
+
"priceRange.minVariantPrice.amount": { less_than_equal: maxPrice }
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
if (filters?.availableForSale != null) {
|
|
2732
|
+
clauses.push({
|
|
2733
|
+
availableForSale: { equals: filters.availableForSale }
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
if (clauses.length === 0) return void 0;
|
|
2737
|
+
if (clauses.length === 1) return clauses[0];
|
|
2738
|
+
return { and: clauses };
|
|
2739
|
+
}
|
|
2740
|
+
function buildProductListingPageUrl(params) {
|
|
2741
|
+
const options = {
|
|
2742
|
+
page: params.page,
|
|
2743
|
+
limit: params.limit,
|
|
2744
|
+
sort: params.sort,
|
|
2745
|
+
where: buildProductListingPageWhere(params)
|
|
2746
|
+
};
|
|
2747
|
+
return params.mode === "full" ? listingGroupsQueryUrl(options) : listingGroupsQueryCatalogUrl(options);
|
|
2748
|
+
}
|
|
2749
|
+
function withProductListingCards(response, options) {
|
|
2750
|
+
return {
|
|
2751
|
+
...response,
|
|
2752
|
+
cards: response.docs.map((item) => buildProductListingCard(item, options))
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
1654
2755
|
function rejectLegacyOptionValueSwatchColor(value) {
|
|
1655
2756
|
if (Object.prototype.hasOwnProperty.call(
|
|
1656
2757
|
value,
|
|
1657
2758
|
"swatchColor"
|
|
1658
2759
|
)) {
|
|
1659
2760
|
throw new TypeError(
|
|
1660
|
-
'Product upsert option values no longer accept
|
|
2761
|
+
'Product upsert option values no longer accept removed flat "swatchColor"; use nested "swatch" instead.'
|
|
1661
2762
|
);
|
|
1662
2763
|
}
|
|
1663
2764
|
}
|
|
@@ -1715,6 +2816,14 @@ var ProductApi = class extends BaseApi {
|
|
|
1715
2816
|
{ method: "GET" }
|
|
1716
2817
|
);
|
|
1717
2818
|
}
|
|
2819
|
+
async listingPage(params = {}) {
|
|
2820
|
+
if (params.mode === "full") {
|
|
2821
|
+
const response2 = await this.request(buildProductListingPageUrl(params), void 0, { method: "GET" });
|
|
2822
|
+
return withProductListingCards(response2, params);
|
|
2823
|
+
}
|
|
2824
|
+
const response = await this.request(buildProductListingPageUrl(params), void 0, { method: "GET" });
|
|
2825
|
+
return withProductListingCards(response, params);
|
|
2826
|
+
}
|
|
1718
2827
|
/**
|
|
1719
2828
|
* Fetch full product detail by slug or id.
|
|
1720
2829
|
* Returns a discriminated result so storefronts can distinguish missing,
|
|
@@ -1858,6 +2967,12 @@ var OrderApi = class extends BaseApi {
|
|
|
1858
2967
|
idempotencyRequestOptions(params.idempotencyKey)
|
|
1859
2968
|
);
|
|
1860
2969
|
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Returns a `PublicOrder`-compatible shape even when the server backs the
|
|
2972
|
+
* response with a Checkout (checkout/order separation, #1285). `orderNumber`
|
|
2973
|
+
* stays the stable cross-window key. A breaking `PublicCheckout` split is a
|
|
2974
|
+
* deferred follow-up (design D10 / Q3-b); do not change this return type here.
|
|
2975
|
+
*/
|
|
1861
2976
|
checkout(params) {
|
|
1862
2977
|
const { body, idempotencyKey } = splitIdempotencyKey(params);
|
|
1863
2978
|
return this.request(
|
|
@@ -1986,6 +3101,7 @@ var ServerCommerceClient = class {
|
|
|
1986
3101
|
this.product = {
|
|
1987
3102
|
stockCheck: productApi.stockCheck.bind(productApi),
|
|
1988
3103
|
listingGroups: productApi.listingGroups.bind(productApi),
|
|
3104
|
+
listingPage: productApi.listingPage.bind(productApi),
|
|
1989
3105
|
detail: productApi.detail.bind(productApi),
|
|
1990
3106
|
previewDetail: productApi.previewDetail.bind(productApi),
|
|
1991
3107
|
upsert: productApi.upsert.bind(productApi)
|