@01.software/sdk 0.10.0 → 0.11.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 CHANGED
@@ -429,7 +429,7 @@ const handler = createTypedWebhookHandler('orders', async (event) => {
429
429
  | Carts | `carts`, `cart-items` |
430
430
  | Commerce | `discounts`, `shipping-policies` |
431
431
  | Content | `posts`, `post-authors`, `post-categories`, `post-tags`, `documents`, `document-categories`, `document-types` |
432
- | Playlists | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `playlist-tracks`, `track-categories`, `track-tags` |
432
+ | Playlists | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `track-categories`, `track-tags` |
433
433
  | Galleries | `galleries`, `gallery-items`, `gallery-categories`, `gallery-tags` |
434
434
  | Canvas | `canvases`, `canvas-node-types`, `canvas-edge-types`, `canvas-categories`, `canvas-tags` |
435
435
  | Videos | `videos`, `video-categories`, `video-tags` |
@@ -1,4 +1,4 @@
1
- import { C as Config } from './payload-types-D0zFPijW.js';
1
+ import { C as Config } from './payload-types-BX9al1wy.js';
2
2
 
3
3
  /**
4
4
  * Collection type derived from Payload Config.
@@ -9,7 +9,7 @@ type Collection = keyof Config['collections'];
9
9
  * Array of all public collection names for runtime use (e.g., Zod enum validation).
10
10
  * This is the single source of truth for which collections are publicly accessible via SDK.
11
11
  */
12
- declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "shipping-policies", "documents", "document-categories", "document-types", "posts", "post-authors", "post-categories", "post-tags", "playlists", "playlist-categories", "playlist-tags", "tracks", "playlist-tracks", "track-categories", "track-tags", "galleries", "gallery-categories", "gallery-tags", "gallery-items", "links", "link-categories", "link-tags", "canvases", "canvas-node-types", "canvas-edge-types", "canvas-categories", "canvas-tags", "videos", "video-categories", "video-tags", "live-streams", "images", "forms", "form-submissions", "threads", "comments", "reactions", "reaction-types", "bookmarks", "thread-categories", "reports", "community-bans"];
12
+ declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "shipping-policies", "documents", "document-categories", "document-types", "posts", "post-authors", "post-categories", "post-tags", "playlists", "playlist-categories", "playlist-tags", "tracks", "track-categories", "track-tags", "galleries", "gallery-categories", "gallery-tags", "gallery-items", "links", "link-categories", "link-tags", "canvases", "canvas-node-types", "canvas-edge-types", "canvas-categories", "canvas-tags", "videos", "video-categories", "video-tags", "live-streams", "images", "forms", "form-submissions", "threads", "comments", "reactions", "reaction-types", "bookmarks", "thread-categories", "reports", "community-bans"];
13
13
  /**
14
14
  * Public collections available for SDK access.
15
15
  * Derived from the COLLECTIONS array (single source of truth).
@@ -1,4 +1,4 @@
1
- import { C as Config } from './payload-types-D0zFPijW.cjs';
1
+ import { C as Config } from './payload-types-BX9al1wy.cjs';
2
2
 
3
3
  /**
4
4
  * Collection type derived from Payload Config.
@@ -9,7 +9,7 @@ type Collection = keyof Config['collections'];
9
9
  * Array of all public collection names for runtime use (e.g., Zod enum validation).
10
10
  * This is the single source of truth for which collections are publicly accessible via SDK.
11
11
  */
12
- declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "shipping-policies", "documents", "document-categories", "document-types", "posts", "post-authors", "post-categories", "post-tags", "playlists", "playlist-categories", "playlist-tags", "tracks", "playlist-tracks", "track-categories", "track-tags", "galleries", "gallery-categories", "gallery-tags", "gallery-items", "links", "link-categories", "link-tags", "canvases", "canvas-node-types", "canvas-edge-types", "canvas-categories", "canvas-tags", "videos", "video-categories", "video-tags", "live-streams", "images", "forms", "form-submissions", "threads", "comments", "reactions", "reaction-types", "bookmarks", "thread-categories", "reports", "community-bans"];
12
+ declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "shipping-policies", "documents", "document-categories", "document-types", "posts", "post-authors", "post-categories", "post-tags", "playlists", "playlist-categories", "playlist-tags", "tracks", "track-categories", "track-tags", "galleries", "gallery-categories", "gallery-tags", "gallery-items", "links", "link-categories", "link-tags", "canvases", "canvas-node-types", "canvas-edge-types", "canvas-categories", "canvas-tags", "videos", "video-categories", "video-tags", "live-streams", "images", "forms", "form-submissions", "threads", "comments", "reactions", "reaction-types", "bookmarks", "thread-categories", "reports", "community-bans"];
13
13
  /**
14
14
  * Public collections available for SDK access.
15
15
  * Derived from the COLLECTIONS array (single source of truth).
package/dist/index.cjs CHANGED
@@ -45,6 +45,9 @@ __export(src_exports, {
45
45
  TimeoutError: () => TimeoutError,
46
46
  UsageLimitError: () => UsageLimitError,
47
47
  ValidationError: () => ValidationError,
48
+ buildProductListingGroupsByOption: () => buildProductListingGroupsByOption,
49
+ buildProductListingProjection: () => buildProductListingProjection,
50
+ buildProductOptionMatrix: () => buildProductOptionMatrix,
48
51
  collectionKeys: () => collectionKeys,
49
52
  createClient: () => createClient,
50
53
  createServerClient: () => createServerClient,
@@ -52,12 +55,14 @@ __export(src_exports, {
52
55
  customerKeys: () => customerKeys,
53
56
  formatOrderName: () => formatOrderName,
54
57
  generateOrderNumber: () => generateOrderNumber,
58
+ getAvailableOptionValues: () => getAvailableOptionValues,
55
59
  getImageLqip: () => getImageLqip,
56
60
  getImagePalette: () => getImagePalette,
57
61
  getImagePlaceholderStyle: () => getImagePlaceholderStyle,
58
62
  getImageSrcSet: () => getImageSrcSet,
59
63
  getImageUrl: () => getImageUrl,
60
64
  getQueryClient: () => getQueryClient,
65
+ getSelectedValueByOptionId: () => getSelectedValueByOptionId,
61
66
  getVideoGif: () => getVideoGif,
62
67
  getVideoMp4Url: () => getVideoMp4Url,
63
68
  getVideoStoryboard: () => getVideoStoryboard,
@@ -74,7 +79,10 @@ __export(src_exports, {
74
79
  isUsageLimitError: () => isUsageLimitError,
75
80
  isValidWebhookEvent: () => isValidWebhookEvent,
76
81
  isValidationError: () => isValidationError,
77
- resolveRelation: () => resolveRelation
82
+ normalizeSelectedValueIds: () => normalizeSelectedValueIds,
83
+ productKeys: () => productKeys,
84
+ resolveRelation: () => resolveRelation,
85
+ resolveVariantForSelection: () => resolveVariantForSelection
78
86
  });
79
87
  module.exports = __toCommonJS(src_exports);
80
88
 
@@ -644,6 +652,12 @@ var ProductApi = class extends BaseApi {
644
652
  stockCheck(params) {
645
653
  return this.request("/api/products/stock-check", params);
646
654
  }
655
+ listingGroups(params) {
656
+ return this.request(
657
+ "/api/products/listing-groups",
658
+ params
659
+ );
660
+ }
647
661
  };
648
662
 
649
663
  // src/utils/types.ts
@@ -988,6 +1002,18 @@ var CollectionClient = class extends HttpClient {
988
1002
  });
989
1003
  return this.parseFindResponse(response);
990
1004
  }
1005
+ /**
1006
+ * Find-like response from a custom endpoint
1007
+ * POST /api/...custom-endpoint
1008
+ */
1009
+ async requestFindEndpoint(endpoint, data) {
1010
+ const response = await httpFetch(endpoint, {
1011
+ ...this.defaultOptions,
1012
+ method: "POST",
1013
+ body: data ? JSON.stringify(data) : void 0
1014
+ });
1015
+ return this.parseFindResponse(response);
1016
+ }
991
1017
  /**
992
1018
  * Find document by ID
993
1019
  * GET /api/{collection}/{id}
@@ -1136,7 +1162,6 @@ var COLLECTIONS = [
1136
1162
  "playlist-categories",
1137
1163
  "playlist-tags",
1138
1164
  "tracks",
1139
- "playlist-tracks",
1140
1165
  "track-categories",
1141
1166
  "track-tags",
1142
1167
  "galleries",
@@ -1631,6 +1656,9 @@ function getQueryClient() {
1631
1656
  return browserQueryClient;
1632
1657
  }
1633
1658
 
1659
+ // src/core/query/query-hooks.ts
1660
+ var import_react_query4 = require("@tanstack/react-query");
1661
+
1634
1662
  // src/core/query/collection-hooks.ts
1635
1663
  var import_react_query2 = require("@tanstack/react-query");
1636
1664
 
@@ -1650,6 +1678,10 @@ var customerKeys = {
1650
1678
  all: ["customer"],
1651
1679
  me: () => ["customer", "me"]
1652
1680
  };
1681
+ var productKeys = {
1682
+ listingGroups: (options) => ["products", "listing-groups", "list", options],
1683
+ listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options]
1684
+ };
1653
1685
 
1654
1686
  // src/core/query/collection-hooks.ts
1655
1687
  var DEFAULT_PAGE_SIZE = 20;
@@ -2024,6 +2056,98 @@ var QueryHooks = class extends CollectionHooks {
2024
2056
  this.setCustomerData = (data) => this._customer.setCustomerData(data);
2025
2057
  this._customer = new CustomerHooks(queryClient, customerAuth);
2026
2058
  }
2059
+ useProductListingGroupsQuery(params, options) {
2060
+ const queryOptions = params.options;
2061
+ const { placeholderData, ...restOptions } = options ?? {};
2062
+ return (0, import_react_query4.useQuery)({
2063
+ queryKey: productKeys.listingGroups(queryOptions),
2064
+ queryFn: async () => this.collectionClient.requestFindEndpoint(
2065
+ "/api/products/listing-groups/query",
2066
+ { options: queryOptions }
2067
+ ),
2068
+ ...restOptions,
2069
+ ...placeholderData !== void 0 && {
2070
+ placeholderData
2071
+ }
2072
+ });
2073
+ }
2074
+ useSuspenseProductListingGroupsQuery(params, options) {
2075
+ const queryOptions = params.options;
2076
+ return (0, import_react_query4.useSuspenseQuery)({
2077
+ queryKey: productKeys.listingGroups(queryOptions),
2078
+ queryFn: async () => this.collectionClient.requestFindEndpoint(
2079
+ "/api/products/listing-groups/query",
2080
+ { options: queryOptions }
2081
+ ),
2082
+ ...options
2083
+ });
2084
+ }
2085
+ useInfiniteProductListingGroupsQuery(params, options) {
2086
+ const {
2087
+ options: queryOptions,
2088
+ pageSize = 20
2089
+ } = params;
2090
+ return (0, import_react_query4.useInfiniteQuery)({
2091
+ queryKey: productKeys.listingGroupsInfinite(queryOptions),
2092
+ queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2093
+ "/api/products/listing-groups/query",
2094
+ {
2095
+ options: { ...queryOptions, page: pageParam, limit: pageSize }
2096
+ }
2097
+ ),
2098
+ initialPageParam: 1,
2099
+ getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2100
+ ...options
2101
+ });
2102
+ }
2103
+ useSuspenseInfiniteProductListingGroupsQuery(params, options) {
2104
+ const {
2105
+ options: queryOptions,
2106
+ pageSize = 20
2107
+ } = params;
2108
+ return (0, import_react_query4.useSuspenseInfiniteQuery)({
2109
+ queryKey: productKeys.listingGroupsInfinite(queryOptions),
2110
+ queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2111
+ "/api/products/listing-groups/query",
2112
+ {
2113
+ options: { ...queryOptions, page: pageParam, limit: pageSize }
2114
+ }
2115
+ ),
2116
+ initialPageParam: 1,
2117
+ getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2118
+ ...options
2119
+ });
2120
+ }
2121
+ async prefetchProductListingGroupsQuery(params, options) {
2122
+ const queryOptions = params.options;
2123
+ return this.queryClient.prefetchQuery({
2124
+ queryKey: productKeys.listingGroups(queryOptions),
2125
+ queryFn: async () => this.collectionClient.requestFindEndpoint(
2126
+ "/api/products/listing-groups/query",
2127
+ { options: queryOptions }
2128
+ ),
2129
+ ...options
2130
+ });
2131
+ }
2132
+ async prefetchInfiniteProductListingGroupsQuery(params, options) {
2133
+ const {
2134
+ options: queryOptions,
2135
+ pageSize = 20
2136
+ } = params;
2137
+ return this.queryClient.prefetchInfiniteQuery({
2138
+ queryKey: productKeys.listingGroupsInfinite(queryOptions),
2139
+ queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
2140
+ "/api/products/listing-groups/query",
2141
+ {
2142
+ options: { ...queryOptions, page: pageParam, limit: pageSize }
2143
+ }
2144
+ ),
2145
+ initialPageParam: 1,
2146
+ getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
2147
+ pages: options?.pages ?? 1,
2148
+ staleTime: options?.staleTime
2149
+ });
2150
+ }
2027
2151
  };
2028
2152
 
2029
2153
  // src/core/client/client.ts
@@ -2364,6 +2488,242 @@ function createTypedWebhookHandler(collection, handler) {
2364
2488
  };
2365
2489
  }
2366
2490
 
2491
+ // src/utils/ecommerce.ts
2492
+ function getRelationID(value) {
2493
+ if (typeof value === "string") return value;
2494
+ if (typeof value === "number") return String(value);
2495
+ if (value && typeof value === "object" && "id" in value) {
2496
+ const id = value.id;
2497
+ if (typeof id === "string") return id;
2498
+ if (typeof id === "number") return String(id);
2499
+ }
2500
+ return void 0;
2501
+ }
2502
+ function compareOrder(left, right) {
2503
+ return (left ?? "").localeCompare(right ?? "");
2504
+ }
2505
+ function isProductOptionValueDoc(value) {
2506
+ return typeof value === "object" && value !== null && "id" in value && ("value" in value || "slug" in value);
2507
+ }
2508
+ function extractEntityId(value) {
2509
+ if (typeof value === "string") return value;
2510
+ if (typeof value === "number") return String(value);
2511
+ if (value && typeof value === "object" && "id" in value) {
2512
+ if (typeof value.id === "string") return value.id;
2513
+ if (typeof value.id === "number") return String(value.id);
2514
+ }
2515
+ return null;
2516
+ }
2517
+ function getFirstMediaId(values) {
2518
+ if (!Array.isArray(values)) return null;
2519
+ for (const value of values) {
2520
+ const id = extractEntityId(value);
2521
+ if (id != null) return id;
2522
+ }
2523
+ return null;
2524
+ }
2525
+ function getVariantPrimaryImage(variant) {
2526
+ if (!variant) return null;
2527
+ return extractEntityId(variant.thumbnail) ?? getFirstMediaId(variant.images);
2528
+ }
2529
+ function normalizeOptionValue(value, fallbackOptionId) {
2530
+ return {
2531
+ id: String(value.id),
2532
+ optionId: getRelationID(value.option) ?? fallbackOptionId,
2533
+ label: value.value || value.slug || String(value.id),
2534
+ slug: value.slug ?? null,
2535
+ order: value._order ?? value["_product-option-values_values_order"] ?? ""
2536
+ };
2537
+ }
2538
+ function normalizeVariantOptionValues(variant, valueToOptionId, optionIds) {
2539
+ const optionValueByOptionId = /* @__PURE__ */ new Map();
2540
+ for (const rawValue of Array.isArray(variant.optionValues) ? variant.optionValues : []) {
2541
+ const valueId = getRelationID(rawValue);
2542
+ if (!valueId) continue;
2543
+ const optionId = valueToOptionId.get(valueId) ?? (isProductOptionValueDoc(rawValue) ? getRelationID(rawValue.option) : void 0);
2544
+ if (!optionId || optionValueByOptionId.has(optionId)) continue;
2545
+ optionValueByOptionId.set(optionId, valueId);
2546
+ }
2547
+ const optionValueIds = optionIds.map((optionId) => optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId));
2548
+ return {
2549
+ id: String(variant.id),
2550
+ optionValueIds,
2551
+ optionValueByOptionId,
2552
+ source: variant
2553
+ };
2554
+ }
2555
+ function buildProductOptionMatrix({
2556
+ options,
2557
+ variants = []
2558
+ }) {
2559
+ const normalizedOptions = options.map((option) => {
2560
+ const valuesById = /* @__PURE__ */ new Map();
2561
+ for (const rawValue of option.values?.docs ?? []) {
2562
+ if (!isProductOptionValueDoc(rawValue)) continue;
2563
+ const normalizedValue = normalizeOptionValue(rawValue, String(option.id));
2564
+ valuesById.set(normalizedValue.id, normalizedValue);
2565
+ }
2566
+ return {
2567
+ id: String(option.id),
2568
+ title: option.title ?? String(option.id),
2569
+ order: option._order ?? option["_product-options_options_order"] ?? "",
2570
+ values: Array.from(valuesById.values()).sort(
2571
+ (left, right) => compareOrder(left.order, right.order)
2572
+ )
2573
+ };
2574
+ }).sort((left, right) => compareOrder(left.order, right.order));
2575
+ const optionById = new Map(normalizedOptions.map((option) => [option.id, option]));
2576
+ const valueById = /* @__PURE__ */ new Map();
2577
+ const valueToOptionId = /* @__PURE__ */ new Map();
2578
+ for (const option of normalizedOptions) {
2579
+ for (const value of option.values) {
2580
+ valueById.set(value.id, value);
2581
+ valueToOptionId.set(value.id, option.id);
2582
+ }
2583
+ }
2584
+ const optionIds = normalizedOptions.map((option) => option.id);
2585
+ const normalizedVariants = variants.map(
2586
+ (variant) => normalizeVariantOptionValues(variant, valueToOptionId, optionIds)
2587
+ );
2588
+ return {
2589
+ options: normalizedOptions,
2590
+ optionIds,
2591
+ optionById,
2592
+ valueById,
2593
+ valueToOptionId,
2594
+ variants: normalizedVariants
2595
+ };
2596
+ }
2597
+ function getSelectedValueByOptionId(matrix, selectedValueIds) {
2598
+ const selectedByOption = /* @__PURE__ */ new Map();
2599
+ for (const rawValue of selectedValueIds) {
2600
+ const valueId = getRelationID(rawValue);
2601
+ if (!valueId) continue;
2602
+ const optionId = matrix.valueToOptionId.get(valueId);
2603
+ if (!optionId || selectedByOption.has(optionId)) continue;
2604
+ selectedByOption.set(optionId, valueId);
2605
+ }
2606
+ return selectedByOption;
2607
+ }
2608
+ function normalizeSelectedValueIds(matrix, selectedValueIds) {
2609
+ const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2610
+ return matrix.optionIds.map((optionId) => selectedByOption.get(optionId)).filter((valueId) => Boolean(valueId));
2611
+ }
2612
+ function getAvailableOptionValues(matrix, optionId, selectedValueIds) {
2613
+ const option = matrix.optionById.get(optionId);
2614
+ if (!option) return [];
2615
+ if (matrix.variants.length === 0) {
2616
+ return option.values;
2617
+ }
2618
+ const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2619
+ selectedByOption.delete(optionId);
2620
+ const matchingVariants = matrix.variants.filter(
2621
+ (variant) => Array.from(selectedByOption.entries()).every(
2622
+ ([selectedOptionId, selectedValueId]) => variant.optionValueByOptionId.get(selectedOptionId) === selectedValueId
2623
+ )
2624
+ );
2625
+ const availableValueIds = new Set(
2626
+ matchingVariants.map((variant) => variant.optionValueByOptionId.get(optionId)).filter((valueId) => Boolean(valueId))
2627
+ );
2628
+ return option.values.filter((value) => availableValueIds.has(value.id));
2629
+ }
2630
+ function resolveVariantForSelection(matrix, selectedValueIds) {
2631
+ const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
2632
+ if (selectedByOption.size !== matrix.optionIds.length) {
2633
+ return void 0;
2634
+ }
2635
+ return matrix.variants.find(
2636
+ (variant) => matrix.optionIds.every(
2637
+ (optionId) => variant.optionValueByOptionId.get(optionId) === selectedByOption.get(optionId)
2638
+ )
2639
+ );
2640
+ }
2641
+ function compareVariantOrder(a, b) {
2642
+ const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
2643
+ const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
2644
+ if (Number.isFinite(aOrder) && Number.isFinite(bOrder) && aOrder !== bOrder) {
2645
+ return aOrder - bOrder;
2646
+ }
2647
+ const aId = String(getRelationID(a.id) ?? "");
2648
+ const bId = String(getRelationID(b.id) ?? "");
2649
+ return aId.localeCompare(bId);
2650
+ }
2651
+ function isVariantAvailableForSale(variant) {
2652
+ if (variant.isActive === false) return false;
2653
+ if (variant.isUnlimited) return true;
2654
+ return (variant.stock ?? 0) > 0;
2655
+ }
2656
+ function getMinMax(values) {
2657
+ const numbers = values.filter((value) => typeof value === "number");
2658
+ if (numbers.length === 0) {
2659
+ return { min: null, max: null };
2660
+ }
2661
+ return {
2662
+ min: Math.min(...numbers),
2663
+ max: Math.max(...numbers)
2664
+ };
2665
+ }
2666
+ function buildProductListingProjection(product, variants) {
2667
+ const orderedVariants = [...variants].sort(compareVariantOrder);
2668
+ const activeVariants = orderedVariants.filter((variant) => variant.isActive !== false);
2669
+ const availableVariants = activeVariants.filter(isVariantAvailableForSale);
2670
+ const selectionHintVariant = availableVariants[0] ?? activeVariants[0] ?? null;
2671
+ const { min: minPrice, max: maxPrice } = getMinMax(
2672
+ activeVariants.map((variant) => variant.price)
2673
+ );
2674
+ const { min: minCompareAtPrice, max: maxCompareAtPrice } = getMinMax(
2675
+ activeVariants.map((variant) => variant.compareAtPrice)
2676
+ );
2677
+ const productPrimaryImage = extractEntityId(product?.thumbnail) ?? getFirstMediaId(product?.images);
2678
+ const variantPrimaryImage = getVariantPrimaryImage(selectionHintVariant) ?? getVariantPrimaryImage(activeVariants[0]) ?? getVariantPrimaryImage(orderedVariants[0]);
2679
+ return {
2680
+ selectionHintVariant: getRelationID(selectionHintVariant?.id) ?? null,
2681
+ primaryImage: productPrimaryImage ?? variantPrimaryImage,
2682
+ minPrice,
2683
+ maxPrice,
2684
+ minCompareAtPrice,
2685
+ maxCompareAtPrice,
2686
+ isPriceRange: minPrice !== null && maxPrice !== null ? minPrice !== maxPrice : false,
2687
+ availableForSale: availableVariants.length > 0
2688
+ };
2689
+ }
2690
+ function buildProductListingGroupsByOption(args) {
2691
+ const primaryOptionId = args.primaryOptionId ?? void 0;
2692
+ if (!primaryOptionId) return [];
2693
+ const matrix = buildProductOptionMatrix({
2694
+ options: args.options,
2695
+ variants: args.variants
2696
+ });
2697
+ const primaryOption = matrix.optionById.get(primaryOptionId);
2698
+ if (!primaryOption) return [];
2699
+ return primaryOption.values.flatMap((value) => {
2700
+ const variants = matrix.variants.filter(
2701
+ (variant) => variant.optionValueByOptionId.get(primaryOption.id) === value.id
2702
+ ).map((variant) => variant.source);
2703
+ if (variants.length === 0) {
2704
+ return [];
2705
+ }
2706
+ const listingBase = buildProductListingProjection(void 0, variants);
2707
+ const productFallbackImage = extractEntityId(args.product?.thumbnail) ?? getFirstMediaId(args.product?.images);
2708
+ return [
2709
+ {
2710
+ optionId: primaryOption.id,
2711
+ optionTitle: primaryOption.title,
2712
+ optionValueId: value.id,
2713
+ optionValueLabel: value.label,
2714
+ optionValueSlug: value.slug,
2715
+ variantIds: variants.map((variant) => String(variant.id)),
2716
+ variantCount: variants.length,
2717
+ variants,
2718
+ listing: {
2719
+ ...listingBase,
2720
+ primaryImage: listingBase.primaryImage ?? productFallbackImage
2721
+ }
2722
+ }
2723
+ ];
2724
+ });
2725
+ }
2726
+
2367
2727
  // src/utils/image.ts
2368
2728
  var IMAGE_SIZES = [384, 768, 1536];
2369
2729
  function getImageUrl(image, displayWidth, dpr = 1) {