@spree/sdk 0.6.7 → 0.6.9

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 CHANGED
@@ -364,14 +364,37 @@ const completed = await client.store.orders.paymentSessions.complete(
364
364
  console.log(completed.status); // 'completed'
365
365
  ```
366
366
 
367
+ ### Markets
368
+
369
+ ```typescript
370
+ // List all markets
371
+ const { data: markets } = await client.store.markets.list();
372
+ // [{ id: "mkt_xxx", name: "North America", currency: "USD", default_locale: "en", ... }]
373
+
374
+ // Get a single market
375
+ const market = await client.store.markets.get('mkt_xxx');
376
+
377
+ // Resolve which market applies for a country
378
+ const market = await client.store.markets.resolve('DE');
379
+ // => { id: "mkt_xxx", name: "Europe", currency: "EUR", default_locale: "de", ... }
380
+
381
+ // List countries in a market
382
+ const { data: countries } = await client.store.markets.countries.list('mkt_xxx');
383
+
384
+ // Get a country in a market (with states for address forms)
385
+ const country = await client.store.markets.countries.get('mkt_xxx', 'DE', {
386
+ expand: ['states'],
387
+ });
388
+ ```
389
+
367
390
  ### Geography
368
391
 
369
392
  ```typescript
370
393
  // List countries available for checkout
371
394
  const { data: countries } = await client.store.countries.list();
372
395
 
373
- // Get country by ISO code (includes states)
374
- const usa = await client.store.countries.get('US');
396
+ // Get country by ISO code (with states)
397
+ const usa = await client.store.countries.get('US', { expand: ['states'] });
375
398
  console.log(usa.states); // Array of states
376
399
  ```
377
400
 
@@ -501,6 +524,7 @@ The SDK uses a resource builder pattern for nested resources:
501
524
  | `store.customer` | `addresses` | `list`, `get`, `create`, `update`, `delete`, `markAsDefault` |
502
525
  | `store.customer` | `creditCards` | `list`, `get`, `delete` |
503
526
  | `store.customer` | `giftCards` | `list`, `get` |
527
+ | `store.markets` | `countries` | `list`, `get` |
504
528
  | `store.taxons` | `products` | `list` |
505
529
  | `store.wishlists` | `items` | `create`, `update`, `delete` |
506
530
 
@@ -511,6 +535,7 @@ await client.store.orders.lineItems.create(orderId, params, options);
511
535
  await client.store.orders.payments.list(orderId, options);
512
536
  await client.store.orders.shipments.update(orderId, shipmentId, params, options);
513
537
  await client.store.customer.addresses.list({}, options);
538
+ await client.store.markets.countries.list(marketId);
514
539
  await client.store.taxons.products.list(taxonId, params, options);
515
540
  await client.store.wishlists.items.create(wishlistId, params, options);
516
541
  ```
@@ -608,6 +633,7 @@ The SDK exports all Store API types:
608
633
  - `StoreState` - State/province
609
634
  - `StoreAddress` - Customer address
610
635
  - `StoreCustomer` - Customer profile
636
+ - `StoreMarket` - Market configuration (currency, locales, countries)
611
637
  - `StoreStore` - Store configuration
612
638
 
613
639
  ### Commerce Types
package/dist/index.cjs CHANGED
@@ -21,20 +21,23 @@ function calculateDelay(attempt, config) {
21
21
  function sleep(ms) {
22
22
  return new Promise((resolve) => setTimeout(resolve, ms));
23
23
  }
24
- function shouldRetryOnStatus(method, status, config) {
25
- const isIdempotent = method === "GET" || method === "HEAD";
24
+ function generateIdempotencyKey() {
25
+ return `spree-sdk-retry-${crypto.randomUUID()}`;
26
+ }
27
+ function shouldRetryOnStatus(method, status, config, hasIdempotencyKey) {
28
+ const isIdempotent = method === "GET" || method === "HEAD" || hasIdempotencyKey;
26
29
  if (isIdempotent) {
27
30
  return config.retryOnStatus.includes(status);
28
31
  }
29
32
  return status === 429;
30
33
  }
31
- function shouldRetryOnNetworkError(method, config) {
34
+ function shouldRetryOnNetworkError(method, config, hasIdempotencyKey) {
32
35
  if (!config.retryOnNetworkError) return false;
33
- return method === "GET" || method === "HEAD";
36
+ return method === "GET" || method === "HEAD" || hasIdempotencyKey;
34
37
  }
35
38
  function createRequestFn(config, basePath, auth, defaults) {
36
39
  return async function request(method, path, options = {}) {
37
- const { token, orderToken, headers = {}, body, params } = options;
40
+ const { token, orderToken, idempotencyKey, headers = {}, body, params } = options;
38
41
  const locale = options.locale ?? defaults?.locale;
39
42
  const currency = options.currency ?? defaults?.currency;
40
43
  const country = options.country ?? defaults?.country;
@@ -50,14 +53,13 @@ function createRequestFn(config, basePath, auth, defaults) {
50
53
  }
51
54
  });
52
55
  }
53
- if (orderToken) {
54
- url.searchParams.set("order_token", orderToken);
55
- }
56
56
  const requestHeaders = {
57
57
  "Content-Type": "application/json",
58
- [auth.headerName]: auth.headerValue,
59
58
  ...headers
60
59
  };
60
+ if (auth.headerName && auth.headerValue) {
61
+ requestHeaders[auth.headerName] = auth.headerValue;
62
+ }
61
63
  if (token) {
62
64
  requestHeaders["Authorization"] = `Bearer ${token}`;
63
65
  }
@@ -73,6 +75,12 @@ function createRequestFn(config, basePath, auth, defaults) {
73
75
  if (country) {
74
76
  requestHeaders["x-spree-country"] = country;
75
77
  }
78
+ const isMutating = method !== "GET" && method !== "HEAD";
79
+ const effectiveIdempotencyKey = idempotencyKey ?? (isMutating && config.retryConfig ? generateIdempotencyKey() : void 0);
80
+ if (effectiveIdempotencyKey) {
81
+ requestHeaders["Idempotency-Key"] = effectiveIdempotencyKey;
82
+ }
83
+ const hasIdempotencyKey = !!effectiveIdempotencyKey;
76
84
  const maxAttempts = config.retryConfig ? config.retryConfig.maxRetries + 1 : 1;
77
85
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
78
86
  try {
@@ -83,7 +91,7 @@ function createRequestFn(config, basePath, auth, defaults) {
83
91
  });
84
92
  if (!response.ok) {
85
93
  const isLastAttempt = attempt >= maxAttempts - 1;
86
- if (!isLastAttempt && config.retryConfig && shouldRetryOnStatus(method, response.status, config.retryConfig)) {
94
+ if (!isLastAttempt && config.retryConfig && shouldRetryOnStatus(method, response.status, config.retryConfig, hasIdempotencyKey)) {
87
95
  const retryAfter = response.headers.get("Retry-After");
88
96
  const delay = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3, config.retryConfig.maxDelay) : calculateDelay(attempt, config.retryConfig);
89
97
  await sleep(delay);
@@ -101,7 +109,7 @@ function createRequestFn(config, basePath, auth, defaults) {
101
109
  throw error;
102
110
  }
103
111
  const isLastAttempt = attempt >= maxAttempts - 1;
104
- if (!isLastAttempt && config.retryConfig && shouldRetryOnNetworkError(method, config.retryConfig)) {
112
+ if (!isLastAttempt && config.retryConfig && shouldRetryOnNetworkError(method, config.retryConfig, hasIdempotencyKey)) {
105
113
  const delay = calculateDelay(attempt, config.retryConfig);
106
114
  await sleep(delay);
107
115
  continue;
@@ -114,7 +122,7 @@ function createRequestFn(config, basePath, auth, defaults) {
114
122
  }
115
123
 
116
124
  // src/params.ts
117
- var PASSTHROUGH_KEYS = /* @__PURE__ */ new Set(["page", "limit", "expand", "sort"]);
125
+ var PASSTHROUGH_KEYS = /* @__PURE__ */ new Set(["page", "limit", "expand", "sort", "fields"]);
118
126
  function transformListParams(params) {
119
127
  const result = {};
120
128
  for (const [key, value] of Object.entries(params)) {
@@ -138,9 +146,12 @@ function transformListParams(params) {
138
146
  }
139
147
 
140
148
  // src/store-client.ts
141
- function expandParams(params) {
142
- if (!params?.expand?.length) return void 0;
143
- return { expand: params.expand.join(",") };
149
+ function getParams(params) {
150
+ if (!params) return void 0;
151
+ const result = {};
152
+ if (params.expand?.length) result.expand = params.expand.join(",");
153
+ if (params.fields?.length) result.fields = params.fields.join(",");
154
+ return Object.keys(result).length > 0 ? result : void 0;
144
155
  }
145
156
  var StoreClient = class {
146
157
  request;
@@ -165,15 +176,6 @@ var StoreClient = class {
165
176
  refresh: (options) => this.request("POST", "/auth/refresh", options)
166
177
  };
167
178
  // ============================================
168
- // Store
169
- // ============================================
170
- store = {
171
- /**
172
- * Get current store information
173
- */
174
- get: (options) => this.request("GET", "/store", options)
175
- };
176
- // ============================================
177
179
  // Products
178
180
  // ============================================
179
181
  products = {
@@ -182,14 +184,14 @@ var StoreClient = class {
182
184
  */
183
185
  list: (params, options) => this.request("GET", "/products", {
184
186
  ...options,
185
- params: params ? transformListParams(params) : void 0
187
+ params: transformListParams({ ...params })
186
188
  }),
187
189
  /**
188
190
  * Get a product by ID or slug
189
191
  */
190
192
  get: (idOrSlug, params, options) => this.request("GET", `/products/${idOrSlug}`, {
191
193
  ...options,
192
- params: expandParams(params)
194
+ params: getParams(params)
193
195
  }),
194
196
  /**
195
197
  * Get available filters for products
@@ -209,14 +211,14 @@ var StoreClient = class {
209
211
  */
210
212
  list: (params, options) => this.request("GET", "/taxonomies", {
211
213
  ...options,
212
- params
214
+ params: transformListParams({ ...params })
213
215
  }),
214
216
  /**
215
217
  * Get a taxonomy by ID
216
218
  */
217
219
  get: (id, params, options) => this.request("GET", `/taxonomies/${id}`, {
218
220
  ...options,
219
- params: expandParams(params)
221
+ params: getParams(params)
220
222
  })
221
223
  };
222
224
  taxons = {
@@ -225,14 +227,14 @@ var StoreClient = class {
225
227
  */
226
228
  list: (params, options) => this.request("GET", "/taxons", {
227
229
  ...options,
228
- params: params ? transformListParams(params) : void 0
230
+ params: transformListParams({ ...params })
229
231
  }),
230
232
  /**
231
233
  * Get a taxon by ID or permalink
232
234
  */
233
235
  get: (idOrPermalink, params, options) => this.request("GET", `/taxons/${idOrPermalink}`, {
234
236
  ...options,
235
- params: expandParams(params)
237
+ params: getParams(params)
236
238
  }),
237
239
  /**
238
240
  * Nested resource: Products in a taxon
@@ -247,7 +249,7 @@ var StoreClient = class {
247
249
  `/taxons/${taxonId}/products`,
248
250
  {
249
251
  ...options,
250
- params: params ? transformListParams(params) : void 0
252
+ params: transformListParams({ ...params })
251
253
  }
252
254
  )
253
255
  }
@@ -268,7 +270,7 @@ var StoreClient = class {
268
270
  */
269
271
  get: (iso, params, options) => this.request("GET", `/countries/${iso}`, {
270
272
  ...options,
271
- params: expandParams(params)
273
+ params: getParams(params)
272
274
  })
273
275
  };
274
276
  currencies = {
@@ -325,7 +327,7 @@ var StoreClient = class {
325
327
  get: (marketId, iso, params, options) => this.request(
326
328
  "GET",
327
329
  `/markets/${marketId}/countries/${iso}`,
328
- { ...options, params: expandParams(params) }
330
+ { ...options, params: getParams(params) }
329
331
  )
330
332
  }
331
333
  };
@@ -362,7 +364,7 @@ var StoreClient = class {
362
364
  */
363
365
  get: (idOrNumber, params, options) => this.request("GET", `/orders/${idOrNumber}`, {
364
366
  ...options,
365
- params: expandParams(params)
367
+ params: getParams(params)
366
368
  }),
367
369
  /**
368
370
  * Update an order
@@ -580,7 +582,7 @@ var StoreClient = class {
580
582
  list: (params, options) => this.request(
581
583
  "GET",
582
584
  "/customer/addresses",
583
- { ...options, params }
585
+ { ...options, params: transformListParams({ ...params }) }
584
586
  ),
585
587
  /**
586
588
  * Get an address by ID
@@ -622,7 +624,7 @@ var StoreClient = class {
622
624
  list: (params, options) => this.request(
623
625
  "GET",
624
626
  "/customer/credit_cards",
625
- { ...options, params }
627
+ { ...options, params: transformListParams({ ...params }) }
626
628
  ),
627
629
  /**
628
630
  * Get a credit card by ID
@@ -644,7 +646,7 @@ var StoreClient = class {
644
646
  list: (params, options) => this.request(
645
647
  "GET",
646
648
  "/customer/gift_cards",
647
- { ...options, params }
649
+ { ...options, params: transformListParams({ ...params }) }
648
650
  ),
649
651
  /**
650
652
  * Get a gift card by ID
@@ -660,7 +662,7 @@ var StoreClient = class {
660
662
  */
661
663
  list: (params, options) => this.request("GET", "/customer/orders", {
662
664
  ...options,
663
- params: params ? transformListParams(params) : void 0
665
+ params: transformListParams({ ...params })
664
666
  })
665
667
  },
666
668
  /**
@@ -700,14 +702,14 @@ var StoreClient = class {
700
702
  */
701
703
  list: (params, options) => this.request("GET", "/wishlists", {
702
704
  ...options,
703
- params
705
+ params: transformListParams({ ...params })
704
706
  }),
705
707
  /**
706
708
  * Get a wishlist by ID
707
709
  */
708
710
  get: (id, params, options) => this.request("GET", `/wishlists/${id}`, {
709
711
  ...options,
710
- params: expandParams(params)
712
+ params: getParams(params)
711
713
  }),
712
714
  /**
713
715
  * Create a wishlist
@@ -776,6 +778,9 @@ var SpreeClient = class {
776
778
  admin;
777
779
  _defaults;
778
780
  constructor(config) {
781
+ if (!config.publishableKey && !config.secretKey) {
782
+ throw new Error("SpreeClient requires at least one of publishableKey or secretKey");
783
+ }
779
784
  const baseUrl = config.baseUrl.replace(/\/$/, "");
780
785
  const fetchFn = config.fetch || fetch.bind(globalThis);
781
786
  this._defaults = {
@@ -799,7 +804,7 @@ var SpreeClient = class {
799
804
  const storeRequestFn = createRequestFn(
800
805
  requestConfig,
801
806
  "/api/v3/store",
802
- { headerName: "x-spree-api-key", headerValue: config.publishableKey },
807
+ config.publishableKey ? { headerName: "x-spree-api-key", headerValue: config.publishableKey } : { headerName: "", headerValue: "" },
803
808
  this._defaults
804
809
  );
805
810
  const adminRequestFn = createRequestFn(