@atomic-solutions/woocommerce-api-client 0.3.1 → 0.5.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.
@@ -1,4 +1,4 @@
1
- import axios, { isAxiosError } from 'axios';
1
+ import axios, { AxiosError, isAxiosError } from 'axios';
2
2
  import { z } from 'zod';
3
3
 
4
4
  // src/client/createClient.ts
@@ -278,34 +278,34 @@ var WooCommerceDataValidationError = class _WooCommerceDataValidationError exten
278
278
  };
279
279
 
280
280
  // src/http/handleApiResponse.ts
281
- var handleApiResponse = (response, schema, options) => {
281
+ var handleApiData = (data, schema, options, url) => {
282
282
  const validationMode = options?.validationMode ?? "strict";
283
283
  if (validationMode === "warn") {
284
- const result = schema.safeParse(response.data);
284
+ const result = schema.safeParse(data);
285
285
  if (!result.success) {
286
286
  const validationError = new WooCommerceDataValidationError({
287
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
288
- url: response.config.url,
287
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
288
+ url,
289
289
  zodError: result.error,
290
- value: response.data,
290
+ value: data,
291
291
  userMessage: "Received unexpected data from WooCommerce"
292
292
  });
293
293
  options?.onValidationError?.(validationError);
294
294
  options?.errorReporter?.(validationError);
295
295
  console.warn("[woocommerce-utils] Validation warning:", validationError.message);
296
- return response.data;
296
+ return data;
297
297
  }
298
298
  return result.data;
299
299
  }
300
300
  try {
301
- return schema.parse(response.data);
301
+ return schema.parse(data);
302
302
  } catch (error2) {
303
303
  if (error2 && typeof error2 === "object" && "issues" in error2) {
304
304
  const validationError = new WooCommerceDataValidationError({
305
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
306
- url: response.config.url,
305
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
306
+ url,
307
307
  zodError: error2,
308
- value: response.data,
308
+ value: data,
309
309
  userMessage: "Received unexpected data from WooCommerce"
310
310
  });
311
311
  options?.onValidationError?.(validationError);
@@ -315,6 +315,7 @@ var handleApiResponse = (response, schema, options) => {
315
315
  throw error2;
316
316
  }
317
317
  };
318
+ var handleApiResponse = (response, schema, options) => handleApiData(response.data, schema, options, response.config.url);
318
319
 
319
320
  // src/utils/calculatePagination.ts
320
321
  var calculatePagination = (input) => {
@@ -429,6 +430,12 @@ var error = (...args) => {
429
430
  console.error(DEBUG_PREFIX, ...args);
430
431
  };
431
432
 
433
+ // src/http/storeRoutes.ts
434
+ var CART_ROUTE = /\/wc\/store\/(v\d+\/)?(cart|checkout|batch)/;
435
+ var BATCH_ROUTE = /\/wc\/store\/(v\d+\/)?batch/;
436
+ var isCartRoute = (url) => !!url && CART_ROUTE.test(url);
437
+ var isBatchRoute = (url) => !!url && BATCH_ROUTE.test(url);
438
+
432
439
  // src/http/interceptors/request.ts
433
440
  var requiresAuth = (url) => {
434
441
  if (!url) return false;
@@ -442,30 +449,26 @@ var requiresAuth = (url) => {
442
449
  ];
443
450
  return authEndpoints.some((endpoint) => url.includes(endpoint));
444
451
  };
445
- var requiresCartAuth = (url) => {
446
- if (!url) return false;
447
- return /\/wc\/store\/(v\d+\/)?cart/.test(url) || /\/wc\/store\/(v\d+\/)?checkout/.test(url);
448
- };
449
452
  var setupRequestInterceptor = (axiosInstance, config, client) => {
450
453
  axiosInstance.interceptors.request.use(
451
454
  async (requestConfig) => {
452
455
  const url = requestConfig.url;
453
456
  if (config.jwtToken) {
454
457
  const token = await config.jwtToken.get();
455
- if (token && (requiresAuth(url) || requiresCartAuth(url))) {
458
+ if (token && (requiresAuth(url) || isCartRoute(url))) {
456
459
  requestConfig.headers.Authorization = `Bearer ${token}`;
457
460
  if (config.debug) {
458
461
  debug("Injected JWT token for", url);
459
462
  }
460
463
  }
461
464
  }
462
- if (requiresCartAuth(url)) {
465
+ if (isCartRoute(url)) {
463
466
  const headers = await config.cartHeaders.get();
464
467
  const isAuthenticated = !!await config.jwtToken?.get();
465
468
  if (headers.nonce) {
466
469
  requestConfig.headers.Nonce = headers.nonce;
467
470
  }
468
- if (headers.cartToken && !isAuthenticated) {
471
+ if (headers.cartToken && (!isAuthenticated || isBatchRoute(url))) {
469
472
  requestConfig.headers["Cart-Token"] = headers.cartToken;
470
473
  }
471
474
  requestConfig.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
@@ -494,16 +497,19 @@ var setupRequestInterceptor = (axiosInstance, config, client) => {
494
497
  }
495
498
  );
496
499
  };
497
-
498
- // src/http/interceptors/response.ts
499
- var isCartResponse = (url) => {
500
- if (!url) return false;
501
- return /\/wc\/store\/(v\d+\/)?cart/.test(url) || /\/wc\/store\/(v\d+\/)?checkout/.test(url);
500
+ var NONCE_ERROR_CODES = ["woocommerce_rest_missing_nonce", "woocommerce_rest_invalid_nonce"];
501
+ var isNonceRejectedBatch = (data) => {
502
+ const responses = data?.responses;
503
+ if (!Array.isArray(responses) || responses.length === 0) return false;
504
+ return responses.every((response) => {
505
+ const code = response?.body?.code;
506
+ return typeof code === "string" && NONCE_ERROR_CODES.includes(code);
507
+ });
502
508
  };
503
509
  var setupResponseInterceptor = (axiosInstance, config, client) => {
504
510
  axiosInstance.interceptors.response.use(
505
511
  async (response) => {
506
- if (isCartResponse(response.config.url)) {
512
+ if (isCartRoute(response.config.url)) {
507
513
  const nonce = response.headers["nonce"] || response.headers["Nonce"];
508
514
  const cartToken = response.headers["cart-token"] || response.headers["Cart-Token"];
509
515
  if (nonce || cartToken) {
@@ -523,6 +529,15 @@ var setupResponseInterceptor = (axiosInstance, config, client) => {
523
529
  }
524
530
  }
525
531
  }
532
+ if (isBatchRoute(response.config.url) && isNonceRejectedBatch(response.data)) {
533
+ throw new AxiosError(
534
+ "Batch rejected for a nonce error",
535
+ AxiosError.ERR_BAD_REQUEST,
536
+ response.config,
537
+ response.request,
538
+ { ...response, status: 401, data: { code: "woocommerce_rest_missing_nonce" } }
539
+ );
540
+ }
526
541
  if (config.debug) {
527
542
  debug("Response:", response.config.url, response.status);
528
543
  }
@@ -961,7 +976,7 @@ var cartSchema = z.object({
961
976
  cross_sells: z.array(z.unknown()),
962
977
  fees: z.array(z.unknown())
963
978
  });
964
- z.object({
979
+ var addToCartInputSchema = z.object({
965
980
  id: z.number(),
966
981
  quantity: z.number().int().positive(),
967
982
  variation: z.array(
@@ -971,6 +986,18 @@ z.object({
971
986
  })
972
987
  ).optional()
973
988
  });
989
+ z.object({
990
+ items: z.array(addToCartInputSchema).min(1)
991
+ });
992
+ var batchResponseSchema = z.object({
993
+ responses: z.array(
994
+ z.object({
995
+ status: z.number(),
996
+ body: z.unknown(),
997
+ headers: z.record(z.string(), z.unknown()).optional()
998
+ })
999
+ )
1000
+ });
974
1001
  z.object({
975
1002
  key: z.string(),
976
1003
  quantity: z.number().int().positive()
@@ -1330,6 +1357,19 @@ z.object({
1330
1357
  var ORDER_ATTRIBUTION_NAMESPACE = "woocommerce/order-attribution";
1331
1358
 
1332
1359
  // src/api/cart.ts
1360
+ var BATCH_MAX_REQUESTS = 25;
1361
+ var chunk = (list, size) => Array.from(
1362
+ { length: Math.ceil(list.length / size) },
1363
+ (_, index) => list.slice(index * size, index * size + size)
1364
+ );
1365
+ var isAccepted = (status) => status >= 200 && status < 300;
1366
+ var bodyError = (body) => {
1367
+ const data = body;
1368
+ return {
1369
+ code: typeof data?.code === "string" ? data.code : void 0,
1370
+ message: typeof data?.message === "string" ? data.message : void 0
1371
+ };
1372
+ };
1333
1373
  var createCartAPI = (client, endpoints, options) => ({
1334
1374
  get: async () => {
1335
1375
  const response = await client.get(endpoints.cart);
@@ -1339,6 +1379,48 @@ var createCartAPI = (client, endpoints, options) => ({
1339
1379
  const response = await client.post(endpoints.addItem, input);
1340
1380
  return handleApiResponse(response, cartSchema, options);
1341
1381
  },
1382
+ addItems: async ({ items }) => {
1383
+ const responses = [];
1384
+ let chunkFailed = false;
1385
+ for (const batch of chunk(items, BATCH_MAX_REQUESTS)) {
1386
+ try {
1387
+ const response = await client.post(endpoints.batch, {
1388
+ requests: batch.map((item) => ({
1389
+ method: "POST",
1390
+ path: endpoints.addItem,
1391
+ body: item
1392
+ }))
1393
+ });
1394
+ responses.push(...handleApiResponse(response, batchResponseSchema, options).responses);
1395
+ } catch (error2) {
1396
+ if (responses.length === 0) throw error2;
1397
+ chunkFailed = true;
1398
+ break;
1399
+ }
1400
+ }
1401
+ const results = items.map((item, index) => {
1402
+ const entry = responses[index];
1403
+ const { code, message } = bodyError(entry?.body);
1404
+ return {
1405
+ item,
1406
+ status: entry?.status ?? 500,
1407
+ ...code ? { code } : {},
1408
+ ...message ? { message } : {}
1409
+ };
1410
+ });
1411
+ const lastAccepted = [...responses].reverse().find((entry) => isAccepted(entry.status));
1412
+ const batchCart = lastAccepted ? handleApiData(lastAccepted.body, cartSchema, options, endpoints.batch) : void 0;
1413
+ let cart = chunkFailed ? void 0 : batchCart;
1414
+ if (!cart) {
1415
+ try {
1416
+ cart = handleApiResponse(await client.get(endpoints.cart), cartSchema, options);
1417
+ } catch (error2) {
1418
+ if (!batchCart) throw error2;
1419
+ cart = batchCart;
1420
+ }
1421
+ }
1422
+ return { cart, results };
1423
+ },
1342
1424
  updateItem: async (input) => {
1343
1425
  const response = await client.post(endpoints.updateItem, input);
1344
1426
  return handleApiResponse(response, cartSchema, options);
@@ -1428,6 +1510,8 @@ var createEndpoints = (version = "legacy") => {
1428
1510
  removeCoupon: `${prefix}/cart/remove-coupon`,
1429
1511
  updateCustomer: `${prefix}/cart/update-customer`,
1430
1512
  selectShippingRate: `${prefix}/cart/select-shipping-rate`,
1513
+ // Batch
1514
+ batch: `${prefix}/batch`,
1431
1515
  // Checkout
1432
1516
  checkout: `${prefix}/checkout`,
1433
1517
  // Orders