@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.
package/dist/index.mjs CHANGED
@@ -1,7 +1,14 @@
1
- import axios, { isAxiosError } from 'axios';
1
+ import axios, { AxiosError, isCancel, isAxiosError } from 'axios';
2
+ import { BaseError as BaseError$1 } from '@atomic-solutions/errors';
2
3
  import { z } from 'zod';
3
4
 
4
- // src/client/createClient.ts
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
9
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
10
+ 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);
11
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
5
12
 
6
13
  // src/errors/classes/BaseError.ts
7
14
  var BaseError = class extends Error {
@@ -47,6 +54,7 @@ Caused by: ${this.options.cause.stack}`;
47
54
 
48
55
  // src/errors/mapping/mapWooCommerceCode.ts
49
56
  var mapWooCommerceCode = (apiCode) => {
57
+ if (!apiCode) return "unknown_error";
50
58
  const directMatches = {
51
59
  woocommerce_rest_cart_empty: "cart_empty",
52
60
  woocommerce_rest_cart_coupon_error: "invalid_coupon",
@@ -130,8 +138,16 @@ var WOOCOMMERCE_ERROR_METADATA = {
130
138
  }
131
139
  };
132
140
 
141
+ // src/errors/classes/http-error.ts
142
+ 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("/");
143
+ var classifyHttpStatus = (status) => ({
144
+ domain: status === void 0 ? "network" : status === 401 || status === 403 ? "auth" : "integration",
145
+ severity: status !== void 0 && status < 500 ? "warning" : "error"
146
+ });
147
+
133
148
  // src/errors/classes/WooCommerceApiError.ts
134
- var WooCommerceApiError = class extends BaseError {
149
+ var _options;
150
+ var WooCommerceApiError = class extends BaseError$1 {
135
151
  constructor(options) {
136
152
  const metadata = WOOCOMMERCE_ERROR_METADATA[options.code] ?? {
137
153
  operation: "woocommerce_api_request",
@@ -143,7 +159,30 @@ var WooCommerceApiError = class extends BaseError {
143
159
  userMessage: options.userMessage || metadata.userMessage,
144
160
  retryable: options.retryable ?? metadata.retryable
145
161
  };
146
- super(enrichedOptions);
162
+ const status = enrichedOptions.statusCode;
163
+ const { domain, severity } = classifyHttpStatus(status);
164
+ super({
165
+ domain,
166
+ severity,
167
+ operation: enrichedOptions.operation,
168
+ message: enrichedOptions.message,
169
+ code: enrichedOptions.code,
170
+ retryable: enrichedOptions.retryable,
171
+ userMessage: enrichedOptions.userMessage,
172
+ cause: enrichedOptions.cause,
173
+ fingerprint: [domain, enrichedOptions.code, String(status ?? "network")],
174
+ extras: {
175
+ originalCode: enrichedOptions.originalCode,
176
+ statusCode: status,
177
+ path: redactPath(enrichedOptions.url),
178
+ method: enrichedOptions.method
179
+ }
180
+ });
181
+ __privateAdd(this, _options);
182
+ __privateSet(this, _options, enrichedOptions);
183
+ }
184
+ get options() {
185
+ return __privateGet(this, _options);
147
186
  }
148
187
  // Convenient getters for frequently accessed fields (no duplication)
149
188
  get wooCode() {
@@ -170,24 +209,8 @@ var WooCommerceApiError = class extends BaseError {
170
209
  get data() {
171
210
  return this.options.data;
172
211
  }
173
- /**
174
- * Get reportable data for error tracking/logging
175
- * Includes HTTP context and WooCommerce-specific fields
176
- */
177
- getReportableData() {
178
- return {
179
- code: this.options.code,
180
- operation: this.options.operation,
181
- userMessage: this.options.userMessage,
182
- retryable: this.options.retryable,
183
- statusCode: this.options.statusCode,
184
- url: this.options.url,
185
- method: this.options.method,
186
- wooCode: this.options.code,
187
- originalCode: this.options.originalCode
188
- };
189
- }
190
212
  };
213
+ _options = new WeakMap();
191
214
 
192
215
  // src/errors/classes/WooCommerceDataValidationError.ts
193
216
  var WooCommerceDataValidationError = class _WooCommerceDataValidationError extends BaseError {
@@ -278,34 +301,34 @@ var WooCommerceDataValidationError = class _WooCommerceDataValidationError exten
278
301
  };
279
302
 
280
303
  // src/http/handleApiResponse.ts
281
- var handleApiResponse = (response, schema, options) => {
304
+ var handleApiData = (data, schema, options, url) => {
282
305
  const validationMode = options?.validationMode ?? "strict";
283
306
  if (validationMode === "warn") {
284
- const result = schema.safeParse(response.data);
307
+ const result = schema.safeParse(data);
285
308
  if (!result.success) {
286
309
  const validationError = new WooCommerceDataValidationError({
287
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
288
- url: response.config.url,
310
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
311
+ url,
289
312
  zodError: result.error,
290
- value: response.data,
313
+ value: data,
291
314
  userMessage: "Received unexpected data from WooCommerce"
292
315
  });
293
316
  options?.onValidationError?.(validationError);
294
317
  options?.errorReporter?.(validationError);
295
318
  console.warn("[woocommerce-utils] Validation warning:", validationError.message);
296
- return response.data;
319
+ return data;
297
320
  }
298
321
  return result.data;
299
322
  }
300
323
  try {
301
- return schema.parse(response.data);
324
+ return schema.parse(data);
302
325
  } catch (error2) {
303
326
  if (error2 && typeof error2 === "object" && "issues" in error2) {
304
327
  const validationError = new WooCommerceDataValidationError({
305
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
306
- url: response.config.url,
328
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
329
+ url,
307
330
  zodError: error2,
308
- value: response.data,
331
+ value: data,
309
332
  userMessage: "Received unexpected data from WooCommerce"
310
333
  });
311
334
  options?.onValidationError?.(validationError);
@@ -315,6 +338,7 @@ var handleApiResponse = (response, schema, options) => {
315
338
  throw error2;
316
339
  }
317
340
  };
341
+ var handleApiResponse = (response, schema, options) => handleApiData(response.data, schema, options, response.config.url);
318
342
 
319
343
  // src/utils/calculatePagination.ts
320
344
  var calculatePagination = (input) => {
@@ -429,6 +453,12 @@ var error = (...args) => {
429
453
  console.error(DEBUG_PREFIX, ...args);
430
454
  };
431
455
 
456
+ // src/http/storeRoutes.ts
457
+ var CART_ROUTE = /\/wc\/store\/(v\d+\/)?(cart|checkout|batch)/;
458
+ var BATCH_ROUTE = /\/wc\/store\/(v\d+\/)?batch/;
459
+ var isCartRoute = (url) => !!url && CART_ROUTE.test(url);
460
+ var isBatchRoute = (url) => !!url && BATCH_ROUTE.test(url);
461
+
432
462
  // src/http/interceptors/request.ts
433
463
  var requiresAuth = (url) => {
434
464
  if (!url) return false;
@@ -442,30 +472,26 @@ var requiresAuth = (url) => {
442
472
  ];
443
473
  return authEndpoints.some((endpoint) => url.includes(endpoint));
444
474
  };
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
475
  var setupRequestInterceptor = (axiosInstance, config, client) => {
450
476
  axiosInstance.interceptors.request.use(
451
477
  async (requestConfig) => {
452
478
  const url = requestConfig.url;
453
479
  if (config.jwtToken) {
454
480
  const token = await config.jwtToken.get();
455
- if (token && (requiresAuth(url) || requiresCartAuth(url))) {
481
+ if (token && (requiresAuth(url) || isCartRoute(url))) {
456
482
  requestConfig.headers.Authorization = `Bearer ${token}`;
457
483
  if (config.debug) {
458
484
  debug("Injected JWT token for", url);
459
485
  }
460
486
  }
461
487
  }
462
- if (requiresCartAuth(url)) {
488
+ if (isCartRoute(url)) {
463
489
  const headers = await config.cartHeaders.get();
464
490
  const isAuthenticated = !!await config.jwtToken?.get();
465
491
  if (headers.nonce) {
466
492
  requestConfig.headers.Nonce = headers.nonce;
467
493
  }
468
- if (headers.cartToken && !isAuthenticated) {
494
+ if (headers.cartToken && (!isAuthenticated || isBatchRoute(url))) {
469
495
  requestConfig.headers["Cart-Token"] = headers.cartToken;
470
496
  }
471
497
  requestConfig.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
@@ -494,16 +520,19 @@ var setupRequestInterceptor = (axiosInstance, config, client) => {
494
520
  }
495
521
  );
496
522
  };
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);
523
+ var NONCE_ERROR_CODES = ["woocommerce_rest_missing_nonce", "woocommerce_rest_invalid_nonce"];
524
+ var isNonceRejectedBatch = (data) => {
525
+ const responses = data?.responses;
526
+ if (!Array.isArray(responses) || responses.length === 0) return false;
527
+ return responses.every((response) => {
528
+ const code = response?.body?.code;
529
+ return typeof code === "string" && NONCE_ERROR_CODES.includes(code);
530
+ });
502
531
  };
503
532
  var setupResponseInterceptor = (axiosInstance, config, client) => {
504
533
  axiosInstance.interceptors.response.use(
505
534
  async (response) => {
506
- if (isCartResponse(response.config.url)) {
535
+ if (isCartRoute(response.config.url)) {
507
536
  const nonce = response.headers["nonce"] || response.headers["Nonce"];
508
537
  const cartToken = response.headers["cart-token"] || response.headers["Cart-Token"];
509
538
  if (nonce || cartToken) {
@@ -523,6 +552,15 @@ var setupResponseInterceptor = (axiosInstance, config, client) => {
523
552
  }
524
553
  }
525
554
  }
555
+ if (isBatchRoute(response.config.url) && isNonceRejectedBatch(response.data)) {
556
+ throw new AxiosError(
557
+ "Batch rejected for a nonce error",
558
+ AxiosError.ERR_BAD_REQUEST,
559
+ response.config,
560
+ response.request,
561
+ { ...response, status: 401, data: { code: "woocommerce_rest_missing_nonce" } }
562
+ );
563
+ }
526
564
  if (config.debug) {
527
565
  debug("Response:", response.config.url, response.status);
528
566
  }
@@ -565,12 +603,15 @@ var setupErrorInterceptor = (axiosInstance, config, client) => {
565
603
  axiosInstance.interceptors.response.use(
566
604
  (response) => response,
567
605
  async (axiosError) => {
568
- const status = axiosError.response?.status || 500;
606
+ if (isCancel(axiosError)) {
607
+ return Promise.reject(axiosError);
608
+ }
609
+ const status = axiosError.response?.status;
569
610
  const errorData = axiosError.response?.data;
611
+ const isNetworkError = isAxiosError(axiosError) && !axiosError.response;
570
612
  const apiCode = errorData?.code || "unknown_error";
571
613
  const wpMessage = errorData?.message || axiosError.message || "Unknown error";
572
- const errorCode = mapWooCommerceCode(apiCode);
573
- const errorMetadata = WOOCOMMERCE_ERROR_METADATA[errorCode];
614
+ const errorCode = isNetworkError ? "network_error" : mapWooCommerceCode(apiCode);
574
615
  const wcError = new WooCommerceApiError({
575
616
  message: wpMessage,
576
617
  statusCode: status,
@@ -578,9 +619,6 @@ var setupErrorInterceptor = (axiosInstance, config, client) => {
578
619
  // Transformed app-level code
579
620
  originalCode: apiCode,
580
621
  // Original WooCommerce API code
581
- retryable: errorMetadata.retryable,
582
- userMessage: errorMetadata.userMessage,
583
- operation: errorMetadata.operation,
584
622
  url: axiosError.config?.url || "",
585
623
  method: axiosError.config?.method?.toUpperCase() || "GET",
586
624
  requestBody: axiosError.config?.data,
@@ -1020,6 +1058,18 @@ var addToCartInputSchema = z.object({
1020
1058
  })
1021
1059
  ).optional()
1022
1060
  });
1061
+ var addItemsInputSchema = z.object({
1062
+ items: z.array(addToCartInputSchema).min(1)
1063
+ });
1064
+ var batchResponseSchema = z.object({
1065
+ responses: z.array(
1066
+ z.object({
1067
+ status: z.number(),
1068
+ body: z.unknown(),
1069
+ headers: z.record(z.string(), z.unknown()).optional()
1070
+ })
1071
+ )
1072
+ });
1023
1073
  var updateCartItemInputSchema = z.object({
1024
1074
  key: z.string(),
1025
1075
  quantity: z.number().int().positive()
@@ -1363,6 +1413,8 @@ var storeProductsSearchParamsSchema = z.object({
1363
1413
  search: z.string().optional(),
1364
1414
  /** Exact slug match */
1365
1415
  slug: z.string().optional(),
1416
+ /** Exact SKU match */
1417
+ sku: z.string().optional(),
1366
1418
  // Date filtering
1367
1419
  /** Filter products published after this date */
1368
1420
  after: z.string().datetime().optional(),
@@ -1377,6 +1429,8 @@ var storeProductsSearchParamsSchema = z.object({
1377
1429
  include: z.array(z.number()).optional(),
1378
1430
  /** Exclude specific product IDs */
1379
1431
  exclude: z.array(z.number()).optional(),
1432
+ /** Return products related to this product ID */
1433
+ related: z.number().optional(),
1380
1434
  // Parent/Child
1381
1435
  /** Filter by parent product IDs */
1382
1436
  parent: z.array(z.number()).optional(),
@@ -1476,6 +1530,19 @@ z.object({
1476
1530
  var ORDER_ATTRIBUTION_NAMESPACE = "woocommerce/order-attribution";
1477
1531
 
1478
1532
  // src/api/cart.ts
1533
+ var BATCH_MAX_REQUESTS = 25;
1534
+ var chunk = (list, size) => Array.from(
1535
+ { length: Math.ceil(list.length / size) },
1536
+ (_, index) => list.slice(index * size, index * size + size)
1537
+ );
1538
+ var isAccepted = (status) => status >= 200 && status < 300;
1539
+ var bodyError = (body) => {
1540
+ const data = body;
1541
+ return {
1542
+ code: typeof data?.code === "string" ? data.code : void 0,
1543
+ message: typeof data?.message === "string" ? data.message : void 0
1544
+ };
1545
+ };
1479
1546
  var createCartAPI = (client, endpoints, options) => ({
1480
1547
  get: async () => {
1481
1548
  const response = await client.get(endpoints.cart);
@@ -1485,6 +1552,48 @@ var createCartAPI = (client, endpoints, options) => ({
1485
1552
  const response = await client.post(endpoints.addItem, input);
1486
1553
  return handleApiResponse(response, cartSchema, options);
1487
1554
  },
1555
+ addItems: async ({ items }) => {
1556
+ const responses = [];
1557
+ let chunkFailed = false;
1558
+ for (const batch of chunk(items, BATCH_MAX_REQUESTS)) {
1559
+ try {
1560
+ const response = await client.post(endpoints.batch, {
1561
+ requests: batch.map((item) => ({
1562
+ method: "POST",
1563
+ path: endpoints.addItem,
1564
+ body: item
1565
+ }))
1566
+ });
1567
+ responses.push(...handleApiResponse(response, batchResponseSchema, options).responses);
1568
+ } catch (error2) {
1569
+ if (responses.length === 0) throw error2;
1570
+ chunkFailed = true;
1571
+ break;
1572
+ }
1573
+ }
1574
+ const results = items.map((item, index) => {
1575
+ const entry = responses[index];
1576
+ const { code, message } = bodyError(entry?.body);
1577
+ return {
1578
+ item,
1579
+ status: entry?.status ?? 500,
1580
+ ...code ? { code } : {},
1581
+ ...message ? { message } : {}
1582
+ };
1583
+ });
1584
+ const lastAccepted = [...responses].reverse().find((entry) => isAccepted(entry.status));
1585
+ const batchCart = lastAccepted ? handleApiData(lastAccepted.body, cartSchema, options, endpoints.batch) : void 0;
1586
+ let cart = chunkFailed ? void 0 : batchCart;
1587
+ if (!cart) {
1588
+ try {
1589
+ cart = handleApiResponse(await client.get(endpoints.cart), cartSchema, options);
1590
+ } catch (error2) {
1591
+ if (!batchCart) throw error2;
1592
+ cart = batchCart;
1593
+ }
1594
+ }
1595
+ return { cart, results };
1596
+ },
1488
1597
  updateItem: async (input) => {
1489
1598
  const response = await client.post(endpoints.updateItem, input);
1490
1599
  return handleApiResponse(response, cartSchema, options);
@@ -1574,6 +1683,8 @@ var createEndpoints = (version = "legacy") => {
1574
1683
  removeCoupon: `${prefix}/cart/remove-coupon`,
1575
1684
  updateCustomer: `${prefix}/cart/update-customer`,
1576
1685
  selectShippingRate: `${prefix}/cart/select-shipping-rate`,
1686
+ // Batch
1687
+ batch: `${prefix}/batch`,
1577
1688
  // Checkout
1578
1689
  checkout: `${prefix}/checkout`,
1579
1690
  // Orders
@@ -2160,6 +2271,6 @@ var schemas = {
2160
2271
  }
2161
2272
  };
2162
2273
 
2163
- export { WooCommerceApiError, WooCommerceDataValidationError, addToCartInputSchema, addressSchema, categoryFilterSchema as adminCategoryFilterSchema, couponFilterSchema as adminCouponFilterSchema, couponSchema as adminCouponSchema, orderFilterSchema as adminOrderFilterSchema, orderSchema as adminOrderSchema, productCategorySchema2 as adminProductCategorySchema, productFilterSchema as adminProductFilterSchema, productSchema2 as adminProductSchema, billingAddressSchemaMandatory as billingAddressSchema, cartItemImageSchema, cartSchema, checkoutInputSchema, checkoutSchema, couponInputSchema, createCategorySchema, createClient, createCouponSchema, createCustomerSchema, createPaginatedSchema, customerFilterSchema, customerSchema, getAppEnumRaw, getAppEnumZod, makeAddressFormSchema, makeBillingAddressFormSchema, paginationMetaSchema, paginationParamsSchema, paymentMethodFilterSchema, paymentMethodSchema, productCategorySchema, productImageSchema, productSchema, removeCartItemInputSchema, schemas, storeProductsSearchParamsSchema as searchParamsSchema, selectShippingRateInputSchema, storeApiOrderSchema, updateCartItemInputSchema, updateCustomerInputSchema, updateCustomerSchema, wcBaseParamsSchema, wcErrorResponseSchema, wcPaginationMetaSchema };
2274
+ export { WooCommerceApiError, WooCommerceDataValidationError, addItemsInputSchema, addToCartInputSchema, addressSchema, categoryFilterSchema as adminCategoryFilterSchema, couponFilterSchema as adminCouponFilterSchema, couponSchema as adminCouponSchema, orderFilterSchema as adminOrderFilterSchema, orderSchema as adminOrderSchema, productCategorySchema2 as adminProductCategorySchema, productFilterSchema as adminProductFilterSchema, productSchema2 as adminProductSchema, batchResponseSchema, billingAddressSchemaMandatory as billingAddressSchema, cartItemImageSchema, cartSchema, checkoutInputSchema, checkoutSchema, couponInputSchema, createCategorySchema, createClient, createCouponSchema, createCustomerSchema, createPaginatedSchema, customerFilterSchema, customerSchema, getAppEnumRaw, getAppEnumZod, makeAddressFormSchema, makeBillingAddressFormSchema, mapWooCommerceCode, paginationMetaSchema, paginationParamsSchema, paymentMethodFilterSchema, paymentMethodSchema, productCategorySchema, productImageSchema, productSchema, removeCartItemInputSchema, schemas, storeProductsSearchParamsSchema as searchParamsSchema, selectShippingRateInputSchema, storeApiOrderSchema, updateCartItemInputSchema, updateCustomerInputSchema, updateCustomerSchema, wcBaseParamsSchema, wcErrorResponseSchema, wcPaginationMetaSchema };
2164
2275
  //# sourceMappingURL=index.mjs.map
2165
2276
  //# sourceMappingURL=index.mjs.map