@behio/storefront-sdk 0.2.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.
@@ -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 {
@@ -107,6 +141,7 @@ var BehioStorefront = class {
107
141
  this.returns = new ReturnsModule(this);
108
142
  this.consent = new ConsentModule(this);
109
143
  this.quotes = new QuotesModule(this);
144
+ this.addresses = new AddressModule(this);
110
145
  }
111
146
  // --- Public methods ---
112
147
  /** Get basic shop info */
@@ -196,11 +231,17 @@ var BehioStorefront = class {
196
231
  this.isRefreshing = true;
197
232
  this.refreshPromise = (async () => {
198
233
  try {
199
- 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
+ }
200
240
  this.emit("auth:token-refresh");
201
- } catch {
241
+ } catch (err2) {
202
242
  this.clearTokens();
203
243
  this.emit("auth:token-refresh-failed");
244
+ if (err2 instanceof BehioApiError) throw err2;
204
245
  throw new BehioApiError(401, null, "Token refresh failed");
205
246
  } finally {
206
247
  this.isRefreshing = false;
@@ -209,9 +250,29 @@ var BehioStorefront = class {
209
250
  })();
210
251
  return this.refreshPromise;
211
252
  }
212
- // --- Internal fetch ---
213
- /** @internal */
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
+ */
214
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) {
215
276
  const params = new URLSearchParams();
216
277
  if (options?.query) {
217
278
  for (const [key, value] of Object.entries(options.query)) {
@@ -274,11 +335,11 @@ var BehioStorefront = class {
274
335
  body: interceptedConfig.body,
275
336
  signal: controller.signal
276
337
  });
277
- } catch (err) {
338
+ } catch (err2) {
278
339
  clearTimeout(timeoutId);
279
- const isAbort = err instanceof DOMException && err.name === "AbortError";
340
+ const isAbort = err2 instanceof DOMException && err2.name === "AbortError";
280
341
  const networkErr = new BehioNetworkError(
281
- isAbort ? "Request timed out" : err.message || "Network error",
342
+ isAbort ? "Request timed out" : err2.message || "Network error",
282
343
  isAbort
283
344
  );
284
345
  this.emit("error", networkErr);
@@ -304,7 +365,7 @@ var BehioStorefront = class {
304
365
  if (res.status === 401 && this.refreshToken && options?.auth !== false && !options?._isRetryAfterRefresh) {
305
366
  try {
306
367
  await this.handleTokenRefresh();
307
- return this.request(method, path, { ...options, _isRetryAfterRefresh: true });
368
+ return this.rawRequest(method, path, { ...options, _isRetryAfterRefresh: true });
308
369
  } catch {
309
370
  this.emit("error", apiError);
310
371
  throw apiError;
@@ -370,7 +431,8 @@ var CatalogModule = class {
370
431
  /** Get category tree */
371
432
  async getCategories(locale) {
372
433
  const res = await this.client.request("GET", "/catalog/categories", { query: { locale } });
373
- return { categories: res.categories || res.items || [] };
434
+ if (res.error) return res;
435
+ return ok({ categories: res.data.categories || res.data.items || [] });
374
436
  }
375
437
  /** Get category detail by slug */
376
438
  async getCategory(slug, locale) {
@@ -391,7 +453,8 @@ var CatalogModule = class {
391
453
  /** Get all labels */
392
454
  async getLabels(locale) {
393
455
  const res = await this.client.request("GET", "/catalog/labels", { query: { locale } });
394
- return { labels: res.labels || res.items || [] };
456
+ if (res.error) return res;
457
+ return ok({ labels: res.data.labels || res.data.items || [] });
395
458
  }
396
459
  /** Get featured products */
397
460
  async getFeatured(options) {
@@ -417,7 +480,10 @@ var CatalogModule = class {
417
480
  }
418
481
  /** Cross-sell / related / upsell products for a product */
419
482
  async getCrossSell(productSlug) {
420
- return this.client.request("GET", `/catalog/products/${productSlug}/cross-sell`);
483
+ return this.client.request(
484
+ "GET",
485
+ `/catalog/products/${productSlug}/cross-sell`
486
+ );
421
487
  }
422
488
  /** Active promotions applicable to a product (with countdown end time) */
423
489
  async getProductPromotions(productSlug) {
@@ -434,44 +500,57 @@ var AuthModule = class {
434
500
  }
435
501
  /** Register a new customer */
436
502
  async register(input) {
437
- const tokens = await this.client.request("POST", "/auth/register", {
503
+ const res = await this.client.request("POST", "/auth/register", {
438
504
  body: input,
439
505
  auth: false
440
506
  });
441
- this.client.setTokens(tokens);
507
+ if (res.error) return res;
508
+ this.client.setTokens(res.data);
442
509
  this.client.emit("auth:login", { email: input.email });
443
- return tokens;
510
+ return res;
444
511
  }
445
512
  /** Login with email and password */
446
513
  async login(input) {
447
- const tokens = await this.client.request("POST", "/auth/login", {
514
+ const res = await this.client.request("POST", "/auth/login", {
448
515
  body: input,
449
516
  auth: false
450
517
  });
451
- this.client.setTokens(tokens);
518
+ if (res.error) return res;
519
+ this.client.setTokens(res.data);
452
520
  this.client.emit("auth:login", { email: input.email });
453
- return tokens;
521
+ return res;
454
522
  }
455
523
  /** Refresh access token using refresh token */
456
524
  async refresh(refreshToken) {
457
525
  const token = refreshToken || this.client.getRefreshToken();
458
- if (!token) throw new Error("No refresh token available");
459
- const tokens = await this.client.request("POST", "/auth/refresh", {
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", {
460
538
  body: { refreshToken: token },
461
539
  auth: false
462
540
  });
463
- this.client.setTokens(tokens);
464
- return tokens;
541
+ if (res.error) return res;
542
+ this.client.setTokens(res.data);
543
+ return res;
465
544
  }
466
545
  /** Logout (invalidate refresh token) */
467
546
  async logout(refreshToken) {
468
547
  const token = refreshToken || this.client.getRefreshToken();
469
- const result = await this.client.request("POST", "/auth/logout", {
548
+ const res = await this.client.request("POST", "/auth/logout", {
470
549
  body: { refreshToken: token }
471
550
  });
472
551
  this.client.clearTokens();
473
552
  this.client.emit("auth:logout");
474
- return result;
553
+ return res;
475
554
  }
476
555
  /** Request password reset email */
477
556
  async forgotPassword(email) {
@@ -509,90 +588,102 @@ var CartModule = class {
509
588
  }
510
589
  /** Add item to cart */
511
590
  async addItem(input) {
512
- const result = await this.client.request("POST", "/cart/items", {
591
+ const res = await this.client.request("POST", "/cart/items", {
513
592
  body: { whItemId: input.productId, quantity: input.quantity }
514
593
  });
515
- if (result.newSessionToken) {
516
- this.client.setCartSession(result.newSessionToken);
594
+ if (res.error) return res;
595
+ if (res.data.newSessionToken) {
596
+ this.client.setCartSession(res.data.newSessionToken);
517
597
  }
518
- this.client.emit("cart:updated", result);
519
- return result;
598
+ this.client.emit("cart:updated", res.data);
599
+ return res;
520
600
  }
521
601
  /** Update item quantity */
522
602
  async updateQuantity(itemId, quantity) {
523
- const result = await this.client.request("PATCH", `/cart/items/${itemId}`, {
603
+ const res = await this.client.request("PATCH", `/cart/items/${itemId}`, {
524
604
  body: { quantity }
525
605
  });
526
- this.client.emit("cart:updated", result);
527
- return result;
606
+ if (res.error) return res;
607
+ this.client.emit("cart:updated", res.data);
608
+ return res;
528
609
  }
529
610
  /** Remove item from cart */
530
611
  async removeItem(itemId) {
531
- const result = await this.client.request("DELETE", `/cart/items/${itemId}`);
532
- this.client.emit("cart:updated", result);
533
- return result;
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;
534
616
  }
535
617
  /** Clear entire cart */
536
618
  async clear() {
537
- const result = await this.client.request("DELETE", "/cart");
619
+ const res = await this.client.request("DELETE", "/cart");
620
+ if (res.error) return res;
538
621
  this.client.emit("cart:cleared");
539
- return result;
622
+ return res;
540
623
  }
541
624
  /** Apply a gift card code to the cart. Balance is deducted at checkout. */
542
625
  async applyGiftCard(code) {
543
- const result = await this.client.request("POST", "/cart/gift-card", {
626
+ const res = await this.client.request("POST", "/cart/gift-card", {
544
627
  body: { code }
545
628
  });
546
- this.client.emit("cart:updated", result);
547
- return result;
629
+ if (res.error) return res;
630
+ this.client.emit("cart:updated", res.data);
631
+ return res;
548
632
  }
549
633
  /** Remove a gift card from the cart */
550
634
  async removeGiftCard() {
551
- const result = await this.client.request("DELETE", "/cart/gift-card");
552
- this.client.emit("cart:updated", result);
553
- return result;
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;
554
639
  }
555
640
  /** Add a bundle to the cart (price is locked at the bundle's current price) */
556
641
  async addBundle(bundleId, quantity = 1) {
557
- const result = await this.client.request("POST", "/cart/bundles", {
642
+ const res = await this.client.request("POST", "/cart/bundles", {
558
643
  body: { bundleId, quantity }
559
644
  });
560
- this.client.emit("cart:updated", result);
561
- return result;
645
+ if (res.error) return res;
646
+ this.client.emit("cart:updated", res.data);
647
+ return res;
562
648
  }
563
649
  /** Update quantity of a bundle already in the cart */
564
650
  async updateBundleQuantity(bundleId, quantity) {
565
- const result = await this.client.request("PATCH", `/cart/bundles/${bundleId}`, {
651
+ const res = await this.client.request("PATCH", `/cart/bundles/${bundleId}`, {
566
652
  body: { quantity }
567
653
  });
568
- this.client.emit("cart:updated", result);
569
- return result;
654
+ if (res.error) return res;
655
+ this.client.emit("cart:updated", res.data);
656
+ return res;
570
657
  }
571
658
  /** Remove a bundle from the cart */
572
659
  async removeBundle(bundleId) {
573
- const result = await this.client.request("DELETE", `/cart/bundles/${bundleId}`);
574
- this.client.emit("cart:updated", result);
575
- return result;
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;
576
664
  }
577
665
  /** Merge anonymous cart into authenticated customer cart */
578
666
  async merge() {
579
- const result = await this.client.request("POST", "/cart/merge");
580
- this.client.emit("cart:updated", result);
581
- return result;
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;
582
671
  }
583
672
  /** Apply discount code */
584
673
  async applyDiscount(code) {
585
- const result = await this.client.request("POST", "/cart/discount", {
674
+ const res = await this.client.request("POST", "/cart/discount", {
586
675
  body: { code }
587
676
  });
588
- this.client.emit("cart:updated", result);
589
- return result;
677
+ if (res.error) return res;
678
+ this.client.emit("cart:updated", res.data);
679
+ return res;
590
680
  }
591
681
  /** Remove discount code */
592
682
  async removeDiscount() {
593
- const result = await this.client.request("DELETE", "/cart/discount");
594
- this.client.emit("cart:updated", result);
595
- return result;
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;
596
687
  }
597
688
  };
598
689
  var CheckoutModule = class {
@@ -601,13 +692,14 @@ var CheckoutModule = class {
601
692
  }
602
693
  /** Create order from cart */
603
694
  async createOrder(input) {
604
- const result = await this.client.request("POST", "/checkout", {
695
+ const res = await this.client.request("POST", "/checkout", {
605
696
  body: input
606
697
  });
698
+ if (res.error) return res;
607
699
  this.client.clearCartSession();
608
- this.client.emit("order:created", result);
700
+ this.client.emit("order:created", res.data);
609
701
  this.client.emit("cart:cleared");
610
- return result;
702
+ return res;
611
703
  }
612
704
  };
613
705
  var OrdersModule = class {
@@ -753,6 +845,24 @@ var QuotesModule = class {
753
845
  return this.client.request("GET", `/quotes/${quoteId}`);
754
846
  }
755
847
  };
848
+ var AddressModule = class {
849
+ constructor(client) {
850
+ this.client = client;
851
+ }
852
+ /** Search for address suggestions (debounce on your side, or use the React hook) */
853
+ async autocomplete(query, country) {
854
+ if (!query || query.length < 2) return ok({ suggestions: [] });
855
+ return this.client.request("GET", "/addresses/autocomplete", {
856
+ query: { q: query, country }
857
+ });
858
+ }
859
+ /** Get full structured address from a suggestion's placeId */
860
+ async getDetail(placeId) {
861
+ return this.client.request("GET", "/addresses/place-detail", {
862
+ query: { placeId }
863
+ });
864
+ }
865
+ };
756
866
 
757
867
  export {
758
868
  ProductSort,
@@ -762,5 +872,8 @@ export {
762
872
  AddressTypes,
763
873
  BehioApiError,
764
874
  BehioNetworkError,
875
+ ok,
876
+ err,
877
+ toSdkError,
765
878
  BehioStorefront
766
879
  };