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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +912 -0
  3. package/dist/checkout.d.mts +31 -0
  4. package/dist/checkout.d.ts +31 -0
  5. package/dist/checkout.js +115 -0
  6. package/dist/checkout.js.map +1 -0
  7. package/dist/checkout.mjs +7 -0
  8. package/dist/checkout.mjs.map +1 -0
  9. package/dist/chunk-2KBOKOAD.mjs +177 -0
  10. package/dist/chunk-2KBOKOAD.mjs.map +1 -0
  11. package/dist/chunk-BWKBRM2Z.mjs +136 -0
  12. package/dist/chunk-BWKBRM2Z.mjs.map +1 -0
  13. package/dist/chunk-O4BPIIQ6.mjs +503 -0
  14. package/dist/chunk-O4BPIIQ6.mjs.map +1 -0
  15. package/dist/chunk-QCTICSBE.mjs +398 -0
  16. package/dist/chunk-QCTICSBE.mjs.map +1 -0
  17. package/dist/chunk-QL5OUZGP.mjs +91 -0
  18. package/dist/chunk-QL5OUZGP.mjs.map +1 -0
  19. package/dist/chunk-WTK5HUFI.mjs +1287 -0
  20. package/dist/chunk-WTK5HUFI.mjs.map +1 -0
  21. package/dist/collections.d.mts +64 -0
  22. package/dist/collections.d.ts +64 -0
  23. package/dist/collections.js +540 -0
  24. package/dist/collections.js.map +1 -0
  25. package/dist/collections.mjs +9 -0
  26. package/dist/collections.mjs.map +1 -0
  27. package/dist/index.d.mts +233 -0
  28. package/dist/index.d.ts +233 -0
  29. package/dist/index.js +3241 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/index.mjs +702 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/products.d.mts +63 -0
  34. package/dist/products.d.ts +63 -0
  35. package/dist/products.js +1206 -0
  36. package/dist/products.js.map +1 -0
  37. package/dist/products.mjs +9 -0
  38. package/dist/products.mjs.map +1 -0
  39. package/dist/store-CJVUz2Yb.d.mts +608 -0
  40. package/dist/store-CJVUz2Yb.d.ts +608 -0
  41. package/dist/store.d.mts +1 -0
  42. package/dist/store.d.ts +1 -0
  43. package/dist/store.js +698 -0
  44. package/dist/store.js.map +1 -0
  45. package/dist/store.mjs +9 -0
  46. package/dist/store.mjs.map +1 -0
  47. package/dist/utils/rate-limit.d.mts +25 -0
  48. package/dist/utils/rate-limit.d.ts +25 -0
  49. package/dist/utils/rate-limit.js +203 -0
  50. package/dist/utils/rate-limit.js.map +1 -0
  51. package/dist/utils/rate-limit.mjs +11 -0
  52. package/dist/utils/rate-limit.mjs.map +1 -0
  53. package/package.json +116 -0
@@ -0,0 +1,398 @@
1
+ import {
2
+ formatPrice
3
+ } from "./chunk-BWKBRM2Z.mjs";
4
+ import {
5
+ rateLimitedFetch
6
+ } from "./chunk-2KBOKOAD.mjs";
7
+
8
+ // src/collections.ts
9
+ import { filter, isNonNullish } from "remeda";
10
+ function createCollectionOperations(baseUrl, storeDomain, fetchCollections, collectionsDto, fetchPaginatedProductsFromCollection, getStoreInfo, findCollection) {
11
+ function applyCurrencyOverride(product, currency) {
12
+ var _a, _b, _c, _d, _e, _f;
13
+ const priceMin = (_b = (_a = product.priceMin) != null ? _a : product.price) != null ? _b : 0;
14
+ const priceMax = (_d = (_c = product.priceMax) != null ? _c : product.price) != null ? _d : 0;
15
+ const compareAtMin = (_f = (_e = product.compareAtPriceMin) != null ? _e : product.compareAtPrice) != null ? _f : 0;
16
+ return {
17
+ ...product,
18
+ currency,
19
+ localizedPricing: {
20
+ currency,
21
+ priceFormatted: formatPrice(priceMin, currency),
22
+ priceMinFormatted: formatPrice(priceMin, currency),
23
+ priceMaxFormatted: formatPrice(priceMax, currency),
24
+ compareAtPriceFormatted: formatPrice(compareAtMin, currency)
25
+ }
26
+ };
27
+ }
28
+ function maybeOverrideProductsCurrency(products, currency) {
29
+ if (!products || !currency) return products;
30
+ return products.map((p) => applyCurrencyOverride(p, currency));
31
+ }
32
+ return {
33
+ /**
34
+ * Fetches collections with pagination support.
35
+ *
36
+ * @param options - Pagination options
37
+ * @param options.page - Page number (default: 1)
38
+ * @param options.limit - Number of collections per page (default: 10, max: 250)
39
+ *
40
+ * @returns {Promise<Collection[] | null>} Collections for the requested page, or null on error
41
+ */
42
+ paginated: async (options) => {
43
+ var _a, _b;
44
+ const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
45
+ const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 10;
46
+ if (page < 1 || limit < 1 || limit > 250) {
47
+ throw new Error(
48
+ "Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
49
+ );
50
+ }
51
+ try {
52
+ const collections = await fetchCollections(page, limit);
53
+ return collections != null ? collections : null;
54
+ } catch (error) {
55
+ console.error(
56
+ "Failed to fetch paginated collections:",
57
+ storeDomain,
58
+ error
59
+ );
60
+ return null;
61
+ }
62
+ },
63
+ /**
64
+ * Fetches all collections from the store across all pages.
65
+ *
66
+ * @returns {Promise<Collection[]>} Array of all collections
67
+ *
68
+ * @throws {Error} When there's a network error or API failure
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const shop = new ShopClient('https://exampleshop.com');
73
+ * const allCollections = await shop.collections.all();
74
+ *
75
+ * console.log(`Found ${allCollections.length} collections`);
76
+ * allCollections.forEach(collection => {
77
+ * console.log(collection.title, collection.handle);
78
+ * });
79
+ * ```
80
+ */
81
+ all: async () => {
82
+ const limit = 250;
83
+ const allCollections = [];
84
+ async function fetchAll() {
85
+ let currentPage = 1;
86
+ while (true) {
87
+ const collections = await fetchCollections(currentPage, limit);
88
+ if (!collections || collections.length === 0 || collections.length < limit) {
89
+ if (!collections) {
90
+ console.warn(
91
+ "fetchCollections returned null, treating as empty array."
92
+ );
93
+ break;
94
+ }
95
+ if (collections && collections.length > 0) {
96
+ allCollections.push(...collections);
97
+ }
98
+ break;
99
+ }
100
+ allCollections.push(...collections);
101
+ currentPage++;
102
+ }
103
+ return allCollections;
104
+ }
105
+ try {
106
+ const collections = await fetchAll();
107
+ return collections || [];
108
+ } catch (error) {
109
+ console.error("Failed to fetch all collections:", storeDomain, error);
110
+ throw error;
111
+ }
112
+ },
113
+ /**
114
+ * Finds a specific collection by its handle.
115
+ *
116
+ * @param collectionHandle - The collection handle (URL slug) to search for
117
+ *
118
+ * @returns {Promise<Collection | null>} The collection if found, null if not found
119
+ *
120
+ * @throws {Error} When the handle is invalid or there's a network error
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const shop = new ShopClient('https://example.myshopify.com');
125
+ * const collection = await shop.collections.find('summer-collection');
126
+ * if (collection) {
127
+ * console.log(collection.title); // "Summer Collection"
128
+ * }
129
+ * ```
130
+ */
131
+ find: async (collectionHandle) => {
132
+ var _a, _b;
133
+ if (!collectionHandle || typeof collectionHandle !== "string") {
134
+ throw new Error("Collection handle is required and must be a string");
135
+ }
136
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
137
+ if (!sanitizedHandle) {
138
+ throw new Error("Invalid collection handle format");
139
+ }
140
+ if (sanitizedHandle.length > 255) {
141
+ throw new Error("Collection handle is too long");
142
+ }
143
+ try {
144
+ const url = `${baseUrl}collections/${encodeURIComponent(sanitizedHandle)}.json`;
145
+ const response = await rateLimitedFetch(url);
146
+ if (!response.ok) {
147
+ if (response.status === 404) {
148
+ return null;
149
+ }
150
+ throw new Error(`HTTP error! status: ${response.status}`);
151
+ }
152
+ const result = await response.json();
153
+ let collectionImage = result.collection.image;
154
+ if (!collectionImage) {
155
+ const collectionProduct = (_a = await fetchPaginatedProductsFromCollection(
156
+ result.collection.handle,
157
+ {
158
+ limit: 1,
159
+ page: 1
160
+ }
161
+ )) == null ? void 0 : _a.at(0);
162
+ const collectionProductImage = (_b = collectionProduct == null ? void 0 : collectionProduct.images) == null ? void 0 : _b[0];
163
+ if (collectionProduct && collectionProductImage) {
164
+ collectionImage = {
165
+ id: collectionProductImage.id,
166
+ src: collectionProductImage.src,
167
+ alt: collectionProductImage.alt || collectionProduct.title,
168
+ created_at: collectionProductImage.createdAt || (/* @__PURE__ */ new Date()).toISOString()
169
+ };
170
+ }
171
+ }
172
+ const collectionData = collectionsDto([
173
+ {
174
+ ...result.collection,
175
+ image: collectionImage
176
+ }
177
+ ]);
178
+ return collectionData[0] || null;
179
+ } catch (error) {
180
+ if (error instanceof Error) {
181
+ console.error(
182
+ `Error fetching collection ${sanitizedHandle}:`,
183
+ baseUrl,
184
+ error.message
185
+ );
186
+ }
187
+ throw error;
188
+ }
189
+ },
190
+ /**
191
+ * Fetches collections that are showcased/featured on the store's homepage.
192
+ *
193
+ * @returns {Promise<Collection[]>} Array of showcased collections found on the homepage
194
+ *
195
+ * @throws {Error} When there's a network error or API failure
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const shop = new ShopClient('https://exampleshop.com');
200
+ * const showcasedCollections = await shop.collections.showcased();
201
+ *
202
+ * console.log(`Found ${showcasedCollections.length} showcased collections`);
203
+ * showcasedCollections.forEach(collection => {
204
+ * console.log(`Featured: ${collection.title} - ${collection.productsCount} products`);
205
+ * });
206
+ * ```
207
+ */
208
+ showcased: async () => {
209
+ const storeInfo = await getStoreInfo();
210
+ const collections = await Promise.all(
211
+ storeInfo.showcase.collections.map(
212
+ (collectionHandle) => findCollection(collectionHandle)
213
+ )
214
+ );
215
+ return filter(collections, isNonNullish);
216
+ },
217
+ products: {
218
+ /**
219
+ * Fetches products from a specific collection with pagination support.
220
+ *
221
+ * @param collectionHandle - The collection handle to fetch products from
222
+ * @param options - Pagination options
223
+ * @param options.page - Page number (default: 1)
224
+ * @param options.limit - Number of products per page (default: 250, max: 250)
225
+ *
226
+ * @returns {Promise<Product[] | null>} Array of products from the collection or null if error occurs
227
+ *
228
+ * @throws {Error} When the collection handle is invalid or there's a network error
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * const shop = new ShopClient('https://example.myshopify.com');
233
+ *
234
+ * // Get first page of products from a collection
235
+ * const products = await shop.collections.products.paginated('summer-collection');
236
+ *
237
+ * // Get second page with custom limit
238
+ * const moreProducts = await shop.collections.products.paginated(
239
+ * 'summer-collection',
240
+ * { page: 2, limit: 50 }
241
+ * );
242
+ * ```
243
+ */
244
+ paginated: async (collectionHandle, options) => {
245
+ var _a, _b;
246
+ if (!collectionHandle || typeof collectionHandle !== "string") {
247
+ throw new Error("Collection handle is required and must be a string");
248
+ }
249
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
250
+ if (!sanitizedHandle) {
251
+ throw new Error("Invalid collection handle format");
252
+ }
253
+ if (sanitizedHandle.length > 255) {
254
+ throw new Error("Collection handle is too long");
255
+ }
256
+ const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
257
+ const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 250;
258
+ if (page < 1 || limit < 1 || limit > 250) {
259
+ throw new Error(
260
+ "Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
261
+ );
262
+ }
263
+ const products = await fetchPaginatedProductsFromCollection(
264
+ sanitizedHandle,
265
+ { page, limit }
266
+ );
267
+ return maybeOverrideProductsCurrency(products, options == null ? void 0 : options.currency);
268
+ },
269
+ /**
270
+ * Fetches all products from a specific collection.
271
+ *
272
+ * @param collectionHandle - The collection handle to fetch products from
273
+ *
274
+ * @returns {Promise<Product[] | null>} Array of all products from the collection or null if error occurs
275
+ *
276
+ * @throws {Error} When the collection handle is invalid or there's a network error
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * const shop = new ShopClient('https://exampleshop.com');
281
+ * const allProducts = await shop.collections.products.all('summer-collection');
282
+ *
283
+ * if (allProducts) {
284
+ * console.log(`Found ${allProducts.length} products in the collection`);
285
+ * allProducts.forEach(product => {
286
+ * console.log(`${product.title} - $${product.price}`);
287
+ * });
288
+ * }
289
+ * ```
290
+ */
291
+ all: async (collectionHandle, options) => {
292
+ if (!collectionHandle || typeof collectionHandle !== "string") {
293
+ throw new Error("Collection handle is required and must be a string");
294
+ }
295
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
296
+ if (!sanitizedHandle) {
297
+ throw new Error("Invalid collection handle format");
298
+ }
299
+ if (sanitizedHandle.length > 255) {
300
+ throw new Error("Collection handle is too long");
301
+ }
302
+ try {
303
+ const limit = 250;
304
+ const allProducts = [];
305
+ let currentPage = 1;
306
+ while (true) {
307
+ const products = await fetchPaginatedProductsFromCollection(
308
+ sanitizedHandle,
309
+ {
310
+ page: currentPage,
311
+ limit
312
+ }
313
+ );
314
+ if (!products || products.length === 0 || products.length < limit) {
315
+ if (products && products.length > 0) {
316
+ allProducts.push(...products);
317
+ }
318
+ break;
319
+ }
320
+ allProducts.push(...products);
321
+ currentPage++;
322
+ }
323
+ return maybeOverrideProductsCurrency(allProducts, options == null ? void 0 : options.currency);
324
+ } catch (error) {
325
+ console.error(
326
+ `Error fetching all products for collection ${sanitizedHandle}:`,
327
+ baseUrl,
328
+ error
329
+ );
330
+ return null;
331
+ }
332
+ },
333
+ /**
334
+ * Fetches all product slugs from a specific collection.
335
+ *
336
+ * @param collectionHandle - The collection handle to fetch product slugs from
337
+ *
338
+ * @returns {Promise<string[] | null>} Array of product slugs from the collection or null if error occurs
339
+ *
340
+ * @throws {Error} When the collection handle is invalid or there's a network error
341
+ *
342
+ * @example
343
+ * ```typescript
344
+ * const shop = new ShopClient('https://exampleshop.com');
345
+ * const productSlugs = await shop.collections.products.slugs('summer-collection');
346
+ * console.log(productSlugs);
347
+ * ```
348
+ */
349
+ slugs: async (collectionHandle) => {
350
+ if (!collectionHandle || typeof collectionHandle !== "string") {
351
+ throw new Error("Collection handle is required and must be a string");
352
+ }
353
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
354
+ if (!sanitizedHandle) {
355
+ throw new Error("Invalid collection handle format");
356
+ }
357
+ if (sanitizedHandle.length > 255) {
358
+ throw new Error("Collection handle is too long");
359
+ }
360
+ try {
361
+ const limit = 250;
362
+ const slugs = [];
363
+ let currentPage = 1;
364
+ while (true) {
365
+ const products = await fetchPaginatedProductsFromCollection(
366
+ sanitizedHandle,
367
+ {
368
+ page: currentPage,
369
+ limit
370
+ }
371
+ );
372
+ if (!products || products.length === 0 || products.length < limit) {
373
+ if (products && products.length > 0) {
374
+ slugs.push(...products.map((p) => p.slug));
375
+ }
376
+ break;
377
+ }
378
+ slugs.push(...products.map((p) => p.slug));
379
+ currentPage++;
380
+ }
381
+ return slugs;
382
+ } catch (error) {
383
+ console.error(
384
+ `Error fetching product slugs for collection ${sanitizedHandle}:`,
385
+ baseUrl,
386
+ error
387
+ );
388
+ return null;
389
+ }
390
+ }
391
+ }
392
+ };
393
+ }
394
+
395
+ export {
396
+ createCollectionOperations
397
+ };
398
+ //# sourceMappingURL=chunk-QCTICSBE.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/collections.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"],"mappings":"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","names":[]}
@@ -0,0 +1,91 @@
1
+ // src/checkout.ts
2
+ function createCheckoutOperations(baseUrl) {
3
+ return {
4
+ /**
5
+ * Creates a Shopify checkout URL with pre-filled customer information and cart items.
6
+ *
7
+ * @param params - Checkout parameters
8
+ * @param params.email - Customer's email address (must be valid email format)
9
+ * @param params.items - Array of products to add to cart
10
+ * @param params.items[].productVariantId - Shopify product variant ID
11
+ * @param params.items[].quantity - Quantity as string (must be positive number)
12
+ * @param params.address - Customer's shipping address
13
+ * @param params.address.firstName - Customer's first name
14
+ * @param params.address.lastName - Customer's last name
15
+ * @param params.address.address1 - Street address
16
+ * @param params.address.city - City name
17
+ * @param params.address.zip - Postal/ZIP code
18
+ * @param params.address.country - Country name
19
+ * @param params.address.province - State/Province name
20
+ * @param params.address.phone - Phone number
21
+ *
22
+ * @returns {string} Complete Shopify checkout URL with pre-filled information
23
+ *
24
+ * @throws {Error} When email is invalid, items array is empty, or required address fields are missing
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const shop = new ShopClient('https://exampleshop.com');
29
+ * const checkoutUrl = await shop.checkout.create([
30
+ * { variantId: '123', quantity: 2 },
31
+ * { variantId: '456', quantity: 1 }
32
+ * ]);
33
+ * console.log(checkoutUrl);
34
+ * ```
35
+ */
36
+ createUrl: ({
37
+ email,
38
+ items,
39
+ address
40
+ }) => {
41
+ if (!email || !email.includes("@")) {
42
+ throw new Error("Invalid email address");
43
+ }
44
+ if (!items || items.length === 0) {
45
+ throw new Error("Items array cannot be empty");
46
+ }
47
+ for (const item of items) {
48
+ if (!item.productVariantId || !item.quantity) {
49
+ throw new Error("Each item must have productVariantId and quantity");
50
+ }
51
+ const qty = Number.parseInt(item.quantity, 10);
52
+ if (Number.isNaN(qty) || qty <= 0) {
53
+ throw new Error("Quantity must be a positive number");
54
+ }
55
+ }
56
+ const requiredFields = [
57
+ "firstName",
58
+ "lastName",
59
+ "address1",
60
+ "city",
61
+ "zip",
62
+ "country"
63
+ ];
64
+ for (const field of requiredFields) {
65
+ if (!address[field]) {
66
+ throw new Error(`Address field '${field}' is required`);
67
+ }
68
+ }
69
+ const cartPath = items.map(
70
+ (item) => `${encodeURIComponent(item.productVariantId)}:${encodeURIComponent(item.quantity)}`
71
+ ).join(",");
72
+ const params = new URLSearchParams({
73
+ "checkout[email]": email,
74
+ "checkout[shipping_address][first_name]": address.firstName,
75
+ "checkout[shipping_address][last_name]": address.lastName,
76
+ "checkout[shipping_address][address1]": address.address1,
77
+ "checkout[shipping_address][city]": address.city,
78
+ "checkout[shipping_address][zip]": address.zip,
79
+ "checkout[shipping_address][country]": address.country,
80
+ "checkout[shipping_address][province]": address.province,
81
+ "checkout[shipping_address][phone]": address.phone
82
+ });
83
+ return `${baseUrl}cart/${cartPath}?${params.toString()}`;
84
+ }
85
+ };
86
+ }
87
+
88
+ export {
89
+ createCheckoutOperations
90
+ };
91
+ //# sourceMappingURL=chunk-QL5OUZGP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/checkout.ts"],"sourcesContent":["/**\n * Interface for checkout operations\n */\nexport interface CheckoutOperations {\n /**\n * Creates a Shopify checkout URL with pre-filled customer information and cart items.\n */\n createUrl(params: {\n email: string;\n items: Array<{ productVariantId: string; quantity: string }>;\n address: {\n firstName: string;\n lastName: string;\n address1: string;\n city: string;\n zip: string;\n country: string;\n province: string;\n phone: string;\n };\n }): string;\n}\n\n/**\n * Creates checkout operations for a store instance\n */\nexport function createCheckoutOperations(baseUrl: string): CheckoutOperations {\n return {\n /**\n * Creates a Shopify checkout URL with pre-filled customer information and cart items.\n *\n * @param params - Checkout parameters\n * @param params.email - Customer's email address (must be valid email format)\n * @param params.items - Array of products to add to cart\n * @param params.items[].productVariantId - Shopify product variant ID\n * @param params.items[].quantity - Quantity as string (must be positive number)\n * @param params.address - Customer's shipping address\n * @param params.address.firstName - Customer's first name\n * @param params.address.lastName - Customer's last name\n * @param params.address.address1 - Street address\n * @param params.address.city - City name\n * @param params.address.zip - Postal/ZIP code\n * @param params.address.country - Country name\n * @param params.address.province - State/Province name\n * @param params.address.phone - Phone number\n *\n * @returns {string} Complete Shopify checkout URL with pre-filled information\n *\n * @throws {Error} When email is invalid, items array is empty, or required address fields are missing\n *\n * @example\n * ```typescript\n * const shop = new ShopClient('https://exampleshop.com');\n * const checkoutUrl = await shop.checkout.create([\n * { variantId: '123', quantity: 2 },\n * { variantId: '456', quantity: 1 }\n * ]);\n * console.log(checkoutUrl);\n * ```\n */\n createUrl: ({\n email,\n items,\n address,\n }: {\n email: string;\n items: Array<{ productVariantId: string; quantity: string }>;\n address: {\n firstName: string;\n lastName: string;\n address1: string;\n city: string;\n zip: string;\n country: string;\n province: string;\n phone: string;\n };\n }) => {\n // Validate and sanitize inputs\n if (!email || !email.includes(\"@\")) {\n throw new Error(\"Invalid email address\");\n }\n\n if (!items || items.length === 0) {\n throw new Error(\"Items array cannot be empty\");\n }\n\n // Validate items\n for (const item of items) {\n if (!item.productVariantId || !item.quantity) {\n throw new Error(\"Each item must have productVariantId and quantity\");\n }\n // Ensure quantity is a positive number\n const qty = Number.parseInt(item.quantity, 10);\n\n if (Number.isNaN(qty) || qty <= 0) {\n throw new Error(\"Quantity must be a positive number\");\n }\n }\n\n // Validate required address fields\n const requiredFields = [\n \"firstName\",\n \"lastName\",\n \"address1\",\n \"city\",\n \"zip\",\n \"country\",\n ];\n\n for (const field of requiredFields) {\n if (!address[field as keyof typeof address]) {\n throw new Error(`Address field '${field}' is required`);\n }\n }\n\n // Properly encode all URL parameters to prevent injection attacks\n const cartPath = items\n .map(\n (item) =>\n `${encodeURIComponent(item.productVariantId)}:${encodeURIComponent(item.quantity)}`\n )\n .join(\",\");\n\n const params = new URLSearchParams({\n \"checkout[email]\": email,\n \"checkout[shipping_address][first_name]\": address.firstName,\n \"checkout[shipping_address][last_name]\": address.lastName,\n \"checkout[shipping_address][address1]\": address.address1,\n \"checkout[shipping_address][city]\": address.city,\n \"checkout[shipping_address][zip]\": address.zip,\n \"checkout[shipping_address][country]\": address.country,\n \"checkout[shipping_address][province]\": address.province,\n \"checkout[shipping_address][phone]\": address.phone,\n });\n\n return `${baseUrl}cart/${cartPath}?${params.toString()}`;\n },\n };\n}\n"],"mappings":";AA0BO,SAAS,yBAAyB,SAAqC;AAC5E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiCL,WAAW,CAAC;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAaM;AAEJ,UAAI,CAAC,SAAS,CAAC,MAAM,SAAS,GAAG,GAAG;AAClC,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC;AAEA,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,cAAM,IAAI,MAAM,6BAA6B;AAAA,MAC/C;AAGA,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,oBAAoB,CAAC,KAAK,UAAU;AAC5C,gBAAM,IAAI,MAAM,mDAAmD;AAAA,QACrE;AAEA,cAAM,MAAM,OAAO,SAAS,KAAK,UAAU,EAAE;AAE7C,YAAI,OAAO,MAAM,GAAG,KAAK,OAAO,GAAG;AACjC,gBAAM,IAAI,MAAM,oCAAoC;AAAA,QACtD;AAAA,MACF;AAGA,YAAM,iBAAiB;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,iBAAW,SAAS,gBAAgB;AAClC,YAAI,CAAC,QAAQ,KAA6B,GAAG;AAC3C,gBAAM,IAAI,MAAM,kBAAkB,KAAK,eAAe;AAAA,QACxD;AAAA,MACF;AAGA,YAAM,WAAW,MACd;AAAA,QACC,CAAC,SACC,GAAG,mBAAmB,KAAK,gBAAgB,CAAC,IAAI,mBAAmB,KAAK,QAAQ,CAAC;AAAA,MACrF,EACC,KAAK,GAAG;AAEX,YAAM,SAAS,IAAI,gBAAgB;AAAA,QACjC,mBAAmB;AAAA,QACnB,0CAA0C,QAAQ;AAAA,QAClD,yCAAyC,QAAQ;AAAA,QACjD,wCAAwC,QAAQ;AAAA,QAChD,oCAAoC,QAAQ;AAAA,QAC5C,mCAAmC,QAAQ;AAAA,QAC3C,uCAAuC,QAAQ;AAAA,QAC/C,wCAAwC,QAAQ;AAAA,QAChD,qCAAqC,QAAQ;AAAA,MAC/C,CAAC;AAED,aAAO,GAAG,OAAO,QAAQ,QAAQ,IAAI,OAAO,SAAS,CAAC;AAAA,IACxD;AAAA,EACF;AACF;","names":[]}