@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.js CHANGED
@@ -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 {
@@ -53,6 +60,7 @@ Caused by: ${this.options.cause.stack}`;
53
60
 
54
61
  // src/errors/mapping/mapWooCommerceCode.ts
55
62
  var mapWooCommerceCode = (apiCode) => {
63
+ if (!apiCode) return "unknown_error";
56
64
  const directMatches = {
57
65
  woocommerce_rest_cart_empty: "cart_empty",
58
66
  woocommerce_rest_cart_coupon_error: "invalid_coupon",
@@ -136,8 +144,16 @@ var WOOCOMMERCE_ERROR_METADATA = {
136
144
  }
137
145
  };
138
146
 
147
+ // src/errors/classes/http-error.ts
148
+ 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("/");
149
+ var classifyHttpStatus = (status) => ({
150
+ domain: status === void 0 ? "network" : status === 401 || status === 403 ? "auth" : "integration",
151
+ severity: status !== void 0 && status < 500 ? "warning" : "error"
152
+ });
153
+
139
154
  // src/errors/classes/WooCommerceApiError.ts
140
- var WooCommerceApiError = class extends BaseError {
155
+ var _options;
156
+ var WooCommerceApiError = class extends errors.BaseError {
141
157
  constructor(options) {
142
158
  const metadata = WOOCOMMERCE_ERROR_METADATA[options.code] ?? {
143
159
  operation: "woocommerce_api_request",
@@ -149,7 +165,30 @@ var WooCommerceApiError = class extends BaseError {
149
165
  userMessage: options.userMessage || metadata.userMessage,
150
166
  retryable: options.retryable ?? metadata.retryable
151
167
  };
152
- super(enrichedOptions);
168
+ const status = enrichedOptions.statusCode;
169
+ const { domain, severity } = classifyHttpStatus(status);
170
+ super({
171
+ domain,
172
+ severity,
173
+ operation: enrichedOptions.operation,
174
+ message: enrichedOptions.message,
175
+ code: enrichedOptions.code,
176
+ retryable: enrichedOptions.retryable,
177
+ userMessage: enrichedOptions.userMessage,
178
+ cause: enrichedOptions.cause,
179
+ fingerprint: [domain, enrichedOptions.code, String(status ?? "network")],
180
+ extras: {
181
+ originalCode: enrichedOptions.originalCode,
182
+ statusCode: status,
183
+ path: redactPath(enrichedOptions.url),
184
+ method: enrichedOptions.method
185
+ }
186
+ });
187
+ __privateAdd(this, _options);
188
+ __privateSet(this, _options, enrichedOptions);
189
+ }
190
+ get options() {
191
+ return __privateGet(this, _options);
153
192
  }
154
193
  // Convenient getters for frequently accessed fields (no duplication)
155
194
  get wooCode() {
@@ -176,24 +215,8 @@ var WooCommerceApiError = class extends BaseError {
176
215
  get data() {
177
216
  return this.options.data;
178
217
  }
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
218
  };
219
+ _options = new WeakMap();
197
220
 
198
221
  // src/errors/classes/WooCommerceDataValidationError.ts
199
222
  var WooCommerceDataValidationError = class _WooCommerceDataValidationError extends BaseError {
@@ -284,34 +307,34 @@ var WooCommerceDataValidationError = class _WooCommerceDataValidationError exten
284
307
  };
285
308
 
286
309
  // src/http/handleApiResponse.ts
287
- var handleApiResponse = (response, schema, options) => {
310
+ var handleApiData = (data, schema, options, url) => {
288
311
  const validationMode = options?.validationMode ?? "strict";
289
312
  if (validationMode === "warn") {
290
- const result = schema.safeParse(response.data);
313
+ const result = schema.safeParse(data);
291
314
  if (!result.success) {
292
315
  const validationError = new WooCommerceDataValidationError({
293
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
294
- url: response.config.url,
316
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
317
+ url,
295
318
  zodError: result.error,
296
- value: response.data,
319
+ value: data,
297
320
  userMessage: "Received unexpected data from WooCommerce"
298
321
  });
299
322
  options?.onValidationError?.(validationError);
300
323
  options?.errorReporter?.(validationError);
301
324
  console.warn("[woocommerce-utils] Validation warning:", validationError.message);
302
- return response.data;
325
+ return data;
303
326
  }
304
327
  return result.data;
305
328
  }
306
329
  try {
307
- return schema.parse(response.data);
330
+ return schema.parse(data);
308
331
  } catch (error2) {
309
332
  if (error2 && typeof error2 === "object" && "issues" in error2) {
310
333
  const validationError = new WooCommerceDataValidationError({
311
- message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
312
- url: response.config.url,
334
+ message: `WooCommerce API response validation failed for ${url || "unknown endpoint"}`,
335
+ url,
313
336
  zodError: error2,
314
- value: response.data,
337
+ value: data,
315
338
  userMessage: "Received unexpected data from WooCommerce"
316
339
  });
317
340
  options?.onValidationError?.(validationError);
@@ -321,6 +344,7 @@ var handleApiResponse = (response, schema, options) => {
321
344
  throw error2;
322
345
  }
323
346
  };
347
+ var handleApiResponse = (response, schema, options) => handleApiData(response.data, schema, options, response.config.url);
324
348
 
325
349
  // src/utils/calculatePagination.ts
326
350
  var calculatePagination = (input) => {
@@ -435,6 +459,12 @@ var error = (...args) => {
435
459
  console.error(DEBUG_PREFIX, ...args);
436
460
  };
437
461
 
462
+ // src/http/storeRoutes.ts
463
+ var CART_ROUTE = /\/wc\/store\/(v\d+\/)?(cart|checkout|batch)/;
464
+ var BATCH_ROUTE = /\/wc\/store\/(v\d+\/)?batch/;
465
+ var isCartRoute = (url) => !!url && CART_ROUTE.test(url);
466
+ var isBatchRoute = (url) => !!url && BATCH_ROUTE.test(url);
467
+
438
468
  // src/http/interceptors/request.ts
439
469
  var requiresAuth = (url) => {
440
470
  if (!url) return false;
@@ -448,30 +478,26 @@ var requiresAuth = (url) => {
448
478
  ];
449
479
  return authEndpoints.some((endpoint) => url.includes(endpoint));
450
480
  };
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
481
  var setupRequestInterceptor = (axiosInstance, config, client) => {
456
482
  axiosInstance.interceptors.request.use(
457
483
  async (requestConfig) => {
458
484
  const url = requestConfig.url;
459
485
  if (config.jwtToken) {
460
486
  const token = await config.jwtToken.get();
461
- if (token && (requiresAuth(url) || requiresCartAuth(url))) {
487
+ if (token && (requiresAuth(url) || isCartRoute(url))) {
462
488
  requestConfig.headers.Authorization = `Bearer ${token}`;
463
489
  if (config.debug) {
464
490
  debug("Injected JWT token for", url);
465
491
  }
466
492
  }
467
493
  }
468
- if (requiresCartAuth(url)) {
494
+ if (isCartRoute(url)) {
469
495
  const headers = await config.cartHeaders.get();
470
496
  const isAuthenticated = !!await config.jwtToken?.get();
471
497
  if (headers.nonce) {
472
498
  requestConfig.headers.Nonce = headers.nonce;
473
499
  }
474
- if (headers.cartToken && !isAuthenticated) {
500
+ if (headers.cartToken && (!isAuthenticated || isBatchRoute(url))) {
475
501
  requestConfig.headers["Cart-Token"] = headers.cartToken;
476
502
  }
477
503
  requestConfig.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
@@ -500,16 +526,19 @@ var setupRequestInterceptor = (axiosInstance, config, client) => {
500
526
  }
501
527
  );
502
528
  };
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);
529
+ var NONCE_ERROR_CODES = ["woocommerce_rest_missing_nonce", "woocommerce_rest_invalid_nonce"];
530
+ var isNonceRejectedBatch = (data) => {
531
+ const responses = data?.responses;
532
+ if (!Array.isArray(responses) || responses.length === 0) return false;
533
+ return responses.every((response) => {
534
+ const code = response?.body?.code;
535
+ return typeof code === "string" && NONCE_ERROR_CODES.includes(code);
536
+ });
508
537
  };
509
538
  var setupResponseInterceptor = (axiosInstance, config, client) => {
510
539
  axiosInstance.interceptors.response.use(
511
540
  async (response) => {
512
- if (isCartResponse(response.config.url)) {
541
+ if (isCartRoute(response.config.url)) {
513
542
  const nonce = response.headers["nonce"] || response.headers["Nonce"];
514
543
  const cartToken = response.headers["cart-token"] || response.headers["Cart-Token"];
515
544
  if (nonce || cartToken) {
@@ -529,6 +558,15 @@ var setupResponseInterceptor = (axiosInstance, config, client) => {
529
558
  }
530
559
  }
531
560
  }
561
+ if (isBatchRoute(response.config.url) && isNonceRejectedBatch(response.data)) {
562
+ throw new axios.AxiosError(
563
+ "Batch rejected for a nonce error",
564
+ axios.AxiosError.ERR_BAD_REQUEST,
565
+ response.config,
566
+ response.request,
567
+ { ...response, status: 401, data: { code: "woocommerce_rest_missing_nonce" } }
568
+ );
569
+ }
532
570
  if (config.debug) {
533
571
  debug("Response:", response.config.url, response.status);
534
572
  }
@@ -571,12 +609,15 @@ var setupErrorInterceptor = (axiosInstance, config, client) => {
571
609
  axiosInstance.interceptors.response.use(
572
610
  (response) => response,
573
611
  async (axiosError) => {
574
- const status = axiosError.response?.status || 500;
612
+ if (axios.isCancel(axiosError)) {
613
+ return Promise.reject(axiosError);
614
+ }
615
+ const status = axiosError.response?.status;
575
616
  const errorData = axiosError.response?.data;
617
+ const isNetworkError = axios.isAxiosError(axiosError) && !axiosError.response;
576
618
  const apiCode = errorData?.code || "unknown_error";
577
619
  const wpMessage = errorData?.message || axiosError.message || "Unknown error";
578
- const errorCode = mapWooCommerceCode(apiCode);
579
- const errorMetadata = WOOCOMMERCE_ERROR_METADATA[errorCode];
620
+ const errorCode = isNetworkError ? "network_error" : mapWooCommerceCode(apiCode);
580
621
  const wcError = new WooCommerceApiError({
581
622
  message: wpMessage,
582
623
  statusCode: status,
@@ -584,9 +625,6 @@ var setupErrorInterceptor = (axiosInstance, config, client) => {
584
625
  // Transformed app-level code
585
626
  originalCode: apiCode,
586
627
  // Original WooCommerce API code
587
- retryable: errorMetadata.retryable,
588
- userMessage: errorMetadata.userMessage,
589
- operation: errorMetadata.operation,
590
628
  url: axiosError.config?.url || "",
591
629
  method: axiosError.config?.method?.toUpperCase() || "GET",
592
630
  requestBody: axiosError.config?.data,
@@ -1026,6 +1064,18 @@ var addToCartInputSchema = zod.z.object({
1026
1064
  })
1027
1065
  ).optional()
1028
1066
  });
1067
+ var addItemsInputSchema = zod.z.object({
1068
+ items: zod.z.array(addToCartInputSchema).min(1)
1069
+ });
1070
+ var batchResponseSchema = zod.z.object({
1071
+ responses: zod.z.array(
1072
+ zod.z.object({
1073
+ status: zod.z.number(),
1074
+ body: zod.z.unknown(),
1075
+ headers: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1076
+ })
1077
+ )
1078
+ });
1029
1079
  var updateCartItemInputSchema = zod.z.object({
1030
1080
  key: zod.z.string(),
1031
1081
  quantity: zod.z.number().int().positive()
@@ -1369,6 +1419,8 @@ var storeProductsSearchParamsSchema = zod.z.object({
1369
1419
  search: zod.z.string().optional(),
1370
1420
  /** Exact slug match */
1371
1421
  slug: zod.z.string().optional(),
1422
+ /** Exact SKU match */
1423
+ sku: zod.z.string().optional(),
1372
1424
  // Date filtering
1373
1425
  /** Filter products published after this date */
1374
1426
  after: zod.z.string().datetime().optional(),
@@ -1383,6 +1435,8 @@ var storeProductsSearchParamsSchema = zod.z.object({
1383
1435
  include: zod.z.array(zod.z.number()).optional(),
1384
1436
  /** Exclude specific product IDs */
1385
1437
  exclude: zod.z.array(zod.z.number()).optional(),
1438
+ /** Return products related to this product ID */
1439
+ related: zod.z.number().optional(),
1386
1440
  // Parent/Child
1387
1441
  /** Filter by parent product IDs */
1388
1442
  parent: zod.z.array(zod.z.number()).optional(),
@@ -1482,6 +1536,19 @@ zod.z.object({
1482
1536
  var ORDER_ATTRIBUTION_NAMESPACE = "woocommerce/order-attribution";
1483
1537
 
1484
1538
  // src/api/cart.ts
1539
+ var BATCH_MAX_REQUESTS = 25;
1540
+ var chunk = (list, size) => Array.from(
1541
+ { length: Math.ceil(list.length / size) },
1542
+ (_, index) => list.slice(index * size, index * size + size)
1543
+ );
1544
+ var isAccepted = (status) => status >= 200 && status < 300;
1545
+ var bodyError = (body) => {
1546
+ const data = body;
1547
+ return {
1548
+ code: typeof data?.code === "string" ? data.code : void 0,
1549
+ message: typeof data?.message === "string" ? data.message : void 0
1550
+ };
1551
+ };
1485
1552
  var createCartAPI = (client, endpoints, options) => ({
1486
1553
  get: async () => {
1487
1554
  const response = await client.get(endpoints.cart);
@@ -1491,6 +1558,48 @@ var createCartAPI = (client, endpoints, options) => ({
1491
1558
  const response = await client.post(endpoints.addItem, input);
1492
1559
  return handleApiResponse(response, cartSchema, options);
1493
1560
  },
1561
+ addItems: async ({ items }) => {
1562
+ const responses = [];
1563
+ let chunkFailed = false;
1564
+ for (const batch of chunk(items, BATCH_MAX_REQUESTS)) {
1565
+ try {
1566
+ const response = await client.post(endpoints.batch, {
1567
+ requests: batch.map((item) => ({
1568
+ method: "POST",
1569
+ path: endpoints.addItem,
1570
+ body: item
1571
+ }))
1572
+ });
1573
+ responses.push(...handleApiResponse(response, batchResponseSchema, options).responses);
1574
+ } catch (error2) {
1575
+ if (responses.length === 0) throw error2;
1576
+ chunkFailed = true;
1577
+ break;
1578
+ }
1579
+ }
1580
+ const results = items.map((item, index) => {
1581
+ const entry = responses[index];
1582
+ const { code, message } = bodyError(entry?.body);
1583
+ return {
1584
+ item,
1585
+ status: entry?.status ?? 500,
1586
+ ...code ? { code } : {},
1587
+ ...message ? { message } : {}
1588
+ };
1589
+ });
1590
+ const lastAccepted = [...responses].reverse().find((entry) => isAccepted(entry.status));
1591
+ const batchCart = lastAccepted ? handleApiData(lastAccepted.body, cartSchema, options, endpoints.batch) : void 0;
1592
+ let cart = chunkFailed ? void 0 : batchCart;
1593
+ if (!cart) {
1594
+ try {
1595
+ cart = handleApiResponse(await client.get(endpoints.cart), cartSchema, options);
1596
+ } catch (error2) {
1597
+ if (!batchCart) throw error2;
1598
+ cart = batchCart;
1599
+ }
1600
+ }
1601
+ return { cart, results };
1602
+ },
1494
1603
  updateItem: async (input) => {
1495
1604
  const response = await client.post(endpoints.updateItem, input);
1496
1605
  return handleApiResponse(response, cartSchema, options);
@@ -1580,6 +1689,8 @@ var createEndpoints = (version = "legacy") => {
1580
1689
  removeCoupon: `${prefix}/cart/remove-coupon`,
1581
1690
  updateCustomer: `${prefix}/cart/update-customer`,
1582
1691
  selectShippingRate: `${prefix}/cart/select-shipping-rate`,
1692
+ // Batch
1693
+ batch: `${prefix}/batch`,
1583
1694
  // Checkout
1584
1695
  checkout: `${prefix}/checkout`,
1585
1696
  // Orders
@@ -2168,6 +2279,7 @@ var schemas = {
2168
2279
 
2169
2280
  exports.WooCommerceApiError = WooCommerceApiError;
2170
2281
  exports.WooCommerceDataValidationError = WooCommerceDataValidationError;
2282
+ exports.addItemsInputSchema = addItemsInputSchema;
2171
2283
  exports.addToCartInputSchema = addToCartInputSchema;
2172
2284
  exports.addressSchema = addressSchema;
2173
2285
  exports.adminCategoryFilterSchema = categoryFilterSchema;
@@ -2178,6 +2290,7 @@ exports.adminOrderSchema = orderSchema;
2178
2290
  exports.adminProductCategorySchema = productCategorySchema2;
2179
2291
  exports.adminProductFilterSchema = productFilterSchema;
2180
2292
  exports.adminProductSchema = productSchema2;
2293
+ exports.batchResponseSchema = batchResponseSchema;
2181
2294
  exports.billingAddressSchema = billingAddressSchemaMandatory;
2182
2295
  exports.cartItemImageSchema = cartItemImageSchema;
2183
2296
  exports.cartSchema = cartSchema;
@@ -2195,6 +2308,7 @@ exports.getAppEnumRaw = getAppEnumRaw;
2195
2308
  exports.getAppEnumZod = getAppEnumZod;
2196
2309
  exports.makeAddressFormSchema = makeAddressFormSchema;
2197
2310
  exports.makeBillingAddressFormSchema = makeBillingAddressFormSchema;
2311
+ exports.mapWooCommerceCode = mapWooCommerceCode;
2198
2312
  exports.paginationMetaSchema = paginationMetaSchema;
2199
2313
  exports.paginationParamsSchema = paginationParamsSchema;
2200
2314
  exports.paymentMethodFilterSchema = paymentMethodFilterSchema;