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.
- package/README.md +74 -7
- package/dist/ai/enrich.d.ts +1 -1
- package/dist/ai/enrich.mjs +2 -2
- package/dist/{chunk-QCB3U4AO.mjs → chunk-242GBM2V.mjs} +136 -14
- package/dist/{chunk-THCO3JT4.mjs → chunk-3KRUT5BD.mjs} +360 -219
- package/dist/{chunk-ZX4IG4TY.mjs → chunk-5TGMDRUF.mjs} +88 -44
- package/dist/{chunk-D5MTUWFO.mjs → chunk-O77Z6OBJ.mjs} +21 -6
- package/dist/{chunk-G7OCMGA6.mjs → chunk-SBHTEKLB.mjs} +3 -1
- package/dist/{chunk-OA76XD32.mjs → chunk-YWAW6C74.mjs} +5 -5
- package/dist/collections.d.ts +16 -2
- package/dist/collections.mjs +2 -2
- package/dist/index.d.ts +8 -4
- package/dist/index.mjs +180 -79
- package/dist/products.d.ts +53 -2
- package/dist/products.mjs +2 -2
- package/dist/store.d.ts +1 -1
- package/dist/store.mjs +3 -3
- package/dist/{types-BRXamZMS.d.ts → types-w9n4csBQ.d.ts} +27 -1
- package/dist/utils/detect-country.d.ts +1 -1
- package/dist/utils/detect-country.mjs +1 -1
- package/dist/utils/func.d.ts +1 -1
- package/dist/utils/rate-limit.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
2
|
rateLimitedFetch
|
|
3
|
-
} from "./chunk-
|
|
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,
|
|
10
|
+
function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDto, productDto, getStoreInfo, _findProduct, ai) {
|
|
11
11
|
const cacheExpiryMs = 5 * 60 * 1e3;
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
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
|
-
|
|
18
|
+
findCacheFull.delete(key);
|
|
18
19
|
return void 0;
|
|
19
20
|
};
|
|
20
|
-
const
|
|
21
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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(
|
|
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 = (((
|
|
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 = (((
|
|
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 = (((
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
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;
|