shop-client 3.8.2 → 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.
- package/LICENSE +1 -1
- package/README.md +158 -1
- package/dist/ai/enrich.d.ts +93 -0
- package/dist/ai/enrich.js +25 -0
- package/dist/checkout.js +5 -114
- package/dist/{chunk-2KBOKOAD.mjs → chunk-2MF53V33.js} +32 -13
- package/dist/{chunk-BWKBRM2Z.mjs → chunk-CN7L3BHG.js} +12 -1
- package/dist/chunk-CXUCPK6X.js +460 -0
- package/dist/{chunk-QCTICSBE.mjs → chunk-MOBWPEY4.js} +29 -7
- package/dist/chunk-ROH545KI.js +274 -0
- package/dist/{chunk-QL5OUZGP.mjs → chunk-RR6YTQWP.js} +0 -1
- package/dist/{chunk-O4BPIIQ6.mjs → chunk-V52MFQZE.js} +11 -281
- package/dist/{chunk-WTK5HUFI.mjs → chunk-VPPCOJC3.js} +13 -435
- package/dist/collections.d.ts +2 -1
- package/dist/collections.js +7 -539
- package/dist/index.d.ts +28 -87
- package/dist/index.js +109 -2597
- package/dist/products.d.ts +2 -1
- package/dist/products.js +7 -1205
- package/dist/store.d.ts +53 -1
- package/dist/store.js +8 -697
- package/dist/{store-CJVUz2Yb.d.ts → types-luPg5O08.d.ts} +1 -208
- package/dist/utils/detect-country.d.ts +32 -0
- package/dist/utils/detect-country.js +6 -0
- package/dist/utils/func.d.ts +61 -0
- package/dist/utils/func.js +24 -0
- package/dist/utils/rate-limit.d.ts +5 -0
- package/dist/utils/rate-limit.js +7 -200
- package/package.json +21 -10
- package/dist/checkout.d.mts +0 -31
- package/dist/checkout.js.map +0 -1
- package/dist/checkout.mjs +0 -7
- package/dist/checkout.mjs.map +0 -1
- package/dist/chunk-2KBOKOAD.mjs.map +0 -1
- package/dist/chunk-BWKBRM2Z.mjs.map +0 -1
- package/dist/chunk-O4BPIIQ6.mjs.map +0 -1
- package/dist/chunk-QCTICSBE.mjs.map +0 -1
- package/dist/chunk-QL5OUZGP.mjs.map +0 -1
- package/dist/chunk-WTK5HUFI.mjs.map +0 -1
- package/dist/collections.d.mts +0 -64
- package/dist/collections.js.map +0 -1
- package/dist/collections.mjs +0 -9
- package/dist/collections.mjs.map +0 -1
- package/dist/index.d.mts +0 -233
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -702
- package/dist/index.mjs.map +0 -1
- package/dist/products.d.mts +0 -63
- package/dist/products.js.map +0 -1
- package/dist/products.mjs +0 -9
- package/dist/products.mjs.map +0 -1
- package/dist/store-CJVUz2Yb.d.mts +0 -608
- package/dist/store.d.mts +0 -1
- package/dist/store.js.map +0 -1
- package/dist/store.mjs +0 -9
- package/dist/store.mjs.map +0 -1
- package/dist/utils/rate-limit.d.mts +0 -25
- package/dist/utils/rate-limit.js.map +0 -1
- package/dist/utils/rate-limit.mjs +0 -11
- 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
|
|
|
@@ -530,6 +622,45 @@ Notes:
|
|
|
530
622
|
- npm publishes use OIDC with provenance; no `NPM_TOKEN` secret is required.
|
|
531
623
|
- Ensure your npm package settings add this GitHub repo as a trusted publisher and set the environment name to `npm-publish`.
|
|
532
624
|
|
|
625
|
+
### Additional Utilities
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
import {
|
|
629
|
+
calculateDiscount,
|
|
630
|
+
extractDomainWithoutSuffix,
|
|
631
|
+
generateStoreSlug,
|
|
632
|
+
genProductSlug,
|
|
633
|
+
detectShopCountry,
|
|
634
|
+
} from 'shop-client';
|
|
635
|
+
|
|
636
|
+
// Discount calculation (percentage, rounded to nearest integer)
|
|
637
|
+
calculateDiscount(8000, 10000); // 20
|
|
638
|
+
|
|
639
|
+
// Extract base domain without public suffix
|
|
640
|
+
extractDomainWithoutSuffix('www.example.co.uk'); // 'example'
|
|
641
|
+
|
|
642
|
+
// Create an SEO-friendly store slug
|
|
643
|
+
generateStoreSlug('https://shop.example.com'); // 'shop-example-com'
|
|
644
|
+
|
|
645
|
+
// Create a product slug from product data
|
|
646
|
+
genProductSlug({
|
|
647
|
+
title: 'Summer Dress',
|
|
648
|
+
handle: 'summer-dress',
|
|
649
|
+
vendor: 'Acme',
|
|
650
|
+
}); // 'acme-summer-dress'
|
|
651
|
+
|
|
652
|
+
// Detect Shopify store country with confidence score
|
|
653
|
+
const result = await detectShopCountry('anuki.in');
|
|
654
|
+
// result.country → 'IN', result.confidence → 0.9
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Notes:
|
|
658
|
+
- `calculateDiscount` expects prices in the same unit (e.g., cents) and returns an integer percentage.
|
|
659
|
+
- `extractDomainWithoutSuffix` removes known TLDs/suffixes, leaving the registrable label.
|
|
660
|
+
- `generateStoreSlug` preserves domain components and replaces separators with hyphens.
|
|
661
|
+
- `genProductSlug` builds a stable, vendor-prefixed slug using product fields.
|
|
662
|
+
- `detectShopCountry` combines multiple signals to infer store country and confidence.
|
|
663
|
+
|
|
533
664
|
### Store Type Classification
|
|
534
665
|
|
|
535
666
|
Determine the store’s primary verticals and target audiences using showcased products. Classification uses only each product’s `body_html` content and aggregates per-product results, optionally pruned by store-level signals.
|
|
@@ -565,6 +696,32 @@ Details:
|
|
|
565
696
|
- If `OPENROUTER_API_KEY` is absent or `OPENROUTER_OFFLINE=1`, uses offline regex heuristics.
|
|
566
697
|
- Applies store-level pruning based on title/description to improve consistency.
|
|
567
698
|
|
|
699
|
+
### AI Enrichment
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
import { classifyProduct, generateSEOContent } from 'shop-client';
|
|
703
|
+
|
|
704
|
+
// Classify a single product (offline or via OpenRouter when configured)
|
|
705
|
+
const classification = await classifyProduct({
|
|
706
|
+
title: 'Organic Cotton T-Shirt',
|
|
707
|
+
bodyHtml: '<p>Soft, breathable cotton tee</p>',
|
|
708
|
+
tags: ['organic', 'cotton', 'unisex'],
|
|
709
|
+
});
|
|
710
|
+
// classification → { adult_unisex: { clothing: ['t-shirts'] } }
|
|
711
|
+
|
|
712
|
+
// Generate basic SEO content for a product
|
|
713
|
+
const seo = await generateSEOContent({
|
|
714
|
+
title: 'Organic Cotton T-Shirt',
|
|
715
|
+
description: 'Soft, breathable tee for everyday wear',
|
|
716
|
+
tags: ['organic', 'cotton', 'unisex'],
|
|
717
|
+
});
|
|
718
|
+
// seo.metaTitle, seo.metaDescription, seo.keywords
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Notes:
|
|
722
|
+
- `classifyProduct` mirrors the store-level classification logic but operates on a single product.
|
|
723
|
+
- `generateSEOContent` produces lightweight, deterministic metadata suitable for catalogs and PDPs.
|
|
724
|
+
|
|
568
725
|
## 🏗️ Type Definitions
|
|
569
726
|
|
|
570
727
|
### StoreInfo
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { k as ProductClassification, l as SEOContent, a as ShopifySingleProduct } from '../types-luPg5O08.js';
|
|
2
|
+
|
|
3
|
+
interface EnrichedProductResult {
|
|
4
|
+
bodyHtml: string;
|
|
5
|
+
pageHtml: string;
|
|
6
|
+
extractedMainHtml: string;
|
|
7
|
+
mergedMarkdown: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Fetch Shopify Product AJAX API
|
|
11
|
+
* /products/{handle}.js
|
|
12
|
+
*/
|
|
13
|
+
declare function fetchAjaxProduct(domain: string, handle: string): Promise<ShopifySingleProduct>;
|
|
14
|
+
/**
|
|
15
|
+
* Fetch full product page HTML
|
|
16
|
+
*/
|
|
17
|
+
declare function fetchProductPage(domain: string, handle: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Extract the main Shopify product section WITHOUT cheerio
|
|
20
|
+
* Uses regex + indexing (fast & reliable)
|
|
21
|
+
*/
|
|
22
|
+
declare function extractMainSection(html: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Convert HTML → Clean Markdown using Turndown
|
|
25
|
+
* Includes Shopify cleanup rules + GFM support
|
|
26
|
+
*/
|
|
27
|
+
declare function htmlToMarkdown(html: string | null, options?: {
|
|
28
|
+
useGfm?: boolean;
|
|
29
|
+
}): string;
|
|
30
|
+
/**
|
|
31
|
+
* Merge the two markdown sources using OpenAI GPT
|
|
32
|
+
*/
|
|
33
|
+
declare function mergeWithLLM(bodyInput: string, pageInput: string, options?: {
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
inputType?: "markdown" | "html";
|
|
36
|
+
model?: string;
|
|
37
|
+
outputFormat?: "markdown" | "json";
|
|
38
|
+
}): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* MAIN WORKFLOW
|
|
41
|
+
*/
|
|
42
|
+
declare function enrichProduct(domain: string, handle: string, options?: {
|
|
43
|
+
apiKey?: string;
|
|
44
|
+
useGfm?: boolean;
|
|
45
|
+
inputType?: "markdown" | "html";
|
|
46
|
+
model?: string;
|
|
47
|
+
outputFormat?: "markdown" | "json";
|
|
48
|
+
}): Promise<EnrichedProductResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Classify product content into a three-tier hierarchy using LLM.
|
|
51
|
+
* Returns strictly validated JSON with audience, vertical, and optional category/subCategory.
|
|
52
|
+
*/
|
|
53
|
+
declare function classifyProduct(productContent: string, options?: {
|
|
54
|
+
apiKey?: string;
|
|
55
|
+
model?: string;
|
|
56
|
+
}): Promise<ProductClassification>;
|
|
57
|
+
/**
|
|
58
|
+
* Generate SEO and marketing content for a product. Returns strictly validated JSON.
|
|
59
|
+
*/
|
|
60
|
+
declare function generateSEOContent(product: {
|
|
61
|
+
title: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
vendor?: string;
|
|
64
|
+
price?: number;
|
|
65
|
+
tags?: string[];
|
|
66
|
+
}, options?: {
|
|
67
|
+
apiKey?: string;
|
|
68
|
+
model?: string;
|
|
69
|
+
}): Promise<SEOContent>;
|
|
70
|
+
/**
|
|
71
|
+
* Determine store type (primary vertical and audience) from store information.
|
|
72
|
+
* Accepts flexible input for showcase products/collections (titles or handles) and returns
|
|
73
|
+
* strictly validated `vertical` and `audience` values.
|
|
74
|
+
*/
|
|
75
|
+
declare function determineStoreType(storeInfo: {
|
|
76
|
+
title: string;
|
|
77
|
+
description?: string | null;
|
|
78
|
+
showcase: {
|
|
79
|
+
products: Array<{
|
|
80
|
+
title: string;
|
|
81
|
+
productType?: string | null;
|
|
82
|
+
}> | string[];
|
|
83
|
+
collections: Array<{
|
|
84
|
+
title: string;
|
|
85
|
+
}> | string[];
|
|
86
|
+
};
|
|
87
|
+
}, options?: {
|
|
88
|
+
apiKey?: string;
|
|
89
|
+
model?: string;
|
|
90
|
+
}): Promise<Partial<Record<ProductClassification["audience"], Partial<Record<ProductClassification["vertical"], string[]>>>>>;
|
|
91
|
+
declare function pruneBreakdownForSignals(breakdown: Partial<Record<ProductClassification["audience"], Partial<Record<ProductClassification["vertical"], string[]>>>>, text: string): Partial<Record<ProductClassification["audience"], Partial<Record<ProductClassification["vertical"], string[]>>>>;
|
|
92
|
+
|
|
93
|
+
export { type EnrichedProductResult, classifyProduct, determineStoreType, enrichProduct, extractMainSection, fetchAjaxProduct, fetchProductPage, generateSEOContent, htmlToMarkdown, mergeWithLLM, pruneBreakdownForSignals };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyProduct,
|
|
3
|
+
determineStoreType,
|
|
4
|
+
enrichProduct,
|
|
5
|
+
extractMainSection,
|
|
6
|
+
fetchAjaxProduct,
|
|
7
|
+
fetchProductPage,
|
|
8
|
+
generateSEOContent,
|
|
9
|
+
htmlToMarkdown,
|
|
10
|
+
mergeWithLLM,
|
|
11
|
+
pruneBreakdownForSignals
|
|
12
|
+
} from "../chunk-VPPCOJC3.js";
|
|
13
|
+
import "../chunk-2MF53V33.js";
|
|
14
|
+
export {
|
|
15
|
+
classifyProduct,
|
|
16
|
+
determineStoreType,
|
|
17
|
+
enrichProduct,
|
|
18
|
+
extractMainSection,
|
|
19
|
+
fetchAjaxProduct,
|
|
20
|
+
fetchProductPage,
|
|
21
|
+
generateSEOContent,
|
|
22
|
+
htmlToMarkdown,
|
|
23
|
+
mergeWithLLM,
|
|
24
|
+
pruneBreakdownForSignals
|
|
25
|
+
};
|
package/dist/checkout.js
CHANGED
|
@@ -1,115 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/checkout.ts
|
|
21
|
-
var checkout_exports = {};
|
|
22
|
-
__export(checkout_exports, {
|
|
23
|
-
createCheckoutOperations: () => createCheckoutOperations
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(checkout_exports);
|
|
26
|
-
function createCheckoutOperations(baseUrl) {
|
|
27
|
-
return {
|
|
28
|
-
/**
|
|
29
|
-
* Creates a Shopify checkout URL with pre-filled customer information and cart items.
|
|
30
|
-
*
|
|
31
|
-
* @param params - Checkout parameters
|
|
32
|
-
* @param params.email - Customer's email address (must be valid email format)
|
|
33
|
-
* @param params.items - Array of products to add to cart
|
|
34
|
-
* @param params.items[].productVariantId - Shopify product variant ID
|
|
35
|
-
* @param params.items[].quantity - Quantity as string (must be positive number)
|
|
36
|
-
* @param params.address - Customer's shipping address
|
|
37
|
-
* @param params.address.firstName - Customer's first name
|
|
38
|
-
* @param params.address.lastName - Customer's last name
|
|
39
|
-
* @param params.address.address1 - Street address
|
|
40
|
-
* @param params.address.city - City name
|
|
41
|
-
* @param params.address.zip - Postal/ZIP code
|
|
42
|
-
* @param params.address.country - Country name
|
|
43
|
-
* @param params.address.province - State/Province name
|
|
44
|
-
* @param params.address.phone - Phone number
|
|
45
|
-
*
|
|
46
|
-
* @returns {string} Complete Shopify checkout URL with pre-filled information
|
|
47
|
-
*
|
|
48
|
-
* @throws {Error} When email is invalid, items array is empty, or required address fields are missing
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* const shop = new ShopClient('https://exampleshop.com');
|
|
53
|
-
* const checkoutUrl = await shop.checkout.create([
|
|
54
|
-
* { variantId: '123', quantity: 2 },
|
|
55
|
-
* { variantId: '456', quantity: 1 }
|
|
56
|
-
* ]);
|
|
57
|
-
* console.log(checkoutUrl);
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
createUrl: ({
|
|
61
|
-
email,
|
|
62
|
-
items,
|
|
63
|
-
address
|
|
64
|
-
}) => {
|
|
65
|
-
if (!email || !email.includes("@")) {
|
|
66
|
-
throw new Error("Invalid email address");
|
|
67
|
-
}
|
|
68
|
-
if (!items || items.length === 0) {
|
|
69
|
-
throw new Error("Items array cannot be empty");
|
|
70
|
-
}
|
|
71
|
-
for (const item of items) {
|
|
72
|
-
if (!item.productVariantId || !item.quantity) {
|
|
73
|
-
throw new Error("Each item must have productVariantId and quantity");
|
|
74
|
-
}
|
|
75
|
-
const qty = Number.parseInt(item.quantity, 10);
|
|
76
|
-
if (Number.isNaN(qty) || qty <= 0) {
|
|
77
|
-
throw new Error("Quantity must be a positive number");
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const requiredFields = [
|
|
81
|
-
"firstName",
|
|
82
|
-
"lastName",
|
|
83
|
-
"address1",
|
|
84
|
-
"city",
|
|
85
|
-
"zip",
|
|
86
|
-
"country"
|
|
87
|
-
];
|
|
88
|
-
for (const field of requiredFields) {
|
|
89
|
-
if (!address[field]) {
|
|
90
|
-
throw new Error(`Address field '${field}' is required`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const cartPath = items.map(
|
|
94
|
-
(item) => `${encodeURIComponent(item.productVariantId)}:${encodeURIComponent(item.quantity)}`
|
|
95
|
-
).join(",");
|
|
96
|
-
const params = new URLSearchParams({
|
|
97
|
-
"checkout[email]": email,
|
|
98
|
-
"checkout[shipping_address][first_name]": address.firstName,
|
|
99
|
-
"checkout[shipping_address][last_name]": address.lastName,
|
|
100
|
-
"checkout[shipping_address][address1]": address.address1,
|
|
101
|
-
"checkout[shipping_address][city]": address.city,
|
|
102
|
-
"checkout[shipping_address][zip]": address.zip,
|
|
103
|
-
"checkout[shipping_address][country]": address.country,
|
|
104
|
-
"checkout[shipping_address][province]": address.province,
|
|
105
|
-
"checkout[shipping_address][phone]": address.phone
|
|
106
|
-
});
|
|
107
|
-
return `${baseUrl}cart/${cartPath}?${params.toString()}`;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
112
|
-
0 && (module.exports = {
|
|
1
|
+
import {
|
|
113
2
|
createCheckoutOperations
|
|
114
|
-
}
|
|
115
|
-
|
|
3
|
+
} from "./chunk-RR6YTQWP.js";
|
|
4
|
+
export {
|
|
5
|
+
createCheckoutOperations
|
|
6
|
+
};
|
|
@@ -17,12 +17,6 @@ var RateLimiter = class {
|
|
|
17
17
|
this.refillTimer.unref();
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
stopRefill() {
|
|
21
|
-
if (this.refillTimer) {
|
|
22
|
-
clearInterval(this.refillTimer);
|
|
23
|
-
this.refillTimer = null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
20
|
ensureRefillStarted() {
|
|
27
21
|
if (!this.refillTimer) {
|
|
28
22
|
this.startRefill();
|
|
@@ -151,16 +145,42 @@ function configureRateLimit(options) {
|
|
|
151
145
|
}
|
|
152
146
|
}
|
|
153
147
|
}
|
|
148
|
+
function sleep(ms) {
|
|
149
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
150
|
+
}
|
|
154
151
|
async function rateLimitedFetch(input, init) {
|
|
155
|
-
var _a;
|
|
156
|
-
if (!enabled) {
|
|
157
|
-
return fetch(input, init);
|
|
158
|
-
}
|
|
152
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
159
153
|
const klass = init == null ? void 0 : init.rateLimitClass;
|
|
160
154
|
const byClass = klass ? classLimiters.get(klass) : void 0;
|
|
161
155
|
const byHost = getHostLimiter(getHost(input));
|
|
162
|
-
const eff = (_a = byClass != null ? byClass : byHost) != null ? _a : limiter;
|
|
163
|
-
|
|
156
|
+
const eff = enabled ? (_a = byClass != null ? byClass : byHost) != null ? _a : limiter : void 0;
|
|
157
|
+
const maxRetries = Math.max(0, (_c = (_b = init == null ? void 0 : init.retry) == null ? void 0 : _b.maxRetries) != null ? _c : 2);
|
|
158
|
+
const baseDelayMs = Math.max(0, (_e = (_d = init == null ? void 0 : init.retry) == null ? void 0 : _d.baseDelayMs) != null ? _e : 200);
|
|
159
|
+
const retryOnStatuses = (_g = (_f = init == null ? void 0 : init.retry) == null ? void 0 : _f.retryOnStatuses) != null ? _g : [429, 503];
|
|
160
|
+
let attempt = 0;
|
|
161
|
+
let lastError = null;
|
|
162
|
+
let response = null;
|
|
163
|
+
while (attempt <= maxRetries) {
|
|
164
|
+
try {
|
|
165
|
+
if (eff) {
|
|
166
|
+
response = await eff.schedule(() => fetch(input, init));
|
|
167
|
+
} else {
|
|
168
|
+
response = await fetch(input, init);
|
|
169
|
+
}
|
|
170
|
+
if (!response || response.ok || !retryOnStatuses.includes(response.status)) {
|
|
171
|
+
return response;
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
lastError = err;
|
|
175
|
+
}
|
|
176
|
+
attempt += 1;
|
|
177
|
+
if (attempt > maxRetries) break;
|
|
178
|
+
const jitter = Math.floor(Math.random() * 100);
|
|
179
|
+
const delay = baseDelayMs * 2 ** (attempt - 1) + jitter;
|
|
180
|
+
await sleep(delay);
|
|
181
|
+
}
|
|
182
|
+
if (response) return response;
|
|
183
|
+
throw lastError != null ? lastError : new Error("rateLimitedFetch failed without response");
|
|
164
184
|
}
|
|
165
185
|
function getRateLimitStatus() {
|
|
166
186
|
return {
|
|
@@ -174,4 +194,3 @@ export {
|
|
|
174
194
|
rateLimitedFetch,
|
|
175
195
|
getRateLimitStatus
|
|
176
196
|
};
|
|
177
|
-
//# sourceMappingURL=chunk-2KBOKOAD.mjs.map
|
|
@@ -110,6 +110,17 @@ function buildVariantOptionsMap(optionNames, variants) {
|
|
|
110
110
|
}
|
|
111
111
|
return map;
|
|
112
112
|
}
|
|
113
|
+
function buildVariantKey(obj) {
|
|
114
|
+
const parts = [];
|
|
115
|
+
for (const [name, value] of Object.entries(obj)) {
|
|
116
|
+
if (value) {
|
|
117
|
+
parts.push(`${normalizeKey(name)}#${normalizeKey(value)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (parts.length === 0) return "";
|
|
121
|
+
parts.sort((a, b) => a.localeCompare(b));
|
|
122
|
+
return parts.join("##");
|
|
123
|
+
}
|
|
113
124
|
function formatPrice(amountInCents, currency) {
|
|
114
125
|
try {
|
|
115
126
|
return new Intl.NumberFormat(void 0, {
|
|
@@ -131,6 +142,6 @@ export {
|
|
|
131
142
|
safeParseDate,
|
|
132
143
|
normalizeKey,
|
|
133
144
|
buildVariantOptionsMap,
|
|
145
|
+
buildVariantKey,
|
|
134
146
|
formatPrice
|
|
135
147
|
};
|
|
136
|
-
//# sourceMappingURL=chunk-BWKBRM2Z.mjs.map
|