@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.
package/dist/index.js CHANGED
@@ -53,6 +53,7 @@ Caused by: ${this.options.cause.stack}`;
53
53
 
54
54
  // src/errors/mapping/mapWooCommerceCode.ts
55
55
  var mapWooCommerceCode = (apiCode) => {
56
+ if (!apiCode) return "unknown_error";
56
57
  const directMatches = {
57
58
  woocommerce_rest_cart_empty: "cart_empty",
58
59
  woocommerce_rest_cart_coupon_error: "invalid_coupon",
@@ -284,34 +285,34 @@ var WooCommerceDataValidationError = class _WooCommerceDataValidationError exten
284
285
  };
285
286
 
286
287
  // src/http/handleApiResponse.ts
287
- var handleApiResponse = (response, schema, options) => {
288
+ var handleApiData = (data, schema, options, url) => {
288
289
  const validationMode = options?.validationMode ?? "strict";
289
290
  if (validationMode === "warn") {
290
- const result = schema.safeParse(response.data);
291
+ const result = schema.safeParse(data);
291
292
  if (!result.success) {
292
293
  const validationError = new WooCommerceDataValidationError({
293
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
294
- url: response.config.url,
294
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
295
+ url,
295
296
  zodError: result.error,
296
- value: response.data,
297
+ value: data,
297
298
  userMessage: "Received unexpected data from WooCommerce"
298
299
  });
299
300
  options?.onValidationError?.(validationError);
300
301
  options?.errorReporter?.(validationError);
301
302
  console.warn("[woocommerce-utils] Validation warning:", validationError.message);
302
- return response.data;
303
+ return data;
303
304
  }
304
305
  return result.data;
305
306
  }
306
307
  try {
307
- return schema.parse(response.data);
308
+ return schema.parse(data);
308
309
  } catch (error2) {
309
310
  if (error2 && typeof error2 === "object" && "issues" in error2) {
310
311
  const validationError = new WooCommerceDataValidationError({
311
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
312
- url: response.config.url,
312
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
313
+ url,
313
314
  zodError: error2,
314
- value: response.data,
315
+ value: data,
315
316
  userMessage: "Received unexpected data from WooCommerce"
316
317
  });
317
318
  options?.onValidationError?.(validationError);
@@ -321,6 +322,7 @@ var handleApiResponse = (response, schema, options) => {
321
322
  throw error2;
322
323
  }
323
324
  };
325
+ var handleApiResponse = (response, schema, options) => handleApiData(response.data, schema, options, response.config.url);
324
326
 
325
327
  // src/utils/calculatePagination.ts
326
328
  var calculatePagination = (input) => {
@@ -435,6 +437,12 @@ var error = (...args) => {
435
437
  console.error(DEBUG_PREFIX, ...args);
436
438
  };
437
439
 
440
+ // src/http/storeRoutes.ts
441
+ var CART_ROUTE = /\/wc\/store\/(v\d+\/)?(cart|checkout|batch)/;
442
+ var BATCH_ROUTE = /\/wc\/store\/(v\d+\/)?batch/;
443
+ var isCartRoute = (url) => !!url && CART_ROUTE.test(url);
444
+ var isBatchRoute = (url) => !!url && BATCH_ROUTE.test(url);
445
+
438
446
  // src/http/interceptors/request.ts
439
447
  var requiresAuth = (url) => {
440
448
  if (!url) return false;
@@ -448,30 +456,26 @@ var requiresAuth = (url) => {
448
456
  ];
449
457
  return authEndpoints.some((endpoint) => url.includes(endpoint));
450
458
  };
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
459
  var setupRequestInterceptor = (axiosInstance, config, client) => {
456
460
  axiosInstance.interceptors.request.use(
457
461
  async (requestConfig) => {
458
462
  const url = requestConfig.url;
459
463
  if (config.jwtToken) {
460
464
  const token = await config.jwtToken.get();
461
- if (token && (requiresAuth(url) || requiresCartAuth(url))) {
465
+ if (token && (requiresAuth(url) || isCartRoute(url))) {
462
466
  requestConfig.headers.Authorization = `Bearer ${token}`;
463
467
  if (config.debug) {
464
468
  debug("Injected JWT token for", url);
465
469
  }
466
470
  }
467
471
  }
468
- if (requiresCartAuth(url)) {
472
+ if (isCartRoute(url)) {
469
473
  const headers = await config.cartHeaders.get();
470
474
  const isAuthenticated = !!await config.jwtToken?.get();
471
475
  if (headers.nonce) {
472
476
  requestConfig.headers.Nonce = headers.nonce;
473
477
  }
474
- if (headers.cartToken && !isAuthenticated) {
478
+ if (headers.cartToken && (!isAuthenticated || isBatchRoute(url))) {
475
479
  requestConfig.headers["Cart-Token"] = headers.cartToken;
476
480
  }
477
481
  requestConfig.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
@@ -500,16 +504,19 @@ var setupRequestInterceptor = (axiosInstance, config, client) => {
500
504
  }
501
505
  );
502
506
  };
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);
507
+ var NONCE_ERROR_CODES = ["woocommerce_rest_missing_nonce", "woocommerce_rest_invalid_nonce"];
508
+ var isNonceRejectedBatch = (data) => {
509
+ const responses = data?.responses;
510
+ if (!Array.isArray(responses) || responses.length === 0) return false;
511
+ return responses.every((response) => {
512
+ const code = response?.body?.code;
513
+ return typeof code === "string" && NONCE_ERROR_CODES.includes(code);
514
+ });
508
515
  };
509
516
  var setupResponseInterceptor = (axiosInstance, config, client) => {
510
517
  axiosInstance.interceptors.response.use(
511
518
  async (response) => {
512
- if (isCartResponse(response.config.url)) {
519
+ if (isCartRoute(response.config.url)) {
513
520
  const nonce = response.headers["nonce"] || response.headers["Nonce"];
514
521
  const cartToken = response.headers["cart-token"] || response.headers["Cart-Token"];
515
522
  if (nonce || cartToken) {
@@ -529,6 +536,15 @@ var setupResponseInterceptor = (axiosInstance, config, client) => {
529
536
  }
530
537
  }
531
538
  }
539
+ if (isBatchRoute(response.config.url) && isNonceRejectedBatch(response.data)) {
540
+ throw new axios.AxiosError(
541
+ "Batch rejected for a nonce error",
542
+ axios.AxiosError.ERR_BAD_REQUEST,
543
+ response.config,
544
+ response.request,
545
+ { ...response, status: 401, data: { code: "woocommerce_rest_missing_nonce" } }
546
+ );
547
+ }
532
548
  if (config.debug) {
533
549
  debug("Response:", response.config.url, response.status);
534
550
  }
@@ -1026,6 +1042,18 @@ var addToCartInputSchema = zod.z.object({
1026
1042
  })
1027
1043
  ).optional()
1028
1044
  });
1045
+ var addItemsInputSchema = zod.z.object({
1046
+ items: zod.z.array(addToCartInputSchema).min(1)
1047
+ });
1048
+ var batchResponseSchema = zod.z.object({
1049
+ responses: zod.z.array(
1050
+ zod.z.object({
1051
+ status: zod.z.number(),
1052
+ body: zod.z.unknown(),
1053
+ headers: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1054
+ })
1055
+ )
1056
+ });
1029
1057
  var updateCartItemInputSchema = zod.z.object({
1030
1058
  key: zod.z.string(),
1031
1059
  quantity: zod.z.number().int().positive()
@@ -1369,6 +1397,8 @@ var storeProductsSearchParamsSchema = zod.z.object({
1369
1397
  search: zod.z.string().optional(),
1370
1398
  /** Exact slug match */
1371
1399
  slug: zod.z.string().optional(),
1400
+ /** Exact SKU match */
1401
+ sku: zod.z.string().optional(),
1372
1402
  // Date filtering
1373
1403
  /** Filter products published after this date */
1374
1404
  after: zod.z.string().datetime().optional(),
@@ -1383,6 +1413,8 @@ var storeProductsSearchParamsSchema = zod.z.object({
1383
1413
  include: zod.z.array(zod.z.number()).optional(),
1384
1414
  /** Exclude specific product IDs */
1385
1415
  exclude: zod.z.array(zod.z.number()).optional(),
1416
+ /** Return products related to this product ID */
1417
+ related: zod.z.number().optional(),
1386
1418
  // Parent/Child
1387
1419
  /** Filter by parent product IDs */
1388
1420
  parent: zod.z.array(zod.z.number()).optional(),
@@ -1482,6 +1514,19 @@ zod.z.object({
1482
1514
  var ORDER_ATTRIBUTION_NAMESPACE = "woocommerce/order-attribution";
1483
1515
 
1484
1516
  // src/api/cart.ts
1517
+ var BATCH_MAX_REQUESTS = 25;
1518
+ var chunk = (list, size) => Array.from(
1519
+ { length: Math.ceil(list.length / size) },
1520
+ (_, index) => list.slice(index * size, index * size + size)
1521
+ );
1522
+ var isAccepted = (status) => status >= 200 && status < 300;
1523
+ var bodyError = (body) => {
1524
+ const data = body;
1525
+ return {
1526
+ code: typeof data?.code === "string" ? data.code : void 0,
1527
+ message: typeof data?.message === "string" ? data.message : void 0
1528
+ };
1529
+ };
1485
1530
  var createCartAPI = (client, endpoints, options) => ({
1486
1531
  get: async () => {
1487
1532
  const response = await client.get(endpoints.cart);
@@ -1491,6 +1536,48 @@ var createCartAPI = (client, endpoints, options) => ({
1491
1536
  const response = await client.post(endpoints.addItem, input);
1492
1537
  return handleApiResponse(response, cartSchema, options);
1493
1538
  },
1539
+ addItems: async ({ items }) => {
1540
+ const responses = [];
1541
+ let chunkFailed = false;
1542
+ for (const batch of chunk(items, BATCH_MAX_REQUESTS)) {
1543
+ try {
1544
+ const response = await client.post(endpoints.batch, {
1545
+ requests: batch.map((item) => ({
1546
+ method: "POST",
1547
+ path: endpoints.addItem,
1548
+ body: item
1549
+ }))
1550
+ });
1551
+ responses.push(...handleApiResponse(response, batchResponseSchema, options).responses);
1552
+ } catch (error2) {
1553
+ if (responses.length === 0) throw error2;
1554
+ chunkFailed = true;
1555
+ break;
1556
+ }
1557
+ }
1558
+ const results = items.map((item, index) => {
1559
+ const entry = responses[index];
1560
+ const { code, message } = bodyError(entry?.body);
1561
+ return {
1562
+ item,
1563
+ status: entry?.status ?? 500,
1564
+ ...code ? { code } : {},
1565
+ ...message ? { message } : {}
1566
+ };
1567
+ });
1568
+ const lastAccepted = [...responses].reverse().find((entry) => isAccepted(entry.status));
1569
+ const batchCart = lastAccepted ? handleApiData(lastAccepted.body, cartSchema, options, endpoints.batch) : void 0;
1570
+ let cart = chunkFailed ? void 0 : batchCart;
1571
+ if (!cart) {
1572
+ try {
1573
+ cart = handleApiResponse(await client.get(endpoints.cart), cartSchema, options);
1574
+ } catch (error2) {
1575
+ if (!batchCart) throw error2;
1576
+ cart = batchCart;
1577
+ }
1578
+ }
1579
+ return { cart, results };
1580
+ },
1494
1581
  updateItem: async (input) => {
1495
1582
  const response = await client.post(endpoints.updateItem, input);
1496
1583
  return handleApiResponse(response, cartSchema, options);
@@ -1580,6 +1667,8 @@ var createEndpoints = (version = "legacy") => {
1580
1667
  removeCoupon: `${prefix}/cart/remove-coupon`,
1581
1668
  updateCustomer: `${prefix}/cart/update-customer`,
1582
1669
  selectShippingRate: `${prefix}/cart/select-shipping-rate`,
1670
+ // Batch
1671
+ batch: `${prefix}/batch`,
1583
1672
  // Checkout
1584
1673
  checkout: `${prefix}/checkout`,
1585
1674
  // Orders
@@ -2168,6 +2257,7 @@ var schemas = {
2168
2257
 
2169
2258
  exports.WooCommerceApiError = WooCommerceApiError;
2170
2259
  exports.WooCommerceDataValidationError = WooCommerceDataValidationError;
2260
+ exports.addItemsInputSchema = addItemsInputSchema;
2171
2261
  exports.addToCartInputSchema = addToCartInputSchema;
2172
2262
  exports.addressSchema = addressSchema;
2173
2263
  exports.adminCategoryFilterSchema = categoryFilterSchema;
@@ -2178,6 +2268,7 @@ exports.adminOrderSchema = orderSchema;
2178
2268
  exports.adminProductCategorySchema = productCategorySchema2;
2179
2269
  exports.adminProductFilterSchema = productFilterSchema;
2180
2270
  exports.adminProductSchema = productSchema2;
2271
+ exports.batchResponseSchema = batchResponseSchema;
2181
2272
  exports.billingAddressSchema = billingAddressSchemaMandatory;
2182
2273
  exports.cartItemImageSchema = cartItemImageSchema;
2183
2274
  exports.cartSchema = cartSchema;
@@ -2195,6 +2286,7 @@ exports.getAppEnumRaw = getAppEnumRaw;
2195
2286
  exports.getAppEnumZod = getAppEnumZod;
2196
2287
  exports.makeAddressFormSchema = makeAddressFormSchema;
2197
2288
  exports.makeBillingAddressFormSchema = makeBillingAddressFormSchema;
2289
+ exports.mapWooCommerceCode = mapWooCommerceCode;
2198
2290
  exports.paginationMetaSchema = paginationMetaSchema;
2199
2291
  exports.paginationParamsSchema = paginationParamsSchema;
2200
2292
  exports.paymentMethodFilterSchema = paymentMethodFilterSchema;