shop-client 3.16.0 → 3.17.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.
@@ -1,24 +1,35 @@
1
1
  import {
2
2
  rateLimitedFetch
3
- } from "./chunk-D5MTUWFO.mjs";
3
+ } from "./chunk-O77Z6OBJ.mjs";
4
4
  import {
5
5
  formatPrice
6
6
  } from "./chunk-U3RQRBXZ.mjs";
7
7
 
8
8
  // src/products.ts
9
9
  import { filter, isNonNullish } from "remeda";
10
- function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDto, productDto, getStoreInfo, findProduct, ai) {
10
+ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDto, productDto, getStoreInfo, _findProduct, ai) {
11
11
  const cacheExpiryMs = 5 * 60 * 1e3;
12
- const findCache = /* @__PURE__ */ new Map();
13
- const getCached = (key) => {
14
- const entry = findCache.get(key);
12
+ const findCacheFull = /* @__PURE__ */ new Map();
13
+ const findCacheMinimal = /* @__PURE__ */ new Map();
14
+ const getCachedFull = (key) => {
15
+ const entry = findCacheFull.get(key);
15
16
  if (!entry) return void 0;
16
17
  if (Date.now() - entry.ts < cacheExpiryMs) return entry.value;
17
- findCache.delete(key);
18
+ findCacheFull.delete(key);
18
19
  return void 0;
19
20
  };
20
- const setCached = (key, value) => {
21
- findCache.set(key, { ts: Date.now(), value });
21
+ const setCachedFull = (key, value) => {
22
+ findCacheFull.set(key, { ts: Date.now(), value });
23
+ };
24
+ const getCachedMinimal = (key) => {
25
+ const entry = findCacheMinimal.get(key);
26
+ if (!entry) return void 0;
27
+ if (Date.now() - entry.ts < cacheExpiryMs) return entry.value;
28
+ findCacheMinimal.delete(key);
29
+ return void 0;
30
+ };
31
+ const setCachedMinimal = (key, value) => {
32
+ findCacheMinimal.set(key, { ts: Date.now(), value });
22
33
  };
23
34
  function applyCurrencyOverride(product, currency) {
24
35
  var _a, _b, _c, _d, _e, _f;
@@ -37,10 +48,256 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
37
48
  }
38
49
  };
39
50
  }
51
+ function applyCurrencyOverrideMinimal(product, currency) {
52
+ var _a;
53
+ const compareAtPrice = (_a = product.compareAtPrice) != null ? _a : 0;
54
+ return {
55
+ ...product,
56
+ localizedPricing: {
57
+ priceFormatted: formatPrice(product.price, currency),
58
+ compareAtPriceFormatted: formatPrice(compareAtPrice, currency)
59
+ }
60
+ };
61
+ }
40
62
  function maybeOverrideProductsCurrency(products, currency) {
41
- if (!products || !currency) return products;
63
+ if (!products || !currency || products.length === 0) return products;
42
64
  return products.map((p) => applyCurrencyOverride(p, currency));
43
65
  }
66
+ function maybeOverrideMinimalProductsCurrency(products, currency) {
67
+ if (!products || !currency || products.length === 0) return products;
68
+ return products.map((p) => applyCurrencyOverrideMinimal(p, currency));
69
+ }
70
+ async function allInternal(options) {
71
+ const limit = 250;
72
+ const allProducts = [];
73
+ async function fetchAll() {
74
+ let currentPage = 1;
75
+ while (true) {
76
+ const products = await fetchProducts(currentPage, limit, {
77
+ minimal: options.minimal
78
+ });
79
+ if (!products || products.length === 0 || products.length < limit) {
80
+ if (products && products.length > 0) {
81
+ allProducts.push(...products);
82
+ }
83
+ break;
84
+ }
85
+ allProducts.push(...products);
86
+ currentPage++;
87
+ }
88
+ return allProducts;
89
+ }
90
+ try {
91
+ const products = await fetchAll();
92
+ return options.minimal ? maybeOverrideMinimalProductsCurrency(
93
+ products,
94
+ options.currency
95
+ ) : maybeOverrideProductsCurrency(
96
+ products,
97
+ options.currency
98
+ );
99
+ } catch (error) {
100
+ console.error("Failed to fetch all products:", storeDomain, error);
101
+ throw error;
102
+ }
103
+ }
104
+ async function paginatedInternal(options) {
105
+ var _a, _b;
106
+ const page = (_a = options.page) != null ? _a : 1;
107
+ const limit = Math.min((_b = options.limit) != null ? _b : 250, 250);
108
+ const url = `${baseUrl}products.json?limit=${limit}&page=${page}`;
109
+ try {
110
+ const response = await rateLimitedFetch(url, {
111
+ rateLimitClass: "products:paginated"
112
+ });
113
+ if (!response.ok) {
114
+ console.error(
115
+ `HTTP error! status: ${response.status} for ${storeDomain} page ${page}`
116
+ );
117
+ throw new Error(`HTTP error! status: ${response.status}`);
118
+ }
119
+ const data = await response.json();
120
+ if (data.products.length === 0) {
121
+ return [];
122
+ }
123
+ const normalized = productsDto(data.products, {
124
+ minimal: options.minimal
125
+ });
126
+ return options.minimal ? maybeOverrideMinimalProductsCurrency(
127
+ normalized || null,
128
+ options.currency
129
+ ) : maybeOverrideProductsCurrency(
130
+ normalized || null,
131
+ options.currency
132
+ );
133
+ } catch (error) {
134
+ console.error(
135
+ `Error fetching products for ${storeDomain} page ${page} with limit ${limit}:`,
136
+ error
137
+ );
138
+ return null;
139
+ }
140
+ }
141
+ async function findInternal(productHandle, options) {
142
+ var _a, _b;
143
+ if (!productHandle || typeof productHandle !== "string") {
144
+ throw new Error("Product handle is required and must be a string");
145
+ }
146
+ try {
147
+ let qs = null;
148
+ if (productHandle.includes("?")) {
149
+ const parts = productHandle.split("?");
150
+ const handlePart = (_a = parts[0]) != null ? _a : productHandle;
151
+ const qsPart = (_b = parts[1]) != null ? _b : null;
152
+ productHandle = handlePart;
153
+ qs = qsPart;
154
+ }
155
+ const sanitizedHandle = productHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
156
+ if (!sanitizedHandle) {
157
+ throw new Error("Invalid product handle format");
158
+ }
159
+ if (sanitizedHandle.length > 255) {
160
+ throw new Error("Product handle is too long");
161
+ }
162
+ const cached = options.minimal ? getCachedMinimal(sanitizedHandle) : getCachedFull(sanitizedHandle);
163
+ if (typeof cached !== "undefined") {
164
+ if (!cached || !options.currency) return cached;
165
+ return options.minimal ? applyCurrencyOverrideMinimal(
166
+ cached,
167
+ options.currency
168
+ ) : applyCurrencyOverride(cached, options.currency);
169
+ }
170
+ let finalHandle = sanitizedHandle;
171
+ try {
172
+ const htmlResp = await rateLimitedFetch(
173
+ `${baseUrl}products/${encodeURIComponent(sanitizedHandle)}`,
174
+ { rateLimitClass: "products:resolve" }
175
+ );
176
+ if (htmlResp.ok) {
177
+ const finalUrl = htmlResp.url;
178
+ if (finalUrl) {
179
+ const pathname = new URL(finalUrl).pathname.replace(/\/$/, "");
180
+ const parts = pathname.split("/").filter(Boolean);
181
+ const idx = parts.indexOf("products");
182
+ const maybeHandle = idx >= 0 ? parts[idx + 1] : void 0;
183
+ if (typeof maybeHandle === "string" && maybeHandle.length) {
184
+ finalHandle = maybeHandle;
185
+ }
186
+ }
187
+ }
188
+ } catch {
189
+ }
190
+ const url = `${baseUrl}products/${encodeURIComponent(finalHandle)}.js${qs ? `?${qs}` : ""}`;
191
+ const response = await rateLimitedFetch(url, {
192
+ rateLimitClass: "products:single"
193
+ });
194
+ if (!response.ok) {
195
+ if (response.status === 404) {
196
+ return null;
197
+ }
198
+ throw new Error(`HTTP error! status: ${response.status}`);
199
+ }
200
+ const product = await response.json();
201
+ const productData = productDto(product, { minimal: options.minimal });
202
+ if (options.minimal) {
203
+ const minimalData = productData;
204
+ setCachedMinimal(sanitizedHandle, minimalData);
205
+ if (finalHandle !== sanitizedHandle)
206
+ setCachedMinimal(finalHandle, minimalData);
207
+ return options.currency ? applyCurrencyOverrideMinimal(minimalData, options.currency) : minimalData;
208
+ }
209
+ const fullData = productData;
210
+ setCachedFull(sanitizedHandle, fullData);
211
+ if (finalHandle !== sanitizedHandle) setCachedFull(finalHandle, fullData);
212
+ return options.currency ? applyCurrencyOverride(fullData, options.currency) : fullData;
213
+ } catch (error) {
214
+ if (error instanceof Error) {
215
+ console.error(
216
+ `Error fetching product ${productHandle}:`,
217
+ baseUrl,
218
+ error.message
219
+ );
220
+ }
221
+ throw error;
222
+ }
223
+ }
224
+ async function predictiveSearchInternal(query, options) {
225
+ var _a, _b, _c, _d, _e, _f;
226
+ if (!query || typeof query !== "string") {
227
+ throw new Error("Query is required and must be a string");
228
+ }
229
+ const limit = Math.max(1, Math.min((_a = options.limit) != null ? _a : 10, 10));
230
+ const unavailable = options.unavailableProducts === "show" || options.unavailableProducts === "hide" ? options.unavailableProducts : "hide";
231
+ const localeValue = options.locale && options.locale.trim() || "en";
232
+ const localePrefix = `${localeValue.replace(/^\/|\/$/g, "")}/`;
233
+ const url = `${baseUrl}${localePrefix}search/suggest.json?q=${encodeURIComponent(query)}&resources[type]=product&resources[limit]=${limit}&resources[options][unavailable_products]=${unavailable}`;
234
+ const response = await rateLimitedFetch(url, {
235
+ rateLimitClass: "search:predictive",
236
+ timeoutMs: 7e3,
237
+ retry: { maxRetries: 2, baseDelayMs: 300 }
238
+ });
239
+ let resp = response;
240
+ if (!resp.ok && (resp.status === 404 || resp.status === 417)) {
241
+ const fallbackUrl = `${baseUrl}search/suggest.json?q=${encodeURIComponent(query)}&resources[type]=product&resources[limit]=${limit}&resources[options][unavailable_products]=${unavailable}`;
242
+ resp = await rateLimitedFetch(fallbackUrl, {
243
+ rateLimitClass: "search:predictive",
244
+ timeoutMs: 7e3,
245
+ retry: { maxRetries: 2, baseDelayMs: 300 }
246
+ });
247
+ }
248
+ if (!resp.ok) {
249
+ throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
250
+ }
251
+ const data = await resp.json();
252
+ const raw = (_d = (_c = (_b = data == null ? void 0 : data.resources) == null ? void 0 : _b.results) == null ? void 0 : _c.products) != null ? _d : [];
253
+ const handles = raw.filter((p) => p.available !== false).map((p) => p.handle).filter((h) => typeof h === "string" && h.length > 0).slice(0, limit);
254
+ const fetched = await Promise.all(
255
+ handles.map((h) => findInternal(h, { minimal: options.minimal }))
256
+ );
257
+ const results = filter(fetched, isNonNullish);
258
+ const finalProducts = options.minimal ? (_e = maybeOverrideMinimalProductsCurrency(
259
+ results,
260
+ options.currency
261
+ )) != null ? _e : [] : (_f = maybeOverrideProductsCurrency(
262
+ results,
263
+ options.currency
264
+ )) != null ? _f : [];
265
+ return finalProducts;
266
+ }
267
+ async function recommendationsInternal(productId, options) {
268
+ var _a, _b;
269
+ if (!Number.isFinite(productId) || productId <= 0) {
270
+ throw new Error("Valid productId is required");
271
+ }
272
+ const limit = Math.max(1, Math.min((_a = options.limit) != null ? _a : 10, 10));
273
+ const intent = (_b = options.intent) != null ? _b : "related";
274
+ const localeValue = options.locale && options.locale.trim() || "en";
275
+ const localePrefix = `${localeValue.replace(/^\/|\/$/g, "")}/`;
276
+ const url = `${baseUrl}${localePrefix}recommendations/products.json?product_id=${encodeURIComponent(String(productId))}&limit=${limit}&intent=${intent}`;
277
+ const resp = await rateLimitedFetch(url, {
278
+ rateLimitClass: "products:recommendations",
279
+ timeoutMs: 7e3,
280
+ retry: { maxRetries: 2, baseDelayMs: 300 }
281
+ });
282
+ if (!resp.ok) {
283
+ if (resp.status === 404) {
284
+ return [];
285
+ }
286
+ throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
287
+ }
288
+ const data = await resp.json();
289
+ const isRecord = (v) => typeof v === "object" && v !== null;
290
+ const productsArray = Array.isArray(data) ? data : isRecord(data) && Array.isArray(data.products) ? data.products : [];
291
+ const normalized = productsDto(productsArray, { minimal: options.minimal }) || [];
292
+ const finalProducts = options.minimal ? maybeOverrideMinimalProductsCurrency(
293
+ normalized,
294
+ options.currency
295
+ ) : maybeOverrideProductsCurrency(
296
+ normalized,
297
+ options.currency
298
+ );
299
+ return finalProducts != null ? finalProducts : [];
300
+ }
44
301
  const operations = {
45
302
  /**
46
303
  * Fetches all products from the store across all pages.
@@ -60,32 +317,7 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
60
317
  * });
61
318
  * ```
62
319
  */
63
- all: async (options) => {
64
- const limit = 250;
65
- const allProducts = [];
66
- async function fetchAll() {
67
- let currentPage = 1;
68
- while (true) {
69
- const products = await fetchProducts(currentPage, limit);
70
- if (!products || products.length === 0 || products.length < limit) {
71
- if (products && products.length > 0) {
72
- allProducts.push(...products);
73
- }
74
- break;
75
- }
76
- allProducts.push(...products);
77
- currentPage++;
78
- }
79
- return allProducts;
80
- }
81
- try {
82
- const products = await fetchAll();
83
- return maybeOverrideProductsCurrency(products, options == null ? void 0 : options.currency);
84
- } catch (error) {
85
- console.error("Failed to fetch all products:", storeDomain, error);
86
- throw error;
87
- }
88
- },
320
+ all: async (options) => allInternal({ currency: options == null ? void 0 : options.currency, minimal: false }),
89
321
  /**
90
322
  * Fetches products with pagination support.
91
323
  *
@@ -108,35 +340,12 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
108
340
  * const secondPage = await shop.products.paginated({ page: 2, limit: 50 });
109
341
  * ```
110
342
  */
111
- paginated: async (options) => {
112
- var _a, _b;
113
- const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
114
- const limit = Math.min((_b = options == null ? void 0 : options.limit) != null ? _b : 250, 250);
115
- const url = `${baseUrl}products.json?limit=${limit}&page=${page}`;
116
- try {
117
- const response = await rateLimitedFetch(url, {
118
- rateLimitClass: "products:paginated"
119
- });
120
- if (!response.ok) {
121
- console.error(
122
- `HTTP error! status: ${response.status} for ${storeDomain} page ${page}`
123
- );
124
- throw new Error(`HTTP error! status: ${response.status}`);
125
- }
126
- const data = await response.json();
127
- if (data.products.length === 0) {
128
- return [];
129
- }
130
- const normalized = productsDto(data.products);
131
- return maybeOverrideProductsCurrency(normalized, options == null ? void 0 : options.currency);
132
- } catch (error) {
133
- console.error(
134
- `Error fetching products for ${storeDomain} page ${page} with limit ${limit}:`,
135
- error
136
- );
137
- return null;
138
- }
139
- },
343
+ paginated: async (options) => paginatedInternal({
344
+ page: options == null ? void 0 : options.page,
345
+ limit: options == null ? void 0 : options.limit,
346
+ currency: options == null ? void 0 : options.currency,
347
+ minimal: false
348
+ }),
140
349
  /**
141
350
  * Finds a specific product by its handle.
142
351
  *
@@ -162,78 +371,10 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
162
371
  * const productWithVariant = await shop.products.find('t-shirt?variant=123');
163
372
  * ```
164
373
  */
165
- find: async (productHandle, options) => {
166
- var _a, _b;
167
- if (!productHandle || typeof productHandle !== "string") {
168
- throw new Error("Product handle is required and must be a string");
169
- }
170
- try {
171
- let qs = null;
172
- if (productHandle.includes("?")) {
173
- const parts = productHandle.split("?");
174
- const handlePart = (_a = parts[0]) != null ? _a : productHandle;
175
- const qsPart = (_b = parts[1]) != null ? _b : null;
176
- productHandle = handlePart;
177
- qs = qsPart;
178
- }
179
- const sanitizedHandle = productHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
180
- if (!sanitizedHandle) {
181
- throw new Error("Invalid product handle format");
182
- }
183
- if (sanitizedHandle.length > 255) {
184
- throw new Error("Product handle is too long");
185
- }
186
- const cached = getCached(sanitizedHandle);
187
- if (typeof cached !== "undefined") {
188
- return (options == null ? void 0 : options.currency) ? cached ? applyCurrencyOverride(cached, options.currency) : null : cached;
189
- }
190
- let finalHandle = sanitizedHandle;
191
- try {
192
- const htmlResp = await rateLimitedFetch(
193
- `${baseUrl}products/${encodeURIComponent(sanitizedHandle)}`,
194
- { rateLimitClass: "products:resolve" }
195
- );
196
- if (htmlResp.ok) {
197
- const finalUrl = htmlResp.url;
198
- if (finalUrl) {
199
- const pathname = new URL(finalUrl).pathname.replace(/\/$/, "");
200
- const parts = pathname.split("/").filter(Boolean);
201
- const idx = parts.indexOf("products");
202
- const maybeHandle = idx >= 0 ? parts[idx + 1] : void 0;
203
- if (typeof maybeHandle === "string" && maybeHandle.length) {
204
- finalHandle = maybeHandle;
205
- }
206
- }
207
- }
208
- } catch {
209
- }
210
- const url = `${baseUrl}products/${encodeURIComponent(finalHandle)}.js${qs ? `?${qs}` : ""}`;
211
- const response = await rateLimitedFetch(url, {
212
- rateLimitClass: "products:single"
213
- });
214
- if (!response.ok) {
215
- if (response.status === 404) {
216
- return null;
217
- }
218
- throw new Error(`HTTP error! status: ${response.status}`);
219
- }
220
- const product = await response.json();
221
- const productData = productDto(product);
222
- setCached(sanitizedHandle, productData);
223
- if (finalHandle !== sanitizedHandle)
224
- setCached(finalHandle, productData);
225
- return (options == null ? void 0 : options.currency) ? applyCurrencyOverride(productData, options.currency) : productData;
226
- } catch (error) {
227
- if (error instanceof Error) {
228
- console.error(
229
- `Error fetching product ${productHandle}:`,
230
- baseUrl,
231
- error.message
232
- );
233
- }
234
- throw error;
235
- }
236
- },
374
+ find: async (productHandle, options) => findInternal(productHandle, {
375
+ minimal: false,
376
+ currency: options == null ? void 0 : options.currency
377
+ }),
237
378
  /**
238
379
  * Enrich a product by generating merged markdown from body_html and product page.
239
380
  * Adds `enriched_content` to the returned product.
@@ -243,9 +384,7 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
243
384
  throw new Error("Product handle is required and must be a string");
244
385
  }
245
386
  const baseProduct = await operations.find(productHandle);
246
- if (!baseProduct) {
247
- return null;
248
- }
387
+ if (!baseProduct) return null;
249
388
  const handle = baseProduct.handle;
250
389
  const { enrichProduct } = await import("./ai/enrich.mjs");
251
390
  const enriched = await enrichProduct(storeDomain, handle, {
@@ -267,9 +406,7 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
267
406
  throw new Error("Product handle is required and must be a string");
268
407
  }
269
408
  const baseProduct = await operations.find(productHandle);
270
- if (!baseProduct) {
271
- throw new Error("Product not found");
272
- }
409
+ if (!baseProduct) throw new Error("Product not found");
273
410
  const handle = baseProduct.handle;
274
411
  const { buildEnrichPromptForProduct } = await import("./ai/enrich.mjs");
275
412
  return buildEnrichPromptForProduct(storeDomain, handle, {
@@ -323,9 +460,7 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
323
460
  throw new Error("Product handle is required and must be a string");
324
461
  }
325
462
  const baseProduct = await operations.find(productHandle);
326
- if (!baseProduct) {
327
- throw new Error("Product not found");
328
- }
463
+ if (!baseProduct) throw new Error("Product not found");
329
464
  const handle = baseProduct.handle;
330
465
  const { buildClassifyPromptForProduct } = await import("./ai/enrich.mjs");
331
466
  return buildClassifyPromptForProduct(storeDomain, handle, {
@@ -347,13 +482,7 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
347
482
  price: baseProduct.price,
348
483
  tags: baseProduct.tags
349
484
  };
350
- const {
351
- extractMainSection,
352
- fetchAjaxProduct,
353
- fetchProductPage,
354
- generateSEOContent: generateSEOContentLLM,
355
- mergeWithLLM
356
- } = await import("./ai/enrich.mjs");
485
+ const { generateSEOContent: generateSEOContentLLM } = await import("./ai/enrich.mjs");
357
486
  const seo = await generateSEOContentLLM(payload, {
358
487
  apiKey: options == null ? void 0 : options.apiKey,
359
488
  openRouter: ai == null ? void 0 : ai.openRouter,
@@ -423,7 +552,9 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
423
552
  uniqueHandles.push(base);
424
553
  }
425
554
  const products = await Promise.all(
426
- uniqueHandles.map((productHandle) => findProduct(productHandle))
555
+ uniqueHandles.map(
556
+ (productHandle) => findInternal(productHandle, { minimal: false })
557
+ )
427
558
  );
428
559
  return filter(products, isNonNullish);
429
560
  },
@@ -477,9 +608,10 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
477
608
  });
478
609
  }
479
610
  product.variants.forEach((variant) => {
480
- var _a, _b, _c, _d, _e, _f;
611
+ var _a, _b, _c, _d, _e, _f, _g;
612
+ if ((_a = product.options) == null ? void 0 : _a.length) return;
481
613
  if (variant.option1) {
482
- const optionName = (((_b = (_a = product.options) == null ? void 0 : _a[0]) == null ? void 0 : _b.name) || "Option 1").toLowerCase();
614
+ const optionName = (((_c = (_b = product.options) == null ? void 0 : _b[0]) == null ? void 0 : _c.name) || "Option 1").toLowerCase();
483
615
  let set1 = filterMap[optionName];
484
616
  if (!set1) {
485
617
  set1 = /* @__PURE__ */ new Set();
@@ -488,7 +620,7 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
488
620
  set1.add(variant.option1.trim().toLowerCase());
489
621
  }
490
622
  if (variant.option2) {
491
- const optionName = (((_d = (_c = product.options) == null ? void 0 : _c[1]) == null ? void 0 : _d.name) || "Option 2").toLowerCase();
623
+ const optionName = (((_e = (_d = product.options) == null ? void 0 : _d[1]) == null ? void 0 : _e.name) || "Option 2").toLowerCase();
492
624
  let set2 = filterMap[optionName];
493
625
  if (!set2) {
494
626
  set2 = /* @__PURE__ */ new Set();
@@ -497,7 +629,7 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
497
629
  set2.add(variant.option2.trim().toLowerCase());
498
630
  }
499
631
  if (variant.option3) {
500
- const optionName = (((_f = (_e = product.options) == null ? void 0 : _e[2]) == null ? void 0 : _f.name) || "Option 3").toLowerCase();
632
+ const optionName = (((_g = (_f = product.options) == null ? void 0 : _f[2]) == null ? void 0 : _g.name) || "Option 3").toLowerCase();
501
633
  if (!filterMap[optionName]) {
502
634
  filterMap[optionName] = /* @__PURE__ */ new Set();
503
635
  }
@@ -516,73 +648,82 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
516
648
  throw error;
517
649
  }
518
650
  },
519
- predictiveSearch: async (query, options) => {
520
- var _a, _b, _c, _d, _e;
521
- if (!query || typeof query !== "string") {
522
- throw new Error("Query is required and must be a string");
523
- }
524
- const limit = Math.max(1, Math.min((_a = options == null ? void 0 : options.limit) != null ? _a : 10, 10));
525
- const unavailable = (options == null ? void 0 : options.unavailableProducts) === "show" || (options == null ? void 0 : options.unavailableProducts) === "hide" ? options.unavailableProducts : "hide";
526
- const localeValue = (options == null ? void 0 : options.locale) && options.locale.trim() || "en";
527
- const localePrefix = `${localeValue.replace(/^\/|\/$/g, "")}/`;
528
- const url = `${baseUrl}${localePrefix}search/suggest.json?q=${encodeURIComponent(query)}&resources[type]=product&resources[limit]=${limit}&resources[options][unavailable_products]=${unavailable}`;
529
- const response = await rateLimitedFetch(url, {
530
- rateLimitClass: "search:predictive",
531
- timeoutMs: 7e3,
532
- retry: { maxRetries: 2, baseDelayMs: 300 }
533
- });
534
- let resp = response;
535
- if (!resp.ok && (resp.status === 404 || resp.status === 417)) {
536
- const fallbackUrl = `${baseUrl}search/suggest.json?q=${encodeURIComponent(query)}&resources[type]=product&resources[limit]=${limit}&resources[options][unavailable_products]=${unavailable}`;
537
- resp = await rateLimitedFetch(fallbackUrl, {
538
- rateLimitClass: "search:predictive",
539
- timeoutMs: 7e3,
540
- retry: { maxRetries: 2, baseDelayMs: 300 }
651
+ predictiveSearch: async (query, options) => predictiveSearchInternal(query, {
652
+ limit: options == null ? void 0 : options.limit,
653
+ locale: options == null ? void 0 : options.locale,
654
+ currency: options == null ? void 0 : options.currency,
655
+ unavailableProducts: options == null ? void 0 : options.unavailableProducts,
656
+ minimal: false
657
+ }),
658
+ recommendations: async (productId, options) => recommendationsInternal(productId, {
659
+ limit: options == null ? void 0 : options.limit,
660
+ intent: options == null ? void 0 : options.intent,
661
+ locale: options == null ? void 0 : options.locale,
662
+ currency: options == null ? void 0 : options.currency,
663
+ minimal: false
664
+ }),
665
+ minimal: {
666
+ all: async (options) => {
667
+ return allInternal({ minimal: true, currency: options == null ? void 0 : options.currency });
668
+ },
669
+ paginated: async (options) => {
670
+ return paginatedInternal({
671
+ page: options == null ? void 0 : options.page,
672
+ limit: options == null ? void 0 : options.limit,
673
+ currency: options == null ? void 0 : options.currency,
674
+ minimal: true
675
+ });
676
+ },
677
+ find: async (productHandle, options) => {
678
+ return findInternal(productHandle, {
679
+ minimal: true,
680
+ currency: options == null ? void 0 : options.currency
681
+ });
682
+ },
683
+ showcased: async () => {
684
+ const res = await operations.showcase.minimal();
685
+ return res || [];
686
+ },
687
+ predictiveSearch: async (query, options) => {
688
+ return predictiveSearchInternal(query, {
689
+ limit: options == null ? void 0 : options.limit,
690
+ locale: options == null ? void 0 : options.locale,
691
+ currency: options == null ? void 0 : options.currency,
692
+ unavailableProducts: options == null ? void 0 : options.unavailableProducts,
693
+ minimal: true
694
+ });
695
+ },
696
+ recommendations: async (productId, options) => {
697
+ return recommendationsInternal(productId, {
698
+ limit: options == null ? void 0 : options.limit,
699
+ intent: options == null ? void 0 : options.intent,
700
+ locale: options == null ? void 0 : options.locale,
701
+ currency: options == null ? void 0 : options.currency,
702
+ minimal: true
541
703
  });
542
704
  }
543
- if (!resp.ok) {
544
- throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
545
- }
546
- const data = await resp.json();
547
- const raw = (_d = (_c = (_b = data == null ? void 0 : data.resources) == null ? void 0 : _b.results) == null ? void 0 : _c.products) != null ? _d : [];
548
- const handles = raw.filter((p) => {
549
- var _a2;
550
- return Boolean((_a2 = p == null ? void 0 : p.available) != null ? _a2 : true);
551
- }).map((p) => {
552
- var _a2;
553
- return String((_a2 = p == null ? void 0 : p.handle) != null ? _a2 : "");
554
- }).filter((h) => h.length > 0).slice(0, limit);
555
- const fetched = await Promise.all(handles.map((h) => findProduct(h)));
556
- const results = filter(fetched, isNonNullish);
557
- const finalProducts = (_e = maybeOverrideProductsCurrency(results, options == null ? void 0 : options.currency)) != null ? _e : [];
558
- return finalProducts;
559
705
  },
560
- recommendations: async (productId, options) => {
561
- var _a, _b;
562
- if (!Number.isFinite(productId) || productId <= 0) {
563
- throw new Error("Valid productId is required");
564
- }
565
- const limit = Math.max(1, Math.min((_a = options == null ? void 0 : options.limit) != null ? _a : 10, 10));
566
- const intent = (options == null ? void 0 : options.intent) === "complementary" ? "complementary" : "related";
567
- const localeValue = (options == null ? void 0 : options.locale) && options.locale.trim() || "en";
568
- const localePrefix = `${localeValue.replace(/^\/|\/$/g, "")}/`;
569
- const url = `${baseUrl}${localePrefix}recommendations/products.json?product_id=${encodeURIComponent(String(productId))}&limit=${limit}&intent=${intent}`;
570
- const resp = await rateLimitedFetch(url, {
571
- rateLimitClass: "products:recommendations",
572
- timeoutMs: 7e3,
573
- retry: { maxRetries: 2, baseDelayMs: 300 }
574
- });
575
- if (!resp.ok) {
576
- if (resp.status === 404) {
577
- return [];
706
+ showcase: {
707
+ minimal: async () => {
708
+ const storeInfo = await getStoreInfo();
709
+ const normalizedHandles = storeInfo.showcase.products.map((h) => {
710
+ var _a;
711
+ return (_a = h.split("?")[0]) == null ? void 0 : _a.replace(/^\/|\/$/g, "");
712
+ }).filter((base) => Boolean(base));
713
+ const seen = /* @__PURE__ */ new Set();
714
+ const uniqueHandles = [];
715
+ for (const base of normalizedHandles) {
716
+ if (seen.has(base)) continue;
717
+ seen.add(base);
718
+ uniqueHandles.push(base);
578
719
  }
579
- throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
720
+ const products = await Promise.all(
721
+ uniqueHandles.map(
722
+ (productHandle) => findInternal(productHandle, { minimal: true })
723
+ )
724
+ );
725
+ return filter(products, isNonNullish);
580
726
  }
581
- const data = await resp.json();
582
- const productsArray = Array.isArray(data) ? data : Array.isArray(data == null ? void 0 : data.products) ? data.products : [];
583
- const normalized = productsDto(productsArray) || [];
584
- const finalProducts = (_b = maybeOverrideProductsCurrency(normalized, options == null ? void 0 : options.currency)) != null ? _b : [];
585
- return finalProducts;
586
727
  }
587
728
  };
588
729
  return operations;