@atomic-solutions/woocommerce-api-client 0.4.0 → 0.5.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.
@@ -1,5 +1,6 @@
1
- import { S as StoreApiVersion, C as CartHeadersAdapter, J as JwtTokenAdapter } from '../types-BF4_UUqp.mjs';
1
+ import { S as StoreApiVersion, C as CartHeadersAdapter, J as JwtTokenAdapter } from '../types-9I6oS_83.mjs';
2
2
  import 'axios';
3
+ import '@atomic-solutions/errors';
3
4
  import 'zod';
4
5
 
5
6
  type DescribeFn = (name: string, fn: () => void) => void;
@@ -1,5 +1,6 @@
1
- import { S as StoreApiVersion, C as CartHeadersAdapter, J as JwtTokenAdapter } from '../types-BF4_UUqp.js';
1
+ import { S as StoreApiVersion, C as CartHeadersAdapter, J as JwtTokenAdapter } from '../types-9I6oS_83.js';
2
2
  import 'axios';
3
+ import '@atomic-solutions/errors';
3
4
  import 'zod';
4
5
 
5
6
  type DescribeFn = (name: string, fn: () => void) => void;
@@ -1,13 +1,20 @@
1
1
  'use strict';
2
2
 
3
3
  var axios = require('axios');
4
+ var errors = require('@atomic-solutions/errors');
4
5
  var zod = require('zod');
5
6
 
6
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
8
 
8
9
  var axios__default = /*#__PURE__*/_interopDefault(axios);
9
10
 
10
- // src/client/createClient.ts
11
+ var __typeError = (msg) => {
12
+ throw TypeError(msg);
13
+ };
14
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
15
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
16
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
17
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
11
18
 
12
19
  // src/errors/classes/BaseError.ts
13
20
  var BaseError = class extends Error {
@@ -136,8 +143,16 @@ var WOOCOMMERCE_ERROR_METADATA = {
136
143
  }
137
144
  };
138
145
 
146
+ // src/errors/classes/http-error.ts
147
+ var redactPath = (url) => url.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, "").replace(/[?#].*$/, "").split("/").map((segment) => /^(\d+|[0-9a-f]{16,}|[0-9a-f-]{36})$/i.test(segment) ? ":id" : segment).join("/");
148
+ var classifyHttpStatus = (status) => ({
149
+ domain: status === void 0 ? "network" : status === 401 || status === 403 ? "auth" : "integration",
150
+ severity: status !== void 0 && status < 500 ? "warning" : "error"
151
+ });
152
+
139
153
  // src/errors/classes/WooCommerceApiError.ts
140
- var WooCommerceApiError = class extends BaseError {
154
+ var _options;
155
+ var WooCommerceApiError = class extends errors.BaseError {
141
156
  constructor(options) {
142
157
  const metadata = WOOCOMMERCE_ERROR_METADATA[options.code] ?? {
143
158
  operation: "woocommerce_api_request",
@@ -149,7 +164,30 @@ var WooCommerceApiError = class extends BaseError {
149
164
  userMessage: options.userMessage || metadata.userMessage,
150
165
  retryable: options.retryable ?? metadata.retryable
151
166
  };
152
- super(enrichedOptions);
167
+ const status = enrichedOptions.statusCode;
168
+ const { domain, severity } = classifyHttpStatus(status);
169
+ super({
170
+ domain,
171
+ severity,
172
+ operation: enrichedOptions.operation,
173
+ message: enrichedOptions.message,
174
+ code: enrichedOptions.code,
175
+ retryable: enrichedOptions.retryable,
176
+ userMessage: enrichedOptions.userMessage,
177
+ cause: enrichedOptions.cause,
178
+ fingerprint: [domain, enrichedOptions.code, String(status ?? "network")],
179
+ extras: {
180
+ originalCode: enrichedOptions.originalCode,
181
+ statusCode: status,
182
+ path: redactPath(enrichedOptions.url),
183
+ method: enrichedOptions.method
184
+ }
185
+ });
186
+ __privateAdd(this, _options);
187
+ __privateSet(this, _options, enrichedOptions);
188
+ }
189
+ get options() {
190
+ return __privateGet(this, _options);
153
191
  }
154
192
  // Convenient getters for frequently accessed fields (no duplication)
155
193
  get wooCode() {
@@ -176,24 +214,8 @@ var WooCommerceApiError = class extends BaseError {
176
214
  get data() {
177
215
  return this.options.data;
178
216
  }
179
- /**
180
- * Get reportable data for error tracking/logging
181
- * Includes HTTP context and WooCommerce-specific fields
182
- */
183
- getReportableData() {
184
- return {
185
- code: this.options.code,
186
- operation: this.options.operation,
187
- userMessage: this.options.userMessage,
188
- retryable: this.options.retryable,
189
- statusCode: this.options.statusCode,
190
- url: this.options.url,
191
- method: this.options.method,
192
- wooCode: this.options.code,
193
- originalCode: this.options.originalCode
194
- };
195
- }
196
217
  };
218
+ _options = new WeakMap();
197
219
 
198
220
  // src/errors/classes/WooCommerceDataValidationError.ts
199
221
  var WooCommerceDataValidationError = class _WooCommerceDataValidationError extends BaseError {
@@ -284,34 +306,34 @@ var WooCommerceDataValidationError = class _WooCommerceDataValidationError exten
284
306
  };
285
307
 
286
308
  // src/http/handleApiResponse.ts
287
- var handleApiResponse = (response, schema, options) => {
309
+ var handleApiData = (data, schema, options, url) => {
288
310
  const validationMode = options?.validationMode ?? "strict";
289
311
  if (validationMode === "warn") {
290
- const result = schema.safeParse(response.data);
312
+ const result = schema.safeParse(data);
291
313
  if (!result.success) {
292
314
  const validationError = new WooCommerceDataValidationError({
293
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
294
- url: response.config.url,
315
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
316
+ url,
295
317
  zodError: result.error,
296
- value: response.data,
318
+ value: data,
297
319
  userMessage: "Received unexpected data from WooCommerce"
298
320
  });
299
321
  options?.onValidationError?.(validationError);
300
322
  options?.errorReporter?.(validationError);
301
323
  console.warn("[woocommerce-utils] Validation warning:", validationError.message);
302
- return response.data;
324
+ return data;
303
325
  }
304
326
  return result.data;
305
327
  }
306
328
  try {
307
- return schema.parse(response.data);
329
+ return schema.parse(data);
308
330
  } catch (error2) {
309
331
  if (error2 && typeof error2 === "object" && "issues" in error2) {
310
332
  const validationError = new WooCommerceDataValidationError({
311
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
312
- url: response.config.url,
333
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
334
+ url,
313
335
  zodError: error2,
314
- value: response.data,
336
+ value: data,
315
337
  userMessage: "Received unexpected data from WooCommerce"
316
338
  });
317
339
  options?.onValidationError?.(validationError);
@@ -321,6 +343,7 @@ var handleApiResponse = (response, schema, options) => {
321
343
  throw error2;
322
344
  }
323
345
  };
346
+ var handleApiResponse = (response, schema, options) => handleApiData(response.data, schema, options, response.config.url);
324
347
 
325
348
  // src/utils/calculatePagination.ts
326
349
  var calculatePagination = (input) => {
@@ -435,6 +458,12 @@ var error = (...args) => {
435
458
  console.error(DEBUG_PREFIX, ...args);
436
459
  };
437
460
 
461
+ // src/http/storeRoutes.ts
462
+ var CART_ROUTE = /\/wc\/store\/(v\d+\/)?(cart|checkout|batch)/;
463
+ var BATCH_ROUTE = /\/wc\/store\/(v\d+\/)?batch/;
464
+ var isCartRoute = (url) => !!url && CART_ROUTE.test(url);
465
+ var isBatchRoute = (url) => !!url && BATCH_ROUTE.test(url);
466
+
438
467
  // src/http/interceptors/request.ts
439
468
  var requiresAuth = (url) => {
440
469
  if (!url) return false;
@@ -448,30 +477,26 @@ var requiresAuth = (url) => {
448
477
  ];
449
478
  return authEndpoints.some((endpoint) => url.includes(endpoint));
450
479
  };
451
- var requiresCartAuth = (url) => {
452
- if (!url) return false;
453
- return /\/wc\/store\/(v\d+\/)?cart/.test(url) || /\/wc\/store\/(v\d+\/)?checkout/.test(url);
454
- };
455
480
  var setupRequestInterceptor = (axiosInstance, config, client) => {
456
481
  axiosInstance.interceptors.request.use(
457
482
  async (requestConfig) => {
458
483
  const url = requestConfig.url;
459
484
  if (config.jwtToken) {
460
485
  const token = await config.jwtToken.get();
461
- if (token && (requiresAuth(url) || requiresCartAuth(url))) {
486
+ if (token && (requiresAuth(url) || isCartRoute(url))) {
462
487
  requestConfig.headers.Authorization = `Bearer ${token}`;
463
488
  if (config.debug) {
464
489
  debug("Injected JWT token for", url);
465
490
  }
466
491
  }
467
492
  }
468
- if (requiresCartAuth(url)) {
493
+ if (isCartRoute(url)) {
469
494
  const headers = await config.cartHeaders.get();
470
495
  const isAuthenticated = !!await config.jwtToken?.get();
471
496
  if (headers.nonce) {
472
497
  requestConfig.headers.Nonce = headers.nonce;
473
498
  }
474
- if (headers.cartToken && !isAuthenticated) {
499
+ if (headers.cartToken && (!isAuthenticated || isBatchRoute(url))) {
475
500
  requestConfig.headers["Cart-Token"] = headers.cartToken;
476
501
  }
477
502
  requestConfig.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
@@ -500,16 +525,19 @@ var setupRequestInterceptor = (axiosInstance, config, client) => {
500
525
  }
501
526
  );
502
527
  };
503
-
504
- // src/http/interceptors/response.ts
505
- var isCartResponse = (url) => {
506
- if (!url) return false;
507
- return /\/wc\/store\/(v\d+\/)?cart/.test(url) || /\/wc\/store\/(v\d+\/)?checkout/.test(url);
528
+ var NONCE_ERROR_CODES = ["woocommerce_rest_missing_nonce", "woocommerce_rest_invalid_nonce"];
529
+ var isNonceRejectedBatch = (data) => {
530
+ const responses = data?.responses;
531
+ if (!Array.isArray(responses) || responses.length === 0) return false;
532
+ return responses.every((response) => {
533
+ const code = response?.body?.code;
534
+ return typeof code === "string" && NONCE_ERROR_CODES.includes(code);
535
+ });
508
536
  };
509
537
  var setupResponseInterceptor = (axiosInstance, config, client) => {
510
538
  axiosInstance.interceptors.response.use(
511
539
  async (response) => {
512
- if (isCartResponse(response.config.url)) {
540
+ if (isCartRoute(response.config.url)) {
513
541
  const nonce = response.headers["nonce"] || response.headers["Nonce"];
514
542
  const cartToken = response.headers["cart-token"] || response.headers["Cart-Token"];
515
543
  if (nonce || cartToken) {
@@ -529,6 +557,15 @@ var setupResponseInterceptor = (axiosInstance, config, client) => {
529
557
  }
530
558
  }
531
559
  }
560
+ if (isBatchRoute(response.config.url) && isNonceRejectedBatch(response.data)) {
561
+ throw new axios.AxiosError(
562
+ "Batch rejected for a nonce error",
563
+ axios.AxiosError.ERR_BAD_REQUEST,
564
+ response.config,
565
+ response.request,
566
+ { ...response, status: 401, data: { code: "woocommerce_rest_missing_nonce" } }
567
+ );
568
+ }
532
569
  if (config.debug) {
533
570
  debug("Response:", response.config.url, response.status);
534
571
  }
@@ -571,12 +608,15 @@ var setupErrorInterceptor = (axiosInstance, config, client) => {
571
608
  axiosInstance.interceptors.response.use(
572
609
  (response) => response,
573
610
  async (axiosError) => {
574
- const status = axiosError.response?.status || 500;
611
+ if (axios.isCancel(axiosError)) {
612
+ return Promise.reject(axiosError);
613
+ }
614
+ const status = axiosError.response?.status;
575
615
  const errorData = axiosError.response?.data;
616
+ const isNetworkError = axios.isAxiosError(axiosError) && !axiosError.response;
576
617
  const apiCode = errorData?.code || "unknown_error";
577
618
  const wpMessage = errorData?.message || axiosError.message || "Unknown error";
578
- const errorCode = mapWooCommerceCode(apiCode);
579
- const errorMetadata = WOOCOMMERCE_ERROR_METADATA[errorCode];
619
+ const errorCode = isNetworkError ? "network_error" : mapWooCommerceCode(apiCode);
580
620
  const wcError = new WooCommerceApiError({
581
621
  message: wpMessage,
582
622
  statusCode: status,
@@ -584,9 +624,6 @@ var setupErrorInterceptor = (axiosInstance, config, client) => {
584
624
  // Transformed app-level code
585
625
  originalCode: apiCode,
586
626
  // Original WooCommerce API code
587
- retryable: errorMetadata.retryable,
588
- userMessage: errorMetadata.userMessage,
589
- operation: errorMetadata.operation,
590
627
  url: axiosError.config?.url || "",
591
628
  method: axiosError.config?.method?.toUpperCase() || "GET",
592
629
  requestBody: axiosError.config?.data,
@@ -967,7 +1004,7 @@ var cartSchema = zod.z.object({
967
1004
  cross_sells: zod.z.array(zod.z.unknown()),
968
1005
  fees: zod.z.array(zod.z.unknown())
969
1006
  });
970
- zod.z.object({
1007
+ var addToCartInputSchema = zod.z.object({
971
1008
  id: zod.z.number(),
972
1009
  quantity: zod.z.number().int().positive(),
973
1010
  variation: zod.z.array(
@@ -977,6 +1014,18 @@ zod.z.object({
977
1014
  })
978
1015
  ).optional()
979
1016
  });
1017
+ zod.z.object({
1018
+ items: zod.z.array(addToCartInputSchema).min(1)
1019
+ });
1020
+ var batchResponseSchema = zod.z.object({
1021
+ responses: zod.z.array(
1022
+ zod.z.object({
1023
+ status: zod.z.number(),
1024
+ body: zod.z.unknown(),
1025
+ headers: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1026
+ })
1027
+ )
1028
+ });
980
1029
  zod.z.object({
981
1030
  key: zod.z.string(),
982
1031
  quantity: zod.z.number().int().positive()
@@ -1336,6 +1385,19 @@ zod.z.object({
1336
1385
  var ORDER_ATTRIBUTION_NAMESPACE = "woocommerce/order-attribution";
1337
1386
 
1338
1387
  // src/api/cart.ts
1388
+ var BATCH_MAX_REQUESTS = 25;
1389
+ var chunk = (list, size) => Array.from(
1390
+ { length: Math.ceil(list.length / size) },
1391
+ (_, index) => list.slice(index * size, index * size + size)
1392
+ );
1393
+ var isAccepted = (status) => status >= 200 && status < 300;
1394
+ var bodyError = (body) => {
1395
+ const data = body;
1396
+ return {
1397
+ code: typeof data?.code === "string" ? data.code : void 0,
1398
+ message: typeof data?.message === "string" ? data.message : void 0
1399
+ };
1400
+ };
1339
1401
  var createCartAPI = (client, endpoints, options) => ({
1340
1402
  get: async () => {
1341
1403
  const response = await client.get(endpoints.cart);
@@ -1345,6 +1407,48 @@ var createCartAPI = (client, endpoints, options) => ({
1345
1407
  const response = await client.post(endpoints.addItem, input);
1346
1408
  return handleApiResponse(response, cartSchema, options);
1347
1409
  },
1410
+ addItems: async ({ items }) => {
1411
+ const responses = [];
1412
+ let chunkFailed = false;
1413
+ for (const batch of chunk(items, BATCH_MAX_REQUESTS)) {
1414
+ try {
1415
+ const response = await client.post(endpoints.batch, {
1416
+ requests: batch.map((item) => ({
1417
+ method: "POST",
1418
+ path: endpoints.addItem,
1419
+ body: item
1420
+ }))
1421
+ });
1422
+ responses.push(...handleApiResponse(response, batchResponseSchema, options).responses);
1423
+ } catch (error2) {
1424
+ if (responses.length === 0) throw error2;
1425
+ chunkFailed = true;
1426
+ break;
1427
+ }
1428
+ }
1429
+ const results = items.map((item, index) => {
1430
+ const entry = responses[index];
1431
+ const { code, message } = bodyError(entry?.body);
1432
+ return {
1433
+ item,
1434
+ status: entry?.status ?? 500,
1435
+ ...code ? { code } : {},
1436
+ ...message ? { message } : {}
1437
+ };
1438
+ });
1439
+ const lastAccepted = [...responses].reverse().find((entry) => isAccepted(entry.status));
1440
+ const batchCart = lastAccepted ? handleApiData(lastAccepted.body, cartSchema, options, endpoints.batch) : void 0;
1441
+ let cart = chunkFailed ? void 0 : batchCart;
1442
+ if (!cart) {
1443
+ try {
1444
+ cart = handleApiResponse(await client.get(endpoints.cart), cartSchema, options);
1445
+ } catch (error2) {
1446
+ if (!batchCart) throw error2;
1447
+ cart = batchCart;
1448
+ }
1449
+ }
1450
+ return { cart, results };
1451
+ },
1348
1452
  updateItem: async (input) => {
1349
1453
  const response = await client.post(endpoints.updateItem, input);
1350
1454
  return handleApiResponse(response, cartSchema, options);
@@ -1434,6 +1538,8 @@ var createEndpoints = (version = "legacy") => {
1434
1538
  removeCoupon: `${prefix}/cart/remove-coupon`,
1435
1539
  updateCustomer: `${prefix}/cart/update-customer`,
1436
1540
  selectShippingRate: `${prefix}/cart/select-shipping-rate`,
1541
+ // Batch
1542
+ batch: `${prefix}/batch`,
1437
1543
  // Checkout
1438
1544
  checkout: `${prefix}/checkout`,
1439
1545
  // Orders