shop-client 3.8.2
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/LICENSE +21 -0
- package/README.md +912 -0
- package/dist/checkout.d.mts +31 -0
- package/dist/checkout.d.ts +31 -0
- package/dist/checkout.js +115 -0
- package/dist/checkout.js.map +1 -0
- package/dist/checkout.mjs +7 -0
- package/dist/checkout.mjs.map +1 -0
- package/dist/chunk-2KBOKOAD.mjs +177 -0
- package/dist/chunk-2KBOKOAD.mjs.map +1 -0
- package/dist/chunk-BWKBRM2Z.mjs +136 -0
- package/dist/chunk-BWKBRM2Z.mjs.map +1 -0
- package/dist/chunk-O4BPIIQ6.mjs +503 -0
- package/dist/chunk-O4BPIIQ6.mjs.map +1 -0
- package/dist/chunk-QCTICSBE.mjs +398 -0
- package/dist/chunk-QCTICSBE.mjs.map +1 -0
- package/dist/chunk-QL5OUZGP.mjs +91 -0
- package/dist/chunk-QL5OUZGP.mjs.map +1 -0
- package/dist/chunk-WTK5HUFI.mjs +1287 -0
- package/dist/chunk-WTK5HUFI.mjs.map +1 -0
- package/dist/collections.d.mts +64 -0
- package/dist/collections.d.ts +64 -0
- package/dist/collections.js +540 -0
- package/dist/collections.js.map +1 -0
- package/dist/collections.mjs +9 -0
- package/dist/collections.mjs.map +1 -0
- package/dist/index.d.mts +233 -0
- package/dist/index.d.ts +233 -0
- package/dist/index.js +3241 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +702 -0
- package/dist/index.mjs.map +1 -0
- package/dist/products.d.mts +63 -0
- package/dist/products.d.ts +63 -0
- package/dist/products.js +1206 -0
- package/dist/products.js.map +1 -0
- package/dist/products.mjs +9 -0
- package/dist/products.mjs.map +1 -0
- package/dist/store-CJVUz2Yb.d.mts +608 -0
- package/dist/store-CJVUz2Yb.d.ts +608 -0
- package/dist/store.d.mts +1 -0
- package/dist/store.d.ts +1 -0
- package/dist/store.js +698 -0
- package/dist/store.js.map +1 -0
- package/dist/store.mjs +9 -0
- package/dist/store.mjs.map +1 -0
- package/dist/utils/rate-limit.d.mts +25 -0
- package/dist/utils/rate-limit.d.ts +25 -0
- package/dist/utils/rate-limit.js +203 -0
- package/dist/utils/rate-limit.js.map +1 -0
- package/dist/utils/rate-limit.mjs +11 -0
- package/dist/utils/rate-limit.mjs.map +1 -0
- package/package.json +116 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/collections.ts
|
|
21
|
+
var collections_exports = {};
|
|
22
|
+
__export(collections_exports, {
|
|
23
|
+
createCollectionOperations: () => createCollectionOperations
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(collections_exports);
|
|
26
|
+
var import_remeda = require("remeda");
|
|
27
|
+
|
|
28
|
+
// src/utils/func.ts
|
|
29
|
+
var import_tldts = require("tldts");
|
|
30
|
+
function formatPrice(amountInCents, currency) {
|
|
31
|
+
try {
|
|
32
|
+
return new Intl.NumberFormat(void 0, {
|
|
33
|
+
style: "currency",
|
|
34
|
+
currency
|
|
35
|
+
}).format((amountInCents || 0) / 100);
|
|
36
|
+
} catch {
|
|
37
|
+
const val = (amountInCents || 0) / 100;
|
|
38
|
+
return `${val} ${currency}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/utils/rate-limit.ts
|
|
43
|
+
var RateLimiter = class {
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.queue = [];
|
|
46
|
+
this.inFlight = 0;
|
|
47
|
+
this.refillTimer = null;
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.tokens = options.maxRequestsPerInterval;
|
|
50
|
+
}
|
|
51
|
+
startRefill() {
|
|
52
|
+
if (this.refillTimer) return;
|
|
53
|
+
this.refillTimer = setInterval(() => {
|
|
54
|
+
this.tokens = this.options.maxRequestsPerInterval;
|
|
55
|
+
this.tryRun();
|
|
56
|
+
}, this.options.intervalMs);
|
|
57
|
+
if (this.refillTimer && typeof this.refillTimer.unref === "function") {
|
|
58
|
+
this.refillTimer.unref();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
stopRefill() {
|
|
62
|
+
if (this.refillTimer) {
|
|
63
|
+
clearInterval(this.refillTimer);
|
|
64
|
+
this.refillTimer = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
ensureRefillStarted() {
|
|
68
|
+
if (!this.refillTimer) {
|
|
69
|
+
this.startRefill();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
configure(next) {
|
|
73
|
+
this.options = { ...this.options, ...next };
|
|
74
|
+
this.options.maxRequestsPerInterval = Math.max(
|
|
75
|
+
1,
|
|
76
|
+
this.options.maxRequestsPerInterval
|
|
77
|
+
);
|
|
78
|
+
this.options.intervalMs = Math.max(10, this.options.intervalMs);
|
|
79
|
+
this.options.maxConcurrency = Math.max(1, this.options.maxConcurrency);
|
|
80
|
+
}
|
|
81
|
+
schedule(fn) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
this.ensureRefillStarted();
|
|
84
|
+
this.queue.push({ fn, resolve, reject });
|
|
85
|
+
this.tryRun();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
tryRun() {
|
|
89
|
+
while (this.queue.length > 0 && this.inFlight < this.options.maxConcurrency && this.tokens > 0) {
|
|
90
|
+
const task = this.queue.shift();
|
|
91
|
+
this.tokens -= 1;
|
|
92
|
+
this.inFlight += 1;
|
|
93
|
+
Promise.resolve().then(task.fn).then((result) => task.resolve(result)).catch((err) => task.reject(err)).finally(() => {
|
|
94
|
+
this.inFlight -= 1;
|
|
95
|
+
setTimeout(() => this.tryRun(), 0);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var enabled = false;
|
|
101
|
+
var defaultOptions = {
|
|
102
|
+
maxRequestsPerInterval: 5,
|
|
103
|
+
// 5 requests
|
|
104
|
+
intervalMs: 1e3,
|
|
105
|
+
// per second
|
|
106
|
+
maxConcurrency: 5
|
|
107
|
+
// up to 5 in parallel
|
|
108
|
+
};
|
|
109
|
+
var limiter = new RateLimiter(defaultOptions);
|
|
110
|
+
var hostLimiters = /* @__PURE__ */ new Map();
|
|
111
|
+
var classLimiters = /* @__PURE__ */ new Map();
|
|
112
|
+
function getHost(input) {
|
|
113
|
+
try {
|
|
114
|
+
if (typeof input === "string") {
|
|
115
|
+
return new URL(input).host;
|
|
116
|
+
}
|
|
117
|
+
if (input instanceof URL) {
|
|
118
|
+
return input.host;
|
|
119
|
+
}
|
|
120
|
+
const url = input.url;
|
|
121
|
+
if (url) {
|
|
122
|
+
return new URL(url).host;
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
function getHostLimiter(host) {
|
|
129
|
+
if (!host) return void 0;
|
|
130
|
+
const exact = hostLimiters.get(host);
|
|
131
|
+
if (exact) return exact;
|
|
132
|
+
for (const [key, lim] of hostLimiters.entries()) {
|
|
133
|
+
if (key.startsWith("*.") && host.endsWith(key.slice(2))) {
|
|
134
|
+
return lim;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
async function rateLimitedFetch(input, init) {
|
|
140
|
+
var _a;
|
|
141
|
+
if (!enabled) {
|
|
142
|
+
return fetch(input, init);
|
|
143
|
+
}
|
|
144
|
+
const klass = init == null ? void 0 : init.rateLimitClass;
|
|
145
|
+
const byClass = klass ? classLimiters.get(klass) : void 0;
|
|
146
|
+
const byHost = getHostLimiter(getHost(input));
|
|
147
|
+
const eff = (_a = byClass != null ? byClass : byHost) != null ? _a : limiter;
|
|
148
|
+
return eff.schedule(() => fetch(input, init));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/collections.ts
|
|
152
|
+
function createCollectionOperations(baseUrl, storeDomain, fetchCollections, collectionsDto, fetchPaginatedProductsFromCollection, getStoreInfo, findCollection) {
|
|
153
|
+
function applyCurrencyOverride(product, currency) {
|
|
154
|
+
var _a, _b, _c, _d, _e, _f;
|
|
155
|
+
const priceMin = (_b = (_a = product.priceMin) != null ? _a : product.price) != null ? _b : 0;
|
|
156
|
+
const priceMax = (_d = (_c = product.priceMax) != null ? _c : product.price) != null ? _d : 0;
|
|
157
|
+
const compareAtMin = (_f = (_e = product.compareAtPriceMin) != null ? _e : product.compareAtPrice) != null ? _f : 0;
|
|
158
|
+
return {
|
|
159
|
+
...product,
|
|
160
|
+
currency,
|
|
161
|
+
localizedPricing: {
|
|
162
|
+
currency,
|
|
163
|
+
priceFormatted: formatPrice(priceMin, currency),
|
|
164
|
+
priceMinFormatted: formatPrice(priceMin, currency),
|
|
165
|
+
priceMaxFormatted: formatPrice(priceMax, currency),
|
|
166
|
+
compareAtPriceFormatted: formatPrice(compareAtMin, currency)
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function maybeOverrideProductsCurrency(products, currency) {
|
|
171
|
+
if (!products || !currency) return products;
|
|
172
|
+
return products.map((p) => applyCurrencyOverride(p, currency));
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
/**
|
|
176
|
+
* Fetches collections with pagination support.
|
|
177
|
+
*
|
|
178
|
+
* @param options - Pagination options
|
|
179
|
+
* @param options.page - Page number (default: 1)
|
|
180
|
+
* @param options.limit - Number of collections per page (default: 10, max: 250)
|
|
181
|
+
*
|
|
182
|
+
* @returns {Promise<Collection[] | null>} Collections for the requested page, or null on error
|
|
183
|
+
*/
|
|
184
|
+
paginated: async (options) => {
|
|
185
|
+
var _a, _b;
|
|
186
|
+
const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
|
|
187
|
+
const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 10;
|
|
188
|
+
if (page < 1 || limit < 1 || limit > 250) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const collections = await fetchCollections(page, limit);
|
|
195
|
+
return collections != null ? collections : null;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(
|
|
198
|
+
"Failed to fetch paginated collections:",
|
|
199
|
+
storeDomain,
|
|
200
|
+
error
|
|
201
|
+
);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
/**
|
|
206
|
+
* Fetches all collections from the store across all pages.
|
|
207
|
+
*
|
|
208
|
+
* @returns {Promise<Collection[]>} Array of all collections
|
|
209
|
+
*
|
|
210
|
+
* @throws {Error} When there's a network error or API failure
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const shop = new ShopClient('https://exampleshop.com');
|
|
215
|
+
* const allCollections = await shop.collections.all();
|
|
216
|
+
*
|
|
217
|
+
* console.log(`Found ${allCollections.length} collections`);
|
|
218
|
+
* allCollections.forEach(collection => {
|
|
219
|
+
* console.log(collection.title, collection.handle);
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
all: async () => {
|
|
224
|
+
const limit = 250;
|
|
225
|
+
const allCollections = [];
|
|
226
|
+
async function fetchAll() {
|
|
227
|
+
let currentPage = 1;
|
|
228
|
+
while (true) {
|
|
229
|
+
const collections = await fetchCollections(currentPage, limit);
|
|
230
|
+
if (!collections || collections.length === 0 || collections.length < limit) {
|
|
231
|
+
if (!collections) {
|
|
232
|
+
console.warn(
|
|
233
|
+
"fetchCollections returned null, treating as empty array."
|
|
234
|
+
);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
if (collections && collections.length > 0) {
|
|
238
|
+
allCollections.push(...collections);
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
allCollections.push(...collections);
|
|
243
|
+
currentPage++;
|
|
244
|
+
}
|
|
245
|
+
return allCollections;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const collections = await fetchAll();
|
|
249
|
+
return collections || [];
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error("Failed to fetch all collections:", storeDomain, error);
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
/**
|
|
256
|
+
* Finds a specific collection by its handle.
|
|
257
|
+
*
|
|
258
|
+
* @param collectionHandle - The collection handle (URL slug) to search for
|
|
259
|
+
*
|
|
260
|
+
* @returns {Promise<Collection | null>} The collection if found, null if not found
|
|
261
|
+
*
|
|
262
|
+
* @throws {Error} When the handle is invalid or there's a network error
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const shop = new ShopClient('https://example.myshopify.com');
|
|
267
|
+
* const collection = await shop.collections.find('summer-collection');
|
|
268
|
+
* if (collection) {
|
|
269
|
+
* console.log(collection.title); // "Summer Collection"
|
|
270
|
+
* }
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
find: async (collectionHandle) => {
|
|
274
|
+
var _a, _b;
|
|
275
|
+
if (!collectionHandle || typeof collectionHandle !== "string") {
|
|
276
|
+
throw new Error("Collection handle is required and must be a string");
|
|
277
|
+
}
|
|
278
|
+
const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
|
|
279
|
+
if (!sanitizedHandle) {
|
|
280
|
+
throw new Error("Invalid collection handle format");
|
|
281
|
+
}
|
|
282
|
+
if (sanitizedHandle.length > 255) {
|
|
283
|
+
throw new Error("Collection handle is too long");
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const url = `${baseUrl}collections/${encodeURIComponent(sanitizedHandle)}.json`;
|
|
287
|
+
const response = await rateLimitedFetch(url);
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
if (response.status === 404) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
293
|
+
}
|
|
294
|
+
const result = await response.json();
|
|
295
|
+
let collectionImage = result.collection.image;
|
|
296
|
+
if (!collectionImage) {
|
|
297
|
+
const collectionProduct = (_a = await fetchPaginatedProductsFromCollection(
|
|
298
|
+
result.collection.handle,
|
|
299
|
+
{
|
|
300
|
+
limit: 1,
|
|
301
|
+
page: 1
|
|
302
|
+
}
|
|
303
|
+
)) == null ? void 0 : _a.at(0);
|
|
304
|
+
const collectionProductImage = (_b = collectionProduct == null ? void 0 : collectionProduct.images) == null ? void 0 : _b[0];
|
|
305
|
+
if (collectionProduct && collectionProductImage) {
|
|
306
|
+
collectionImage = {
|
|
307
|
+
id: collectionProductImage.id,
|
|
308
|
+
src: collectionProductImage.src,
|
|
309
|
+
alt: collectionProductImage.alt || collectionProduct.title,
|
|
310
|
+
created_at: collectionProductImage.createdAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const collectionData = collectionsDto([
|
|
315
|
+
{
|
|
316
|
+
...result.collection,
|
|
317
|
+
image: collectionImage
|
|
318
|
+
}
|
|
319
|
+
]);
|
|
320
|
+
return collectionData[0] || null;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (error instanceof Error) {
|
|
323
|
+
console.error(
|
|
324
|
+
`Error fetching collection ${sanitizedHandle}:`,
|
|
325
|
+
baseUrl,
|
|
326
|
+
error.message
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
/**
|
|
333
|
+
* Fetches collections that are showcased/featured on the store's homepage.
|
|
334
|
+
*
|
|
335
|
+
* @returns {Promise<Collection[]>} Array of showcased collections found on the homepage
|
|
336
|
+
*
|
|
337
|
+
* @throws {Error} When there's a network error or API failure
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```typescript
|
|
341
|
+
* const shop = new ShopClient('https://exampleshop.com');
|
|
342
|
+
* const showcasedCollections = await shop.collections.showcased();
|
|
343
|
+
*
|
|
344
|
+
* console.log(`Found ${showcasedCollections.length} showcased collections`);
|
|
345
|
+
* showcasedCollections.forEach(collection => {
|
|
346
|
+
* console.log(`Featured: ${collection.title} - ${collection.productsCount} products`);
|
|
347
|
+
* });
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
showcased: async () => {
|
|
351
|
+
const storeInfo = await getStoreInfo();
|
|
352
|
+
const collections = await Promise.all(
|
|
353
|
+
storeInfo.showcase.collections.map(
|
|
354
|
+
(collectionHandle) => findCollection(collectionHandle)
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
return (0, import_remeda.filter)(collections, import_remeda.isNonNullish);
|
|
358
|
+
},
|
|
359
|
+
products: {
|
|
360
|
+
/**
|
|
361
|
+
* Fetches products from a specific collection with pagination support.
|
|
362
|
+
*
|
|
363
|
+
* @param collectionHandle - The collection handle to fetch products from
|
|
364
|
+
* @param options - Pagination options
|
|
365
|
+
* @param options.page - Page number (default: 1)
|
|
366
|
+
* @param options.limit - Number of products per page (default: 250, max: 250)
|
|
367
|
+
*
|
|
368
|
+
* @returns {Promise<Product[] | null>} Array of products from the collection or null if error occurs
|
|
369
|
+
*
|
|
370
|
+
* @throws {Error} When the collection handle is invalid or there's a network error
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* const shop = new ShopClient('https://example.myshopify.com');
|
|
375
|
+
*
|
|
376
|
+
* // Get first page of products from a collection
|
|
377
|
+
* const products = await shop.collections.products.paginated('summer-collection');
|
|
378
|
+
*
|
|
379
|
+
* // Get second page with custom limit
|
|
380
|
+
* const moreProducts = await shop.collections.products.paginated(
|
|
381
|
+
* 'summer-collection',
|
|
382
|
+
* { page: 2, limit: 50 }
|
|
383
|
+
* );
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
paginated: async (collectionHandle, options) => {
|
|
387
|
+
var _a, _b;
|
|
388
|
+
if (!collectionHandle || typeof collectionHandle !== "string") {
|
|
389
|
+
throw new Error("Collection handle is required and must be a string");
|
|
390
|
+
}
|
|
391
|
+
const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
|
|
392
|
+
if (!sanitizedHandle) {
|
|
393
|
+
throw new Error("Invalid collection handle format");
|
|
394
|
+
}
|
|
395
|
+
if (sanitizedHandle.length > 255) {
|
|
396
|
+
throw new Error("Collection handle is too long");
|
|
397
|
+
}
|
|
398
|
+
const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
|
|
399
|
+
const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 250;
|
|
400
|
+
if (page < 1 || limit < 1 || limit > 250) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
"Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const products = await fetchPaginatedProductsFromCollection(
|
|
406
|
+
sanitizedHandle,
|
|
407
|
+
{ page, limit }
|
|
408
|
+
);
|
|
409
|
+
return maybeOverrideProductsCurrency(products, options == null ? void 0 : options.currency);
|
|
410
|
+
},
|
|
411
|
+
/**
|
|
412
|
+
* Fetches all products from a specific collection.
|
|
413
|
+
*
|
|
414
|
+
* @param collectionHandle - The collection handle to fetch products from
|
|
415
|
+
*
|
|
416
|
+
* @returns {Promise<Product[] | null>} Array of all products from the collection or null if error occurs
|
|
417
|
+
*
|
|
418
|
+
* @throws {Error} When the collection handle is invalid or there's a network error
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```typescript
|
|
422
|
+
* const shop = new ShopClient('https://exampleshop.com');
|
|
423
|
+
* const allProducts = await shop.collections.products.all('summer-collection');
|
|
424
|
+
*
|
|
425
|
+
* if (allProducts) {
|
|
426
|
+
* console.log(`Found ${allProducts.length} products in the collection`);
|
|
427
|
+
* allProducts.forEach(product => {
|
|
428
|
+
* console.log(`${product.title} - $${product.price}`);
|
|
429
|
+
* });
|
|
430
|
+
* }
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
all: async (collectionHandle, options) => {
|
|
434
|
+
if (!collectionHandle || typeof collectionHandle !== "string") {
|
|
435
|
+
throw new Error("Collection handle is required and must be a string");
|
|
436
|
+
}
|
|
437
|
+
const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
|
|
438
|
+
if (!sanitizedHandle) {
|
|
439
|
+
throw new Error("Invalid collection handle format");
|
|
440
|
+
}
|
|
441
|
+
if (sanitizedHandle.length > 255) {
|
|
442
|
+
throw new Error("Collection handle is too long");
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const limit = 250;
|
|
446
|
+
const allProducts = [];
|
|
447
|
+
let currentPage = 1;
|
|
448
|
+
while (true) {
|
|
449
|
+
const products = await fetchPaginatedProductsFromCollection(
|
|
450
|
+
sanitizedHandle,
|
|
451
|
+
{
|
|
452
|
+
page: currentPage,
|
|
453
|
+
limit
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
if (!products || products.length === 0 || products.length < limit) {
|
|
457
|
+
if (products && products.length > 0) {
|
|
458
|
+
allProducts.push(...products);
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
allProducts.push(...products);
|
|
463
|
+
currentPage++;
|
|
464
|
+
}
|
|
465
|
+
return maybeOverrideProductsCurrency(allProducts, options == null ? void 0 : options.currency);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error(
|
|
468
|
+
`Error fetching all products for collection ${sanitizedHandle}:`,
|
|
469
|
+
baseUrl,
|
|
470
|
+
error
|
|
471
|
+
);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
/**
|
|
476
|
+
* Fetches all product slugs from a specific collection.
|
|
477
|
+
*
|
|
478
|
+
* @param collectionHandle - The collection handle to fetch product slugs from
|
|
479
|
+
*
|
|
480
|
+
* @returns {Promise<string[] | null>} Array of product slugs from the collection or null if error occurs
|
|
481
|
+
*
|
|
482
|
+
* @throws {Error} When the collection handle is invalid or there's a network error
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* const shop = new ShopClient('https://exampleshop.com');
|
|
487
|
+
* const productSlugs = await shop.collections.products.slugs('summer-collection');
|
|
488
|
+
* console.log(productSlugs);
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
slugs: async (collectionHandle) => {
|
|
492
|
+
if (!collectionHandle || typeof collectionHandle !== "string") {
|
|
493
|
+
throw new Error("Collection handle is required and must be a string");
|
|
494
|
+
}
|
|
495
|
+
const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
|
|
496
|
+
if (!sanitizedHandle) {
|
|
497
|
+
throw new Error("Invalid collection handle format");
|
|
498
|
+
}
|
|
499
|
+
if (sanitizedHandle.length > 255) {
|
|
500
|
+
throw new Error("Collection handle is too long");
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
const limit = 250;
|
|
504
|
+
const slugs = [];
|
|
505
|
+
let currentPage = 1;
|
|
506
|
+
while (true) {
|
|
507
|
+
const products = await fetchPaginatedProductsFromCollection(
|
|
508
|
+
sanitizedHandle,
|
|
509
|
+
{
|
|
510
|
+
page: currentPage,
|
|
511
|
+
limit
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
if (!products || products.length === 0 || products.length < limit) {
|
|
515
|
+
if (products && products.length > 0) {
|
|
516
|
+
slugs.push(...products.map((p) => p.slug));
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
slugs.push(...products.map((p) => p.slug));
|
|
521
|
+
currentPage++;
|
|
522
|
+
}
|
|
523
|
+
return slugs;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.error(
|
|
526
|
+
`Error fetching product slugs for collection ${sanitizedHandle}:`,
|
|
527
|
+
baseUrl,
|
|
528
|
+
error
|
|
529
|
+
);
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
537
|
+
0 && (module.exports = {
|
|
538
|
+
createCollectionOperations
|
|
539
|
+
});
|
|
540
|
+
//# sourceMappingURL=collections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/collections.ts","../src/utils/func.ts","../src/utils/rate-limit.ts"],"sourcesContent":["import { filter, isNonNullish } from \"remeda\";\nimport type { StoreInfo } from \"./store\";\nimport type {\n Collection,\n CurrencyCode,\n Product,\n ShopifyCollection,\n} from \"./types\";\nimport { formatPrice } from \"./utils/func\";\nimport { rateLimitedFetch } from \"./utils/rate-limit\";\n\n/**\n * Interface for collection operations\n */\nexport interface CollectionOperations {\n /**\n * Fetches all collections from the store across all pages.\n */\n all(): Promise<Collection[]>;\n\n /**\n * Fetches collections with pagination support.\n *\n * @param options - Pagination options\n * @param options.page - Page number (default: 1)\n * @param options.limit - Number of collections per page (default: 10, max: 250)\n *\n * @returns {Promise<Collection[] | null>} Array of collections for the page or null if error occurs\n */\n paginated(options?: {\n page?: number;\n limit?: number;\n }): Promise<Collection[] | null>;\n\n /**\n * Finds a specific collection by its handle.\n */\n find(collectionHandle: string): Promise<Collection | null>;\n\n /**\n * Fetches collections that are showcased/featured on the store's homepage.\n */\n showcased(): Promise<Collection[]>;\n\n /**\n * Product-related methods for fetching products from specific collections.\n */\n products: {\n /**\n * Fetches products from a specific collection with pagination support.\n */\n paginated(\n collectionHandle: string,\n options?: { page?: number; limit?: number; currency?: CurrencyCode }\n ): Promise<Product[] | null>;\n\n /**\n * Fetches all products from a specific collection.\n */\n all(\n collectionHandle: string,\n options?: { currency?: CurrencyCode }\n ): Promise<Product[] | null>;\n /**\n * Fetches all product slugs from a specific collection.\n */\n slugs(collectionHandle: string): Promise<string[] | null>;\n };\n}\n\n/**\n * Creates collection operations for a store instance\n */\nexport function createCollectionOperations(\n baseUrl: string,\n storeDomain: string,\n fetchCollections: (\n page: number,\n limit: number\n ) => Promise<Collection[] | null>,\n collectionsDto: (collections: ShopifyCollection[]) => Collection[],\n fetchPaginatedProductsFromCollection: (\n collectionHandle: string,\n options?: { page?: number; limit?: number }\n ) => Promise<Product[] | null>,\n getStoreInfo: () => Promise<StoreInfo>,\n findCollection: (handle: string) => Promise<Collection | null>\n): CollectionOperations {\n // Use shared formatter from utils\n\n function applyCurrencyOverride(\n product: Product,\n currency: CurrencyCode\n ): Product {\n const priceMin = product.priceMin ?? product.price ?? 0;\n const priceMax = product.priceMax ?? product.price ?? 0;\n const compareAtMin =\n product.compareAtPriceMin ?? product.compareAtPrice ?? 0;\n return {\n ...product,\n currency,\n localizedPricing: {\n currency,\n priceFormatted: formatPrice(priceMin, currency),\n priceMinFormatted: formatPrice(priceMin, currency),\n priceMaxFormatted: formatPrice(priceMax, currency),\n compareAtPriceFormatted: formatPrice(compareAtMin, currency),\n },\n };\n }\n\n function maybeOverrideProductsCurrency(\n products: Product[] | null,\n currency?: CurrencyCode\n ): Product[] | null {\n if (!products || !currency) return products;\n return products.map((p) => applyCurrencyOverride(p, currency));\n }\n\n return {\n /**\n * Fetches collections with pagination support.\n *\n * @param options - Pagination options\n * @param options.page - Page number (default: 1)\n * @param options.limit - Number of collections per page (default: 10, max: 250)\n *\n * @returns {Promise<Collection[] | null>} Collections for the requested page, or null on error\n */\n paginated: async (options?: {\n page?: number;\n limit?: number;\n }): Promise<Collection[] | null> => {\n const page = options?.page ?? 1;\n const limit = options?.limit ?? 10;\n\n if (page < 1 || limit < 1 || limit > 250) {\n throw new Error(\n \"Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250\"\n );\n }\n\n try {\n const collections = await fetchCollections(page, limit);\n return collections ?? null;\n } catch (error) {\n console.error(\n \"Failed to fetch paginated collections:\",\n storeDomain,\n error\n );\n return null;\n }\n },\n /**\n * Fetches all collections from the store across all pages.\n *\n * @returns {Promise<Collection[]>} Array of all collections\n *\n * @throws {Error} When there's a network error or API failure\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://exampleshop.com');\n * const allCollections = await shop.collections.all();\n *\n * console.log(`Found ${allCollections.length} collections`);\n * allCollections.forEach(collection => {\n * console.log(collection.title, collection.handle);\n * });\n * ```\n */\n all: async (): Promise<Collection[]> => {\n const limit = 250;\n const allCollections: Collection[] = [];\n\n async function fetchAll() {\n let currentPage = 1;\n\n while (true) {\n const collections = await fetchCollections(currentPage, limit);\n\n if (\n !collections ||\n collections.length === 0 ||\n collections.length < limit\n ) {\n if (!collections) {\n console.warn(\n \"fetchCollections returned null, treating as empty array.\"\n );\n break;\n }\n if (collections && collections.length > 0) {\n allCollections.push(...collections);\n }\n break;\n }\n\n allCollections.push(...collections);\n currentPage++;\n }\n return allCollections;\n }\n\n try {\n const collections = await fetchAll();\n return collections || [];\n } catch (error) {\n console.error(\"Failed to fetch all collections:\", storeDomain, error);\n throw error;\n }\n },\n\n /**\n * Finds a specific collection by its handle.\n *\n * @param collectionHandle - The collection handle (URL slug) to search for\n *\n * @returns {Promise<Collection | null>} The collection if found, null if not found\n *\n * @throws {Error} When the handle is invalid or there's a network error\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://example.myshopify.com');\n * const collection = await shop.collections.find('summer-collection');\n * if (collection) {\n * console.log(collection.title); // \"Summer Collection\"\n * }\n * ```\n */\n find: async (collectionHandle: string): Promise<Collection | null> => {\n // Validate collection handle\n if (!collectionHandle || typeof collectionHandle !== \"string\") {\n throw new Error(\"Collection handle is required and must be a string\");\n }\n\n // Sanitize handle - remove potentially dangerous characters\n const sanitizedHandle = collectionHandle\n .trim()\n .replace(/[^a-zA-Z0-9\\-_]/g, \"\");\n if (!sanitizedHandle) {\n throw new Error(\"Invalid collection handle format\");\n }\n\n // Check handle length (reasonable limits)\n if (sanitizedHandle.length > 255) {\n throw new Error(\"Collection handle is too long\");\n }\n\n try {\n const url = `${baseUrl}collections/${encodeURIComponent(sanitizedHandle)}.json`;\n const response = await rateLimitedFetch(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const result = (await response.json()) as {\n collection: ShopifyCollection;\n };\n\n let collectionImage = result.collection.image;\n if (!collectionImage) {\n const collectionProduct = (\n await fetchPaginatedProductsFromCollection(\n result.collection.handle,\n {\n limit: 1,\n page: 1,\n }\n )\n )?.at(0);\n const collectionProductImage = collectionProduct?.images?.[0];\n if (collectionProduct && collectionProductImage) {\n collectionImage = {\n id: collectionProductImage.id,\n src: collectionProductImage.src,\n alt: collectionProductImage.alt || collectionProduct.title,\n created_at:\n collectionProductImage.createdAt || new Date().toISOString(),\n };\n }\n }\n\n const collectionData = collectionsDto([\n {\n ...result.collection,\n image: collectionImage,\n },\n ]);\n return collectionData[0] || null;\n } catch (error) {\n if (error instanceof Error) {\n console.error(\n `Error fetching collection ${sanitizedHandle}:`,\n baseUrl,\n error.message\n );\n }\n throw error;\n }\n },\n\n /**\n * Fetches collections that are showcased/featured on the store's homepage.\n *\n * @returns {Promise<Collection[]>} Array of showcased collections found on the homepage\n *\n * @throws {Error} When there's a network error or API failure\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://exampleshop.com');\n * const showcasedCollections = await shop.collections.showcased();\n *\n * console.log(`Found ${showcasedCollections.length} showcased collections`);\n * showcasedCollections.forEach(collection => {\n * console.log(`Featured: ${collection.title} - ${collection.productsCount} products`);\n * });\n * ```\n */\n showcased: async () => {\n const storeInfo = await getStoreInfo();\n const collections = await Promise.all(\n storeInfo.showcase.collections.map((collectionHandle: string) =>\n findCollection(collectionHandle)\n )\n );\n return filter(collections, isNonNullish);\n },\n\n products: {\n /**\n * Fetches products from a specific collection with pagination support.\n *\n * @param collectionHandle - The collection handle to fetch products from\n * @param options - Pagination options\n * @param options.page - Page number (default: 1)\n * @param options.limit - Number of products per page (default: 250, max: 250)\n *\n * @returns {Promise<Product[] | null>} Array of products from the collection or null if error occurs\n *\n * @throws {Error} When the collection handle is invalid or there's a network error\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://example.myshopify.com');\n *\n * // Get first page of products from a collection\n * const products = await shop.collections.products.paginated('summer-collection');\n *\n * // Get second page with custom limit\n * const moreProducts = await shop.collections.products.paginated(\n * 'summer-collection',\n * { page: 2, limit: 50 }\n * );\n * ```\n */\n paginated: async (\n collectionHandle: string,\n options?: { page?: number; limit?: number; currency?: CurrencyCode }\n ) => {\n // Validate collection handle\n if (!collectionHandle || typeof collectionHandle !== \"string\") {\n throw new Error(\"Collection handle is required and must be a string\");\n }\n // Sanitize handle - remove potentially dangerous characters\n const sanitizedHandle = collectionHandle\n .trim()\n .replace(/[^a-zA-Z0-9\\-_]/g, \"\");\n\n if (!sanitizedHandle) {\n throw new Error(\"Invalid collection handle format\");\n }\n\n if (sanitizedHandle.length > 255) {\n // Check handle length (reasonable limits)\n throw new Error(\"Collection handle is too long\");\n }\n\n // Validate pagination options\n const page = options?.page ?? 1;\n const limit = options?.limit ?? 250;\n\n if (page < 1 || limit < 1 || limit > 250) {\n throw new Error(\n \"Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250\"\n );\n }\n\n const products = await fetchPaginatedProductsFromCollection(\n sanitizedHandle,\n { page, limit }\n );\n return maybeOverrideProductsCurrency(products, options?.currency);\n },\n\n /**\n * Fetches all products from a specific collection.\n *\n * @param collectionHandle - The collection handle to fetch products from\n *\n * @returns {Promise<Product[] | null>} Array of all products from the collection or null if error occurs\n *\n * @throws {Error} When the collection handle is invalid or there's a network error\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://exampleshop.com');\n * const allProducts = await shop.collections.products.all('summer-collection');\n *\n * if (allProducts) {\n * console.log(`Found ${allProducts.length} products in the collection`);\n * allProducts.forEach(product => {\n * console.log(`${product.title} - $${product.price}`);\n * });\n * }\n * ```\n */\n all: async (\n collectionHandle: string,\n options?: { currency?: CurrencyCode }\n ): Promise<Product[] | null> => {\n // Validate collection handle\n if (!collectionHandle || typeof collectionHandle !== \"string\") {\n throw new Error(\"Collection handle is required and must be a string\");\n }\n\n // Sanitize handle - remove potentially dangerous characters\n const sanitizedHandle = collectionHandle\n .trim()\n .replace(/[^a-zA-Z0-9\\-_]/g, \"\");\n if (!sanitizedHandle) {\n throw new Error(\"Invalid collection handle format\");\n }\n\n // Check handle length (reasonable limits)\n if (sanitizedHandle.length > 255) {\n throw new Error(\"Collection handle is too long\");\n }\n\n try {\n const limit = 250;\n const allProducts: Product[] = [];\n\n let currentPage = 1;\n\n while (true) {\n const products = await fetchPaginatedProductsFromCollection(\n sanitizedHandle,\n {\n page: currentPage,\n limit,\n }\n );\n\n if (!products || products.length === 0 || products.length < limit) {\n if (products && products.length > 0) {\n allProducts.push(...products);\n }\n break;\n }\n\n allProducts.push(...products);\n currentPage++;\n }\n\n return maybeOverrideProductsCurrency(allProducts, options?.currency);\n } catch (error) {\n console.error(\n `Error fetching all products for collection ${sanitizedHandle}:`,\n baseUrl,\n error\n );\n return null;\n }\n },\n\n /**\n * Fetches all product slugs from a specific collection.\n *\n * @param collectionHandle - The collection handle to fetch product slugs from\n *\n * @returns {Promise<string[] | null>} Array of product slugs from the collection or null if error occurs\n *\n * @throws {Error} When the collection handle is invalid or there's a network error\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://exampleshop.com');\n * const productSlugs = await shop.collections.products.slugs('summer-collection');\n * console.log(productSlugs);\n * ```\n */\n slugs: async (collectionHandle: string): Promise<string[] | null> => {\n // Validate collection handle\n if (!collectionHandle || typeof collectionHandle !== \"string\") {\n throw new Error(\"Collection handle is required and must be a string\");\n }\n\n // Sanitize handle - remove potentially dangerous characters\n const sanitizedHandle = collectionHandle\n .trim()\n .replace(/[^a-zA-Z0-9\\-_]/g, \"\");\n if (!sanitizedHandle) {\n throw new Error(\"Invalid collection handle format\");\n }\n\n // Check handle length (reasonable limits)\n if (sanitizedHandle.length > 255) {\n throw new Error(\"Collection handle is too long\");\n }\n\n try {\n const limit = 250;\n const slugs: string[] = [];\n\n let currentPage = 1;\n\n while (true) {\n const products = await fetchPaginatedProductsFromCollection(\n sanitizedHandle,\n {\n page: currentPage,\n limit,\n }\n );\n\n if (!products || products.length === 0 || products.length < limit) {\n if (products && products.length > 0) {\n slugs.push(...products.map((p) => p.slug));\n }\n break;\n }\n\n slugs.push(...products.map((p) => p.slug));\n currentPage++;\n }\n\n return slugs;\n } catch (error) {\n console.error(\n `Error fetching product slugs for collection ${sanitizedHandle}:`,\n baseUrl,\n error\n );\n return null;\n }\n },\n },\n };\n}\n","import { parse } from \"tldts\";\nimport type { CurrencyCode } from \"../types\";\n\nexport function extractDomainWithoutSuffix(domain: string) {\n const parsedDomain = parse(domain);\n return parsedDomain.domainWithoutSuffix;\n}\n\nexport function generateStoreSlug(domain: string): string {\n const input = new URL(domain);\n const parsedDomain = parse(input.href);\n const domainName =\n parsedDomain.domainWithoutSuffix ?? input.hostname.split(\".\")[0];\n\n return (domainName || \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nexport const genProductSlug = ({\n handle,\n storeDomain,\n}: {\n handle: string;\n storeDomain: string;\n}) => {\n const storeSlug = generateStoreSlug(storeDomain);\n return `${handle}-by-${storeSlug}`;\n};\n\nexport const calculateDiscount = (\n price: number,\n compareAtPrice?: number\n): number =>\n !compareAtPrice || compareAtPrice === 0\n ? 0\n : Math.max(\n 0,\n Math.round(100 - (price / compareAtPrice) * 100) // Removed the decimal precision\n );\n\n/**\n * Normalize and sanitize a domain string.\n *\n * Accepts inputs like full URLs, protocol-relative URLs, bare hostnames,\n * or strings with paths/query/fragment, and returns a normalized domain.\n *\n * Examples:\n * - \"https://WWW.Example.com/path\" -> \"example.com\"\n * - \"//sub.example.co.uk\" -> \"example.co.uk\"\n * - \"www.example.com:8080\" -> \"example.com\"\n * - \"example\" -> \"example\"\n */\nexport function sanitizeDomain(\n input: string,\n opts?: { stripWWW?: boolean }\n): string {\n if (typeof input !== \"string\") {\n throw new Error(\"sanitizeDomain: input must be a string\");\n }\n let raw = input.trim();\n if (!raw) {\n throw new Error(\"sanitizeDomain: input cannot be empty\");\n }\n // Only add protocol if it's missing and not protocol-relative\n const hasProtocol = /^[a-z]+:\\/\\//i.test(raw);\n if (!hasProtocol && !raw.startsWith(\"//\")) {\n raw = `https://${raw}`;\n }\n\n const stripWWW = opts?.stripWWW ?? true;\n\n try {\n let url: URL;\n if (raw.startsWith(\"//\")) {\n url = new URL(`https:${raw}`);\n } else if (raw.includes(\"://\")) {\n url = new URL(raw);\n } else {\n url = new URL(`https://${raw}`);\n }\n let hostname = url.hostname.toLowerCase();\n const hadWWW = /^www\\./i.test(url.hostname);\n if (stripWWW) hostname = hostname.replace(/^www\\./, \"\");\n if (!hostname.includes(\".\")) {\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n const parsed = parse(hostname);\n if (!parsed.publicSuffix || parsed.isIcann === false) {\n // Require a valid public suffix (e.g., TLD); reject bare hostnames\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n if (!stripWWW && hadWWW) {\n return `www.${parsed.domain || hostname}`;\n }\n return parsed.domain || hostname;\n } catch {\n // Fallback: attempt to sanitize without URL parsing\n let hostname = raw.toLowerCase();\n hostname = hostname.replace(/^[a-z]+:\\/\\//, \"\"); // remove protocol if present\n hostname = hostname.replace(/^\\/\\//, \"\"); // remove protocol-relative\n hostname = hostname.replace(/[/:#?].*$/, \"\"); // remove path/query/fragment/port\n const hadWWW = /^www\\./i.test(hostname);\n if (stripWWW) hostname = hostname.replace(/^www\\./, \"\");\n if (!hostname.includes(\".\")) {\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n const parsed = parse(hostname);\n if (!parsed.publicSuffix || parsed.isIcann === false) {\n throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n }\n if (!stripWWW && hadWWW) {\n return `www.${parsed.domain || hostname}`;\n }\n return parsed.domain || hostname;\n }\n}\n\n/**\n * Safely parse a date string into a Date object.\n *\n * Returns `undefined` when input is falsy or cannot be parsed into a valid date.\n * Use `|| null` at call sites that expect `null` instead of `undefined`.\n */\nexport function safeParseDate(input?: string | null): Date | undefined {\n if (!input || typeof input !== \"string\") return undefined;\n const d = new Date(input);\n return Number.isNaN(d.getTime()) ? undefined : d;\n}\n\n/**\n * Normalize an option name or value to a lowercase, underscore-separated key.\n */\nexport function normalizeKey(input: string): string {\n return input.toLowerCase().replace(/\\s+/g, \"_\");\n}\n\n/**\n * Build a map from normalized option combination → variant id strings.\n * Example key: `size#xl##color#blue`.\n */\nexport function buildVariantOptionsMap(\n optionNames: string[],\n variants: Array<{\n id: number;\n option1: string | null;\n option2: string | null;\n option3: string | null;\n }>\n): Record<string, string> {\n const keys = optionNames.map(normalizeKey);\n const map: Record<string, string> = {};\n\n for (const v of variants) {\n const parts: string[] = [];\n if (keys[0] && v.option1)\n parts.push(`${keys[0]}#${normalizeKey(v.option1)}`);\n if (keys[1] && v.option2)\n parts.push(`${keys[1]}#${normalizeKey(v.option2)}`);\n if (keys[2] && v.option3)\n parts.push(`${keys[2]}#${normalizeKey(v.option3)}`);\n\n if (parts.length > 0) {\n // Ensure deterministic alphabetical ordering of parts\n if (parts.length > 1) parts.sort();\n const key = parts.join(\"##\");\n const id = v.id.toString();\n // First-write wins: do not override if key already exists\n if (map[key] === undefined) {\n map[key] = id;\n }\n }\n }\n\n return map;\n}\n\n/**\n * Build a normalized variant key string from an object of option name → value.\n * - Normalizes both names and values using `normalizeKey`\n * - Sorts parts alphabetically for deterministic output\n * - Joins parts using `##` and uses `name#value` for each part\n *\n * Example output: `color#blue##size#xl`\n */\nexport function buildVariantKey(\n obj: Record<string, string | null | undefined>\n): string {\n const parts: string[] = [];\n for (const [name, value] of Object.entries(obj)) {\n if (value) {\n parts.push(`${normalizeKey(name)}#${normalizeKey(value)}`);\n }\n }\n if (parts.length === 0) return \"\";\n parts.sort((a, b) => a.localeCompare(b));\n return parts.join(\"##\");\n}\n\n/**\n * Format a price amount (in cents) using a given ISO 4217 currency code.\n * Falls back to a simple string when Intl formatting fails.\n */\nexport function formatPrice(\n amountInCents: number,\n currency: CurrencyCode\n): string {\n try {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency,\n }).format((amountInCents || 0) / 100);\n } catch {\n const val = (amountInCents || 0) / 100;\n return `${val} ${currency}`;\n }\n}\n","export interface RateLimitOptions {\n maxRequestsPerInterval: number; // tokens refilled every interval\n intervalMs: number; // refill period\n maxConcurrency: number; // simultaneous in-flight requests\n}\n\ntype Task<T> = {\n fn: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (reason?: any) => void;\n};\n\nclass RateLimiter {\n private options: RateLimitOptions;\n private queue: Task<any>[] = [];\n private tokens: number;\n private inFlight = 0;\n private refillTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: RateLimitOptions) {\n this.options = options;\n this.tokens = options.maxRequestsPerInterval;\n }\n\n private startRefill() {\n if (this.refillTimer) return;\n this.refillTimer = setInterval(() => {\n this.tokens = this.options.maxRequestsPerInterval;\n this.tryRun();\n }, this.options.intervalMs);\n // In some runtimes, timers keep process alive; make it best-effort\n if (\n this.refillTimer &&\n typeof (this.refillTimer as any).unref === \"function\"\n ) {\n (this.refillTimer as any).unref();\n }\n }\n\n private stopRefill() {\n if (this.refillTimer) {\n clearInterval(this.refillTimer);\n this.refillTimer = null;\n }\n }\n\n private ensureRefillStarted() {\n if (!this.refillTimer) {\n this.startRefill();\n }\n }\n\n configure(next: Partial<RateLimitOptions>) {\n this.options = { ...this.options, ...next };\n // Clamp to sensible minimums\n this.options.maxRequestsPerInterval = Math.max(\n 1,\n this.options.maxRequestsPerInterval\n );\n this.options.intervalMs = Math.max(10, this.options.intervalMs);\n this.options.maxConcurrency = Math.max(1, this.options.maxConcurrency);\n }\n\n schedule<T>(fn: () => Promise<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n // Start the timer lazily on first schedule/use\n this.ensureRefillStarted();\n this.queue.push({ fn, resolve, reject });\n this.tryRun();\n });\n }\n\n private tryRun() {\n while (\n this.queue.length > 0 &&\n this.inFlight < this.options.maxConcurrency &&\n this.tokens > 0\n ) {\n const task = this.queue.shift()!;\n this.tokens -= 1;\n this.inFlight += 1;\n\n // Execute task and release slot when done\n Promise.resolve()\n .then(task.fn)\n .then((result) => task.resolve(result))\n .catch((err) => task.reject(err))\n .finally(() => {\n this.inFlight -= 1;\n // Yield to event loop, then continue draining\n setTimeout(() => this.tryRun(), 0);\n });\n }\n }\n}\n\nlet enabled = false;\nconst defaultOptions: RateLimitOptions = {\n maxRequestsPerInterval: 5, // 5 requests\n intervalMs: 1000, // per second\n maxConcurrency: 5, // up to 5 in parallel\n};\n\nconst limiter = new RateLimiter(defaultOptions);\nconst hostLimiters = new Map<string, RateLimiter>();\nconst classLimiters = new Map<string, RateLimiter>();\n\nexport type RateLimitedRequestInit = RequestInit & { rateLimitClass?: string };\n\nfunction getHost(input: RequestInfo | URL): string | undefined {\n try {\n if (typeof input === \"string\") {\n return new URL(input).host;\n }\n if (input instanceof URL) {\n return input.host;\n }\n // Request object or similar\n const url = (input as any).url as string | undefined;\n if (url) {\n return new URL(url).host;\n }\n } catch {\n // ignore parsing errors\n }\n return undefined;\n}\n\nfunction getHostLimiter(host?: string): RateLimiter | undefined {\n if (!host) return undefined;\n // Exact match first\n const exact = hostLimiters.get(host);\n if (exact) return exact;\n // Wildcard suffix match: keys of the form '*.example.com'\n for (const [key, lim] of hostLimiters.entries()) {\n if (key.startsWith(\"*.\") && host.endsWith(key.slice(2))) {\n return lim;\n }\n }\n return undefined;\n}\n\nexport function configureRateLimit(\n options: Partial<RateLimitOptions & { enabled: boolean }> & {\n perHost?: Record<string, Partial<RateLimitOptions>>; // key: host (supports '*.example.com')\n perClass?: Record<string, Partial<RateLimitOptions>>; // key: arbitrary class name\n }\n) {\n if (typeof options.enabled === \"boolean\") {\n enabled = options.enabled;\n }\n const { perHost, perClass } = options;\n const globalOpts: Partial<RateLimitOptions> = {};\n if (typeof options.maxRequestsPerInterval === \"number\") {\n globalOpts.maxRequestsPerInterval = options.maxRequestsPerInterval;\n }\n if (typeof options.intervalMs === \"number\") {\n globalOpts.intervalMs = options.intervalMs;\n }\n if (typeof options.maxConcurrency === \"number\") {\n globalOpts.maxConcurrency = options.maxConcurrency;\n }\n if (Object.keys(globalOpts).length) {\n limiter.configure(globalOpts);\n }\n if (perHost) {\n for (const host of Object.keys(perHost)) {\n const opts = perHost[host]!;\n const existing = hostLimiters.get(host);\n if (existing) {\n existing.configure(opts);\n } else {\n hostLimiters.set(\n host,\n new RateLimiter({\n maxRequestsPerInterval:\n opts.maxRequestsPerInterval ??\n defaultOptions.maxRequestsPerInterval,\n intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n maxConcurrency:\n opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n })\n );\n }\n }\n }\n if (perClass) {\n for (const klass of Object.keys(perClass)) {\n const opts = perClass[klass]!;\n const existing = classLimiters.get(klass);\n if (existing) {\n existing.configure(opts);\n } else {\n classLimiters.set(\n klass,\n new RateLimiter({\n maxRequestsPerInterval:\n opts.maxRequestsPerInterval ??\n defaultOptions.maxRequestsPerInterval,\n intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n maxConcurrency:\n opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n })\n );\n }\n }\n }\n}\n\nexport async function rateLimitedFetch(\n input: RequestInfo | URL,\n init?: RateLimitedRequestInit\n): Promise<Response> {\n if (!enabled) {\n return fetch(input as any, init);\n }\n const klass = init?.rateLimitClass;\n const byClass = klass ? classLimiters.get(klass) : undefined;\n const byHost = getHostLimiter(getHost(input));\n const eff = byClass ?? byHost ?? limiter;\n return eff.schedule(() => fetch(input as any, init));\n}\n\nexport function getRateLimitStatus() {\n return {\n enabled,\n options: { ...defaultOptions },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|