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