shop-client 3.9.0 → 3.9.1

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-MOBWPEY4.js +420 -0
  9. package/dist/chunk-ROH545KI.js +274 -0
  10. package/dist/chunk-RR6YTQWP.js +90 -0
  11. package/dist/chunk-V52MFQZE.js +233 -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,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
+ };
@@ -0,0 +1,233 @@
1
+ import {
2
+ rateLimitedFetch
3
+ } from "./chunk-2MF53V33.js";
4
+ import {
5
+ detectShopCountry
6
+ } from "./chunk-ROH545KI.js";
7
+ import {
8
+ extractDomainWithoutSuffix,
9
+ generateStoreSlug,
10
+ sanitizeDomain
11
+ } from "./chunk-CN7L3BHG.js";
12
+
13
+ // src/client/get-info.ts
14
+ import { unique } from "remeda";
15
+ async function getInfoForStore(args) {
16
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
17
+ const {
18
+ baseUrl,
19
+ storeDomain,
20
+ validateProductExists,
21
+ validateCollectionExists,
22
+ validateLinksInBatches
23
+ } = args;
24
+ const response = await rateLimitedFetch(baseUrl, {
25
+ rateLimitClass: "store:info"
26
+ });
27
+ if (!response.ok) {
28
+ throw new Error(`HTTP error! status: ${response.status}`);
29
+ }
30
+ const html = await response.text();
31
+ const getMetaTag = (name2) => {
32
+ const regex = new RegExp(
33
+ `<meta[^>]*name=["']${name2}["'][^>]*content=["'](.*?)["']`
34
+ );
35
+ const match = html.match(regex);
36
+ return match ? match[1] : null;
37
+ };
38
+ const getPropertyMetaTag = (property) => {
39
+ const regex = new RegExp(
40
+ `<meta[^>]*property=["']${property}["'][^>]*content=["'](.*?)["']`
41
+ );
42
+ const match = html.match(regex);
43
+ return match ? match[1] : null;
44
+ };
45
+ const name = (_a = getMetaTag("og:site_name")) != null ? _a : extractDomainWithoutSuffix(baseUrl);
46
+ const title = (_b = getMetaTag("og:title")) != null ? _b : getMetaTag("twitter:title");
47
+ const description = getMetaTag("description") || getPropertyMetaTag("og:description");
48
+ const shopifyWalletId = (_c = getMetaTag("shopify-digital-wallet")) == null ? void 0 : _c.split("/")[1];
49
+ const myShopifySubdomainMatch = html.match(/['"](.*?\.myshopify\.com)['"]/);
50
+ const myShopifySubdomain = myShopifySubdomainMatch ? myShopifySubdomainMatch[1] : null;
51
+ let logoUrl = getPropertyMetaTag("og:image") || getPropertyMetaTag("og:image:secure_url");
52
+ if (!logoUrl) {
53
+ const logoMatch = html.match(
54
+ /<img[^>]+src=["']([^"']+\/cdn\/shop\/[^"']+)["']/
55
+ );
56
+ const matchedUrl = logoMatch == null ? void 0 : logoMatch[1];
57
+ logoUrl = matchedUrl ? matchedUrl.replace("http://", "https://") : null;
58
+ } else {
59
+ logoUrl = logoUrl.replace("http://", "https://");
60
+ }
61
+ const socialLinks = {};
62
+ const socialRegex = /<a[^>]+href=["']([^"']*(?:facebook|twitter|instagram|pinterest|youtube|linkedin|tiktok|vimeo)\.com[^"']*)["']/g;
63
+ for (const match of html.matchAll(socialRegex)) {
64
+ const str = match[1];
65
+ if (!str) continue;
66
+ let href = str;
67
+ try {
68
+ if (href.startsWith("//")) {
69
+ href = `https:${href}`;
70
+ } else if (href.startsWith("/")) {
71
+ href = new URL(href, baseUrl).toString();
72
+ }
73
+ const parsed = new URL(href);
74
+ const domain = parsed.hostname.replace("www.", "").split(".")[0];
75
+ if (domain) {
76
+ socialLinks[domain] = parsed.toString();
77
+ }
78
+ } catch {
79
+ }
80
+ }
81
+ const contactLinks = {
82
+ tel: null,
83
+ email: null,
84
+ contactPage: null
85
+ };
86
+ for (const match of html.matchAll(/href=["']tel:([^"']+)["']/g)) {
87
+ contactLinks.tel = ((_d = match == null ? void 0 : match[1]) == null ? void 0 : _d.trim()) || null;
88
+ }
89
+ for (const match of html.matchAll(/href=["']mailto:([^"']+)["']/g)) {
90
+ contactLinks.email = ((_e = match == null ? void 0 : match[1]) == null ? void 0 : _e.trim()) || null;
91
+ }
92
+ for (const match of html.matchAll(
93
+ /href=["']([^"']*(?:\/contact|\/pages\/contact)[^"']*)["']/g
94
+ )) {
95
+ contactLinks.contactPage = (match == null ? void 0 : match[1]) || null;
96
+ }
97
+ const extractedProductLinks = ((_g = (_f = html.match(/href=["']([^"']*\/products\/[^"']+)["']/g)) == null ? void 0 : _f.map(
98
+ (match) => {
99
+ var _a2, _b2;
100
+ return (_b2 = (_a2 = match == null ? void 0 : match.split("href=")[1]) == null ? void 0 : _a2.replace(/["']/g, "")) == null ? void 0 : _b2.split("/").at(-1);
101
+ }
102
+ )) == null ? void 0 : _g.filter(Boolean)) || [];
103
+ const extractedCollectionLinks = ((_i = (_h = html.match(/href=["']([^"']*\/collections\/[^"']+)["']/g)) == null ? void 0 : _h.map(
104
+ (match) => {
105
+ var _a2, _b2;
106
+ return (_b2 = (_a2 = match == null ? void 0 : match.split("href=")[1]) == null ? void 0 : _a2.replace(/["']/g, "")) == null ? void 0 : _b2.split("/").at(-1);
107
+ }
108
+ )) == null ? void 0 : _i.filter(Boolean)) || [];
109
+ const headerLinks = (_k = (_j = html.match(
110
+ /<(header|nav|div|section)\b[^>]*\b(?:id|class)=["'][^"']*(?=.*shopify-section)(?=.*\b(header|navigation|nav|menu)\b)[^"']*["'][^>]*>[\s\S]*?<\/\1>/gi
111
+ )) == null ? void 0 : _j.flatMap((header) => {
112
+ var _a2, _b2;
113
+ const links = (_a2 = header.match(/href=["']([^"']+)["']/g)) == null ? void 0 : _a2.filter(
114
+ (link) => link.includes("/products/") || link.includes("/collections/") || link.includes("/pages/")
115
+ );
116
+ return (_b2 = links == null ? void 0 : links.map((link) => {
117
+ var _a3;
118
+ const href = (_a3 = link.match(/href=["']([^"']+)["']/)) == null ? void 0 : _a3[1];
119
+ if (href && !href.startsWith("#") && !href.startsWith("javascript:")) {
120
+ try {
121
+ const url = new URL(href, storeDomain);
122
+ return url.pathname.replace(/^\/|\/$/g, "");
123
+ } catch {
124
+ return href.replace(/^\/|\/$/g, "");
125
+ }
126
+ }
127
+ return null;
128
+ }).filter((item) => Boolean(item))) != null ? _b2 : [];
129
+ })) != null ? _k : [];
130
+ const slug = generateStoreSlug(baseUrl);
131
+ const countryDetection = await detectShopCountry(html);
132
+ const [homePageProductLinks, homePageCollectionLinks] = await Promise.all([
133
+ validateLinksInBatches(
134
+ extractedProductLinks.filter(
135
+ (handle) => Boolean(handle)
136
+ ),
137
+ (handle) => validateProductExists(handle)
138
+ ),
139
+ validateLinksInBatches(
140
+ extractedCollectionLinks.filter(
141
+ (handle) => Boolean(handle)
142
+ ),
143
+ (handle) => validateCollectionExists(handle)
144
+ )
145
+ ]);
146
+ const info = {
147
+ name: name || slug,
148
+ domain: sanitizeDomain(baseUrl),
149
+ slug,
150
+ title: title || null,
151
+ description: description || null,
152
+ logoUrl,
153
+ socialLinks,
154
+ contactLinks,
155
+ headerLinks,
156
+ showcase: {
157
+ products: unique(homePageProductLinks != null ? homePageProductLinks : []),
158
+ collections: unique(homePageCollectionLinks != null ? homePageCollectionLinks : [])
159
+ },
160
+ jsonLdData: ((_m = (_l = html.match(
161
+ /<script[^>]*type="application\/ld\+json"[^>]*>([^<]+)<\/script>/g
162
+ )) == null ? void 0 : _l.map(
163
+ (match) => {
164
+ var _a2;
165
+ return ((_a2 = match == null ? void 0 : match.split(">")[1]) == null ? void 0 : _a2.replace(/<\/script/g, "")) || null;
166
+ }
167
+ )) == null ? void 0 : _m.map((json) => json ? JSON.parse(json) : null)) || [],
168
+ techProvider: {
169
+ name: "shopify",
170
+ walletId: shopifyWalletId,
171
+ subDomain: myShopifySubdomain != null ? myShopifySubdomain : null
172
+ },
173
+ country: countryDetection.country
174
+ };
175
+ const currencyCode = countryDetection == null ? void 0 : countryDetection.currencyCode;
176
+ return { info, currencyCode };
177
+ }
178
+
179
+ // src/store.ts
180
+ function createStoreOperations(context) {
181
+ return {
182
+ /**
183
+ * Fetches comprehensive store information including metadata, social links, and showcase content.
184
+ *
185
+ * @returns {Promise<StoreInfo>} Store information object containing:
186
+ * - `name` - Store name from meta tags or domain
187
+ * - `domain` - Store domain URL
188
+ * - `slug` - Generated store slug
189
+ * - `title` - Store title from meta tags
190
+ * - `description` - Store description from meta tags
191
+ * - `logoUrl` - Store logo URL from Open Graph or CDN
192
+ * - `socialLinks` - Object with social media links (facebook, twitter, instagram, etc.)
193
+ * - `contactLinks` - Object with contact information (tel, email, contactPage)
194
+ * - `headerLinks` - Array of navigation links from header
195
+ * - `showcase` - Object with featured products and collections from homepage
196
+ * - `jsonLdData` - Structured data from JSON-LD scripts
197
+ * - `techProvider` - Shopify-specific information (walletId, subDomain)
198
+ * - `country` - Country detection results with ISO 3166-1 alpha-2 codes (e.g., "US", "GB")
199
+ *
200
+ * @throws {Error} When the store URL is unreachable or returns an error
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const shop = new ShopClient('https://exampleshop.com');
205
+ * const storeInfo = await shop.getInfo();
206
+ *
207
+ * console.log(storeInfo.name); // "Example Store"
208
+ * console.log(storeInfo.socialLinks.instagram); // "https://instagram.com/example"
209
+ * console.log(storeInfo.showcase.products); // ["product-handle-1", "product-handle-2"]
210
+ * console.log(storeInfo.country); // "US"
211
+ * ```
212
+ */
213
+ info: async () => {
214
+ try {
215
+ const { info } = await getInfoForStore({
216
+ baseUrl: context.baseUrl,
217
+ storeDomain: context.storeDomain,
218
+ validateProductExists: context.validateProductExists,
219
+ validateCollectionExists: context.validateCollectionExists,
220
+ validateLinksInBatches: context.validateLinksInBatches
221
+ });
222
+ return info;
223
+ } catch (error) {
224
+ context.handleFetchError(error, "fetching store info", context.baseUrl);
225
+ }
226
+ }
227
+ };
228
+ }
229
+
230
+ export {
231
+ getInfoForStore,
232
+ createStoreOperations
233
+ };