shop-client 3.8.2 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +93 -1
- package/dist/checkout.mjs +1 -7
- package/dist/chunk-6GPWNCDO.mjs +130 -0
- package/dist/chunk-EJO5U4BT.mjs +2 -0
- package/dist/chunk-FFKWCNLU.mjs +1 -0
- package/dist/chunk-KYLPIEU3.mjs +2 -0
- package/dist/chunk-MB2INNNP.mjs +1 -0
- package/dist/chunk-MI7754VX.mjs +2 -0
- package/dist/chunk-SZQPMLZG.mjs +1 -0
- package/dist/collections.d.ts +1 -1
- package/dist/collections.mjs +1 -9
- package/dist/enrich-OZHBXKK6.mjs +1 -0
- package/dist/index.d.ts +24 -6
- package/dist/index.mjs +2 -702
- package/dist/products.d.ts +1 -1
- package/dist/products.mjs +1 -9
- package/dist/{store-CJVUz2Yb.d.mts → store-iQARl6J3.d.ts} +3 -3
- package/dist/store.d.ts +1 -1
- package/dist/store.mjs +1 -9
- package/dist/utils/rate-limit.d.ts +5 -0
- package/dist/utils/rate-limit.mjs +1 -11
- package/package.json +8 -10
- package/dist/checkout.d.mts +0 -31
- package/dist/checkout.js +0 -115
- package/dist/checkout.js.map +0 -1
- package/dist/checkout.mjs.map +0 -1
- package/dist/chunk-2KBOKOAD.mjs +0 -177
- package/dist/chunk-2KBOKOAD.mjs.map +0 -1
- package/dist/chunk-BWKBRM2Z.mjs +0 -136
- package/dist/chunk-BWKBRM2Z.mjs.map +0 -1
- package/dist/chunk-O4BPIIQ6.mjs +0 -503
- package/dist/chunk-O4BPIIQ6.mjs.map +0 -1
- package/dist/chunk-QCTICSBE.mjs +0 -398
- package/dist/chunk-QCTICSBE.mjs.map +0 -1
- package/dist/chunk-QL5OUZGP.mjs +0 -91
- package/dist/chunk-QL5OUZGP.mjs.map +0 -1
- package/dist/chunk-WTK5HUFI.mjs +0 -1287
- package/dist/chunk-WTK5HUFI.mjs.map +0 -1
- package/dist/collections.d.mts +0 -64
- package/dist/collections.js +0 -540
- package/dist/collections.js.map +0 -1
- package/dist/collections.mjs.map +0 -1
- package/dist/index.d.mts +0 -233
- package/dist/index.js +0 -3241
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/products.d.mts +0 -63
- package/dist/products.js +0 -1206
- package/dist/products.js.map +0 -1
- package/dist/products.mjs.map +0 -1
- package/dist/store-CJVUz2Yb.d.ts +0 -608
- package/dist/store.d.mts +0 -1
- package/dist/store.js +0 -698
- package/dist/store.js.map +0 -1
- package/dist/store.mjs.map +0 -1
- package/dist/utils/rate-limit.d.mts +0 -25
- package/dist/utils/rate-limit.js +0 -203
- package/dist/utils/rate-limit.js.map +0 -1
- package/dist/utils/rate-limit.mjs.map +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
`shop-client` is a powerful, type-safe TypeScript library for fetching and transforming product data from Shopify stores. Perfect for building e-commerce applications, product catalogs, price comparison tools, and automated store analysis.
|
|
8
8
|
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#-installation)
|
|
12
|
+
- [Quick Start](#-quick-start)
|
|
13
|
+
- [Store Info Caching & Concurrency](#store-info-caching--concurrency)
|
|
14
|
+
- [Browser Usage](#browser-usage)
|
|
15
|
+
- [Server/Edge Usage](#serveredge-usage)
|
|
16
|
+
- [Deep Imports & Tree-Shaking](#deep-imports--tree-shaking)
|
|
17
|
+
- [Rate Limiting](#rate-limiting)
|
|
18
|
+
- [Migration: Barrel → Subpath Imports](#migration-barrel--subpath-imports)
|
|
19
|
+
- [API Docs](#api-docs)
|
|
20
|
+
|
|
9
21
|
## 🚀 Features
|
|
10
22
|
|
|
11
23
|
- **Complete Store Data Access**: Fetch products, collections, and store information
|
|
@@ -17,6 +29,66 @@
|
|
|
17
29
|
- **Zero Dependencies**: Lightweight with minimal external dependencies
|
|
18
30
|
- **Store Type Classification**: Infers audience and verticals from showcased products (body_html-only)
|
|
19
31
|
|
|
32
|
+
## 🧠 Store Info Caching & Concurrency
|
|
33
|
+
|
|
34
|
+
`getInfo()` uses time-based caching and in-flight request deduping to avoid redundant network calls:
|
|
35
|
+
|
|
36
|
+
- Cache window: `5 minutes` (`cacheExpiry`). Fresh cached results return immediately.
|
|
37
|
+
- You can configure this TTL via the `ShopClient` constructor option `cacheTTL` (milliseconds).
|
|
38
|
+
- Cached fields: `infoCacheValue` (last `StoreInfo`) and `infoCacheTimestamp` (last fetch time).
|
|
39
|
+
- In-flight deduping: concurrent calls share a single request via an internal promise; result is cached and returned to all callers.
|
|
40
|
+
- Failure handling: the in-flight marker clears in a `finally` block so subsequent calls can retry.
|
|
41
|
+
|
|
42
|
+
Behavior:
|
|
43
|
+
- Cached and fresh → returns cached `StoreInfo`.
|
|
44
|
+
- Stale/missing and request in-flight → awaits shared promise.
|
|
45
|
+
- Stale/missing and no in-flight → performs fetch, caches, returns.
|
|
46
|
+
|
|
47
|
+
Example: configure cache TTL
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { ShopClient } from "shop-client";
|
|
51
|
+
|
|
52
|
+
// Set store info + validation cache TTL to 10 seconds
|
|
53
|
+
const shop = new ShopClient("https://exampleshop.com", { cacheTTL: 10_000 });
|
|
54
|
+
const info = await shop.getInfo();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Manual invalidation
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { ShopClient } from "shop-client";
|
|
61
|
+
|
|
62
|
+
const shop = new ShopClient("https://exampleshop.com", { cacheTTL: 60_000 });
|
|
63
|
+
|
|
64
|
+
// Fetch and cache
|
|
65
|
+
await shop.getInfo();
|
|
66
|
+
|
|
67
|
+
// Invalidate cache proactively (e.g., after a content update)
|
|
68
|
+
shop.clearInfoCache();
|
|
69
|
+
|
|
70
|
+
// Next call refetches and repopulates cache
|
|
71
|
+
await shop.getInfo();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Force refetch
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { ShopClient } from "shop-client";
|
|
78
|
+
|
|
79
|
+
const shop = new ShopClient("https://exampleshop.com", { cacheTTL: 60_000 });
|
|
80
|
+
|
|
81
|
+
// First call caches within TTL
|
|
82
|
+
await shop.getInfo();
|
|
83
|
+
|
|
84
|
+
// Force a fresh network fetch even if the cache is still fresh
|
|
85
|
+
const fresh = await shop.getInfo({ force: true });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See also:
|
|
89
|
+
- Architecture: [Caching Strategy](./ARCHITECTURE.md#caching-strategy)
|
|
90
|
+
- API Reference (LLM): [ShopClientOptions, getInfo(force), clearInfoCache](./.llm/api-reference.md#constructor)
|
|
91
|
+
|
|
20
92
|
## 📦 Installation
|
|
21
93
|
|
|
22
94
|
```bash
|
|
@@ -133,6 +205,21 @@ Notes:
|
|
|
133
205
|
|
|
134
206
|
### Migration: Barrel → Subpath Imports
|
|
135
207
|
|
|
208
|
+
#### Package Rename: `shop-search` → `shop-client` (v3.8.2)
|
|
209
|
+
- Install: `npm i shop-client` (replaces `shop-search`)
|
|
210
|
+
- Update imports to `shop-client` (API unchanged)
|
|
211
|
+
|
|
212
|
+
TypeScript:
|
|
213
|
+
```ts
|
|
214
|
+
// Before (pre-rename: shop-search)
|
|
215
|
+
import { Store } from 'shop-search';
|
|
216
|
+
const store = new Store("your-store.myshopify.com");
|
|
217
|
+
|
|
218
|
+
// After (post-rename: shop-client v3.8.2+)
|
|
219
|
+
import { ShopClient } from 'shop-client';
|
|
220
|
+
const client = new ShopClient("your-store.myshopify.com");
|
|
221
|
+
```
|
|
222
|
+
|
|
136
223
|
You can keep using the root entry (`shop-client`), but for smaller bundles switch to deep imports. The API remains the same—only the import paths change.
|
|
137
224
|
|
|
138
225
|
Examples:
|
|
@@ -165,7 +252,12 @@ Notes:
|
|
|
165
252
|
- No bundler changes required; deep imports are exposed via `exports`.
|
|
166
253
|
- The root entry continues to work; prefer deep imports for production apps.
|
|
167
254
|
|
|
168
|
-
##
|
|
255
|
+
## API Docs
|
|
256
|
+
|
|
257
|
+
- TypeDoc builds automatically on pushes to `main` and publishes to GitHub Pages.
|
|
258
|
+
- Visit: `https://peppyhop.github.io/shop-client/` (after the first successful workflow run).
|
|
259
|
+
|
|
260
|
+
## Rate Limiting
|
|
169
261
|
|
|
170
262
|
`shop-client` ships with an opt-in, global rate limiter that transparently throttles all internal HTTP requests (products, collections, store info, enrichment). This helps avoid `429 Too Many Requests` responses and keeps crawling stable.
|
|
171
263
|
|
package/dist/checkout.mjs
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {b}from'./chunk-MB2INNNP.mjs';import M from'turndown';import {gfm}from'turndown-plugin-gfm';function C(t){let e=t||process.env.OPENROUTER_API_KEY;if(!e)throw new Error("Missing OpenRouter API key. Set OPENROUTER_API_KEY or pass apiKey.");return e}function T(t){if(t.startsWith("http://")||t.startsWith("https://"))try{let e=new URL(t);return `${e.protocol}//${e.hostname}`}catch{return t}return `https://${t}`}async function x(t,e){let r=`${T(t)}/products/${e}.js`,n=await b(r,{rateLimitClass:"products:ajax"});if(!n.ok)throw new Error(`Failed to fetch AJAX product: ${r}`);return await n.json()}async function D(t,e){let r=`${T(t)}/products/${e}`,n=await b(r,{rateLimitClass:"products:html"});if(!n.ok)throw new Error(`Failed to fetch product page: ${r}`);return n.text()}function K(t){let e=t.match(/<section[^>]*id="shopify-section-template--.*?__main"[^>]*>/);if(!e)return null;let o=t.indexOf(e[0]);if(o===-1)return null;let r=t.indexOf("</section>",o);return r===-1?null:t.substring(o,r+10)}function R(t,e){if(!t)return "";let o=new M({headingStyle:"atx",codeBlockStyle:"fenced",bulletListMarker:"-",emDelimiter:"*",strongDelimiter:"**",linkStyle:"inlined"});(e?.useGfm??true)&&o.use(gfm),["script","style","nav","footer"].forEach(s=>{o.remove(i=>i.nodeName?.toLowerCase()===s);});let n=s=>o.remove(i=>(typeof i.getAttribute=="function"&&i.getAttribute("class")||"").split(/\s+/).includes(s));return ["product-form","shopify-payment-button","shopify-payment-buttons","product__actions","product__media-wrapper","loox-rating","jdgm-widget","stamped-reviews"].forEach(n),["button","input","select","label"].forEach(s=>{o.remove(i=>i.nodeName?.toLowerCase()===s);}),["quantity-selector","product-atc-wrapper"].forEach(n),o.turndown(t)}async function F(t,e,o){let r=o?.inputType??"markdown",n=r==="html"?"BODY HTML":"BODY MARKDOWN",s=r==="html"?"PAGE HTML":"PAGE MARKDOWN",i=o?.outputFormat==="json"?`You are extracting structured buyer-useful information from Shopify product content.
|
|
2
|
+
|
|
3
|
+
Inputs:
|
|
4
|
+
1) ${n}: ${r==="html"?"Raw Shopify product body_html":"Cleaned version of Shopify product body_html"}
|
|
5
|
+
2) ${s}: ${r==="html"?"Raw product page HTML (main section)":"Extracted product page HTML converted to markdown"}
|
|
6
|
+
|
|
7
|
+
Return ONLY valid JSON (no markdown, no code fences) with this shape:
|
|
8
|
+
{
|
|
9
|
+
"title": null | string,
|
|
10
|
+
"description": null | string,
|
|
11
|
+
"materials": string[] | [],
|
|
12
|
+
"care": string[] | [],
|
|
13
|
+
"fit": null | string,
|
|
14
|
+
"images": null | string[],
|
|
15
|
+
"returnPolicy": null | string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Rules:
|
|
19
|
+
- Do not invent facts; if a field is unavailable, use null or []
|
|
20
|
+
- Prefer concise, factual statements
|
|
21
|
+
- Do NOT include product gallery/hero images in "images"; include only documentation images like size charts or measurement guides. If none, set "images": null.
|
|
22
|
+
|
|
23
|
+
${n}:
|
|
24
|
+
${t}
|
|
25
|
+
|
|
26
|
+
${s}:
|
|
27
|
+
${e}
|
|
28
|
+
`:`
|
|
29
|
+
You are enriching a Shopify product for a modern shopping-discovery app.
|
|
30
|
+
|
|
31
|
+
Inputs:
|
|
32
|
+
1) ${n}: ${r==="html"?"Raw Shopify product body_html":"Cleaned version of Shopify product body_html"}
|
|
33
|
+
2) ${s}: ${r==="html"?"Raw product page HTML (main section)":"Extracted product page HTML converted to markdown"}
|
|
34
|
+
|
|
35
|
+
Your tasks:
|
|
36
|
+
- Merge them into a single clean markdown document
|
|
37
|
+
- Remove duplicate content
|
|
38
|
+
- Remove product images
|
|
39
|
+
- Remove UI text, buttons, menus, review widgets, theme junk
|
|
40
|
+
- Remove product options
|
|
41
|
+
- Keep only available buyer-useful info: features, materials, care, fit, size chart, return policy, size chart, care instructions
|
|
42
|
+
- Include image of size-chart if present
|
|
43
|
+
- Don't include statements like information not available.
|
|
44
|
+
- Maintain structured headings (## Description, ## Materials, etc.)
|
|
45
|
+
- Output ONLY markdown (no commentary)
|
|
46
|
+
|
|
47
|
+
${n}:
|
|
48
|
+
${t}
|
|
49
|
+
|
|
50
|
+
${s}:
|
|
51
|
+
${e}
|
|
52
|
+
`,p=C(o?.apiKey),l=process.env.OPENROUTER_MODEL||"openai/gpt-4o-mini",u=o?.model??l,y=await $(u,i,p);if(o?.outputFormat==="json"){let f=y.replace(/```json|```/g,"").trim(),d=S(f);if(!d.ok)throw new Error(`LLM returned invalid JSON: ${d.error}`);let g=J(d.value);if(!g.ok)throw new Error(`LLM JSON schema invalid: ${g.error}`);let a=d.value;if(Array.isArray(a.images)){let b=a.images.filter(c=>{if(typeof c!="string")return false;let m=c.toLowerCase();return !["cdn.shopify.com","/products/","%2Fproducts%2F","_large","_grande","_1024x1024","_2048x"].some(h=>m.includes(h))});a.images=b.length>0?b:null;}return JSON.stringify(a)}return y}function S(t){try{return {ok:!0,value:JSON.parse(t)}}catch(e){return {ok:false,error:e?.message||"Failed to parse JSON"}}}function J(t){if(!t||typeof t!="object"||Array.isArray(t))return {ok:false,error:"Top-level must be a JSON object"};let e=t;if("title"in e&&!(e.title===null||typeof e.title=="string"))return {ok:false,error:"title must be null or string"};if("description"in e&&!(e.description===null||typeof e.description=="string"))return {ok:false,error:"description must be null or string"};if("fit"in e&&!(e.fit===null||typeof e.fit=="string"))return {ok:false,error:"fit must be null or string"};if("returnPolicy"in e&&!(e.returnPolicy===null||typeof e.returnPolicy=="string"))return {ok:false,error:"returnPolicy must be null or string"};let o=(r,n)=>{if(!Array.isArray(r))return {ok:false,error:`${n} must be an array`};for(let s of r)if(typeof s!="string")return {ok:false,error:`${n} items must be strings`};return {ok:true}};if("materials"in e){let r=o(e.materials,"materials");if(!r.ok)return r}if("care"in e){let r=o(e.care,"care");if(!r.ok)return r}if("images"in e){if(!(e.images===null||Array.isArray(e.images)))return {ok:false,error:"images must be null or an array"};if(Array.isArray(e.images)){let r=o(e.images,"images");if(!r.ok)return r}}return {ok:true}}async function $(t,e,o){if(process.env.OPENROUTER_OFFLINE==="1")return U(e);let r={"Content-Type":"application/json",Authorization:`Bearer ${o}`},n=process.env.OPENROUTER_SITE_URL||process.env.SITE_URL,s=process.env.OPENROUTER_APP_TITLE||"Shop Client";n&&(r["HTTP-Referer"]=n),(r["X-Title"]=s);let i=g=>({model:g,messages:[{role:"user",content:e}],temperature:.2}),l=[`${(process.env.OPENROUTER_BASE_URL||"https://openrouter.ai/api/v1").replace(/\/$/,"")}/chat/completions`],u=(process.env.OPENROUTER_FALLBACK_MODELS||"").split(",").map(g=>g.trim()).filter(Boolean),y=process.env.OPENROUTER_MODEL||"openai/gpt-4o-mini",f=Array.from(new Set([t,...u,y])).filter(Boolean),d="";for(let g of f)for(let a of l)try{let b$1=new AbortController,c=setTimeout(()=>b$1.abort(),15e3),m=await b(a,{method:"POST",headers:r,body:JSON.stringify(i(g)),signal:b$1.signal,rateLimitClass:"ai:openrouter"});if(clearTimeout(c),!m.ok){d=await m.text()||`${a}: HTTP ${m.status}`,await new Promise(P=>setTimeout(P,300));continue}let v=await m.json(),w=v?.choices?.[0]?.message?.content;if(typeof w=="string")return w;d=JSON.stringify(v),await new Promise(h=>setTimeout(h,200));}catch(b){d=`${a}: ${b?.message||String(b)}`,await new Promise(c=>setTimeout(c,200));}throw new Error(`OpenRouter request failed: ${d}`)}function U(t){let e=t.toLowerCase();return e.includes("return only valid json")&&e.includes('"audience":')?JSON.stringify({audience:"generic",vertical:"clothing",category:null,subCategory:null}):e.includes("return only valid json")&&e.includes('"materials":')?JSON.stringify({title:null,description:null,materials:[],care:[],fit:null,images:null,returnPolicy:null}):["## Description","Offline merge of product body and page.","","## Materials","- Not available"].join(`
|
|
53
|
+
`)}async function Y(t,e,o){let r=await x(t,e),n=r.description||"",s=await D(t,e),i=K(s),p=o?.inputType??"markdown",l=p==="html"?n:R(n,{useGfm:o?.useGfm}),u=p==="html"?i||s:R(i,{useGfm:o?.useGfm}),y=await F(l,u,{apiKey:o?.apiKey,inputType:p,model:o?.model,outputFormat:o?.outputFormat});if(o?.outputFormat==="json")try{let f=JSON.parse(y);if(f&&Array.isArray(f.images)){let d=[];if(r.featured_image&&d.push(String(r.featured_image)),Array.isArray(r.images))for(let c of r.images)typeof c=="string"&&c.length>0&&d.push(c);if(Array.isArray(r.media))for(let c of r.media)c?.src&&d.push(String(c.src));if(Array.isArray(r.variants))for(let c of r.variants){let m=c?.featured_image;m?.src&&d.push(String(m.src));}let g=new Set(d.map(c=>String(c).toLowerCase())),a=f.images.filter(c=>{if(typeof c!="string")return !1;let m=c.toLowerCase();return g.has(m)?!1:!["cdn.shopify.com","/products/","%2Fproducts%2F","_large","_grande","_1024x1024","_2048x"].some(h=>m.includes(h))});f.images=a.length>0?a:null;let b=JSON.stringify(f);return {bodyHtml:n,pageHtml:s,extractedMainHtml:i||"",mergedMarkdown:b}}}catch{}return {bodyHtml:n,pageHtml:s,extractedMainHtml:i||"",mergedMarkdown:y}}async function V(t,e){let o=C(e?.apiKey),r=process.env.OPENROUTER_MODEL||"openai/gpt-4o-mini",n=e?.model??r,s=`Classify the following product using a three-tiered hierarchy:
|
|
54
|
+
|
|
55
|
+
Product Content:
|
|
56
|
+
${t}
|
|
57
|
+
|
|
58
|
+
Classification Rules:
|
|
59
|
+
1. First determine the vertical (main product category)
|
|
60
|
+
2. Then determine the category (specific type within that vertical)
|
|
61
|
+
3. Finally determine the subCategory (sub-type within that category)
|
|
62
|
+
|
|
63
|
+
Vertical must be one of: clothing, beauty, accessories, home-decor, food-and-beverages
|
|
64
|
+
Audience must be one of: adult_male, adult_female, kid_male, kid_female, generic
|
|
65
|
+
|
|
66
|
+
Hierarchy Examples:
|
|
67
|
+
- Clothing \u2192 tops \u2192 t-shirts
|
|
68
|
+
- Clothing \u2192 footwear \u2192 sneakers
|
|
69
|
+
- Beauty \u2192 skincare \u2192 moisturizers
|
|
70
|
+
- Accessories \u2192 bags \u2192 backpacks
|
|
71
|
+
- Home-decor \u2192 furniture \u2192 chairs
|
|
72
|
+
- Food-and-beverages \u2192 snacks \u2192 chips
|
|
73
|
+
|
|
74
|
+
IMPORTANT CONSTRAINTS:
|
|
75
|
+
- Category must be relevant to the chosen vertical
|
|
76
|
+
- subCategory must be relevant to both vertical and category
|
|
77
|
+
- subCategory must be a single word or hyphenated words (no spaces)
|
|
78
|
+
- subCategory should NOT be material (e.g., "cotton", "leather") or color (e.g., "red", "blue")
|
|
79
|
+
- Focus on product type/function, not attributes
|
|
80
|
+
|
|
81
|
+
If you're not confident about category or sub-category, you can leave them optional.
|
|
82
|
+
|
|
83
|
+
Return ONLY valid JSON (no markdown, no code fences) with keys:
|
|
84
|
+
{
|
|
85
|
+
"audience": "adult_male" | "adult_female" | "kid_male" | "kid_female" | "generic",
|
|
86
|
+
"vertical": "clothing" | "beauty" | "accessories" | "home-decor" | "food-and-beverages",
|
|
87
|
+
"category": null | string,
|
|
88
|
+
"subCategory": null | string
|
|
89
|
+
}`,p=(await $(n,s,o)).replace(/```json|```/g,"").trim(),l=S(p);if(!l.ok)throw new Error(`LLM returned invalid JSON: ${l.error}`);let u=H(l.value);if(!u.ok)throw new Error(`LLM JSON schema invalid: ${u.error}`);return u.value}function H(t){if(!t||typeof t!="object"||Array.isArray(t))return {ok:false,error:"Top-level must be a JSON object"};let e=t,o=["adult_male","adult_female","kid_male","kid_female","generic"];if(typeof e.audience!="string"||!o.includes(e.audience))return {ok:false,error:"audience must be one of: adult_male, adult_female, kid_male, kid_female, generic"};let r=["clothing","beauty","accessories","home-decor","food-and-beverages"];if(typeof e.vertical!="string"||!r.includes(e.vertical))return {ok:false,error:"vertical must be one of: clothing, beauty, accessories, home-decor, food-and-beverages"};if("category"in e&&!(e.category===null||typeof e.category=="string"))return {ok:false,error:"category must be null or string"};if("subCategory"in e&&!(e.subCategory===null||typeof e.subCategory=="string"))return {ok:false,error:"subCategory must be null or string"};if(typeof e.subCategory=="string"){let n=e.subCategory.trim();if(!/^[A-Za-z0-9-]+$/.test(n))return {ok:false,error:"subCategory must be single word or hyphenated, no spaces"}}return {ok:true,value:{audience:e.audience,vertical:e.vertical,category:typeof e.category=="string"?e.category:e.category??null,subCategory:typeof e.subCategory=="string"?e.subCategory:e.subCategory??null}}}async function q(t,e){let o=C(e?.apiKey),r=process.env.OPENROUTER_MODEL||"openai/gpt-4o-mini",n=e?.model??r;if(process.env.OPENROUTER_OFFLINE==="1"){let y=Array.isArray(t.tags)?t.tags.slice(0,6):[],f=t.title.trim().slice(0,50),d=(t.vendor||"").trim(),g=typeof t.price=="number"?`$${t.price}`:"",a=d?`${f} | ${d}`:f,b=`Discover ${t.title}. ${g?`Priced at ${g}. `:""}Crafted to delight customers with quality and style.`.slice(0,160),c=`${t.title} \u2014 ${d||"Premium"} quality, designed to impress.`,m=t.description||`Introducing ${t.title}, combining performance and style for everyday use.`,v=`Get ${t.title} today${g?` for ${g}`:""}. Limited availability \u2014 don\u2019t miss out!`,w={metaTitle:a,metaDescription:b,shortDescription:c,longDescription:m,tags:y.length?y:["new","featured","popular"],marketingCopy:v},h=E(w);if(!h.ok)throw new Error(`Offline SEO content invalid: ${h.error}`);return h.value}let s=`Generate SEO-optimized content for this product:
|
|
90
|
+
|
|
91
|
+
Title: ${t.title}
|
|
92
|
+
Description: ${t.description||"N/A"}
|
|
93
|
+
Vendor: ${t.vendor||"N/A"}
|
|
94
|
+
Price: ${typeof t.price=="number"?`$${t.price}`:"N/A"}
|
|
95
|
+
Tags: ${Array.isArray(t.tags)&&t.tags.length?t.tags.join(", "):"N/A"}
|
|
96
|
+
|
|
97
|
+
Create compelling, SEO-friendly content that will help this product rank well and convert customers.
|
|
98
|
+
|
|
99
|
+
Return ONLY valid JSON (no markdown, no code fences) with keys: {
|
|
100
|
+
"metaTitle": string,
|
|
101
|
+
"metaDescription": string,
|
|
102
|
+
"shortDescription": string,
|
|
103
|
+
"longDescription": string,
|
|
104
|
+
"tags": string[],
|
|
105
|
+
"marketingCopy": string
|
|
106
|
+
}`,p=(await $(n,s,o)).replace(/```json|```/g,"").trim(),l=S(p);if(!l.ok)throw new Error(`LLM returned invalid JSON: ${l.error}`);let u=E(l.value);if(!u.ok)throw new Error(`LLM JSON schema invalid: ${u.error}`);return u.value}function E(t){if(!t||typeof t!="object"||Array.isArray(t))return {ok:false,error:"Top-level must be a JSON object"};let e=t,o=["metaTitle","metaDescription","shortDescription","longDescription","marketingCopy"];for(let r of o)if(typeof e[r]!="string"||!e[r].trim())return {ok:false,error:`${r} must be a non-empty string`};if(!Array.isArray(e.tags))return {ok:false,error:"tags must be an array"};for(let r of e.tags)if(typeof r!="string")return {ok:false,error:"tags items must be strings"};return {ok:true,value:{metaTitle:String(e.metaTitle),metaDescription:String(e.metaDescription),shortDescription:String(e.shortDescription),longDescription:String(e.longDescription),tags:e.tags,marketingCopy:String(e.marketingCopy)}}}async function W(t,e){let o=C(e?.apiKey),r=process.env.OPENROUTER_MODEL||"openai/gpt-4o-mini",n=e?.model??r,s=Array.isArray(t.showcase.products)?t.showcase.products.slice(0,10).map(a=>{if(typeof a=="string")return `- ${a}`;let b=typeof a?.productType=="string"&&a.productType.trim()?a.productType:"N/A";return `- ${String(a?.title||"N/A")}: ${b}`}):[],i=Array.isArray(t.showcase.collections)?t.showcase.collections.slice(0,5).map(a=>typeof a=="string"?`- ${a}`:`- ${String(a?.title||"N/A")}`):[],p=`Store Title: ${t.title}
|
|
107
|
+
Store Description: ${t.description??"N/A"}
|
|
108
|
+
|
|
109
|
+
Sample Products:
|
|
110
|
+
${s.join(`
|
|
111
|
+
`)||"- N/A"}
|
|
112
|
+
|
|
113
|
+
Sample Collections:
|
|
114
|
+
${i.join(`
|
|
115
|
+
`)||"- N/A"}`,l=`${t.title} ${t.description??""} ${s.join(" ")} ${i.join(" ")}`.toLowerCase();if(process.env.OPENROUTER_OFFLINE==="1"){let a=`${t.title} ${t.description??""} ${s.join(" ")} ${i.join(" ")}`.toLowerCase(),b={clothing:/(dress|shirt|pant|jean|hoodie|tee|t[- ]?shirt|sneaker|apparel|clothing)/,beauty:/(skincare|moisturizer|serum|beauty|cosmetic|makeup)/,accessories:/(bag|belt|watch|wallet|accessor(y|ies)|sunglasses|jewell?ery)/,"home-decor":/(sofa|chair|table|decor|home|candle|lamp|rug)/,"food-and-beverages":/(snack|food|beverage|coffee|tea|chocolate|gourmet)/},c={kid:/(\bkid\b|\bchild\b|\bchildren\b|\btoddler\b|\bboy\b|\bgirl\b)/,kid_male:/\bboys\b|\bboy\b/,kid_female:/\bgirls\b|\bgirl\b/,adult_male:/\bmen\b|\bmale\b|\bman\b|\bmens\b/,adult_female:/\bwomen\b|\bfemale\b|\bwoman\b|\bwomens\b/},m=[];c.kid?.test(a)?(c.kid_male?.test(a)&&m.push("kid_male"),c.kid_female?.test(a)&&m.push("kid_female"),!c.kid_male?.test(a)&&!c.kid_female?.test(a)&&m.push("generic")):(c.adult_male?.test(a)&&m.push("adult_male"),c.adult_female?.test(a)&&m.push("adult_female"),m.length===0&&m.push("generic"));let v=Object.entries(b).filter(([,k])=>k.test(a)).map(([k])=>k);v.length===0&&v.push("accessories");let w=s.join(" ").toLowerCase(),P=Object.entries({shirts:/(shirt|t[- ]?shirt|tee)/,pants:/(pant|trouser|chino)/,shorts:/shorts?/,jeans:/jeans?/,dresses:/dress/,skincare:/(serum|moisturizer|skincare|cream)/,accessories:/(belt|watch|wallet|bag)/,footwear:/(sneaker|shoe|boot)/,decor:/(candle|lamp|rug|sofa|chair|table)/,beverages:/(coffee|tea|chocolate)/}).filter(([,k])=>k.test(w)).map(([k])=>k),L=P.length?P:["general"],O={};for(let k of m){O[k]=O[k]||{};for(let N of v)O[k][N]=Array.from(new Set(L));}return A(O,l)}let u=`Analyze this store and build a multi-audience breakdown of verticals and categories.
|
|
116
|
+
Store Information:
|
|
117
|
+
${p}
|
|
118
|
+
|
|
119
|
+
Return ONLY valid JSON (no markdown, no code fences) using this shape:
|
|
120
|
+
{
|
|
121
|
+
"adult_male": { "clothing": ["shirts", "pants"], "accessories": ["belts"] },
|
|
122
|
+
"adult_female": { "beauty": ["skincare"], "clothing": ["dresses"] },
|
|
123
|
+
"generic": { "clothing": ["t-shirts"] }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Rules:
|
|
127
|
+
- Keys MUST be audience: "adult_male" | "adult_female" | "kid_male" | "kid_female" | "generic".
|
|
128
|
+
- Nested keys MUST be vertical: "clothing" | "beauty" | "accessories" | "home-decor" | "food-and-beverages".
|
|
129
|
+
- Values MUST be non-empty arrays of category strings.
|
|
130
|
+
`,f=(await $(n,u,o)).replace(/```json|```/g,"").trim(),d=S(f);if(!d.ok)throw new Error(`LLM returned invalid JSON: ${d.error}`);let g=I(d.value);if(!g.ok)throw new Error(`LLM JSON schema invalid: ${g.error}`);return A(g.value,l)}function I(t){if(!t||typeof t!="object"||Array.isArray(t))return {ok:false,error:"Top-level must be an object keyed by audience"};let e=["adult_male","adult_female","kid_male","kid_female","generic"],o=["clothing","beauty","accessories","home-decor","food-and-beverages"],r=t,n={},s=Object.keys(r);if(s.length===0)return {ok:false,error:"At least one audience key is required"};for(let i of s){if(!e.includes(i))return {ok:false,error:`Invalid audience key: ${i}`};let p=r[i];if(!p||typeof p!="object"||Array.isArray(p))return {ok:false,error:`Audience ${i} must map to an object of verticals`};let l={};for(let u of Object.keys(p)){if(!o.includes(u))return {ok:false,error:`Invalid vertical key ${u} for audience ${i}`};let y=p[u];if(!Array.isArray(y)||y.length===0||!y.every(f=>typeof f=="string"&&f.trim()))return {ok:false,error:`Vertical ${u} for audience ${i} must be a non-empty array of strings`};l[u]=y.map(f=>f.trim());}n[i]=l;}return {ok:true,value:n}}function A(t,e){let o={kid:/(\bkid\b|\bchild\b|\bchildren\b|\btoddler\b|\bboy\b|\bgirl\b)/,kid_male:/\bboys\b|\bboy\b/,kid_female:/\bgirls\b|\bgirl\b/,adult_male:/\bmen\b|\bmale\b|\bman\b|\bmens\b/,adult_female:/\bwomen\b|\bfemale\b|\bwoman\b|\bwomens\b/},r={clothing:/(dress|shirt|pant|jean|hoodie|tee|t[- ]?shirt|sneaker|apparel|clothing)/,beauty:/(skincare|moisturizer|serum|beauty|cosmetic|makeup)/,accessories:/(bag|belt|watch|wallet|accessor(y|ies)|sunglasses|jewell?ery)/,"home-decor":/(sofa|chair|table|candle|lamp|rug|furniture|home[- ]?decor|homeware|housewares|living\s?room|dining\s?table|bed(?:room)?|wall\s?(art|mirror|clock))/,"food-and-beverages":/(snack|food|beverage|coffee|tea|chocolate|gourmet)/},n=new Set;o.kid?.test(e)?(o.kid_male?.test(e)&&n.add("kid_male"),o.kid_female?.test(e)&&n.add("kid_female"),!o.kid_male?.test(e)&&!o.kid_female?.test(e)&&n.add("generic")):(o.adult_male?.test(e)&&n.add("adult_male"),o.adult_female?.test(e)&&n.add("adult_female"),n.size===0&&n.add("generic"));let s=new Set(Object.entries(r).filter(([,l])=>l.test(e)).map(([l])=>l)||[]);s.size===0&&s.add("accessories");let i={};for(let[l,u]of Object.entries(t)){let y=l;if(!n.has(y))continue;let f={};for(let[d,g]of Object.entries(u||{})){let a=d;s.has(a)&&(f[a]=g);}Object.keys(f).length>0&&(i[y]=f);}if(Object.keys(i).length===0){let l={};for(let u of Array.from(s))l[u]=["general"];i.generic=l;}return (i.adult_male&&Object.keys(i.adult_male).length>0||i.adult_female&&Object.keys(i.adult_female).length>0)&&delete i.generic,i}export{x as a,D as b,K as c,R as d,F as e,Y as f,V as g,q as h,W as i,A as j};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {i}from'./chunk-KYLPIEU3.mjs';import {b}from'./chunk-MB2INNNP.mjs';import {filter,isNonNullish}from'remeda';function k(m,l,R,K,A,M,b$1){let h=new Map,T=r=>{let t=h.get(r);if(t){if(Date.now()-t.ts<3e5)return t.value;h.delete(r);}},C=(r,t)=>{h.set(r,{ts:Date.now(),value:t});};function y(r,t){let i$1=r.priceMin??r.price??0,e=r.priceMax??r.price??0,n=r.compareAtPriceMin??r.compareAtPrice??0;return {...r,currency:t,localizedPricing:{currency:t,priceFormatted:i(i$1,t),priceMinFormatted:i(i$1,t),priceMaxFormatted:i(e,t),compareAtPriceFormatted:i(n,t)}}}function E(r,t){return !r||!t?r:r.map(i=>y(i,t))}let u={all:async r=>{let i=[];async function e(){let n=1;for(;;){let o=await R(n,250);if(!o||o.length===0||o.length<250){o&&o.length>0&&i.push(...o);break}i.push(...o),n++;}return i}try{let n=await e();return E(n,r?.currency)}catch(n){throw console.error("Failed to fetch all products:",l,n),n}},paginated:async r=>{let t=r?.page??1,i=Math.min(r?.limit??250,250),e=`${m}products.json?limit=${i}&page=${t}`;try{let n=await b(e,{rateLimitClass:"products:paginated"});if(!n.ok)throw console.error(`HTTP error! status: ${n.status} for ${l} page ${t}`),new Error(`HTTP error! status: ${n.status}`);let o=await n.json();if(o.products.length===0)return [];let a=K(o.products);return E(a,r?.currency)}catch(n){return console.error(`Error fetching products for ${l} page ${t} with limit ${i}:`,n),null}},find:async(r,t)=>{if(!r||typeof r!="string")throw new Error("Product handle is required and must be a string");try{let i=null;if(r.includes("?")){let d=r.split("?"),P=d[0]??r,O=d[1]??null;r=P,i=O;}let e=r.trim().replace(/[^a-zA-Z0-9\-_]/g,"");if(!e)throw new Error("Invalid product handle format");if(e.length>255)throw new Error("Product handle is too long");let n=T(e);if(typeof n<"u")return t?.currency?n?y(n,t.currency):null:n;let o=e;try{let d=await b(`${m}products/${encodeURIComponent(e)}`,{rateLimitClass:"products:resolve"});if(d.ok){let P=d.url;if(P){let S=new URL(P).pathname.replace(/\/$/,"").split("/").filter(Boolean),$=S.indexOf("products"),w=$>=0?S[$+1]:void 0;typeof w=="string"&&w.length&&(o=w);}}}catch{}let a=`${m}products/${encodeURIComponent(o)}.js${i?`?${i}`:""}`,s=await b(a,{rateLimitClass:"products:single"});if(!s.ok){if(s.status===404)return null;throw new Error(`HTTP error! status: ${s.status}`)}let c=await s.json(),f=A(c);return C(e,f),o!==e&&C(o,f),t?.currency?y(f,t.currency):f}catch(i){throw i instanceof Error&&console.error(`Error fetching product ${r}:`,m,i.message),i}},enriched:async(r,t)=>{if(!r||typeof r!="string")throw new Error("Product handle is required and must be a string");let i=t?.apiKey||process.env.OPENROUTER_API_KEY;if(!i)throw new Error("Missing OpenRouter API key. Pass options.apiKey or set OPENROUTER_API_KEY.");let e=await u.find(r);if(!e)return null;let n=e.handle,{enrichProduct:o}=await import('./enrich-OZHBXKK6.mjs'),a=await o(l,n,{apiKey:i,useGfm:t?.useGfm,inputType:t?.inputType,model:t?.model,outputFormat:t?.outputFormat});return {...e,enriched_content:a.mergedMarkdown}},classify:async(r,t)=>{if(!r||typeof r!="string")throw new Error("Product handle is required and must be a string");let i=t?.apiKey||process.env.OPENROUTER_API_KEY;if(!i)throw new Error("Missing OpenRouter API key. Pass options.apiKey or set OPENROUTER_API_KEY.");let e=await u.enriched(r,{apiKey:i,inputType:"html",model:t?.model,outputFormat:"json"});if(!e||!e.enriched_content)return null;let n=e.enriched_content;try{let s=JSON.parse(e.enriched_content),c=[];s.title&&typeof s.title=="string"&&c.push(`Title: ${s.title}`),s.description&&typeof s.description=="string"&&c.push(`Description: ${s.description}`),Array.isArray(s.materials)&&s.materials.length&&c.push(`Materials: ${s.materials.join(", ")}`),Array.isArray(s.care)&&s.care.length&&c.push(`Care: ${s.care.join(", ")}`),s.fit&&typeof s.fit=="string"&&c.push(`Fit: ${s.fit}`),s.returnPolicy&&typeof s.returnPolicy=="string"&&c.push(`ReturnPolicy: ${s.returnPolicy}`),n=c.join(`
|
|
2
|
+
`);}catch{}let{classifyProduct:o}=await import('./enrich-OZHBXKK6.mjs');return await o(n,{apiKey:i,model:t?.model})},generateSEOContent:async(r,t)=>{if(!r||typeof r!="string")throw new Error("Product handle is required and must be a string");let i=t?.apiKey||process.env.OPENROUTER_API_KEY;if(!i)throw new Error("Missing OpenRouter API key. Pass options.apiKey or set OPENROUTER_API_KEY.");let e=await u.find(r);if(!e)return null;let n={title:e.title,description:e.bodyHtml||void 0,vendor:e.vendor,price:e.price,tags:e.tags},{generateSEOContent:o}=await import('./enrich-OZHBXKK6.mjs');return await o(n,{apiKey:i,model:t?.model})},showcased:async()=>{let r=await M(),t=await Promise.all(r.showcase.products.map(i=>b$1(i)));return filter(t,isNonNullish)},filter:async()=>{try{let r=await u.all();if(!r||r.length===0)return {};let t={};r.forEach(e=>{e.variants&&e.variants.length>0&&(e.options&&e.options.length>0&&e.options.forEach(n=>{let o=n.name.toLowerCase();t[o]||(t[o]=new Set),n.values.forEach(a=>{let s=a?.trim();if(s){let c=t[o];c||(c=new Set,t[o]=c),c.add(s.toLowerCase());}});}),e.variants.forEach(n=>{if(n.option1){let o=(e.options?.[0]?.name||"Option 1").toLowerCase(),a=t[o];a||(a=new Set,t[o]=a),a.add(n.option1.trim().toLowerCase());}if(n.option2){let o=(e.options?.[1]?.name||"Option 2").toLowerCase(),a=t[o];a||(a=new Set,t[o]=a),a.add(n.option2.trim().toLowerCase());}if(n.option3){let o=(e.options?.[2]?.name||"Option 3").toLowerCase();t[o]||(t[o]=new Set),t[o].add(n.option3.trim().toLowerCase());}}));});let i={};return Object.entries(t).forEach(([e,n])=>{i[e]=Array.from(n).sort();}),i}catch(r){throw console.error("Failed to create product filters:",l,r),r}}};return u}export{k as a};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {i}from'./chunk-KYLPIEU3.mjs';import {b}from'./chunk-MB2INNNP.mjs';import {filter,isNonNullish}from'remeda';function $(u,p,h,y,m,P,b$1){let g=new Map,E=t=>{let e=g.get(t);if(e){if(Date.now()-e.ts<3e5)return e.value;g.delete(t);}},f=(t,e)=>{g.set(t,{ts:Date.now(),value:e});};function I(t,e){let r=t.priceMin??t.price??0,n=t.priceMax??t.price??0,o=t.compareAtPriceMin??t.compareAtPrice??0;return {...t,currency:e,localizedPricing:{currency:e,priceFormatted:i(r,e),priceMinFormatted:i(r,e),priceMaxFormatted:i(n,e),compareAtPriceFormatted:i(o,e)}}}function w(t,e){return !t||!e?t:t.map(r=>I(r,e))}return {paginated:async t=>{let e=t?.page??1,r=t?.limit??10;if(e<1||r<1||r>250)throw new Error("Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250");try{return await h(e,r)??null}catch(n){return console.error("Failed to fetch paginated collections:",p,n),null}},all:async()=>{let e=[];async function r(){let n=1;for(;;){let o=await h(n,250);if(!o||o.length===0||o.length<250){if(!o){console.warn("fetchCollections returned null, treating as empty array.");break}o&&o.length>0&&e.push(...o);break}e.push(...o),n++;}return e}try{return await r()||[]}catch(n){throw console.error("Failed to fetch all collections:",p,n),n}},find:async t=>{if(!t||typeof t!="string")throw new Error("Collection handle is required and must be a string");let e=t.trim().replace(/[^a-zA-Z0-9\-_]/g,"");if(!e)throw new Error("Invalid collection handle format");if(e.length>255)throw new Error("Collection handle is too long");let r=E(e);if(typeof r<"u")return r;try{let n=`${u}collections/${encodeURIComponent(e)}.json`,o=await b(n,{rateLimitClass:"collections:single"});if(!o.ok){if(o.status===404)return null;throw new Error(`HTTP error! status: ${o.status}`)}let i=await o.json(),l=i.collection.image;if(!l){let d=(await m(i.collection.handle,{limit:1,page:1}))?.at(0),a=d?.images?.[0];d&&a&&(l={id:a.id,src:a.src,alt:a.alt||d.title,created_at:a.createdAt||new Date().toISOString()});}let c=y([{...i.collection,image:l}])[0]||null;return f(e,c),c?.handle&&c.handle!==e&&f(c.handle,c),c}catch(n){throw n instanceof Error&&console.error(`Error fetching collection ${e}:`,u,n.message),n}},showcased:async()=>{let t=await P(),e=await Promise.all(t.showcase.collections.map(r=>b$1(r)));return filter(e,isNonNullish)},products:{paginated:async(t,e)=>{if(!t||typeof t!="string")throw new Error("Collection handle is required and must be a string");let r=t.trim().replace(/[^a-zA-Z0-9\-_]/g,"");if(!r)throw new Error("Invalid collection handle format");if(r.length>255)throw new Error("Collection handle is too long");let n=e?.page??1,o=e?.limit??250;if(n<1||o<1||o>250)throw new Error("Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250");let i=await m(r,{page:n,limit:o});return w(i,e?.currency)},all:async(t,e)=>{if(!t||typeof t!="string")throw new Error("Collection handle is required and must be a string");let r=t.trim().replace(/[^a-zA-Z0-9\-_]/g,"");if(!r)throw new Error("Invalid collection handle format");if(r.length>255)throw new Error("Collection handle is too long");try{let o=[],i=1;for(;;){let l=await m(r,{page:i,limit:250});if(!l||l.length===0||l.length<250){l&&l.length>0&&o.push(...l);break}o.push(...l),i++;}return w(o,e?.currency)}catch(n){return console.error(`Error fetching all products for collection ${r}:`,u,n),null}},slugs:async t=>{if(!t||typeof t!="string")throw new Error("Collection handle is required and must be a string");let e=t.trim().replace(/[^a-zA-Z0-9\-_]/g,"");if(!e)throw new Error("Invalid collection handle format");if(e.length>255)throw new Error("Collection handle is too long");try{let n=[],o=1;for(;;){let i=await m(e,{page:o,limit:250});if(!i||i.length===0||i.length<250){i&&i.length>0&&n.push(...i.map(l=>l.slug));break}n.push(...i.map(l=>l.slug)),o++;}return n}catch(r){return console.error(`Error fetching product slugs for collection ${e}:`,u,r),null}}}}}export{$ as a};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {parse}from'tldts';function m(i){return parse(i).domainWithoutSuffix}function l(i){let e=new URL(i);return ((parse(e.href).domainWithoutSuffix??e.hostname.split(".")[0])||"").toLowerCase().replace(/[^a-z0-9]/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}var d=({handle:i,storeDomain:e})=>{let t=l(e);return `${i}-by-${t}`},g=(i,e)=>!e||e===0?0:Math.max(0,Math.round(100-i/e*100));function w(i,e){if(typeof i!="string")throw new Error("sanitizeDomain: input must be a string");let t=i.trim();if(!t)throw new Error("sanitizeDomain: input cannot be empty");!/^[a-z]+:\/\//i.test(t)&&!t.startsWith("//")&&(t=`https://${t}`);let r=e?.stripWWW??true;try{let n;t.startsWith("//")?n=new URL(`https:${t}`):t.includes("://")?n=new URL(t):n=new URL(`https://${t}`);let o=n.hostname.toLowerCase(),s=/^www\./i.test(n.hostname);if(r&&(o=o.replace(/^www\./,"")),!o.includes("."))throw new Error("sanitizeDomain: invalid domain (missing suffix)");let u=parse(o);if(!u.publicSuffix||u.isIcann===!1)throw new Error("sanitizeDomain: invalid domain (missing suffix)");return !r&&s?`www.${u.domain||o}`:u.domain||o}catch{let n=t.toLowerCase();n=n.replace(/^[a-z]+:\/\//,""),n=n.replace(/^\/\//,""),n=n.replace(/[/:#?].*$/,"");let o=/^www\./i.test(n);if(r&&(n=n.replace(/^www\./,"")),!n.includes("."))throw new Error("sanitizeDomain: invalid domain (missing suffix)");let s=parse(n);if(!s.publicSuffix||s.isIcann===false)throw new Error("sanitizeDomain: invalid domain (missing suffix)");return !r&&o?`www.${s.domain||n}`:s.domain||n}}function h(i){if(!i||typeof i!="string")return;let e=new Date(i);return Number.isNaN(e.getTime())?void 0:e}function f(i){return i.toLowerCase().replace(/\s+/g,"_")}function x(i,e){let t=i.map(f),a={};for(let r of e){let n=[];if(t[0]&&r.option1&&n.push(`${t[0]}#${f(r.option1)}`),t[1]&&r.option2&&n.push(`${t[1]}#${f(r.option2)}`),t[2]&&r.option3&&n.push(`${t[2]}#${f(r.option3)}`),n.length>0){n.length>1&&n.sort();let o=n.join("##"),s=r.id.toString();a[o]===void 0&&(a[o]=s);}}return a}function W(i,e){try{return new Intl.NumberFormat(void 0,{style:"currency",currency:e}).format((i||0)/100)}catch{return `${(i||0)/100} ${e}`}}
|
|
2
|
+
export{m as a,l as b,d as c,g as d,w as e,h as f,f as g,x as h,W as i};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var u=class{constructor(e){this.queue=[];this.inFlight=0;this.refillTimer=null;this.options=e,this.tokens=e.maxRequestsPerInterval;}startRefill(){this.refillTimer||(this.refillTimer=setInterval(()=>{this.tokens=this.options.maxRequestsPerInterval,this.tryRun();},this.options.intervalMs),this.refillTimer&&typeof this.refillTimer.unref=="function"&&this.refillTimer.unref());}ensureRefillStarted(){this.refillTimer||this.startRefill();}configure(e){this.options={...this.options,...e},this.options.maxRequestsPerInterval=Math.max(1,this.options.maxRequestsPerInterval),this.options.intervalMs=Math.max(10,this.options.intervalMs),this.options.maxConcurrency=Math.max(1,this.options.maxConcurrency);}schedule(e){return new Promise((s,n)=>{this.ensureRefillStarted(),this.queue.push({fn:e,resolve:s,reject:n}),this.tryRun();})}tryRun(){for(;this.queue.length>0&&this.inFlight<this.options.maxConcurrency&&this.tokens>0;){let e=this.queue.shift();this.tokens-=1,this.inFlight+=1,Promise.resolve().then(e.fn).then(s=>e.resolve(s)).catch(s=>e.reject(s)).finally(()=>{this.inFlight-=1,setTimeout(()=>this.tryRun(),0);});}}},h=false,l={maxRequestsPerInterval:5,intervalMs:1e3,maxConcurrency:5},R=new u(l),m=new Map,f=new Map;function b(t){try{if(typeof t=="string")return new URL(t).host;if(t instanceof URL)return t.host;let e=t.url;if(e)return new URL(e).host}catch{}}function L(t){if(!t)return;let e=m.get(t);if(e)return e;for(let[s,n]of m.entries())if(s.startsWith("*.")&&t.endsWith(s.slice(2)))return n}function M(t){typeof t.enabled=="boolean"&&(h=t.enabled);let{perHost:e,perClass:s}=t,n={};if(typeof t.maxRequestsPerInterval=="number"&&(n.maxRequestsPerInterval=t.maxRequestsPerInterval),typeof t.intervalMs=="number"&&(n.intervalMs=t.intervalMs),typeof t.maxConcurrency=="number"&&(n.maxConcurrency=t.maxConcurrency),Object.keys(n).length&&R.configure(n),e)for(let i of Object.keys(e)){let r=e[i],a=m.get(i);a?a.configure(r):m.set(i,new u({maxRequestsPerInterval:r.maxRequestsPerInterval??l.maxRequestsPerInterval,intervalMs:r.intervalMs??l.intervalMs,maxConcurrency:r.maxConcurrency??l.maxConcurrency}));}if(s)for(let i of Object.keys(s)){let r=s[i],a=f.get(i);a?a.configure(r):f.set(i,new u({maxRequestsPerInterval:r.maxRequestsPerInterval??l.maxRequestsPerInterval,intervalMs:r.intervalMs??l.intervalMs,maxConcurrency:r.maxConcurrency??l.maxConcurrency}));}}function P(t){return new Promise(e=>setTimeout(e,t))}async function q(t,e){let s=e?.rateLimitClass,n=s?f.get(s):void 0,i=L(b(t)),r=h?n??i??R:void 0,a=Math.max(0,e?.retry?.maxRetries??2),p=Math.max(0,e?.retry?.baseDelayMs??200),v=e?.retry?.retryOnStatuses??[429,503],c=0,y=null,o=null;for(;c<=a;){try{if(r?o=await r.schedule(()=>fetch(t,e)):o=await fetch(t,e),!o||o.ok||!v.includes(o.status))return o}catch(g){y=g;}if(c+=1,c>a)break;let x=Math.floor(Math.random()*100),d=p*2**(c-1)+x;await P(d);}if(o)return o;throw y??new Error("rateLimitedFetch failed without response")}function I(){return {enabled:h,options:{...l}}}export{M as a,q as b,I as c};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {a,b as b$1,e}from'./chunk-KYLPIEU3.mjs';import {b}from'./chunk-MB2INNNP.mjs';import {unique}from'remeda';var O={"+1":"US","+44":"GB","+61":"AU","+65":"SG","+91":"IN","+81":"JP","+49":"DE","+33":"FR","+971":"AE","+39":"IT","+34":"ES","+82":"KR","+55":"BR","+62":"ID","+92":"PK","+7":"RU"},N={Rs:"IN","\u20B9":"IN",$:"US",CA$:"CA",A$:"AU","\xA3":"GB","\u20AC":"EU",AED:"AE","\u20A9":"KR","\xA5":"JP"},K={Rs:"INR","\u20B9":"INR",$:"USD",CA$:"CAD",A$:"AUD","\xA3":"GBP","\u20AC":"EUR",AED:"AED","\u20A9":"KRW","\xA5":"JPY"},T={INR:"IN",USD:"US",CAD:"CA",AUD:"AU",GBP:"GB",EUR:"EU",AED:"AE",KRW:"KR",JPY:"JP"};function d(s,o,f,y){o&&(s[o]||(s[o]={score:0,reasons:[]}),s[o].score+=f,s[o].reasons.push(y));}async function M(s){let o={},f,y=s.match(/<script[^>]+id=["']shopify-features["'][^>]*>([\s\S]*?)<\/script>/);if(y)try{let n=y[1];if(n){let e=JSON.parse(n);if(e.country&&d(o,e.country,1,"shopify-features.country"),e.locale?.includes("-")){let[,r]=e.locale.split("-");r&&d(o,r.toUpperCase(),.7,"shopify-features.locale");}if(e.moneyFormat){for(let r in N)if(e.moneyFormat.includes(r)){let l=N[r];typeof l=="string"&&d(o,l,.6,"moneyFormat symbol");let a=K[r];!f&&typeof a=="string"&&(f=a);}}}}catch{}let P=s.match(/Shopify\.currency\s*=\s*(\{[^}]*\})/);if(P)try{let n=P[1],e=JSON.parse(n||"{}"),r=typeof e?.active=="string"?e.active.toUpperCase():void 0,l=r?T[r]:void 0;r&&(f=r),typeof l=="string"&&d(o,l,.8,"Shopify.currency.active");}catch{}else {let n=s.match(/Shopify\.currency\.active\s*=\s*['"]([A-Za-z]{3})['"]/i);if(n){let e=n[1],r=typeof e=="string"?e.toUpperCase():void 0,l=r?T[r]:void 0;r&&(f=r),typeof l=="string"&&d(o,l,.8,"Shopify.currency.active");}}let U=s.match(/Shopify\.country\s*=\s*['"]([A-Za-z]{2})['"]/i);if(U){let n=U[1],e=typeof n=="string"?n.toUpperCase():void 0;typeof e=="string"&&d(o,e,1,"Shopify.country");}let C=s.match(/\+\d{1,3}[\s\-()0-9]{5,}/g);if(C)for(let n of C){let e=n.match(/^\+\d{1,3}/)?.[0];e&&O[e]&&d(o,O[e],.8,`phone prefix ${e}`);}let c=/<script[^>]+application\/ld\+json[^>]*>(.*?)<\/script>/g,p=c.exec(s);for(;p!==null;){try{let n=p[1];if(n){let e=JSON.parse(n),r=(a,g=[])=>{if(Array.isArray(a)){for(let E of a)r(E,g);return g}if(a&&typeof a=="object"){let E=a,m=E.address;if(m&&typeof m=="object"){let D=m.addressCountry;typeof D=="string"&&g.push(D);}let b=E["@graph"];b&&r(b,g);}return g},l=r(e);for(let a of l)d(o,a,1,"JSON-LD addressCountry");}}catch{}p=c.exec(s);}let S=s.match(/<footer[^>]*>(.*?)<\/footer>/i);if(S){let n=S[1],e=n?n.toLowerCase():"",r={india:"IN","united states":"US",canada:"CA",australia:"AU","united kingdom":"GB",britain:"GB",uk:"GB",japan:"JP","south korea":"KR",korea:"KR",germany:"DE",france:"FR",italy:"IT",spain:"ES",brazil:"BR",russia:"RU",singapore:"SG",indonesia:"ID",pakistan:"PK"};for(let[l,a]of Object.entries(r))e.includes(l)&&d(o,a,.4,"footer mention");}let R=Object.entries(o).sort((n,e)=>e[1].score-n[1].score)[0];return R?{country:R[0],confidence:Math.min(1,R[1].score/2),signals:R[1].reasons,currencyCode:f}:{country:"Unknown",confidence:0,signals:[],currencyCode:f}}async function j(s){let{baseUrl:o,storeDomain:f,validateProductExists:y,validateCollectionExists:P,validateLinksInBatches:U}=s,C=await b(o,{rateLimitClass:"store:info"});if(!C.ok)throw new Error(`HTTP error! status: ${C.status}`);let c=await C.text(),p=t=>{let u=new RegExp(`<meta[^>]*name=["']${t}["'][^>]*content=["'](.*?)["']`),i=c.match(u);return i?i[1]:null},S=t=>{let u=new RegExp(`<meta[^>]*property=["']${t}["'][^>]*content=["'](.*?)["']`),i=c.match(u);return i?i[1]:null},v=p("og:site_name")??a(o),R=p("og:title")??p("twitter:title"),n=p("description")||S("og:description"),e$1=p("shopify-digital-wallet")?.split("/")[1],r=c.match(/['"](.*?\.myshopify\.com)['"]/),l=r?r[1]:null,a$1=S("og:image")||S("og:image:secure_url");if(a$1)a$1=a$1.replace("http://","https://");else {let u=c.match(/<img[^>]+src=["']([^"']+\/cdn\/shop\/[^"']+)["']/)?.[1];a$1=u?u.replace("http://","https://"):null;}let g={},E=/<a[^>]+href=["']([^"']*(?:facebook|twitter|instagram|pinterest|youtube|linkedin|tiktok|vimeo)\.com[^"']*)["']/g;for(let t of c.matchAll(E)){let u=t[1];if(!u)continue;let i=u;try{i.startsWith("//")?i=`https:${i}`:i.startsWith("/")&&(i=new URL(i,o).toString());let h=new URL(i),A=h.hostname.replace("www.","").split(".")[0];A&&(g[A]=h.toString());}catch{}}let m={tel:null,email:null,contactPage:null};for(let t of c.matchAll(/href=["']tel:([^"']+)["']/g))m.tel=t?.[1]?.trim()||null;for(let t of c.matchAll(/href=["']mailto:([^"']+)["']/g))m.email=t?.[1]?.trim()||null;for(let t of c.matchAll(/href=["']([^"']*(?:\/contact|\/pages\/contact)[^"']*)["']/g))m.contactPage=t?.[1]||null;let b$2=c.match(/href=["']([^"']*\/products\/[^"']+)["']/g)?.map(t=>t?.split("href=")[1]?.replace(/["']/g,"")?.split("/").at(-1))?.filter(Boolean)||[],D=c.match(/href=["']([^"']*\/collections\/[^"']+)["']/g)?.map(t=>t?.split("href=")[1]?.replace(/["']/g,"")?.split("/").at(-1))?.filter(Boolean)||[],J=c.match(/<(header|nav|div|section)\b[^>]*\b(?:id|class)=["'][^"']*(?=.*shopify-section)(?=.*\b(header|navigation|nav|menu)\b)[^"']*["'][^>]*>[\s\S]*?<\/\1>/gi)?.flatMap(t=>t.match(/href=["']([^"']+)["']/g)?.filter(i=>i.includes("/products/")||i.includes("/collections/")||i.includes("/pages/"))?.map(i=>{let h=i.match(/href=["']([^"']+)["']/)?.[1];if(h&&!h.startsWith("#")&&!h.startsWith("javascript:"))try{return new URL(h,f).pathname.replace(/^\/|\/$/g,"")}catch{return h.replace(/^\/|\/$/g,"")}return null}).filter(i=>!!i)??[])??[],k=b$1(o),w=await M(c),[F,Y]=await Promise.all([U(b$2.filter(t=>!!t),t=>y(t)),U(D.filter(t=>!!t),t=>P(t))]),$={name:v||k,domain:e(o),slug:k,title:R||null,description:n||null,logoUrl:a$1,socialLinks:g,contactLinks:m,headerLinks:J,showcase:{products:unique(F??[]),collections:unique(Y??[])},jsonLdData:c.match(/<script[^>]*type="application\/ld\+json"[^>]*>([^<]+)<\/script>/g)?.map(t=>t?.split(">")[1]?.replace(/<\/script/g,"")||null)?.map(t=>t?JSON.parse(t):null)||[],techProvider:{name:"shopify",walletId:e$1,subDomain:l??null},country:w.country},G=w?.currencyCode;return {info:$,currencyCode:G}}function X(s){return {info:async()=>{try{let{info:o}=await j({baseUrl:s.baseUrl,storeDomain:s.storeDomain,validateProductExists:s.validateProductExists,validateCollectionExists:s.validateCollectionExists,validateLinksInBatches:s.validateLinksInBatches});return o}catch(o){s.handleFetchError(o,"fetching store info",s.baseUrl);}}}}
|
|
2
|
+
export{M as a,j as b,X as c};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function p(s){return {createUrl:({email:n,items:i,address:t})=>{if(!n||!n.includes("@"))throw new Error("Invalid email address");if(!i||i.length===0)throw new Error("Items array cannot be empty");for(let r of i){if(!r.productVariantId||!r.quantity)throw new Error("Each item must have productVariantId and quantity");let e=Number.parseInt(r.quantity,10);if(Number.isNaN(e)||e<=0)throw new Error("Quantity must be a positive number")}let a=["firstName","lastName","address1","city","zip","country"];for(let r of a)if(!t[r])throw new Error(`Address field '${r}' is required`);let o=i.map(r=>`${encodeURIComponent(r.productVariantId)}:${encodeURIComponent(r.quantity)}`).join(","),c=new URLSearchParams({"checkout[email]":n,"checkout[shipping_address][first_name]":t.firstName,"checkout[shipping_address][last_name]":t.lastName,"checkout[shipping_address][address1]":t.address1,"checkout[shipping_address][city]":t.city,"checkout[shipping_address][zip]":t.zip,"checkout[shipping_address][country]":t.country,"checkout[shipping_address][province]":t.province,"checkout[shipping_address][phone]":t.phone});return `${s}cart/${o}?${c.toString()}`}}}export{p as a};
|
package/dist/collections.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as Collection, y as CurrencyCode, c as Product, e as ShopifyCollection, g as StoreInfo } from './store-
|
|
1
|
+
import { f as Collection, y as CurrencyCode, c as Product, e as ShopifyCollection, g as StoreInfo } from './store-iQARl6J3.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Interface for collection operations
|
package/dist/collections.mjs
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
createCollectionOperations
|
|
3
|
-
} from "./chunk-QCTICSBE.mjs";
|
|
4
|
-
import "./chunk-BWKBRM2Z.mjs";
|
|
5
|
-
import "./chunk-2KBOKOAD.mjs";
|
|
6
|
-
export {
|
|
7
|
-
createCollectionOperations
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=collections.mjs.map
|
|
1
|
+
export{a as createCollectionOperations}from'./chunk-FFKWCNLU.mjs';import'./chunk-KYLPIEU3.mjs';import'./chunk-MB2INNNP.mjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{g as classifyProduct,i as determineStoreType,f as enrichProduct,c as extractMainSection,a as fetchAjaxProduct,b as fetchProductPage,h as generateSEOContent,d as htmlToMarkdown,e as mergeWithLLM,j as pruneBreakdownForSignals}from'./chunk-6GPWNCDO.mjs';import'./chunk-MB2INNNP.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CheckoutOperations } from './checkout.js';
|
|
2
2
|
import { CollectionOperations } from './collections.js';
|
|
3
3
|
import { ProductOperations } from './products.js';
|
|
4
|
-
import { P as ProductClassification, S as SEOContent, C as CountryDetectionResult, a as StoreOperations, b as ShopifyProduct, c as Product, d as ShopifySingleProduct, e as ShopifyCollection, f as Collection, g as StoreInfo, h as StoreTypeBreakdown } from './store-
|
|
5
|
-
export { H as Address, F as CatalogCategory, I as ContactUrls, K as CountryScore, N as CountryScores, J as Coupon, y as CurrencyCode, G as Demographics, Q as JsonLdEntry, L as LocalizedPricing, M as MetaTag, D as ProductImage, z as ProductOption, x as ProductPricing, B as ProductVariant, A as ProductVariantImage, E as ShopifyApiProduct, u as ShopifyBaseProduct, r as ShopifyBaseVariant, k as ShopifyBasicInfo, o as ShopifyFeaturedMedia, O as ShopifyFeaturesData, m as ShopifyImage, l as ShopifyImageDimensions, p as ShopifyMedia, q as ShopifyOption, w as ShopifyPredictiveProductSearch, v as ShopifyProductAndStore, s as ShopifyProductVariant, t as ShopifySingleProductVariant, j as ShopifyTimestamps, n as ShopifyVariantImage, i as StoreCatalog, R as StoreTypeResult, V as ValidStoreCatalog } from './store-
|
|
4
|
+
import { P as ProductClassification, S as SEOContent, C as CountryDetectionResult, a as StoreOperations, b as ShopifyProduct, c as Product, d as ShopifySingleProduct, e as ShopifyCollection, f as Collection, g as StoreInfo, h as StoreTypeBreakdown } from './store-iQARl6J3.js';
|
|
5
|
+
export { H as Address, F as CatalogCategory, I as ContactUrls, K as CountryScore, N as CountryScores, J as Coupon, y as CurrencyCode, G as Demographics, Q as JsonLdEntry, L as LocalizedPricing, M as MetaTag, D as ProductImage, z as ProductOption, x as ProductPricing, B as ProductVariant, A as ProductVariantImage, E as ShopifyApiProduct, u as ShopifyBaseProduct, r as ShopifyBaseVariant, k as ShopifyBasicInfo, o as ShopifyFeaturedMedia, O as ShopifyFeaturesData, m as ShopifyImage, l as ShopifyImageDimensions, p as ShopifyMedia, q as ShopifyOption, w as ShopifyPredictiveProductSearch, v as ShopifyProductAndStore, s as ShopifyProductVariant, t as ShopifySingleProductVariant, j as ShopifyTimestamps, n as ShopifyVariantImage, i as StoreCatalog, R as StoreTypeResult, V as ValidStoreCatalog } from './store-iQARl6J3.js';
|
|
6
6
|
export { configureRateLimit } from './utils/rate-limit.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -91,7 +91,7 @@ declare function safeParseDate(input?: string | null): Date | undefined;
|
|
|
91
91
|
*
|
|
92
92
|
* @example
|
|
93
93
|
* ```typescript
|
|
94
|
-
* import { ShopClient } from 'shop-
|
|
94
|
+
* import { ShopClient } from 'shop-client';
|
|
95
95
|
*
|
|
96
96
|
* const shop = new ShopClient('https://exampleshop.com');
|
|
97
97
|
*
|
|
@@ -102,6 +102,9 @@ declare function safeParseDate(input?: string | null): Date | undefined;
|
|
|
102
102
|
* const storeInfo = await shop.getInfo();
|
|
103
103
|
* ```
|
|
104
104
|
*/
|
|
105
|
+
type ShopClientOptions = {
|
|
106
|
+
cacheTTL?: number;
|
|
107
|
+
};
|
|
105
108
|
declare class ShopClient {
|
|
106
109
|
private storeDomain;
|
|
107
110
|
private baseUrl;
|
|
@@ -111,6 +114,9 @@ declare class ShopClient {
|
|
|
111
114
|
private cacheTimestamps;
|
|
112
115
|
private normalizeImageUrlCache;
|
|
113
116
|
private storeCurrency?;
|
|
117
|
+
private infoCacheValue?;
|
|
118
|
+
private infoCacheTimestamp?;
|
|
119
|
+
private infoInFlight?;
|
|
114
120
|
products: ProductOperations;
|
|
115
121
|
collections: CollectionOperations;
|
|
116
122
|
checkout: CheckoutOperations;
|
|
@@ -135,7 +141,7 @@ declare class ShopClient {
|
|
|
135
141
|
* const shop2 = new ShopClient('https://boutique.fashion');
|
|
136
142
|
* ```
|
|
137
143
|
*/
|
|
138
|
-
constructor(urlPath: string);
|
|
144
|
+
constructor(urlPath: string, options?: ShopClientOptions);
|
|
139
145
|
/**
|
|
140
146
|
* Optimized image URL normalization with caching
|
|
141
147
|
*/
|
|
@@ -217,7 +223,19 @@ declare class ShopClient {
|
|
|
217
223
|
* console.log(storeInfo.country); // "US"
|
|
218
224
|
* ```
|
|
219
225
|
*/
|
|
220
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Optionally bypass cache and force a fresh fetch.
|
|
228
|
+
*
|
|
229
|
+
* @param options - `{ force?: boolean }` when `true`, ignores cached value and TTL.
|
|
230
|
+
*/
|
|
231
|
+
getInfo(options?: {
|
|
232
|
+
force?: boolean;
|
|
233
|
+
}): Promise<StoreInfo>;
|
|
234
|
+
/**
|
|
235
|
+
* Manually clear the cached store info.
|
|
236
|
+
* The next call to `getInfo()` will fetch fresh data regardless of TTL.
|
|
237
|
+
*/
|
|
238
|
+
clearInfoCache(): void;
|
|
221
239
|
/**
|
|
222
240
|
* Determine the store's primary vertical and target audience.
|
|
223
241
|
* Uses `getInfo()` internally; no input required.
|
|
@@ -230,4 +248,4 @@ declare class ShopClient {
|
|
|
230
248
|
}): Promise<StoreTypeBreakdown>;
|
|
231
249
|
}
|
|
232
250
|
|
|
233
|
-
export { CheckoutOperations, Collection, CollectionOperations, CountryDetectionResult, Product, ProductClassification, ProductOperations, SEOContent, ShopClient, ShopifyCollection, ShopifyProduct, ShopifySingleProduct, StoreInfo, StoreOperations, StoreTypeBreakdown, calculateDiscount, classifyProduct, detectShopifyCountry, extractDomainWithoutSuffix, genProductSlug, generateSEOContent, generateStoreSlug, safeParseDate, sanitizeDomain };
|
|
251
|
+
export { CheckoutOperations, Collection, CollectionOperations, CountryDetectionResult, Product, ProductClassification, ProductOperations, SEOContent, ShopClient, type ShopClientOptions, ShopifyCollection, ShopifyProduct, ShopifySingleProduct, StoreInfo, StoreOperations, StoreTypeBreakdown, calculateDiscount, classifyProduct, detectShopifyCountry, extractDomainWithoutSuffix, genProductSlug, generateSEOContent, generateStoreSlug, safeParseDate, sanitizeDomain };
|