shop-client 3.9.0 → 3.9.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 (41) hide show
  1. package/README.md +65 -0
  2. package/dist/ai/enrich.d.ts +93 -0
  3. package/dist/ai/enrich.js +25 -0
  4. package/dist/checkout.js +6 -0
  5. package/dist/chunk-2MF53V33.js +196 -0
  6. package/dist/chunk-CN7L3BHG.js +147 -0
  7. package/dist/chunk-CXUCPK6X.js +460 -0
  8. package/dist/chunk-DJQEZNHG.js +233 -0
  9. package/dist/chunk-MOBWPEY4.js +420 -0
  10. package/dist/chunk-QUDGES3A.js +195 -0
  11. package/dist/chunk-RR6YTQWP.js +90 -0
  12. package/dist/chunk-VPPCOJC3.js +865 -0
  13. package/dist/collections.d.ts +2 -1
  14. package/dist/collections.js +8 -0
  15. package/dist/index.d.ts +7 -84
  16. package/dist/index.js +753 -0
  17. package/dist/products.d.ts +2 -1
  18. package/dist/products.js +8 -0
  19. package/dist/store.d.ts +53 -1
  20. package/dist/store.js +9 -0
  21. package/dist/{store-iQARl6J3.d.ts → types-luPg5O08.d.ts} +1 -208
  22. package/dist/utils/detect-country.d.ts +32 -0
  23. package/dist/utils/detect-country.js +6 -0
  24. package/dist/utils/func.d.ts +61 -0
  25. package/dist/utils/func.js +24 -0
  26. package/dist/utils/rate-limit.js +10 -0
  27. package/package.json +16 -3
  28. package/dist/checkout.mjs +0 -1
  29. package/dist/chunk-6GPWNCDO.mjs +0 -130
  30. package/dist/chunk-EJO5U4BT.mjs +0 -2
  31. package/dist/chunk-FFKWCNLU.mjs +0 -1
  32. package/dist/chunk-KYLPIEU3.mjs +0 -2
  33. package/dist/chunk-MB2INNNP.mjs +0 -1
  34. package/dist/chunk-MI7754VX.mjs +0 -2
  35. package/dist/chunk-SZQPMLZG.mjs +0 -1
  36. package/dist/collections.mjs +0 -1
  37. package/dist/enrich-OZHBXKK6.mjs +0 -1
  38. package/dist/index.mjs +0 -2
  39. package/dist/products.mjs +0 -1
  40. package/dist/store.mjs +0 -1
  41. package/dist/utils/rate-limit.mjs +0 -1
@@ -0,0 +1,420 @@
1
+ import {
2
+ rateLimitedFetch
3
+ } from "./chunk-2MF53V33.js";
4
+ import {
5
+ formatPrice
6
+ } from "./chunk-CN7L3BHG.js";
7
+
8
+ // src/collections.ts
9
+ import { filter, isNonNullish } from "remeda";
10
+ function createCollectionOperations(baseUrl, storeDomain, fetchCollections, collectionsDto, fetchPaginatedProductsFromCollection, getStoreInfo, findCollection) {
11
+ const cacheExpiryMs = 5 * 60 * 1e3;
12
+ const findCache = /* @__PURE__ */ new Map();
13
+ const getCached = (key) => {
14
+ const entry = findCache.get(key);
15
+ if (!entry) return void 0;
16
+ if (Date.now() - entry.ts < cacheExpiryMs) return entry.value;
17
+ findCache.delete(key);
18
+ return void 0;
19
+ };
20
+ const setCached = (key, value) => {
21
+ findCache.set(key, { ts: Date.now(), value });
22
+ };
23
+ function applyCurrencyOverride(product, currency) {
24
+ var _a, _b, _c, _d, _e, _f;
25
+ const priceMin = (_b = (_a = product.priceMin) != null ? _a : product.price) != null ? _b : 0;
26
+ const priceMax = (_d = (_c = product.priceMax) != null ? _c : product.price) != null ? _d : 0;
27
+ const compareAtMin = (_f = (_e = product.compareAtPriceMin) != null ? _e : product.compareAtPrice) != null ? _f : 0;
28
+ return {
29
+ ...product,
30
+ currency,
31
+ localizedPricing: {
32
+ currency,
33
+ priceFormatted: formatPrice(priceMin, currency),
34
+ priceMinFormatted: formatPrice(priceMin, currency),
35
+ priceMaxFormatted: formatPrice(priceMax, currency),
36
+ compareAtPriceFormatted: formatPrice(compareAtMin, currency)
37
+ }
38
+ };
39
+ }
40
+ function maybeOverrideProductsCurrency(products, currency) {
41
+ if (!products || !currency) return products;
42
+ return products.map((p) => applyCurrencyOverride(p, currency));
43
+ }
44
+ return {
45
+ /**
46
+ * Fetches collections with pagination support.
47
+ *
48
+ * @param options - Pagination options
49
+ * @param options.page - Page number (default: 1)
50
+ * @param options.limit - Number of collections per page (default: 10, max: 250)
51
+ *
52
+ * @returns {Promise<Collection[] | null>} Collections for the requested page, or null on error
53
+ */
54
+ paginated: async (options) => {
55
+ var _a, _b;
56
+ const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
57
+ const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 10;
58
+ if (page < 1 || limit < 1 || limit > 250) {
59
+ throw new Error(
60
+ "Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
61
+ );
62
+ }
63
+ try {
64
+ const collections = await fetchCollections(page, limit);
65
+ return collections != null ? collections : null;
66
+ } catch (error) {
67
+ console.error(
68
+ "Failed to fetch paginated collections:",
69
+ storeDomain,
70
+ error
71
+ );
72
+ return null;
73
+ }
74
+ },
75
+ /**
76
+ * Fetches all collections from the store across all pages.
77
+ *
78
+ * @returns {Promise<Collection[]>} Array of all collections
79
+ *
80
+ * @throws {Error} When there's a network error or API failure
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const shop = new ShopClient('https://exampleshop.com');
85
+ * const allCollections = await shop.collections.all();
86
+ *
87
+ * console.log(`Found ${allCollections.length} collections`);
88
+ * allCollections.forEach(collection => {
89
+ * console.log(collection.title, collection.handle);
90
+ * });
91
+ * ```
92
+ */
93
+ all: async () => {
94
+ const limit = 250;
95
+ const allCollections = [];
96
+ async function fetchAll() {
97
+ let currentPage = 1;
98
+ while (true) {
99
+ const collections = await fetchCollections(currentPage, limit);
100
+ if (!collections || collections.length === 0 || collections.length < limit) {
101
+ if (!collections) {
102
+ console.warn(
103
+ "fetchCollections returned null, treating as empty array."
104
+ );
105
+ break;
106
+ }
107
+ if (collections && collections.length > 0) {
108
+ allCollections.push(...collections);
109
+ }
110
+ break;
111
+ }
112
+ allCollections.push(...collections);
113
+ currentPage++;
114
+ }
115
+ return allCollections;
116
+ }
117
+ try {
118
+ const collections = await fetchAll();
119
+ return collections || [];
120
+ } catch (error) {
121
+ console.error("Failed to fetch all collections:", storeDomain, error);
122
+ throw error;
123
+ }
124
+ },
125
+ /**
126
+ * Finds a specific collection by its handle.
127
+ *
128
+ * @param collectionHandle - The collection handle (URL slug) to search for
129
+ *
130
+ * @returns {Promise<Collection | null>} The collection if found, null if not found
131
+ *
132
+ * @throws {Error} When the handle is invalid or there's a network error
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const shop = new ShopClient('https://example.myshopify.com');
137
+ * const collection = await shop.collections.find('summer-collection');
138
+ * if (collection) {
139
+ * console.log(collection.title); // "Summer Collection"
140
+ * }
141
+ * ```
142
+ */
143
+ find: async (collectionHandle) => {
144
+ var _a, _b;
145
+ if (!collectionHandle || typeof collectionHandle !== "string") {
146
+ throw new Error("Collection handle is required and must be a string");
147
+ }
148
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
149
+ if (!sanitizedHandle) {
150
+ throw new Error("Invalid collection handle format");
151
+ }
152
+ if (sanitizedHandle.length > 255) {
153
+ throw new Error("Collection handle is too long");
154
+ }
155
+ const cached = getCached(sanitizedHandle);
156
+ if (typeof cached !== "undefined") {
157
+ return cached;
158
+ }
159
+ try {
160
+ const url = `${baseUrl}collections/${encodeURIComponent(sanitizedHandle)}.json`;
161
+ const response = await rateLimitedFetch(url, {
162
+ rateLimitClass: "collections:single"
163
+ });
164
+ if (!response.ok) {
165
+ if (response.status === 404) {
166
+ return null;
167
+ }
168
+ throw new Error(`HTTP error! status: ${response.status}`);
169
+ }
170
+ const result = await response.json();
171
+ let collectionImage = result.collection.image;
172
+ if (!collectionImage) {
173
+ const collectionProduct = (_a = await fetchPaginatedProductsFromCollection(
174
+ result.collection.handle,
175
+ {
176
+ limit: 1,
177
+ page: 1
178
+ }
179
+ )) == null ? void 0 : _a.at(0);
180
+ const collectionProductImage = (_b = collectionProduct == null ? void 0 : collectionProduct.images) == null ? void 0 : _b[0];
181
+ if (collectionProduct && collectionProductImage) {
182
+ collectionImage = {
183
+ id: collectionProductImage.id,
184
+ src: collectionProductImage.src,
185
+ alt: collectionProductImage.alt || collectionProduct.title,
186
+ created_at: collectionProductImage.createdAt || (/* @__PURE__ */ new Date()).toISOString()
187
+ };
188
+ }
189
+ }
190
+ const collectionData = collectionsDto([
191
+ {
192
+ ...result.collection,
193
+ image: collectionImage
194
+ }
195
+ ]);
196
+ const coll = collectionData[0] || null;
197
+ setCached(sanitizedHandle, coll);
198
+ if ((coll == null ? void 0 : coll.handle) && coll.handle !== sanitizedHandle) {
199
+ setCached(coll.handle, coll);
200
+ }
201
+ return coll;
202
+ } catch (error) {
203
+ if (error instanceof Error) {
204
+ console.error(
205
+ `Error fetching collection ${sanitizedHandle}:`,
206
+ baseUrl,
207
+ error.message
208
+ );
209
+ }
210
+ throw error;
211
+ }
212
+ },
213
+ /**
214
+ * Fetches collections that are showcased/featured on the store's homepage.
215
+ *
216
+ * @returns {Promise<Collection[]>} Array of showcased collections found on the homepage
217
+ *
218
+ * @throws {Error} When there's a network error or API failure
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const shop = new ShopClient('https://exampleshop.com');
223
+ * const showcasedCollections = await shop.collections.showcased();
224
+ *
225
+ * console.log(`Found ${showcasedCollections.length} showcased collections`);
226
+ * showcasedCollections.forEach(collection => {
227
+ * console.log(`Featured: ${collection.title} - ${collection.productsCount} products`);
228
+ * });
229
+ * ```
230
+ */
231
+ showcased: async () => {
232
+ const storeInfo = await getStoreInfo();
233
+ const collections = await Promise.all(
234
+ storeInfo.showcase.collections.map(
235
+ (collectionHandle) => findCollection(collectionHandle)
236
+ )
237
+ );
238
+ return filter(collections, isNonNullish);
239
+ },
240
+ products: {
241
+ /**
242
+ * Fetches products from a specific collection with pagination support.
243
+ *
244
+ * @param collectionHandle - The collection handle to fetch products from
245
+ * @param options - Pagination options
246
+ * @param options.page - Page number (default: 1)
247
+ * @param options.limit - Number of products per page (default: 250, max: 250)
248
+ *
249
+ * @returns {Promise<Product[] | null>} Array of products from the collection or null if error occurs
250
+ *
251
+ * @throws {Error} When the collection handle is invalid or there's a network error
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * const shop = new ShopClient('https://example.myshopify.com');
256
+ *
257
+ * // Get first page of products from a collection
258
+ * const products = await shop.collections.products.paginated('summer-collection');
259
+ *
260
+ * // Get second page with custom limit
261
+ * const moreProducts = await shop.collections.products.paginated(
262
+ * 'summer-collection',
263
+ * { page: 2, limit: 50 }
264
+ * );
265
+ * ```
266
+ */
267
+ paginated: async (collectionHandle, options) => {
268
+ var _a, _b;
269
+ if (!collectionHandle || typeof collectionHandle !== "string") {
270
+ throw new Error("Collection handle is required and must be a string");
271
+ }
272
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
273
+ if (!sanitizedHandle) {
274
+ throw new Error("Invalid collection handle format");
275
+ }
276
+ if (sanitizedHandle.length > 255) {
277
+ throw new Error("Collection handle is too long");
278
+ }
279
+ const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
280
+ const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 250;
281
+ if (page < 1 || limit < 1 || limit > 250) {
282
+ throw new Error(
283
+ "Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
284
+ );
285
+ }
286
+ const products = await fetchPaginatedProductsFromCollection(
287
+ sanitizedHandle,
288
+ { page, limit }
289
+ );
290
+ return maybeOverrideProductsCurrency(products, options == null ? void 0 : options.currency);
291
+ },
292
+ /**
293
+ * Fetches all products from a specific collection.
294
+ *
295
+ * @param collectionHandle - The collection handle to fetch products from
296
+ *
297
+ * @returns {Promise<Product[] | null>} Array of all products from the collection or null if error occurs
298
+ *
299
+ * @throws {Error} When the collection handle is invalid or there's a network error
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * const shop = new ShopClient('https://exampleshop.com');
304
+ * const allProducts = await shop.collections.products.all('summer-collection');
305
+ *
306
+ * if (allProducts) {
307
+ * console.log(`Found ${allProducts.length} products in the collection`);
308
+ * allProducts.forEach(product => {
309
+ * console.log(`${product.title} - $${product.price}`);
310
+ * });
311
+ * }
312
+ * ```
313
+ */
314
+ all: async (collectionHandle, options) => {
315
+ if (!collectionHandle || typeof collectionHandle !== "string") {
316
+ throw new Error("Collection handle is required and must be a string");
317
+ }
318
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
319
+ if (!sanitizedHandle) {
320
+ throw new Error("Invalid collection handle format");
321
+ }
322
+ if (sanitizedHandle.length > 255) {
323
+ throw new Error("Collection handle is too long");
324
+ }
325
+ try {
326
+ const limit = 250;
327
+ const allProducts = [];
328
+ let currentPage = 1;
329
+ while (true) {
330
+ const products = await fetchPaginatedProductsFromCollection(
331
+ sanitizedHandle,
332
+ {
333
+ page: currentPage,
334
+ limit
335
+ }
336
+ );
337
+ if (!products || products.length === 0 || products.length < limit) {
338
+ if (products && products.length > 0) {
339
+ allProducts.push(...products);
340
+ }
341
+ break;
342
+ }
343
+ allProducts.push(...products);
344
+ currentPage++;
345
+ }
346
+ return maybeOverrideProductsCurrency(allProducts, options == null ? void 0 : options.currency);
347
+ } catch (error) {
348
+ console.error(
349
+ `Error fetching all products for collection ${sanitizedHandle}:`,
350
+ baseUrl,
351
+ error
352
+ );
353
+ return null;
354
+ }
355
+ },
356
+ /**
357
+ * Fetches all product slugs from a specific collection.
358
+ *
359
+ * @param collectionHandle - The collection handle to fetch product slugs from
360
+ *
361
+ * @returns {Promise<string[] | null>} Array of product slugs from the collection or null if error occurs
362
+ *
363
+ * @throws {Error} When the collection handle is invalid or there's a network error
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * const shop = new ShopClient('https://exampleshop.com');
368
+ * const productSlugs = await shop.collections.products.slugs('summer-collection');
369
+ * console.log(productSlugs);
370
+ * ```
371
+ */
372
+ slugs: async (collectionHandle) => {
373
+ if (!collectionHandle || typeof collectionHandle !== "string") {
374
+ throw new Error("Collection handle is required and must be a string");
375
+ }
376
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
377
+ if (!sanitizedHandle) {
378
+ throw new Error("Invalid collection handle format");
379
+ }
380
+ if (sanitizedHandle.length > 255) {
381
+ throw new Error("Collection handle is too long");
382
+ }
383
+ try {
384
+ const limit = 250;
385
+ const slugs = [];
386
+ let currentPage = 1;
387
+ while (true) {
388
+ const products = await fetchPaginatedProductsFromCollection(
389
+ sanitizedHandle,
390
+ {
391
+ page: currentPage,
392
+ limit
393
+ }
394
+ );
395
+ if (!products || products.length === 0 || products.length < limit) {
396
+ if (products && products.length > 0) {
397
+ slugs.push(...products.map((p) => p.slug));
398
+ }
399
+ break;
400
+ }
401
+ slugs.push(...products.map((p) => p.slug));
402
+ currentPage++;
403
+ }
404
+ return slugs;
405
+ } catch (error) {
406
+ console.error(
407
+ `Error fetching product slugs for collection ${sanitizedHandle}:`,
408
+ baseUrl,
409
+ error
410
+ );
411
+ return null;
412
+ }
413
+ }
414
+ }
415
+ };
416
+ }
417
+
418
+ export {
419
+ createCollectionOperations
420
+ };
@@ -0,0 +1,195 @@
1
+ // src/utils/detect-country.ts
2
+ var COUNTRY_CODES = {
3
+ "+1": "US",
4
+ // United States (primary) / Canada also uses +1
5
+ "+44": "GB",
6
+ // United Kingdom
7
+ "+61": "AU",
8
+ // Australia
9
+ "+65": "SG",
10
+ // Singapore
11
+ "+91": "IN",
12
+ // India
13
+ "+81": "JP",
14
+ // Japan
15
+ "+49": "DE",
16
+ // Germany
17
+ "+33": "FR",
18
+ // France
19
+ "+971": "AE",
20
+ // United Arab Emirates
21
+ "+39": "IT",
22
+ // Italy
23
+ "+34": "ES",
24
+ // Spain
25
+ "+82": "KR",
26
+ // South Korea
27
+ "+55": "BR",
28
+ // Brazil
29
+ "+62": "ID",
30
+ // Indonesia
31
+ "+92": "PK",
32
+ // Pakistan
33
+ "+7": "RU"
34
+ // Russia
35
+ };
36
+ var CURRENCY_CODE_TO_COUNTRY = {
37
+ INR: "IN",
38
+ USD: "US",
39
+ CAD: "CA",
40
+ AUD: "AU",
41
+ GBP: "GB",
42
+ EUR: "EU",
43
+ AED: "AE",
44
+ KRW: "KR",
45
+ JPY: "JP"
46
+ };
47
+ function scoreCountry(countryScores, country, weight, reason) {
48
+ if (!country) return;
49
+ if (!countryScores[country])
50
+ countryScores[country] = { score: 0, reasons: [] };
51
+ countryScores[country].score += weight;
52
+ countryScores[country].reasons.push(reason);
53
+ }
54
+ async function detectShopCountry(html) {
55
+ var _a;
56
+ const countryScores = {};
57
+ let detectedCurrencyCode;
58
+ const currencyJsonMatch = html.match(/Shopify\.currency\s*=\s*(\{[^}]*\})/);
59
+ if (currencyJsonMatch) {
60
+ try {
61
+ const raw = currencyJsonMatch[1];
62
+ const obj = JSON.parse(raw || "{}");
63
+ const activeCode = typeof (obj == null ? void 0 : obj.active) === "string" ? obj.active.toUpperCase() : void 0;
64
+ const iso = activeCode ? CURRENCY_CODE_TO_COUNTRY[activeCode] : void 0;
65
+ if (activeCode) {
66
+ detectedCurrencyCode = activeCode;
67
+ }
68
+ if (typeof iso === "string") {
69
+ scoreCountry(countryScores, iso, 0.8, "Shopify.currency.active");
70
+ }
71
+ } catch (_error) {
72
+ }
73
+ } else {
74
+ const currencyActiveAssignMatch = html.match(
75
+ /Shopify\.currency\.active\s*=\s*['"]([A-Za-z]{3})['"]/i
76
+ );
77
+ if (currencyActiveAssignMatch) {
78
+ const captured = currencyActiveAssignMatch[1];
79
+ const code = typeof captured === "string" ? captured.toUpperCase() : void 0;
80
+ const iso = code ? CURRENCY_CODE_TO_COUNTRY[code] : void 0;
81
+ if (code) {
82
+ detectedCurrencyCode = code;
83
+ }
84
+ if (typeof iso === "string") {
85
+ scoreCountry(countryScores, iso, 0.8, "Shopify.currency.active");
86
+ }
87
+ }
88
+ }
89
+ const shopifyCountryMatch = html.match(
90
+ /Shopify\.country\s*=\s*['"]([A-Za-z]{2})['"]/i
91
+ );
92
+ if (shopifyCountryMatch) {
93
+ const captured = shopifyCountryMatch[1];
94
+ const iso = typeof captured === "string" ? captured.toUpperCase() : void 0;
95
+ if (typeof iso === "string") {
96
+ scoreCountry(countryScores, iso, 1, "Shopify.country");
97
+ }
98
+ }
99
+ const phones = html.match(/\+\d{1,3}[\s\-()0-9]{5,}/g);
100
+ if (phones) {
101
+ for (const phone of phones) {
102
+ const prefix = (_a = phone.match(/^\+\d{1,3}/)) == null ? void 0 : _a[0];
103
+ if (prefix && COUNTRY_CODES[prefix])
104
+ scoreCountry(
105
+ countryScores,
106
+ COUNTRY_CODES[prefix],
107
+ 0.8,
108
+ `phone prefix ${prefix}`
109
+ );
110
+ }
111
+ }
112
+ const jsonLdRegex = /<script[^>]+application\/ld\+json[^>]*>(.*?)<\/script>/g;
113
+ let jsonLdMatch = jsonLdRegex.exec(html);
114
+ while (jsonLdMatch !== null) {
115
+ try {
116
+ const json = jsonLdMatch[1];
117
+ if (!json) {
118
+ } else {
119
+ const raw = JSON.parse(json);
120
+ const collectAddressCountries = (node, results = []) => {
121
+ if (Array.isArray(node)) {
122
+ for (const item of node) collectAddressCountries(item, results);
123
+ return results;
124
+ }
125
+ if (node && typeof node === "object") {
126
+ const obj = node;
127
+ const address = obj.address;
128
+ if (address && typeof address === "object") {
129
+ const country = address.addressCountry;
130
+ if (typeof country === "string") results.push(country);
131
+ }
132
+ const graph = obj["@graph"];
133
+ if (graph) collectAddressCountries(graph, results);
134
+ }
135
+ return results;
136
+ };
137
+ const countries = collectAddressCountries(raw);
138
+ for (const country of countries) {
139
+ scoreCountry(countryScores, country, 1, "JSON-LD addressCountry");
140
+ }
141
+ }
142
+ } catch (_error) {
143
+ }
144
+ jsonLdMatch = jsonLdRegex.exec(html);
145
+ }
146
+ const footerMatch = html.match(/<footer[^>]*>(.*?)<\/footer>/i);
147
+ if (footerMatch) {
148
+ const footerTextGroup = footerMatch[1];
149
+ const footerText = footerTextGroup ? footerTextGroup.toLowerCase() : "";
150
+ const countryNameToISO = {
151
+ india: "IN",
152
+ "united states": "US",
153
+ canada: "CA",
154
+ australia: "AU",
155
+ "united kingdom": "GB",
156
+ britain: "GB",
157
+ uk: "GB",
158
+ japan: "JP",
159
+ "south korea": "KR",
160
+ korea: "KR",
161
+ germany: "DE",
162
+ france: "FR",
163
+ italy: "IT",
164
+ spain: "ES",
165
+ brazil: "BR",
166
+ russia: "RU",
167
+ singapore: "SG",
168
+ indonesia: "ID",
169
+ pakistan: "PK"
170
+ };
171
+ for (const [countryName, isoCode] of Object.entries(countryNameToISO)) {
172
+ if (footerText.includes(countryName))
173
+ scoreCountry(countryScores, isoCode, 0.4, "footer mention");
174
+ }
175
+ }
176
+ const sorted = Object.entries(countryScores).sort(
177
+ (a, b) => b[1].score - a[1].score
178
+ );
179
+ const best = sorted[0];
180
+ return best ? {
181
+ country: best[0],
182
+ confidence: Math.min(1, best[1].score / 2),
183
+ signals: best[1].reasons,
184
+ currencyCode: detectedCurrencyCode
185
+ } : {
186
+ country: "Unknown",
187
+ confidence: 0,
188
+ signals: [],
189
+ currencyCode: detectedCurrencyCode
190
+ };
191
+ }
192
+
193
+ export {
194
+ detectShopCountry
195
+ };
@@ -0,0 +1,90 @@
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
+ };