@nymbalhq/sdk 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,906 @@
1
+ // src/adapter/fetch-client.ts
2
+ var NymbalApiError = class extends Error {
3
+ constructor(code, message, status, context) {
4
+ super(message);
5
+ this.code = code;
6
+ this.status = status;
7
+ this.context = context;
8
+ this.name = "NymbalApiError";
9
+ }
10
+ code;
11
+ status;
12
+ context;
13
+ };
14
+ var CSRF_HEADER = "x-csrf-token";
15
+ var UNSAFE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
16
+ var FetchClient = class {
17
+ baseUrl;
18
+ credentials;
19
+ getAccessToken;
20
+ getCartToken;
21
+ onUnauthorized;
22
+ handlingUnauthorized = false;
23
+ /** The token `GET /api/csrf` answered: the API's `nymbal.csrf` cookie, which this origin may not be able to read. */
24
+ csrfToken = null;
25
+ constructor(options) {
26
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
27
+ this.credentials = options.credentials ?? "include";
28
+ this.getAccessToken = options.getAccessToken ?? (() => null);
29
+ this.getCartToken = options.getCartToken;
30
+ this.onUnauthorized = options.onUnauthorized;
31
+ }
32
+ async request(method, path, body) {
33
+ return (await this.requestWithMeta(method, path, body)).data;
34
+ }
35
+ /** Like `request`, keeping the envelope's `meta` (pagination cursors live there). */
36
+ async requestWithMeta(method, path, body) {
37
+ let response = await this.doFetch(method, path, body);
38
+ if (await this.isCsrfRejection(response)) {
39
+ this.csrfToken = null;
40
+ response = await this.doFetch(method, path, body);
41
+ }
42
+ if (response.status === 401 && this.onUnauthorized && !this.handlingUnauthorized) {
43
+ this.handlingUnauthorized = true;
44
+ try {
45
+ await this.onUnauthorized();
46
+ } finally {
47
+ this.handlingUnauthorized = false;
48
+ }
49
+ const retry = await this.doFetch(method, path, body);
50
+ return this.parseResponse(retry);
51
+ }
52
+ return this.parseResponse(response);
53
+ }
54
+ csrf() {
55
+ this.csrfToken ??= this.fetchCsrfToken().catch((err) => {
56
+ this.csrfToken = null;
57
+ throw err;
58
+ });
59
+ return this.csrfToken;
60
+ }
61
+ async fetchCsrfToken() {
62
+ const response = await globalThis.fetch(`${this.baseUrl}/api/csrf`, {
63
+ method: "GET",
64
+ credentials: this.credentials
65
+ });
66
+ const { data } = await this.parseResponse(response);
67
+ return data.token;
68
+ }
69
+ async isCsrfRejection(response) {
70
+ if (response.status !== 403) return false;
71
+ const text = await response.clone().text();
72
+ try {
73
+ return JSON.parse(text).error?.code === "csrf.invalid";
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+ async doFetch(method, path, body) {
79
+ const headers = {};
80
+ const cookies = [];
81
+ if (UNSAFE_METHODS.has(method.toUpperCase())) {
82
+ const csrf = await this.csrf();
83
+ headers[CSRF_HEADER] = csrf;
84
+ cookies.push(`nymbal.csrf=${csrf}`);
85
+ }
86
+ if (body !== void 0) {
87
+ headers["Content-Type"] = "application/json";
88
+ }
89
+ const token = this.getAccessToken();
90
+ if (token) {
91
+ headers["Authorization"] = `Bearer ${token}`;
92
+ }
93
+ const cartToken = this.getCartToken?.();
94
+ if (cartToken) {
95
+ cookies.unshift(`nymbal.cart=${cartToken}`);
96
+ }
97
+ if (cookies.length > 0) {
98
+ headers["Cookie"] = cookies.join("; ");
99
+ }
100
+ return globalThis.fetch(`${this.baseUrl}${path}`, {
101
+ method,
102
+ headers,
103
+ credentials: this.credentials,
104
+ ...body !== void 0 && { body: JSON.stringify(body) }
105
+ });
106
+ }
107
+ async parseResponse(response) {
108
+ const text = await response.text();
109
+ if (!text) {
110
+ return { data: void 0, meta: {} };
111
+ }
112
+ const envelope = JSON.parse(text);
113
+ if (envelope.error) {
114
+ throw new NymbalApiError(
115
+ envelope.error.code,
116
+ envelope.error.message,
117
+ response.status,
118
+ envelope.error.context
119
+ );
120
+ }
121
+ return { data: envelope.data, meta: envelope.meta ?? {} };
122
+ }
123
+ };
124
+
125
+ // src/adapter/nymbal-api-adapter.ts
126
+ function createNymbalApiAdapter(fetchClient) {
127
+ return {
128
+ cart: {
129
+ get() {
130
+ return fetchClient.request("GET", "/api/cart");
131
+ },
132
+ addItem(variantId, qty) {
133
+ return fetchClient.request("POST", "/api/cart/items", { variantId, qty });
134
+ },
135
+ removeItem(variantId) {
136
+ return fetchClient.request(
137
+ "DELETE",
138
+ `/api/cart/items/${encodeURIComponent(variantId)}`
139
+ );
140
+ },
141
+ updateQuantity(variantId, qty) {
142
+ return fetchClient.request(
143
+ "PATCH",
144
+ `/api/cart/items/${encodeURIComponent(variantId)}`,
145
+ { qty }
146
+ );
147
+ },
148
+ clear() {
149
+ return fetchClient.request("DELETE", "/api/cart");
150
+ }
151
+ },
152
+ products: {
153
+ getBySlug(slug) {
154
+ return fetchClient.request(
155
+ "GET",
156
+ `/api/products/${encodeURIComponent(slug)}`
157
+ );
158
+ },
159
+ list(params) {
160
+ const search = new URLSearchParams();
161
+ if (params?.limit !== void 0) search.set("limit", String(params.limit));
162
+ if (params?.cursor !== void 0) search.set("cursor", params.cursor);
163
+ if (params?.category !== void 0) search.set("category", params.category);
164
+ if (params?.sort !== void 0) search.set("sort", params.sort);
165
+ const qs = search.toString();
166
+ return fetchClient.request("GET", `/api/products${qs ? `?${qs}` : ""}`);
167
+ },
168
+ search(query, params) {
169
+ const search = new URLSearchParams();
170
+ search.set("q", query);
171
+ if (params?.limit !== void 0) search.set("limit", String(params.limit));
172
+ if (params?.cursor !== void 0) search.set("cursor", params.cursor);
173
+ return fetchClient.request(
174
+ "GET",
175
+ `/api/products/search?${search.toString()}`
176
+ );
177
+ }
178
+ },
179
+ checkout: {
180
+ create(details) {
181
+ return fetchClient.request("POST", "/api/checkout", details);
182
+ }
183
+ },
184
+ customers: {
185
+ register(params) {
186
+ return fetchClient.request("POST", "/api/auth/register", params);
187
+ },
188
+ login(email, password) {
189
+ return fetchClient.request("POST", "/api/auth/login", { email, password });
190
+ },
191
+ async logout() {
192
+ await fetchClient.request("POST", "/api/auth/logout");
193
+ },
194
+ refresh() {
195
+ return fetchClient.request(
196
+ "POST",
197
+ "/api/auth/refresh"
198
+ );
199
+ },
200
+ getProfile() {
201
+ return fetchClient.request("GET", "/api/customers/me");
202
+ },
203
+ updateProfile(updates) {
204
+ return fetchClient.request("PATCH", "/api/customers/me", updates);
205
+ },
206
+ listAddresses() {
207
+ return fetchClient.request("GET", "/api/customers/me/addresses");
208
+ },
209
+ addAddress(address) {
210
+ return fetchClient.request("POST", "/api/customers/me/addresses", address);
211
+ },
212
+ async removeAddress(id) {
213
+ await fetchClient.request(
214
+ "DELETE",
215
+ `/api/customers/me/addresses/${encodeURIComponent(id)}`
216
+ );
217
+ },
218
+ async listOrders(params) {
219
+ const qs = params?.cursor !== void 0 ? `?cursor=${encodeURIComponent(params.cursor)}` : "";
220
+ const { data, meta } = await fetchClient.requestWithMeta("GET", `/api/orders${qs}`);
221
+ const nextCursor = typeof meta.nextCursor === "string" ? meta.nextCursor : null;
222
+ return { items: data ?? [], nextCursor };
223
+ },
224
+ getOrder(orderNumber) {
225
+ return fetchClient.request("GET", `/api/orders/${encodeURIComponent(orderNumber)}`);
226
+ }
227
+ },
228
+ reviews: {
229
+ getForProduct(productId, params) {
230
+ const search = new URLSearchParams();
231
+ if (params?.limit !== void 0) search.set("limit", String(params.limit));
232
+ if (params?.cursor !== void 0) search.set("cursor", params.cursor);
233
+ const qs = search.toString();
234
+ return fetchClient.request(
235
+ "GET",
236
+ `/api/products/${encodeURIComponent(productId)}/reviews${qs ? `?${qs}` : ""}`
237
+ );
238
+ },
239
+ submit(review) {
240
+ return fetchClient.request("POST", "/api/reviews", review);
241
+ }
242
+ }
243
+ };
244
+ }
245
+
246
+ // src/auth/token-manager.ts
247
+ var DEFAULT_STORAGE_KEY = "nymbal.auth.accessToken";
248
+ function createTokenManager(options) {
249
+ const storageMode = options?.storage ?? "memory";
250
+ const storageKey = options?.storageKey ?? DEFAULT_STORAGE_KEY;
251
+ let memoryToken = null;
252
+ let refreshTimer = null;
253
+ let refreshFn = null;
254
+ function getAccessToken() {
255
+ if (storageMode === "localStorage" && typeof globalThis.localStorage !== "undefined") {
256
+ return globalThis.localStorage.getItem(storageKey);
257
+ }
258
+ return memoryToken;
259
+ }
260
+ function setAccessToken(token) {
261
+ memoryToken = token;
262
+ if (storageMode === "localStorage" && typeof globalThis.localStorage !== "undefined") {
263
+ if (token) {
264
+ globalThis.localStorage.setItem(storageKey, token);
265
+ } else {
266
+ globalThis.localStorage.removeItem(storageKey);
267
+ }
268
+ }
269
+ }
270
+ function setTokens(accessToken, expiresIn) {
271
+ setAccessToken(accessToken);
272
+ scheduleAutoRefresh(expiresIn);
273
+ }
274
+ function clear() {
275
+ setAccessToken(null);
276
+ if (refreshTimer !== null) {
277
+ clearTimeout(refreshTimer);
278
+ refreshTimer = null;
279
+ }
280
+ }
281
+ function scheduleAutoRefresh(expiresIn) {
282
+ if (refreshTimer !== null) {
283
+ clearTimeout(refreshTimer);
284
+ }
285
+ const delayMs = Math.max(1e3, expiresIn * 0.8 * 1e3);
286
+ refreshTimer = setTimeout(async () => {
287
+ if (!refreshFn) return;
288
+ try {
289
+ const result = await refreshFn();
290
+ setTokens(result.accessToken, result.expiresIn);
291
+ } catch (err) {
292
+ console.error("[TokenManager] auto-refresh failed, clearing session", err);
293
+ clear();
294
+ }
295
+ }, delayMs);
296
+ }
297
+ function scheduleRefresh(fn) {
298
+ refreshFn = fn;
299
+ }
300
+ function destroy() {
301
+ clear();
302
+ refreshFn = null;
303
+ }
304
+ return { getAccessToken, setTokens, clear, scheduleRefresh, destroy };
305
+ }
306
+
307
+ // src/store/create-store.ts
308
+ function createStore(initialState) {
309
+ let state = initialState;
310
+ const listeners = /* @__PURE__ */ new Set();
311
+ function getState() {
312
+ return state;
313
+ }
314
+ function setState(partial) {
315
+ const next = typeof partial === "function" ? partial(state) : partial;
316
+ state = { ...state, ...next };
317
+ for (const listener of listeners) {
318
+ listener();
319
+ }
320
+ }
321
+ function subscribe(listener) {
322
+ listeners.add(listener);
323
+ return () => {
324
+ listeners.delete(listener);
325
+ };
326
+ }
327
+ function destroy() {
328
+ listeners.clear();
329
+ }
330
+ return { getState, setState, subscribe, destroy };
331
+ }
332
+
333
+ // src/store/cart-store.ts
334
+ function deriveItemCount(items) {
335
+ let count = 0;
336
+ for (const item of items) {
337
+ count += item.qty;
338
+ }
339
+ return count;
340
+ }
341
+ var INITIAL_STATE = {
342
+ items: [],
343
+ subtotalMinor: 0,
344
+ currency: "USD",
345
+ itemCount: 0,
346
+ loading: false,
347
+ error: null
348
+ };
349
+ function createCartStore(adapter) {
350
+ const store = createStore(INITIAL_STATE);
351
+ async function load() {
352
+ store.setState({ loading: true, error: null });
353
+ try {
354
+ const cart = await adapter.cart.get();
355
+ store.setState({
356
+ items: cart.items,
357
+ subtotalMinor: cart.subtotalMinor,
358
+ currency: cart.currency,
359
+ itemCount: deriveItemCount(cart.items),
360
+ loading: false
361
+ });
362
+ } catch (err) {
363
+ store.setState({
364
+ loading: false,
365
+ error: err instanceof Error ? err.message : String(err)
366
+ });
367
+ }
368
+ }
369
+ async function addItem(variantId, qty = 1) {
370
+ store.setState({ loading: true, error: null });
371
+ try {
372
+ const cart = await adapter.cart.addItem(variantId, qty);
373
+ store.setState({
374
+ items: cart.items,
375
+ subtotalMinor: cart.subtotalMinor,
376
+ currency: cart.currency,
377
+ itemCount: deriveItemCount(cart.items),
378
+ loading: false
379
+ });
380
+ } catch (err) {
381
+ store.setState({
382
+ loading: false,
383
+ error: err instanceof Error ? err.message : String(err)
384
+ });
385
+ }
386
+ }
387
+ async function removeItem(variantId) {
388
+ store.setState({ loading: true, error: null });
389
+ try {
390
+ const cart = await adapter.cart.removeItem(variantId);
391
+ store.setState({
392
+ items: cart.items,
393
+ subtotalMinor: cart.subtotalMinor,
394
+ currency: cart.currency,
395
+ itemCount: deriveItemCount(cart.items),
396
+ loading: false
397
+ });
398
+ } catch (err) {
399
+ store.setState({
400
+ loading: false,
401
+ error: err instanceof Error ? err.message : String(err)
402
+ });
403
+ }
404
+ }
405
+ async function updateQuantity(variantId, qty) {
406
+ store.setState({ loading: true, error: null });
407
+ try {
408
+ const cart = await adapter.cart.updateQuantity(variantId, qty);
409
+ store.setState({
410
+ items: cart.items,
411
+ subtotalMinor: cart.subtotalMinor,
412
+ currency: cart.currency,
413
+ itemCount: deriveItemCount(cart.items),
414
+ loading: false
415
+ });
416
+ } catch (err) {
417
+ store.setState({
418
+ loading: false,
419
+ error: err instanceof Error ? err.message : String(err)
420
+ });
421
+ }
422
+ }
423
+ async function clear() {
424
+ store.setState({ loading: true, error: null });
425
+ try {
426
+ await adapter.cart.clear();
427
+ store.setState({
428
+ items: [],
429
+ subtotalMinor: 0,
430
+ itemCount: 0,
431
+ loading: false
432
+ });
433
+ } catch (err) {
434
+ store.setState({
435
+ loading: false,
436
+ error: err instanceof Error ? err.message : String(err)
437
+ });
438
+ }
439
+ }
440
+ return {
441
+ getState: store.getState,
442
+ subscribe: store.subscribe,
443
+ addItem,
444
+ removeItem,
445
+ updateQuantity,
446
+ clear,
447
+ load,
448
+ destroy: store.destroy
449
+ };
450
+ }
451
+
452
+ // src/store/product-store.ts
453
+ var INITIAL_STATE2 = {
454
+ product: null,
455
+ selectedVariant: null,
456
+ loading: false,
457
+ error: null
458
+ };
459
+ function createProductStore(adapter) {
460
+ const store = createStore(INITIAL_STATE2);
461
+ async function loadBySlug(slug) {
462
+ store.setState({ loading: true, error: null });
463
+ try {
464
+ const product = await adapter.products.getBySlug(slug);
465
+ const firstVariant = product.variants[0] ?? null;
466
+ store.setState({
467
+ product,
468
+ selectedVariant: firstVariant,
469
+ loading: false
470
+ });
471
+ } catch (err) {
472
+ store.setState({
473
+ loading: false,
474
+ error: err instanceof Error ? err.message : String(err)
475
+ });
476
+ }
477
+ }
478
+ function selectVariant(variant) {
479
+ store.setState({ selectedVariant: variant });
480
+ }
481
+ return {
482
+ getState: store.getState,
483
+ subscribe: store.subscribe,
484
+ loadBySlug,
485
+ selectVariant,
486
+ destroy: store.destroy
487
+ };
488
+ }
489
+
490
+ // src/store/product-list-store.ts
491
+ import { DEFAULT_PRODUCT_SORT } from "@nymbalhq/types";
492
+ var INITIAL_STATE3 = {
493
+ products: [],
494
+ facets: [],
495
+ filters: {},
496
+ sort: DEFAULT_PRODUCT_SORT,
497
+ pagination: { cursor: null, hasMore: false },
498
+ loading: false,
499
+ error: null
500
+ };
501
+ function createProductListStore(adapter) {
502
+ const store = createStore(INITIAL_STATE3);
503
+ let lastLimit;
504
+ let generation = 0;
505
+ function buildParams() {
506
+ const state = store.getState();
507
+ const params = { sort: state.sort };
508
+ if (lastLimit !== void 0) params.limit = lastLimit;
509
+ const category = state.filters["category"];
510
+ if (typeof category === "string") {
511
+ params.category = category;
512
+ }
513
+ return params;
514
+ }
515
+ async function load(params) {
516
+ if (params !== void 0) {
517
+ if (params.limit !== void 0) lastLimit = params.limit;
518
+ const filters = { ...store.getState().filters };
519
+ if (params.category !== void 0) filters["category"] = params.category;
520
+ else delete filters["category"];
521
+ store.setState({ filters, sort: params.sort ?? DEFAULT_PRODUCT_SORT });
522
+ }
523
+ const request = buildParams();
524
+ if (params?.cursor !== void 0) request.cursor = params.cursor;
525
+ const current = ++generation;
526
+ store.setState({ loading: true });
527
+ try {
528
+ const result = await adapter.products.list(request);
529
+ if (current !== generation) return;
530
+ store.setState({
531
+ products: result.items,
532
+ facets: result.facets ?? store.getState().facets,
533
+ pagination: { cursor: result.nextCursor, hasMore: result.nextCursor !== null },
534
+ loading: false,
535
+ error: null
536
+ });
537
+ } catch (err) {
538
+ if (current !== generation) return;
539
+ console.error("[ProductListStore] load failed", err);
540
+ store.setState({ loading: false, error: err instanceof Error ? err.message : String(err) });
541
+ }
542
+ }
543
+ async function loadMore() {
544
+ const state = store.getState();
545
+ if (!state.pagination.hasMore || state.loading) return;
546
+ const current = ++generation;
547
+ store.setState({ loading: true });
548
+ try {
549
+ const params = buildParams();
550
+ if (state.pagination.cursor !== null) {
551
+ params.cursor = state.pagination.cursor;
552
+ }
553
+ const result = await adapter.products.list(params);
554
+ if (current !== generation) return;
555
+ store.setState({
556
+ products: [...state.products, ...result.items],
557
+ pagination: { cursor: result.nextCursor, hasMore: result.nextCursor !== null },
558
+ loading: false,
559
+ error: null
560
+ });
561
+ } catch (err) {
562
+ if (current !== generation) return;
563
+ console.error("[ProductListStore] loadMore failed", err);
564
+ store.setState({ loading: false, error: err instanceof Error ? err.message : String(err) });
565
+ }
566
+ }
567
+ async function applyFilter(name, value) {
568
+ const filters = { ...store.getState().filters, [name]: value };
569
+ store.setState({ filters });
570
+ await load();
571
+ }
572
+ async function removeFilter(name) {
573
+ const filters = { ...store.getState().filters };
574
+ delete filters[name];
575
+ store.setState({ filters });
576
+ await load();
577
+ }
578
+ async function setSort(sort) {
579
+ store.setState({ sort });
580
+ await load();
581
+ }
582
+ return {
583
+ getState: store.getState,
584
+ subscribe: store.subscribe,
585
+ load,
586
+ loadMore,
587
+ applyFilter,
588
+ removeFilter,
589
+ setSort,
590
+ destroy: store.destroy
591
+ };
592
+ }
593
+
594
+ // src/store/search-store.ts
595
+ var INITIAL_STATE4 = {
596
+ query: "",
597
+ results: [],
598
+ total: 0,
599
+ loading: false
600
+ };
601
+ function createSearchStore(adapter) {
602
+ const store = createStore(INITIAL_STATE4);
603
+ async function search(query) {
604
+ if (!query.trim()) {
605
+ store.setState({ query: "", results: [], total: 0, loading: false });
606
+ return;
607
+ }
608
+ store.setState({ query, loading: true });
609
+ try {
610
+ const result = await adapter.products.search(query);
611
+ store.setState({
612
+ results: result.items,
613
+ total: result.items.length,
614
+ loading: false
615
+ });
616
+ } catch (err) {
617
+ console.error("[SearchStore] search failed", err);
618
+ store.setState({ loading: false });
619
+ }
620
+ }
621
+ function clearSearch() {
622
+ store.setState({ query: "", results: [], total: 0, loading: false });
623
+ }
624
+ return {
625
+ getState: store.getState,
626
+ subscribe: store.subscribe,
627
+ search,
628
+ clearSearch,
629
+ destroy: store.destroy
630
+ };
631
+ }
632
+
633
+ // src/store/checkout-store.ts
634
+ var INITIAL_STATE5 = {
635
+ step: "contact",
636
+ email: "",
637
+ shippingAddress: null,
638
+ billingAddress: null,
639
+ shippingMethod: null,
640
+ paymentStatus: "idle",
641
+ error: null,
642
+ order: null,
643
+ placedOrderNumber: null
644
+ };
645
+ function namedOrder(err) {
646
+ if (!(err instanceof NymbalApiError)) return null;
647
+ const orderNumber = err.context?.["orderNumber"];
648
+ return typeof orderNumber === "string" ? orderNumber : null;
649
+ }
650
+ function createCheckoutStore(adapter, cart) {
651
+ const store = createStore(INITIAL_STATE5);
652
+ function setEmail(email) {
653
+ store.setState({ email, step: "shipping" });
654
+ }
655
+ function setShippingAddress(address) {
656
+ store.setState({ shippingAddress: address });
657
+ }
658
+ function setBillingAddress(address) {
659
+ store.setState({ billingAddress: address });
660
+ }
661
+ function setShippingMethod(method) {
662
+ store.setState({ shippingMethod: method, step: "payment" });
663
+ }
664
+ async function submitPayment() {
665
+ const state = store.getState();
666
+ if (!state.shippingAddress || !state.billingAddress) {
667
+ store.setState({ error: "Shipping and billing addresses are required" });
668
+ return null;
669
+ }
670
+ store.setState({ paymentStatus: "processing", error: null, placedOrderNumber: null });
671
+ try {
672
+ const result = await adapter.checkout.create({
673
+ email: state.email,
674
+ billingAddress: state.billingAddress,
675
+ shippingAddress: state.shippingAddress
676
+ });
677
+ const { status, reason } = result.payment;
678
+ store.setState({
679
+ paymentStatus: status,
680
+ order: {
681
+ orderId: result.orderId,
682
+ orderNumber: result.orderNumber,
683
+ paymentIntent: result.paymentIntent
684
+ },
685
+ error: status === "failed" ? `Your payment did not go through${reason ? ` (${reason})` : ""}. Please try again.` : null,
686
+ ...status === "succeeded" && { step: "confirmation" }
687
+ });
688
+ await cart?.load();
689
+ return result;
690
+ } catch (err) {
691
+ const placedOrderNumber = namedOrder(err);
692
+ store.setState({
693
+ paymentStatus: "failed",
694
+ error: err instanceof Error ? err.message : String(err),
695
+ placedOrderNumber
696
+ });
697
+ if (placedOrderNumber !== null) await cart?.load();
698
+ return null;
699
+ }
700
+ }
701
+ function markConfirmed() {
702
+ store.setState({ paymentStatus: "succeeded", step: "confirmation" });
703
+ }
704
+ function reset() {
705
+ store.setState(INITIAL_STATE5);
706
+ }
707
+ return {
708
+ getState: store.getState,
709
+ subscribe: store.subscribe,
710
+ setEmail,
711
+ setShippingAddress,
712
+ setBillingAddress,
713
+ setShippingMethod,
714
+ submitPayment,
715
+ markConfirmed,
716
+ reset,
717
+ destroy: store.destroy
718
+ };
719
+ }
720
+
721
+ // src/store/auth-store.ts
722
+ var INITIAL_STATE6 = {
723
+ customer: null,
724
+ isAuthenticated: false,
725
+ loading: false,
726
+ error: null
727
+ };
728
+ function createAuthStore(adapter, tokenManager) {
729
+ const store = createStore(INITIAL_STATE6);
730
+ async function login(email, password) {
731
+ store.setState({ loading: true, error: null });
732
+ try {
733
+ const result = await adapter.customers.login(email, password);
734
+ tokenManager.setTokens(result.accessToken, result.expiresIn);
735
+ store.setState({
736
+ customer: result.customer,
737
+ isAuthenticated: true,
738
+ loading: false
739
+ });
740
+ } catch (err) {
741
+ const message = err instanceof Error ? err.message : String(err);
742
+ store.setState({ loading: false, error: message });
743
+ throw err;
744
+ }
745
+ }
746
+ async function register(params) {
747
+ store.setState({ loading: true, error: null });
748
+ try {
749
+ const result = await adapter.customers.register(params);
750
+ tokenManager.setTokens(result.accessToken, result.expiresIn);
751
+ store.setState({
752
+ customer: result.customer,
753
+ isAuthenticated: true,
754
+ loading: false
755
+ });
756
+ } catch (err) {
757
+ const message = err instanceof Error ? err.message : String(err);
758
+ store.setState({ loading: false, error: message });
759
+ throw err;
760
+ }
761
+ }
762
+ async function logout() {
763
+ store.setState({ loading: true, error: null });
764
+ try {
765
+ await adapter.customers.logout();
766
+ tokenManager.clear();
767
+ store.setState({
768
+ customer: null,
769
+ isAuthenticated: false,
770
+ loading: false
771
+ });
772
+ } catch (err) {
773
+ tokenManager.clear();
774
+ store.setState({
775
+ customer: null,
776
+ isAuthenticated: false,
777
+ loading: false,
778
+ error: err instanceof Error ? err.message : String(err)
779
+ });
780
+ }
781
+ }
782
+ async function refreshToken() {
783
+ try {
784
+ const result = await adapter.customers.refresh();
785
+ tokenManager.setTokens(result.accessToken, result.expiresIn);
786
+ } catch (err) {
787
+ console.error("[AuthStore] token refresh failed, logging out", err);
788
+ tokenManager.clear();
789
+ store.setState({
790
+ customer: null,
791
+ isAuthenticated: false
792
+ });
793
+ }
794
+ }
795
+ async function updateProfile(updates) {
796
+ store.setState({ loading: true, error: null });
797
+ try {
798
+ const customer = await adapter.customers.updateProfile(updates);
799
+ store.setState({ customer, loading: false });
800
+ } catch (err) {
801
+ store.setState({
802
+ loading: false,
803
+ error: err instanceof Error ? err.message : String(err)
804
+ });
805
+ }
806
+ }
807
+ async function loadProfile() {
808
+ if (!tokenManager.getAccessToken()) return;
809
+ store.setState({ loading: true, error: null });
810
+ try {
811
+ const customer = await adapter.customers.getProfile();
812
+ store.setState({
813
+ customer,
814
+ isAuthenticated: true,
815
+ loading: false
816
+ });
817
+ } catch (err) {
818
+ store.setState({
819
+ loading: false,
820
+ error: err instanceof Error ? err.message : String(err)
821
+ });
822
+ }
823
+ }
824
+ return {
825
+ getState: store.getState,
826
+ subscribe: store.subscribe,
827
+ login,
828
+ register,
829
+ logout,
830
+ refreshToken,
831
+ updateProfile,
832
+ loadProfile,
833
+ destroy: store.destroy
834
+ };
835
+ }
836
+
837
+ // src/client.ts
838
+ function createNymbalClient(options) {
839
+ const tokenManager = createTokenManager({
840
+ ...options.tokenStorage !== void 0 && { storage: options.tokenStorage }
841
+ });
842
+ let adapter;
843
+ if (options.adapter) {
844
+ adapter = options.adapter;
845
+ } else {
846
+ const fetchClient = new FetchClient({
847
+ baseUrl: options.baseUrl,
848
+ ...options.credentials !== void 0 && { credentials: options.credentials },
849
+ getAccessToken: () => tokenManager.getAccessToken(),
850
+ ...options.getCartToken !== void 0 && { getCartToken: options.getCartToken },
851
+ onUnauthorized: async () => {
852
+ try {
853
+ const result = await adapter.customers.refresh();
854
+ tokenManager.setTokens(result.accessToken, result.expiresIn);
855
+ } catch (err) {
856
+ console.warn("[NymbalClient] token refresh on unauthorized failed", err);
857
+ tokenManager.clear();
858
+ }
859
+ }
860
+ });
861
+ adapter = createNymbalApiAdapter(fetchClient);
862
+ }
863
+ const cart = createCartStore(adapter);
864
+ const product = createProductStore(adapter);
865
+ const productList = createProductListStore(adapter);
866
+ const search = createSearchStore(adapter);
867
+ const checkout = createCheckoutStore(adapter, cart);
868
+ const auth = createAuthStore(adapter, tokenManager);
869
+ const customer = {
870
+ addresses: {
871
+ list: () => adapter.customers.listAddresses(),
872
+ create: (address) => adapter.customers.addAddress(address),
873
+ remove: (id) => adapter.customers.removeAddress(id)
874
+ },
875
+ orders: {
876
+ list: (opts) => adapter.customers.listOrders(opts),
877
+ get: (orderNumber) => adapter.customers.getOrder(orderNumber)
878
+ }
879
+ };
880
+ tokenManager.scheduleRefresh(() => adapter.customers.refresh());
881
+ function destroy() {
882
+ cart.destroy();
883
+ product.destroy();
884
+ productList.destroy();
885
+ search.destroy();
886
+ checkout.destroy();
887
+ auth.destroy();
888
+ tokenManager.destroy();
889
+ }
890
+ return { cart, product, productList, search, checkout, auth, customer, adapter, destroy };
891
+ }
892
+ export {
893
+ FetchClient,
894
+ NymbalApiError,
895
+ createAuthStore,
896
+ createCartStore,
897
+ createCheckoutStore,
898
+ createNymbalApiAdapter,
899
+ createNymbalClient,
900
+ createProductListStore,
901
+ createProductStore,
902
+ createSearchStore,
903
+ createStore,
904
+ createTokenManager
905
+ };
906
+ //# sourceMappingURL=index.js.map