shopify_storefront_sdk 1.0.4 → 2.0.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/README.md +136 -70
- package/dist/cjs/client/client.js +110 -22
- package/dist/cjs/generated/shopify.js +25 -49
- package/dist/cjs/helpers/CartManager.js +55 -0
- package/dist/cjs/helpers/CustomerSession.js +41 -0
- package/dist/cjs/index.js +210 -27
- package/dist/cjs/tests/integration/sdk.test.js +266 -0
- package/dist/cjs/tests/unit/cache.test.js +86 -0
- package/dist/cjs/tests/unit/cartManager.test.js +43 -0
- package/dist/cjs/tests/unit/client.test.js +134 -0
- package/dist/cjs/tests/unit/customerSession.test.js +50 -0
- package/dist/cjs/tests/unit/pagination.test.js +41 -0
- package/dist/cjs/tests/unit/webhooks.test.js +41 -0
- package/dist/cjs/utils/cache.js +29 -0
- package/dist/cjs/utils/errors.js +53 -0
- package/dist/cjs/utils/pagination.js +30 -0
- package/dist/cjs/utils/webhooks.js +33 -0
- package/dist/client/client.d.ts +12 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +111 -21
- package/dist/client/client.js.map +1 -1
- package/dist/generated/shopify.d.ts +4048 -2764
- package/dist/generated/shopify.d.ts.map +1 -1
- package/dist/generated/shopify.js +26 -50
- package/dist/generated/shopify.js.map +1 -1
- package/dist/helpers/CartManager.d.ts +32 -0
- package/dist/helpers/CartManager.d.ts.map +1 -0
- package/dist/helpers/CartManager.js +53 -0
- package/dist/helpers/CartManager.js.map +1 -0
- package/dist/helpers/CustomerSession.d.ts +27 -0
- package/dist/helpers/CustomerSession.d.ts.map +1 -0
- package/dist/helpers/CustomerSession.js +40 -0
- package/dist/helpers/CustomerSession.js.map +1 -0
- package/dist/index.d.ts +179 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -28
- package/dist/index.js.map +1 -1
- package/dist/tests/integration/sdk.test.d.ts +2 -0
- package/dist/tests/integration/sdk.test.d.ts.map +1 -0
- package/dist/tests/integration/sdk.test.js +245 -0
- package/dist/tests/integration/sdk.test.js.map +1 -0
- package/dist/tests/unit/cache.test.d.ts +2 -0
- package/dist/tests/unit/cache.test.d.ts.map +1 -0
- package/dist/tests/unit/cache.test.js +85 -0
- package/dist/tests/unit/cache.test.js.map +1 -0
- package/dist/tests/unit/cartManager.test.d.ts +2 -0
- package/dist/tests/unit/cartManager.test.d.ts.map +1 -0
- package/dist/tests/unit/cartManager.test.js +43 -0
- package/dist/tests/unit/cartManager.test.js.map +1 -0
- package/dist/tests/unit/client.test.d.ts +2 -0
- package/dist/tests/unit/client.test.d.ts.map +1 -0
- package/dist/tests/unit/client.test.js +133 -0
- package/dist/tests/unit/client.test.js.map +1 -0
- package/dist/tests/unit/customerSession.test.d.ts +2 -0
- package/dist/tests/unit/customerSession.test.d.ts.map +1 -0
- package/dist/tests/unit/customerSession.test.js +49 -0
- package/dist/tests/unit/customerSession.test.js.map +1 -0
- package/dist/tests/unit/pagination.test.d.ts +2 -0
- package/dist/tests/unit/pagination.test.d.ts.map +1 -0
- package/dist/tests/unit/pagination.test.js +40 -0
- package/dist/tests/unit/pagination.test.js.map +1 -0
- package/dist/tests/unit/webhooks.test.d.ts +2 -0
- package/dist/tests/unit/webhooks.test.d.ts.map +1 -0
- package/dist/tests/unit/webhooks.test.js +40 -0
- package/dist/tests/unit/webhooks.test.js.map +1 -0
- package/dist/utils/cache.d.ts +14 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +24 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/errors.d.ts +30 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +48 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/pagination.d.ts +20 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +28 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/webhooks.d.ts +10 -0
- package/dist/utils/webhooks.d.ts.map +1 -0
- package/dist/utils/webhooks.js +31 -0
- package/dist/utils/webhooks.js.map +1 -0
- package/package.json +11 -7
- package/dist/cjs/tests/test.js +0 -201
- package/dist/tests/test.d.ts +0 -34
- package/dist/tests/test.d.ts.map +0 -1
- package/dist/tests/test.js +0 -202
- package/dist/tests/test.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# 📦 Shopify Storefront SDK (Node.js + TypeScript)
|
|
2
2
|
|
|
3
|
+
[](https://github.com/kiddo9/shopify_storefront_sdk/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
A fully typed, lightweight, and developer-friendly **Node.js SDK for the Shopify Storefront GraphQL API**.
|
|
4
|
-
Built with TypeScript,
|
|
6
|
+
Built with TypeScript, designed to help developers move faster when building Shopify headless storefronts.
|
|
5
7
|
|
|
6
8
|
✔ Fully typed queries & responses
|
|
7
9
|
✔ Simple, intuitive API
|
|
@@ -15,18 +17,20 @@ Built with TypeScript, GraphQL Codegen, and graphql-request — designed to help
|
|
|
15
17
|
## 🔧 Installation
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
npm install
|
|
20
|
+
npm install shopify_storefront_sdk
|
|
19
21
|
# or
|
|
20
|
-
yarn add
|
|
22
|
+
yarn add shopify_storefront_sdk
|
|
21
23
|
# or
|
|
22
|
-
pnpm add
|
|
24
|
+
pnpm add shopify_storefront_sdk
|
|
23
25
|
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
## 🚀 Quick Start
|
|
27
29
|
|
|
28
30
|
```ts
|
|
29
|
-
import
|
|
31
|
+
import ShopifyStorefront from "shopify-storefront-sdk";
|
|
32
|
+
// or
|
|
33
|
+
const { default: ShopifyStorefront } = require("shopify_storefront_sdk");
|
|
30
34
|
|
|
31
35
|
const shopify = new ShopifyStorefront({
|
|
32
36
|
domain: process.env.SHOPIFY_STORE_DOMAIN!,
|
|
@@ -34,9 +38,10 @@ const shopify = new ShopifyStorefront({
|
|
|
34
38
|
});
|
|
35
39
|
|
|
36
40
|
// Fetch a product
|
|
37
|
-
const product = await
|
|
38
|
-
"
|
|
39
|
-
|
|
41
|
+
const product = await client.getProductById({
|
|
42
|
+
id: "id",
|
|
43
|
+
metafields: [],
|
|
44
|
+
});
|
|
40
45
|
|
|
41
46
|
console.log(product.title);
|
|
42
47
|
```
|
|
@@ -53,10 +58,8 @@ console.log(product.title);
|
|
|
53
58
|
- Carts (create/get/update)
|
|
54
59
|
- Customers (create, login, recovery flow)
|
|
55
60
|
|
|
56
|
-
- **GraphQL Codegen built in**
|
|
57
61
|
- **Consistent error handling**
|
|
58
62
|
- **Supports custom queries**
|
|
59
|
-
- **Zero heavy dependencies**
|
|
60
63
|
|
|
61
64
|
---
|
|
62
65
|
|
|
@@ -68,65 +71,165 @@ console.log(product.title);
|
|
|
68
71
|
const shopify = new ShopifyStorefront({
|
|
69
72
|
domain: "yourstore.myshopify.com",
|
|
70
73
|
token: "your-storefront-access-token",
|
|
74
|
+
apiVersion: "2025-01", // Optional: defaults to 2025-01. Can be set to "unstable" or future versions.
|
|
75
|
+
maxRetries: 3, // Optional: Automatically retries on rate limits (429/503). Defaults to 3.
|
|
71
76
|
});
|
|
72
77
|
```
|
|
73
78
|
|
|
74
79
|
---
|
|
75
80
|
|
|
81
|
+
### Verify Webhooks
|
|
82
|
+
|
|
83
|
+
Easily verify incoming Shopify Webhooks using our static helper method (useful for API routes):
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const isValid = ShopifyStorefront.verifyWebhook(
|
|
87
|
+
rawBodyBuffer,
|
|
88
|
+
req.headers['x-shopify-hmac-sha256'],
|
|
89
|
+
process.env.SHOPIFY_WEBHOOK_SECRET
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!isValid) throw new Error("Unauthorized");
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### Pagination
|
|
98
|
+
|
|
99
|
+
Automatically fetch all pages of a GraphQL connection without manually managing cursors:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const allProducts = await shopify.autoPaginate(
|
|
103
|
+
(cursor) => shopify.getProducts({ first: 250, after: cursor }),
|
|
104
|
+
(response) => response.products
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// allProducts is now a flat array of Product nodes!
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Shop Info
|
|
111
|
+
|
|
112
|
+
Retrieve general store configuration such as name, description, and currency settings:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const shop = await shopify.getShopInfo();
|
|
116
|
+
console.log(shop.shop.name);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
76
121
|
### Products
|
|
77
122
|
|
|
78
123
|
#### Get product by ID
|
|
79
124
|
|
|
80
125
|
```ts
|
|
81
|
-
await shopify.getProductById(
|
|
126
|
+
await shopify.getProductById({
|
|
127
|
+
id: "id",
|
|
128
|
+
metafields: [],
|
|
129
|
+
});
|
|
82
130
|
```
|
|
83
131
|
|
|
84
132
|
#### Get product by handle
|
|
85
133
|
|
|
86
134
|
```ts
|
|
87
|
-
await shopify.getProductByHandle(
|
|
135
|
+
await shopify.getProductByHandle({
|
|
136
|
+
handle: "",
|
|
137
|
+
});
|
|
88
138
|
```
|
|
89
139
|
|
|
90
140
|
#### Recommendations
|
|
91
141
|
|
|
92
142
|
```ts
|
|
93
|
-
await shopify.getProductRecommendations(
|
|
143
|
+
await shopify.getProductRecommendations({
|
|
144
|
+
productId: "",
|
|
145
|
+
});
|
|
94
146
|
```
|
|
95
147
|
|
|
96
148
|
---
|
|
97
149
|
|
|
98
|
-
###
|
|
150
|
+
### ⚡ Advanced Caching
|
|
99
151
|
|
|
100
|
-
|
|
152
|
+
Repeatedly fetching the same data (like Store Info or Product details) can waste network time and hit Shopify rate limits. The SDK comes with a powerful, flexible caching layer.
|
|
101
153
|
|
|
102
154
|
```ts
|
|
103
|
-
|
|
104
|
-
|
|
155
|
+
import { ShopifyStorefront, InMemoryCache } from 'shopify_storefront_sdk';
|
|
156
|
+
|
|
157
|
+
const shopify = new ShopifyStorefront({
|
|
158
|
+
domain: "yourstore.myshopify.com",
|
|
159
|
+
token: "your-storefront-access-token",
|
|
160
|
+
cache: new InMemoryCache(), // Automatically caches GraphQL Queries
|
|
161
|
+
defaultCacheTtl: 60 // Cache duration in seconds (default is 60)
|
|
105
162
|
});
|
|
163
|
+
|
|
164
|
+
// 1st Call: Hits the network (~200ms)
|
|
165
|
+
const shopInfo1 = await shopify.getShopInfo();
|
|
166
|
+
|
|
167
|
+
// 2nd Call: Instantly returns from memory (~0ms)
|
|
168
|
+
const shopInfo2 = await shopify.getShopInfo();
|
|
106
169
|
```
|
|
107
170
|
|
|
108
|
-
|
|
171
|
+
**Note:** The SDK intelligently ignores mutations. `createCart` or `addCartLines` will *never* be cached.
|
|
172
|
+
|
|
173
|
+
You can also use your own cache (like Redis) by passing an object that matches the `ShopifyCache` interface (`get(key)`, `set(key, value, ttl)`).
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### 🛒 Cart Manager (Recommended)
|
|
178
|
+
|
|
179
|
+
Managing a Cart manually can be tedious. The SDK includes a stateful `CartManager` that automatically handles cart creation in the background:
|
|
109
180
|
|
|
110
181
|
```ts
|
|
111
|
-
|
|
182
|
+
// Initialize with no ID to lazily create a cart, or pass an existing ID.
|
|
183
|
+
const cart = shopify.cart();
|
|
184
|
+
|
|
185
|
+
// Will automatically call createCart() in the background!
|
|
186
|
+
await cart.addLines([{ merchandiseId: "gid://...", quantity: 1 }]);
|
|
187
|
+
|
|
188
|
+
// The newly created cart ID is safely stored
|
|
189
|
+
console.log(cart.id);
|
|
190
|
+
|
|
191
|
+
// Easily fetch the latest state
|
|
192
|
+
const state = await cart.get();
|
|
112
193
|
```
|
|
113
194
|
|
|
114
195
|
---
|
|
115
196
|
|
|
116
|
-
###
|
|
197
|
+
### Manual Cart Operations
|
|
198
|
+
|
|
199
|
+
#### Create a cart
|
|
117
200
|
|
|
118
201
|
```ts
|
|
119
|
-
await shopify.
|
|
120
|
-
|
|
121
|
-
|
|
202
|
+
await shopify.createCart({
|
|
203
|
+
input: {
|
|
204
|
+
lines: [
|
|
205
|
+
{
|
|
206
|
+
quantity: 1,
|
|
207
|
+
merchandiseId: "",
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
first: 20,
|
|
212
|
+
});
|
|
122
213
|
```
|
|
123
214
|
|
|
124
215
|
---
|
|
125
216
|
|
|
126
|
-
###
|
|
217
|
+
### 👤 Customer Session Manager
|
|
218
|
+
|
|
219
|
+
Passing the `customerAccessToken` into every method manually can be repetitive. Use the stateful `CustomerSession` to automatically bind the token to all operations for that user:
|
|
127
220
|
|
|
128
221
|
```ts
|
|
129
|
-
|
|
222
|
+
// 1. Log the customer in
|
|
223
|
+
const auth = await shopify.loginCustomer({ email: "...", password: "..." });
|
|
224
|
+
const token = auth.customerAccessTokenCreate.customerAccessToken.accessToken;
|
|
225
|
+
|
|
226
|
+
// 2. Initialize a Customer Session for that user
|
|
227
|
+
const session = shopify.customer(token);
|
|
228
|
+
|
|
229
|
+
// 3. Effortlessly manage their account!
|
|
230
|
+
const isValid = await session.verify();
|
|
231
|
+
const orders = await session.getOrders({ first: 10 });
|
|
232
|
+
await session.update({ customer: { firstName: "John" } });
|
|
130
233
|
```
|
|
131
234
|
|
|
132
235
|
---
|
|
@@ -135,12 +238,11 @@ await shopify.getBlog("news", 10);
|
|
|
135
238
|
|
|
136
239
|
```
|
|
137
240
|
/src
|
|
138
|
-
/
|
|
241
|
+
/client
|
|
242
|
+
/utils
|
|
243
|
+
/graphql → .graphql operations
|
|
139
244
|
/generated → Auto-generated code
|
|
140
245
|
index.ts → SDK export
|
|
141
|
-
|
|
142
|
-
/codegen.yml → Codegen config
|
|
143
|
-
/schema.graphql → Shopify schema
|
|
144
246
|
```
|
|
145
247
|
|
|
146
248
|
---
|
|
@@ -148,8 +250,8 @@ await shopify.getBlog("news", 10);
|
|
|
148
250
|
## 🛠 Development Setup
|
|
149
251
|
|
|
150
252
|
```bash
|
|
151
|
-
git clone https://github.com/
|
|
152
|
-
cd
|
|
253
|
+
git clone https://github.com/kiddo9/shopify_storefront_sdk.git
|
|
254
|
+
cd shopify_storefront_sdk
|
|
153
255
|
npm install
|
|
154
256
|
```
|
|
155
257
|
|
|
@@ -165,14 +267,14 @@ SHOPIFY_STOREFRONT_TOKEN=xxxx
|
|
|
165
267
|
## 🔨 Build
|
|
166
268
|
|
|
167
269
|
```bash
|
|
168
|
-
npm run
|
|
270
|
+
npm run prepare
|
|
169
271
|
```
|
|
170
272
|
|
|
171
273
|
Outputs:
|
|
172
274
|
|
|
173
275
|
```
|
|
276
|
+
dist/cjs/index.js
|
|
174
277
|
dist/index.js
|
|
175
|
-
dist/index.cjs
|
|
176
278
|
dist/index.d.ts
|
|
177
279
|
```
|
|
178
280
|
|
|
@@ -181,36 +283,13 @@ dist/index.d.ts
|
|
|
181
283
|
## 🧪 Testing
|
|
182
284
|
|
|
183
285
|
```bash
|
|
184
|
-
npm run dev
|
|
286
|
+
npm run dev:test
|
|
185
287
|
# after build:
|
|
186
288
|
npm run test
|
|
187
289
|
```
|
|
188
290
|
|
|
189
291
|
---
|
|
190
292
|
|
|
191
|
-
## 🧬 Code Generation
|
|
192
|
-
|
|
193
|
-
Regenerate types & operations:
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
npm run generate
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Scans:
|
|
200
|
-
|
|
201
|
-
```
|
|
202
|
-
src/queries/*.graphql
|
|
203
|
-
schema.graphql
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Generates:
|
|
207
|
-
|
|
208
|
-
```
|
|
209
|
-
src/generated/
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
293
|
## 🌐 Requirements
|
|
215
294
|
|
|
216
295
|
- Node.js 18+
|
|
@@ -243,16 +322,3 @@ it helps others discover it and motivates future improvements!
|
|
|
243
322
|
|
|
244
323
|
---
|
|
245
324
|
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
If you want, I can also generate:
|
|
249
|
-
|
|
250
|
-
✅ Better badges (npm version, downloads, license, TypeScript)
|
|
251
|
-
✅ CONTRIBUTING.md
|
|
252
|
-
✅ API documentation table
|
|
253
|
-
✅ Full GitHub project template (issues, PR templates, workflows)
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
```
|
|
@@ -1,32 +1,120 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const errors_js_1 = require("../utils/errors.js");
|
|
4
5
|
class ShopifyClient {
|
|
5
|
-
constructor({ storeUrl, storefrontToken }) {
|
|
6
|
-
if (!
|
|
7
|
-
throw new
|
|
6
|
+
constructor({ storeUrl, storefrontToken, apiVersion = "2025-01", maxRetries = 3, cache, defaultCacheTtl = 60 }) {
|
|
7
|
+
if (!storeUrl || !storeUrl.includes(".myshopify.com")) {
|
|
8
|
+
throw new errors_js_1.ShopifyInvalidRequestError(`The store URL "${storeUrl}" is invalid or not supported by Shopify.`);
|
|
8
9
|
}
|
|
9
|
-
(
|
|
10
|
-
(
|
|
10
|
+
if (!storefrontToken) {
|
|
11
|
+
throw new errors_js_1.ShopifyInvalidRequestError("A valid storefront access token must be provided.");
|
|
12
|
+
}
|
|
13
|
+
this.endpoint = `https://${storeUrl}/api/${apiVersion}/graphql.json`;
|
|
14
|
+
this.token = storefrontToken;
|
|
15
|
+
this.maxRetries = maxRetries;
|
|
16
|
+
this.cache = cache;
|
|
17
|
+
this.cacheTtl = defaultCacheTtl;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generates a deterministic hash for a GraphQL query and its variables.
|
|
21
|
+
*/
|
|
22
|
+
generateCacheKey(query, variables) {
|
|
23
|
+
return crypto.createHash("sha256").update(query + JSON.stringify(variables || {})).digest("hex");
|
|
11
24
|
}
|
|
12
25
|
async query(query, variables = {}) {
|
|
13
|
-
var _a
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
if ((_a = res.data) === null || _a === void 0 ? void 0 : _a.errors)
|
|
25
|
-
throw new Error(JSON.stringify(res.data.errors, null, 2));
|
|
26
|
-
return (_b = res.data) === null || _b === void 0 ? void 0 : _b.data;
|
|
26
|
+
var _a;
|
|
27
|
+
const isMutation = query.trim().startsWith("mutation");
|
|
28
|
+
let cacheKey = "";
|
|
29
|
+
// 1. Check Cache (Only for Queries, never Mutations)
|
|
30
|
+
if (this.cache && !isMutation) {
|
|
31
|
+
cacheKey = this.generateCacheKey(query, variables);
|
|
32
|
+
const cachedResponse = await this.cache.get(cacheKey);
|
|
33
|
+
if (cachedResponse) {
|
|
34
|
+
return cachedResponse;
|
|
35
|
+
}
|
|
27
36
|
}
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
let attempt = 0;
|
|
38
|
+
while (attempt <= this.maxRetries) {
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(this.endpoint, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"X-Shopify-Storefront-Access-Token": this.token,
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
query,
|
|
48
|
+
variables,
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
// Handle Rate Limits (429) or Server Errors (503) by throwing so we can retry
|
|
53
|
+
if (res.status === 429 || res.status === 503) {
|
|
54
|
+
throw new errors_js_1.ShopifyNetworkError(`Shopify returned HTTP ${res.status}: Rate Limited or Service Unavailable`);
|
|
55
|
+
}
|
|
56
|
+
// Handle other HTTP errors
|
|
57
|
+
if (res.status >= 400 && res.status < 500) {
|
|
58
|
+
throw new errors_js_1.ShopifyInvalidRequestError(`Shopify returned a client error: HTTP ${res.status}`);
|
|
59
|
+
}
|
|
60
|
+
throw new errors_js_1.ShopifyNetworkError(`Shopify Server Error: HTTP ${res.status}`);
|
|
61
|
+
}
|
|
62
|
+
const data = await res.json();
|
|
63
|
+
// 2. Handle top-level GraphQL Errors
|
|
64
|
+
if ((data === null || data === void 0 ? void 0 : data.errors) && data.errors.length > 0) {
|
|
65
|
+
throw new errors_js_1.ShopifyGraphQLError(((_a = data.errors[0]) === null || _a === void 0 ? void 0 : _a.message) || "GraphQL API returned errors", data.errors);
|
|
66
|
+
}
|
|
67
|
+
// 3. Scan for nested User Errors (commonly found in mutations)
|
|
68
|
+
if (data === null || data === void 0 ? void 0 : data.data) {
|
|
69
|
+
this.checkForUserErrors(data.data);
|
|
70
|
+
}
|
|
71
|
+
// 4. Save to Cache
|
|
72
|
+
if (this.cache && !isMutation && (data === null || data === void 0 ? void 0 : data.data)) {
|
|
73
|
+
await this.cache.set(cacheKey, data.data, this.cacheTtl);
|
|
74
|
+
}
|
|
75
|
+
return data === null || data === void 0 ? void 0 : data.data;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// If it's a structural/GraphQL/User error, or a 4xx error (except 429), re-throw immediately.
|
|
79
|
+
if (error instanceof errors_js_1.ShopifyGraphQLError ||
|
|
80
|
+
error instanceof errors_js_1.ShopifyInvalidRequestError ||
|
|
81
|
+
error instanceof errors_js_1.ShopifyUserError) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
// If it's a network error or 429/503 and we have retries left, wait and retry.
|
|
85
|
+
if (attempt < this.maxRetries) {
|
|
86
|
+
attempt++;
|
|
87
|
+
// Exponential backoff: 500ms, 1000ms, 2000ms...
|
|
88
|
+
const backoff = Math.pow(2, attempt) * 500;
|
|
89
|
+
await new Promise(r => setTimeout(r, backoff));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Out of retries or unexpected error
|
|
93
|
+
throw new errors_js_1.ShopifyNetworkError(`Network Error: ${error.message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new errors_js_1.ShopifyNetworkError("Maximum retries exceeded");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Helper function to recursively check for `userErrors` inside the GraphQL response data.
|
|
100
|
+
*/
|
|
101
|
+
checkForUserErrors(data) {
|
|
102
|
+
var _a, _b;
|
|
103
|
+
if (!data || typeof data !== "object")
|
|
104
|
+
return;
|
|
105
|
+
for (const key of Object.keys(data)) {
|
|
106
|
+
const value = data[key];
|
|
107
|
+
if (value && typeof value === "object") {
|
|
108
|
+
if ("userErrors" in value && Array.isArray(value.userErrors) && value.userErrors.length > 0) {
|
|
109
|
+
throw new errors_js_1.ShopifyUserError(((_a = value.userErrors[0]) === null || _a === void 0 ? void 0 : _a.message) || "A user error occurred during the mutation", value.userErrors);
|
|
110
|
+
}
|
|
111
|
+
else if ("customerUserErrors" in value && Array.isArray(value.customerUserErrors) && value.customerUserErrors.length > 0) {
|
|
112
|
+
throw new errors_js_1.ShopifyUserError(((_b = value.customerUserErrors[0]) === null || _b === void 0 ? void 0 : _b.message) || "A customer user error occurred", value.customerUserErrors);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
this.checkForUserErrors(value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
30
118
|
}
|
|
31
119
|
}
|
|
32
120
|
}
|
|
@@ -903,100 +903,76 @@ const defaultWrapper = (action, _operationName, _operationType, _variables) => a
|
|
|
903
903
|
function getSdk(client, withWrapper = defaultWrapper) {
|
|
904
904
|
return {
|
|
905
905
|
getProducts(variables, requestHeaders) {
|
|
906
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductsDocument, variables, {
|
|
907
|
-
...requestHeaders,
|
|
908
|
-
...wrappedRequestHeaders,
|
|
909
|
-
}), "getProducts", "query", variables);
|
|
906
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductsDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'getProducts', 'query', variables);
|
|
910
907
|
},
|
|
911
908
|
getProductById(variables, requestHeaders) {
|
|
912
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductByIdDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
909
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductByIdDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'getProductById', 'query', variables);
|
|
913
910
|
},
|
|
914
911
|
GetProductRecommendations(variables, requestHeaders) {
|
|
915
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductRecommendationsDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
912
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductRecommendationsDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'GetProductRecommendations', 'query', variables);
|
|
916
913
|
},
|
|
917
914
|
GetProductByHandle(variables, requestHeaders) {
|
|
918
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductByHandleDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
915
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetProductByHandleDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'GetProductByHandle', 'query', variables);
|
|
919
916
|
},
|
|
920
917
|
GetAllProductTypes(variables, requestHeaders) {
|
|
921
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetAllProductTypesDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
918
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetAllProductTypesDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'GetAllProductTypes', 'query', variables);
|
|
922
919
|
},
|
|
923
920
|
getCollections(variables, requestHeaders) {
|
|
924
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCollectionsDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
921
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCollectionsDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'getCollections', 'query', variables);
|
|
925
922
|
},
|
|
926
923
|
getShopInfo(variables, requestHeaders) {
|
|
927
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetShopInfoDocument, variables, {
|
|
928
|
-
...requestHeaders,
|
|
929
|
-
...wrappedRequestHeaders,
|
|
930
|
-
}), "getShopInfo", "query", variables);
|
|
924
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetShopInfoDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'getShopInfo', 'query', variables);
|
|
931
925
|
},
|
|
932
926
|
GetCart(variables, requestHeaders) {
|
|
933
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCartDocument, variables, {
|
|
934
|
-
...requestHeaders,
|
|
935
|
-
...wrappedRequestHeaders,
|
|
936
|
-
}), "GetCart", "query", variables);
|
|
927
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCartDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'GetCart', 'query', variables);
|
|
937
928
|
},
|
|
938
929
|
cartCreate(variables, requestHeaders) {
|
|
939
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartCreateDocument, variables, {
|
|
940
|
-
...requestHeaders,
|
|
941
|
-
...wrappedRequestHeaders,
|
|
942
|
-
}), "cartCreate", "mutation", variables);
|
|
930
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartCreateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'cartCreate', 'mutation', variables);
|
|
943
931
|
},
|
|
944
932
|
cartLinesUpdate(variables, requestHeaders) {
|
|
945
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartLinesUpdateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
933
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartLinesUpdateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'cartLinesUpdate', 'mutation', variables);
|
|
946
934
|
},
|
|
947
935
|
cartBuyerIdentityUpdate(variables, requestHeaders) {
|
|
948
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartBuyerIdentityUpdateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
936
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartBuyerIdentityUpdateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'cartBuyerIdentityUpdate', 'mutation', variables);
|
|
949
937
|
},
|
|
950
938
|
cartMetafieldsSet(variables, requestHeaders) {
|
|
951
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartMetafieldsSetDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
939
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartMetafieldsSetDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'cartMetafieldsSet', 'mutation', variables);
|
|
952
940
|
},
|
|
953
941
|
cartLinesAdd(variables, requestHeaders) {
|
|
954
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartLinesAddDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
942
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartLinesAddDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'cartLinesAdd', 'mutation', variables);
|
|
955
943
|
},
|
|
956
944
|
cartLinesRemove(variables, requestHeaders) {
|
|
957
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartLinesRemoveDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
945
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CartLinesRemoveDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'cartLinesRemove', 'mutation', variables);
|
|
958
946
|
},
|
|
959
947
|
customerCreate(variables, requestHeaders) {
|
|
960
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerCreateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
948
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerCreateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'customerCreate', 'mutation', variables);
|
|
961
949
|
},
|
|
962
950
|
customerAccessTokenCreate(variables, requestHeaders) {
|
|
963
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerAccessTokenCreateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
951
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerAccessTokenCreateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'customerAccessTokenCreate', 'mutation', variables);
|
|
964
952
|
},
|
|
965
953
|
customerUpdate(variables, requestHeaders) {
|
|
966
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerUpdateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
954
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerUpdateDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'customerUpdate', 'mutation', variables);
|
|
967
955
|
},
|
|
968
956
|
getCustomer(variables, requestHeaders) {
|
|
969
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCustomerDocument, variables, {
|
|
970
|
-
...requestHeaders,
|
|
971
|
-
...wrappedRequestHeaders,
|
|
972
|
-
}), "getCustomer", "query", variables);
|
|
957
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCustomerDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'getCustomer', 'query', variables);
|
|
973
958
|
},
|
|
974
959
|
customerRecover(variables, requestHeaders) {
|
|
975
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerRecoverDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
960
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.CustomerRecoverDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'customerRecover', 'mutation', variables);
|
|
976
961
|
},
|
|
977
962
|
GetBlogs(variables, requestHeaders) {
|
|
978
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetBlogsDocument, variables, {
|
|
979
|
-
...requestHeaders,
|
|
980
|
-
...wrappedRequestHeaders,
|
|
981
|
-
}), "GetBlogs", "query", variables);
|
|
963
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetBlogsDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'GetBlogs', 'query', variables);
|
|
982
964
|
},
|
|
983
965
|
GetBlog(variables, requestHeaders) {
|
|
984
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetBlogDocument, variables, {
|
|
985
|
-
...requestHeaders,
|
|
986
|
-
...wrappedRequestHeaders,
|
|
987
|
-
}), "GetBlog", "query", variables);
|
|
966
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetBlogDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'GetBlog', 'query', variables);
|
|
988
967
|
},
|
|
989
968
|
GetArticles(variables, requestHeaders) {
|
|
990
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetArticlesDocument, variables, {
|
|
991
|
-
...requestHeaders,
|
|
992
|
-
...wrappedRequestHeaders,
|
|
993
|
-
}), "GetArticles", "query", variables);
|
|
969
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetArticlesDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'GetArticles', 'query', variables);
|
|
994
970
|
},
|
|
995
971
|
getSingleArticle(variables, requestHeaders) {
|
|
996
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetSingleArticleDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
972
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetSingleArticleDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'getSingleArticle', 'query', variables);
|
|
997
973
|
},
|
|
998
974
|
getCustomerOrders(variables, requestHeaders) {
|
|
999
|
-
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCustomerOrdersDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }),
|
|
1000
|
-
}
|
|
975
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(exports.GetCustomerOrdersDocument, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'getCustomerOrders', 'query', variables);
|
|
976
|
+
}
|
|
1001
977
|
};
|
|
1002
978
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CartManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A stateful high-level manager for a Shopify Cart.
|
|
6
|
+
* Automatically handles lazy creation of the cart if an ID is not provided.
|
|
7
|
+
*/
|
|
8
|
+
class CartManager {
|
|
9
|
+
constructor(sdk, cartId) {
|
|
10
|
+
this.sdk = sdk;
|
|
11
|
+
this.id = cartId || null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Ensures a cart exists. If not, it creates one and stores the ID.
|
|
15
|
+
*/
|
|
16
|
+
async ensureCart() {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
if (!this.id) {
|
|
19
|
+
const res = await this.sdk.createCart({ input: {}, first: 250 });
|
|
20
|
+
this.id = ((_b = (_a = res.cartCreate) === null || _a === void 0 ? void 0 : _a.cart) === null || _b === void 0 ? void 0 : _b.id) || null;
|
|
21
|
+
if (!this.id) {
|
|
22
|
+
throw new Error("Failed to create cart during lazy initialization.");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fetches the latest cart details.
|
|
28
|
+
*/
|
|
29
|
+
async get(first = 250) {
|
|
30
|
+
await this.ensureCart();
|
|
31
|
+
return this.sdk.getCart({ cartId: this.id, first });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Adds line items to the cart. Creates the cart if it doesn't exist.
|
|
35
|
+
*/
|
|
36
|
+
async addLines(lines) {
|
|
37
|
+
await this.ensureCart();
|
|
38
|
+
return this.sdk.addCartLines({ cartId: this.id, lines });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Removes line items from the cart. Creates the cart if it doesn't exist.
|
|
42
|
+
*/
|
|
43
|
+
async removeLines(lineIds) {
|
|
44
|
+
await this.ensureCart();
|
|
45
|
+
return this.sdk.removeCartLines({ cartId: this.id, lineIds });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Updates line items in the cart (e.g. modifying quantity).
|
|
49
|
+
*/
|
|
50
|
+
async updateLines(lines, first = 250) {
|
|
51
|
+
await this.ensureCart();
|
|
52
|
+
return this.sdk.updateCartLines({ cartId: this.id, lines, first });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.CartManager = CartManager;
|