@behio/storefront-sdk 0.3.0 → 0.4.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/README.md +1 -1
- package/dist/{chunk-45FTJKSW.mjs → chunk-L5KVNLTD.mjs} +158 -64
- package/dist/{chunk-ZEPWNT56.js → chunk-YLKO3SUR.js} +161 -67
- package/dist/index.d.mts +145 -89
- package/dist/index.d.ts +145 -89
- package/dist/index.js +8 -2
- package/dist/index.mjs +9 -3
- package/dist/react.d.mts +3 -3
- package/dist/react.d.ts +3 -3
- package/dist/react.js +80 -64
- package/dist/react.mjs +79 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ function ProductList() {
|
|
|
83
83
|
| `customer` | Profile, addresses, password change |
|
|
84
84
|
| `wishlist` | Add, remove, check |
|
|
85
85
|
| `reviews` | Submit, list, vote helpful |
|
|
86
|
-
| `addresses` |
|
|
86
|
+
| `addresses` | Address autocomplete with debounce hook |
|
|
87
87
|
| `returns` | Submit return requests |
|
|
88
88
|
| `consent` | Cookie consent (GDPR) |
|
|
89
89
|
| `quotes` | B2B quote requests |
|
|
@@ -72,6 +72,40 @@ var BehioNetworkError = class extends Error {
|
|
|
72
72
|
this.code = isTimeout ? "TIMEOUT" : "NETWORK_ERROR";
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
+
function ok(data) {
|
|
76
|
+
return { data, error: null };
|
|
77
|
+
}
|
|
78
|
+
function err(error) {
|
|
79
|
+
return { data: null, error };
|
|
80
|
+
}
|
|
81
|
+
function toSdkError(err2) {
|
|
82
|
+
if (err2 instanceof BehioApiError) {
|
|
83
|
+
return {
|
|
84
|
+
code: err2.code,
|
|
85
|
+
message: err2.message,
|
|
86
|
+
status: err2.status,
|
|
87
|
+
body: err2.body,
|
|
88
|
+
isRetryable: err2.isRetryable,
|
|
89
|
+
cause: err2
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (err2 instanceof BehioNetworkError) {
|
|
93
|
+
return {
|
|
94
|
+
code: err2.code,
|
|
95
|
+
message: err2.message,
|
|
96
|
+
status: null,
|
|
97
|
+
isRetryable: err2.isRetryable,
|
|
98
|
+
cause: err2
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
code: "UNKNOWN",
|
|
103
|
+
message: err2 instanceof Error ? err2.message : String(err2),
|
|
104
|
+
status: null,
|
|
105
|
+
isRetryable: false,
|
|
106
|
+
cause: err2
|
|
107
|
+
};
|
|
108
|
+
}
|
|
75
109
|
|
|
76
110
|
// src/client.ts
|
|
77
111
|
var BehioStorefront = class {
|
|
@@ -197,11 +231,17 @@ var BehioStorefront = class {
|
|
|
197
231
|
this.isRefreshing = true;
|
|
198
232
|
this.refreshPromise = (async () => {
|
|
199
233
|
try {
|
|
200
|
-
await this.auth.refresh();
|
|
234
|
+
const res = await this.auth.refresh();
|
|
235
|
+
if (res.error) {
|
|
236
|
+
this.clearTokens();
|
|
237
|
+
this.emit("auth:token-refresh-failed");
|
|
238
|
+
throw new BehioApiError(401, null, "Token refresh failed");
|
|
239
|
+
}
|
|
201
240
|
this.emit("auth:token-refresh");
|
|
202
|
-
} catch {
|
|
241
|
+
} catch (err2) {
|
|
203
242
|
this.clearTokens();
|
|
204
243
|
this.emit("auth:token-refresh-failed");
|
|
244
|
+
if (err2 instanceof BehioApiError) throw err2;
|
|
205
245
|
throw new BehioApiError(401, null, "Token refresh failed");
|
|
206
246
|
} finally {
|
|
207
247
|
this.isRefreshing = false;
|
|
@@ -210,9 +250,29 @@ var BehioStorefront = class {
|
|
|
210
250
|
})();
|
|
211
251
|
return this.refreshPromise;
|
|
212
252
|
}
|
|
213
|
-
// ---
|
|
214
|
-
/**
|
|
253
|
+
// --- Public request wrapper (SdkResult) ---
|
|
254
|
+
/**
|
|
255
|
+
* Every public module method funnels through here. Internally calls
|
|
256
|
+
* `rawRequest` (which throws on failure) and maps thrown errors to
|
|
257
|
+
* `SdkError` so the public surface can return `SdkResult<T>`.
|
|
258
|
+
*
|
|
259
|
+
* @internal — don't call from outside the SDK; use the typed module
|
|
260
|
+
* methods (behio.catalog.*, behio.cart.*, …) instead.
|
|
261
|
+
*/
|
|
215
262
|
async request(method, path, options) {
|
|
263
|
+
try {
|
|
264
|
+
const data = await this.rawRequest(method, path, options);
|
|
265
|
+
return ok(data);
|
|
266
|
+
} catch (err2) {
|
|
267
|
+
return { data: null, error: toSdkError(err2) };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Throws on failure (API error / network / timeout). Kept private so
|
|
272
|
+
* internal auth refresh recursion keeps its existing control flow —
|
|
273
|
+
* public callers must go through `request()` which returns Result.
|
|
274
|
+
*/
|
|
275
|
+
async rawRequest(method, path, options) {
|
|
216
276
|
const params = new URLSearchParams();
|
|
217
277
|
if (options?.query) {
|
|
218
278
|
for (const [key, value] of Object.entries(options.query)) {
|
|
@@ -275,11 +335,11 @@ var BehioStorefront = class {
|
|
|
275
335
|
body: interceptedConfig.body,
|
|
276
336
|
signal: controller.signal
|
|
277
337
|
});
|
|
278
|
-
} catch (
|
|
338
|
+
} catch (err2) {
|
|
279
339
|
clearTimeout(timeoutId);
|
|
280
|
-
const isAbort =
|
|
340
|
+
const isAbort = err2 instanceof DOMException && err2.name === "AbortError";
|
|
281
341
|
const networkErr = new BehioNetworkError(
|
|
282
|
-
isAbort ? "Request timed out" :
|
|
342
|
+
isAbort ? "Request timed out" : err2.message || "Network error",
|
|
283
343
|
isAbort
|
|
284
344
|
);
|
|
285
345
|
this.emit("error", networkErr);
|
|
@@ -305,7 +365,7 @@ var BehioStorefront = class {
|
|
|
305
365
|
if (res.status === 401 && this.refreshToken && options?.auth !== false && !options?._isRetryAfterRefresh) {
|
|
306
366
|
try {
|
|
307
367
|
await this.handleTokenRefresh();
|
|
308
|
-
return this.
|
|
368
|
+
return this.rawRequest(method, path, { ...options, _isRetryAfterRefresh: true });
|
|
309
369
|
} catch {
|
|
310
370
|
this.emit("error", apiError);
|
|
311
371
|
throw apiError;
|
|
@@ -371,7 +431,8 @@ var CatalogModule = class {
|
|
|
371
431
|
/** Get category tree */
|
|
372
432
|
async getCategories(locale) {
|
|
373
433
|
const res = await this.client.request("GET", "/catalog/categories", { query: { locale } });
|
|
374
|
-
|
|
434
|
+
if (res.error) return res;
|
|
435
|
+
return ok({ categories: res.data.categories || res.data.items || [] });
|
|
375
436
|
}
|
|
376
437
|
/** Get category detail by slug */
|
|
377
438
|
async getCategory(slug, locale) {
|
|
@@ -392,7 +453,8 @@ var CatalogModule = class {
|
|
|
392
453
|
/** Get all labels */
|
|
393
454
|
async getLabels(locale) {
|
|
394
455
|
const res = await this.client.request("GET", "/catalog/labels", { query: { locale } });
|
|
395
|
-
|
|
456
|
+
if (res.error) return res;
|
|
457
|
+
return ok({ labels: res.data.labels || res.data.items || [] });
|
|
396
458
|
}
|
|
397
459
|
/** Get featured products */
|
|
398
460
|
async getFeatured(options) {
|
|
@@ -418,7 +480,10 @@ var CatalogModule = class {
|
|
|
418
480
|
}
|
|
419
481
|
/** Cross-sell / related / upsell products for a product */
|
|
420
482
|
async getCrossSell(productSlug) {
|
|
421
|
-
return this.client.request(
|
|
483
|
+
return this.client.request(
|
|
484
|
+
"GET",
|
|
485
|
+
`/catalog/products/${productSlug}/cross-sell`
|
|
486
|
+
);
|
|
422
487
|
}
|
|
423
488
|
/** Active promotions applicable to a product (with countdown end time) */
|
|
424
489
|
async getProductPromotions(productSlug) {
|
|
@@ -435,44 +500,57 @@ var AuthModule = class {
|
|
|
435
500
|
}
|
|
436
501
|
/** Register a new customer */
|
|
437
502
|
async register(input) {
|
|
438
|
-
const
|
|
503
|
+
const res = await this.client.request("POST", "/auth/register", {
|
|
439
504
|
body: input,
|
|
440
505
|
auth: false
|
|
441
506
|
});
|
|
442
|
-
|
|
507
|
+
if (res.error) return res;
|
|
508
|
+
this.client.setTokens(res.data);
|
|
443
509
|
this.client.emit("auth:login", { email: input.email });
|
|
444
|
-
return
|
|
510
|
+
return res;
|
|
445
511
|
}
|
|
446
512
|
/** Login with email and password */
|
|
447
513
|
async login(input) {
|
|
448
|
-
const
|
|
514
|
+
const res = await this.client.request("POST", "/auth/login", {
|
|
449
515
|
body: input,
|
|
450
516
|
auth: false
|
|
451
517
|
});
|
|
452
|
-
|
|
518
|
+
if (res.error) return res;
|
|
519
|
+
this.client.setTokens(res.data);
|
|
453
520
|
this.client.emit("auth:login", { email: input.email });
|
|
454
|
-
return
|
|
521
|
+
return res;
|
|
455
522
|
}
|
|
456
523
|
/** Refresh access token using refresh token */
|
|
457
524
|
async refresh(refreshToken) {
|
|
458
525
|
const token = refreshToken || this.client.getRefreshToken();
|
|
459
|
-
if (!token)
|
|
460
|
-
|
|
526
|
+
if (!token) {
|
|
527
|
+
return {
|
|
528
|
+
data: null,
|
|
529
|
+
error: {
|
|
530
|
+
code: "UNAUTHORIZED",
|
|
531
|
+
message: "No refresh token available",
|
|
532
|
+
status: null,
|
|
533
|
+
isRetryable: false
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
const res = await this.client.request("POST", "/auth/refresh", {
|
|
461
538
|
body: { refreshToken: token },
|
|
462
539
|
auth: false
|
|
463
540
|
});
|
|
464
|
-
|
|
465
|
-
|
|
541
|
+
if (res.error) return res;
|
|
542
|
+
this.client.setTokens(res.data);
|
|
543
|
+
return res;
|
|
466
544
|
}
|
|
467
545
|
/** Logout (invalidate refresh token) */
|
|
468
546
|
async logout(refreshToken) {
|
|
469
547
|
const token = refreshToken || this.client.getRefreshToken();
|
|
470
|
-
const
|
|
548
|
+
const res = await this.client.request("POST", "/auth/logout", {
|
|
471
549
|
body: { refreshToken: token }
|
|
472
550
|
});
|
|
473
551
|
this.client.clearTokens();
|
|
474
552
|
this.client.emit("auth:logout");
|
|
475
|
-
return
|
|
553
|
+
return res;
|
|
476
554
|
}
|
|
477
555
|
/** Request password reset email */
|
|
478
556
|
async forgotPassword(email) {
|
|
@@ -510,90 +588,102 @@ var CartModule = class {
|
|
|
510
588
|
}
|
|
511
589
|
/** Add item to cart */
|
|
512
590
|
async addItem(input) {
|
|
513
|
-
const
|
|
591
|
+
const res = await this.client.request("POST", "/cart/items", {
|
|
514
592
|
body: { whItemId: input.productId, quantity: input.quantity }
|
|
515
593
|
});
|
|
516
|
-
if (
|
|
517
|
-
|
|
594
|
+
if (res.error) return res;
|
|
595
|
+
if (res.data.newSessionToken) {
|
|
596
|
+
this.client.setCartSession(res.data.newSessionToken);
|
|
518
597
|
}
|
|
519
|
-
this.client.emit("cart:updated",
|
|
520
|
-
return
|
|
598
|
+
this.client.emit("cart:updated", res.data);
|
|
599
|
+
return res;
|
|
521
600
|
}
|
|
522
601
|
/** Update item quantity */
|
|
523
602
|
async updateQuantity(itemId, quantity) {
|
|
524
|
-
const
|
|
603
|
+
const res = await this.client.request("PATCH", `/cart/items/${itemId}`, {
|
|
525
604
|
body: { quantity }
|
|
526
605
|
});
|
|
527
|
-
|
|
528
|
-
|
|
606
|
+
if (res.error) return res;
|
|
607
|
+
this.client.emit("cart:updated", res.data);
|
|
608
|
+
return res;
|
|
529
609
|
}
|
|
530
610
|
/** Remove item from cart */
|
|
531
611
|
async removeItem(itemId) {
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
612
|
+
const res = await this.client.request("DELETE", `/cart/items/${itemId}`);
|
|
613
|
+
if (res.error) return res;
|
|
614
|
+
this.client.emit("cart:updated", res.data);
|
|
615
|
+
return res;
|
|
535
616
|
}
|
|
536
617
|
/** Clear entire cart */
|
|
537
618
|
async clear() {
|
|
538
|
-
const
|
|
619
|
+
const res = await this.client.request("DELETE", "/cart");
|
|
620
|
+
if (res.error) return res;
|
|
539
621
|
this.client.emit("cart:cleared");
|
|
540
|
-
return
|
|
622
|
+
return res;
|
|
541
623
|
}
|
|
542
624
|
/** Apply a gift card code to the cart. Balance is deducted at checkout. */
|
|
543
625
|
async applyGiftCard(code) {
|
|
544
|
-
const
|
|
626
|
+
const res = await this.client.request("POST", "/cart/gift-card", {
|
|
545
627
|
body: { code }
|
|
546
628
|
});
|
|
547
|
-
|
|
548
|
-
|
|
629
|
+
if (res.error) return res;
|
|
630
|
+
this.client.emit("cart:updated", res.data);
|
|
631
|
+
return res;
|
|
549
632
|
}
|
|
550
633
|
/** Remove a gift card from the cart */
|
|
551
634
|
async removeGiftCard() {
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
635
|
+
const res = await this.client.request("DELETE", "/cart/gift-card");
|
|
636
|
+
if (res.error) return res;
|
|
637
|
+
this.client.emit("cart:updated", res.data);
|
|
638
|
+
return res;
|
|
555
639
|
}
|
|
556
640
|
/** Add a bundle to the cart (price is locked at the bundle's current price) */
|
|
557
641
|
async addBundle(bundleId, quantity = 1) {
|
|
558
|
-
const
|
|
642
|
+
const res = await this.client.request("POST", "/cart/bundles", {
|
|
559
643
|
body: { bundleId, quantity }
|
|
560
644
|
});
|
|
561
|
-
|
|
562
|
-
|
|
645
|
+
if (res.error) return res;
|
|
646
|
+
this.client.emit("cart:updated", res.data);
|
|
647
|
+
return res;
|
|
563
648
|
}
|
|
564
649
|
/** Update quantity of a bundle already in the cart */
|
|
565
650
|
async updateBundleQuantity(bundleId, quantity) {
|
|
566
|
-
const
|
|
651
|
+
const res = await this.client.request("PATCH", `/cart/bundles/${bundleId}`, {
|
|
567
652
|
body: { quantity }
|
|
568
653
|
});
|
|
569
|
-
|
|
570
|
-
|
|
654
|
+
if (res.error) return res;
|
|
655
|
+
this.client.emit("cart:updated", res.data);
|
|
656
|
+
return res;
|
|
571
657
|
}
|
|
572
658
|
/** Remove a bundle from the cart */
|
|
573
659
|
async removeBundle(bundleId) {
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
660
|
+
const res = await this.client.request("DELETE", `/cart/bundles/${bundleId}`);
|
|
661
|
+
if (res.error) return res;
|
|
662
|
+
this.client.emit("cart:updated", res.data);
|
|
663
|
+
return res;
|
|
577
664
|
}
|
|
578
665
|
/** Merge anonymous cart into authenticated customer cart */
|
|
579
666
|
async merge() {
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
667
|
+
const res = await this.client.request("POST", "/cart/merge");
|
|
668
|
+
if (res.error) return res;
|
|
669
|
+
this.client.emit("cart:updated", res.data);
|
|
670
|
+
return res;
|
|
583
671
|
}
|
|
584
672
|
/** Apply discount code */
|
|
585
673
|
async applyDiscount(code) {
|
|
586
|
-
const
|
|
674
|
+
const res = await this.client.request("POST", "/cart/discount", {
|
|
587
675
|
body: { code }
|
|
588
676
|
});
|
|
589
|
-
|
|
590
|
-
|
|
677
|
+
if (res.error) return res;
|
|
678
|
+
this.client.emit("cart:updated", res.data);
|
|
679
|
+
return res;
|
|
591
680
|
}
|
|
592
681
|
/** Remove discount code */
|
|
593
682
|
async removeDiscount() {
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
683
|
+
const res = await this.client.request("DELETE", "/cart/discount");
|
|
684
|
+
if (res.error) return res;
|
|
685
|
+
this.client.emit("cart:updated", res.data);
|
|
686
|
+
return res;
|
|
597
687
|
}
|
|
598
688
|
};
|
|
599
689
|
var CheckoutModule = class {
|
|
@@ -602,13 +692,14 @@ var CheckoutModule = class {
|
|
|
602
692
|
}
|
|
603
693
|
/** Create order from cart */
|
|
604
694
|
async createOrder(input) {
|
|
605
|
-
const
|
|
695
|
+
const res = await this.client.request("POST", "/checkout", {
|
|
606
696
|
body: input
|
|
607
697
|
});
|
|
698
|
+
if (res.error) return res;
|
|
608
699
|
this.client.clearCartSession();
|
|
609
|
-
this.client.emit("order:created",
|
|
700
|
+
this.client.emit("order:created", res.data);
|
|
610
701
|
this.client.emit("cart:cleared");
|
|
611
|
-
return
|
|
702
|
+
return res;
|
|
612
703
|
}
|
|
613
704
|
};
|
|
614
705
|
var OrdersModule = class {
|
|
@@ -760,7 +851,7 @@ var AddressModule = class {
|
|
|
760
851
|
}
|
|
761
852
|
/** Search for address suggestions (debounce on your side, or use the React hook) */
|
|
762
853
|
async autocomplete(query, country) {
|
|
763
|
-
if (!query || query.length < 2) return { suggestions: [] };
|
|
854
|
+
if (!query || query.length < 2) return ok({ suggestions: [] });
|
|
764
855
|
return this.client.request("GET", "/addresses/autocomplete", {
|
|
765
856
|
query: { q: query, country }
|
|
766
857
|
});
|
|
@@ -781,5 +872,8 @@ export {
|
|
|
781
872
|
AddressTypes,
|
|
782
873
|
BehioApiError,
|
|
783
874
|
BehioNetworkError,
|
|
875
|
+
ok,
|
|
876
|
+
err,
|
|
877
|
+
toSdkError,
|
|
784
878
|
BehioStorefront
|
|
785
879
|
};
|