@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nymbal contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # @nymbalhq/sdk
2
+
3
+ The client side of a Nymbal store, independent of any framework: stores for the catalogue, cart, account and
4
+ checkout over the store's API.
5
+
6
+ See the [documentation](https://github.com/nymbalhq/nymbal/blob/main/apps/docs/src/content/docs/index.md).
@@ -0,0 +1,601 @@
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
+ /** A session restore's refresh, which every request sent meanwhile waits on so that none goes out as a guest. */
28
+ held = null;
29
+ constructor(options) {
30
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
31
+ this.credentials = options.credentials ?? "include";
32
+ this.getAccessToken = options.getAccessToken ?? (() => null);
33
+ this.getCartToken = options.getCartToken;
34
+ this.onUnauthorized = options.onUnauthorized;
35
+ }
36
+ /** Starts the session's refresh, or joins the one in flight: a client sends one refresh at a time. */
37
+ refreshSession() {
38
+ if (this.onUnauthorized === void 0) return Promise.resolve();
39
+ return this.refreshing ?? this.refresh(this.onUnauthorized);
40
+ }
41
+ /**
42
+ * As `refreshSession`, and every other request sent until the refresh settles waits for it, at most
43
+ * `REFRESH_WAIT_MS`. Held from this call on, before any await: a page's first requests go in the same tick.
44
+ */
45
+ restoreSession() {
46
+ const held = this.waitFor(this.refreshSession()).then(
47
+ () => void 0,
48
+ () => void 0
49
+ );
50
+ this.held = held;
51
+ void held.then(() => {
52
+ if (this.held === held) this.held = null;
53
+ });
54
+ return held;
55
+ }
56
+ async request(method, path, body, options = {}) {
57
+ return (await this.requestWithMeta(method, path, body, options)).data;
58
+ }
59
+ /** Like `request`, keeping the envelope's `meta` (pagination cursors live there). */
60
+ async requestWithMeta(method, path, body, options = {}) {
61
+ if (this.held !== null && options.authRefresh !== true) await this.held;
62
+ const sentDuring = this.refreshing;
63
+ let { response, bearer } = await this.doFetch(method, path, body, options);
64
+ if (await this.isCsrfRejection(response)) {
65
+ this.csrfToken = null;
66
+ ({ response, bearer } = await this.doFetch(method, path, body, options));
67
+ }
68
+ if (response.status === 401 && this.onUnauthorized && options.authRefresh !== true) {
69
+ const pending = this.refreshing;
70
+ const retry = async () => {
71
+ if (options.keepIdentity === true && bearer !== null && this.getAccessToken() === null) {
72
+ return this.parseResponse(response);
73
+ }
74
+ return this.parseResponse((await this.doFetch(method, path, body, options)).response);
75
+ };
76
+ if (pending === null && this.getAccessToken() !== bearer) {
77
+ return retry();
78
+ }
79
+ if (pending !== null && pending === sentDuring) {
80
+ if (await this.waitFor(pending) && this.getAccessToken() !== bearer) return retry();
81
+ } else if (await this.waitFor(pending ?? this.refresh(this.onUnauthorized))) {
82
+ return retry();
83
+ }
84
+ }
85
+ return this.parseResponse(response);
86
+ }
87
+ /** Whether the refresh ended within `REFRESH_WAIT_MS`; a refresh that fails throws here, as it always has. */
88
+ async waitFor(refresh) {
89
+ let timer;
90
+ const bound = new Promise((resolve) => {
91
+ timer = setTimeout(() => resolve(false), REFRESH_WAIT_MS);
92
+ });
93
+ try {
94
+ return await Promise.race([refresh.then(() => true), bound]);
95
+ } finally {
96
+ clearTimeout(timer);
97
+ }
98
+ }
99
+ refresh(onUnauthorized) {
100
+ const refresh = Promise.resolve().then(onUnauthorized).finally(() => {
101
+ this.refreshing = null;
102
+ });
103
+ this.refreshing = refresh;
104
+ return refresh;
105
+ }
106
+ csrf() {
107
+ this.csrfToken ??= this.fetchCsrfToken().catch((err) => {
108
+ this.csrfToken = null;
109
+ throw err;
110
+ });
111
+ return this.csrfToken;
112
+ }
113
+ async fetchCsrfToken() {
114
+ const response = await globalThis.fetch(`${this.baseUrl}/api/csrf`, {
115
+ method: "GET",
116
+ credentials: this.credentials
117
+ });
118
+ const { data } = await this.parseResponse(response);
119
+ return data.token;
120
+ }
121
+ async isCsrfRejection(response) {
122
+ if (response.status !== 403) return false;
123
+ const text = await response.clone().text();
124
+ try {
125
+ return JSON.parse(text).error?.code === "csrf.invalid";
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+ async doFetch(method, path, body, options) {
131
+ const headers = { ...options.headers };
132
+ const cookies = [];
133
+ if (UNSAFE_METHODS.has(method.toUpperCase())) {
134
+ const csrf = await this.csrf();
135
+ headers[CSRF_HEADER] = csrf;
136
+ cookies.push(`nymbal.csrf=${csrf}`);
137
+ }
138
+ if (body !== void 0) {
139
+ headers["Content-Type"] = "application/json";
140
+ }
141
+ const token = this.getAccessToken();
142
+ if (token) {
143
+ headers["Authorization"] = `Bearer ${token}`;
144
+ }
145
+ const cartToken = this.getCartToken?.();
146
+ if (cartToken) {
147
+ cookies.unshift(`nymbal.cart=${cartToken}`);
148
+ }
149
+ if (cookies.length > 0) {
150
+ headers["Cookie"] = cookies.join("; ");
151
+ }
152
+ const response = await globalThis.fetch(`${this.baseUrl}${path}`, {
153
+ method,
154
+ headers,
155
+ credentials: this.credentials,
156
+ ...body !== void 0 && { body: JSON.stringify(body) }
157
+ });
158
+ return { response, bearer: token };
159
+ }
160
+ async parseResponse(response) {
161
+ const text = await response.text();
162
+ if (!text) {
163
+ return { data: void 0, meta: {} };
164
+ }
165
+ const envelope = JSON.parse(text);
166
+ if (envelope.error) {
167
+ throw new NymbalApiError(
168
+ envelope.error.code,
169
+ envelope.error.message,
170
+ response.status,
171
+ envelope.error.context
172
+ );
173
+ }
174
+ return { data: envelope.data, meta: envelope.meta ?? {} };
175
+ }
176
+ };
177
+
178
+ // src/adapter/nymbal-api-adapter.ts
179
+ import {
180
+ ORDER_EMAIL_HEADER,
181
+ ORDER_KEY_HEADER
182
+ } from "@nymbalhq/types";
183
+ function createNymbalApiAdapter(fetchClient) {
184
+ const getOrder = (orderNumber, { key, email } = {}) => fetchClient.request(
185
+ "GET",
186
+ `/api/orders/${encodeURIComponent(orderNumber)}`,
187
+ void 0,
188
+ {
189
+ headers: {
190
+ ...key !== void 0 && { [ORDER_KEY_HEADER]: key },
191
+ ...email !== void 0 && { [ORDER_EMAIL_HEADER]: email }
192
+ }
193
+ }
194
+ );
195
+ return {
196
+ capabilities: {
197
+ get() {
198
+ return fetchClient.request("GET", "/api/capabilities");
199
+ }
200
+ },
201
+ cart: {
202
+ get() {
203
+ return fetchClient.request("GET", "/api/cart");
204
+ },
205
+ addItem(variantId, qty) {
206
+ return fetchClient.request("POST", "/api/cart/items", { variantId, qty });
207
+ },
208
+ removeItem(variantId) {
209
+ return fetchClient.request(
210
+ "DELETE",
211
+ `/api/cart/items/${encodeURIComponent(variantId)}`
212
+ );
213
+ },
214
+ updateQuantity(variantId, qty) {
215
+ return fetchClient.request(
216
+ "PATCH",
217
+ `/api/cart/items/${encodeURIComponent(variantId)}`,
218
+ { qty }
219
+ );
220
+ },
221
+ clear() {
222
+ return fetchClient.request("DELETE", "/api/cart");
223
+ }
224
+ },
225
+ products: {
226
+ getBySlug(slug) {
227
+ return fetchClient.request(
228
+ "GET",
229
+ `/api/products/${encodeURIComponent(slug)}`
230
+ );
231
+ },
232
+ list(params) {
233
+ const search = new URLSearchParams();
234
+ if (params?.limit !== void 0) search.set("limit", String(params.limit));
235
+ if (params?.cursor !== void 0) search.set("cursor", params.cursor);
236
+ if (params?.category !== void 0) search.set("category", params.category);
237
+ if (params?.sort !== void 0) search.set("sort", params.sort);
238
+ const qs = search.toString();
239
+ return fetchClient.request("GET", `/api/products${qs ? `?${qs}` : ""}`);
240
+ },
241
+ search(query, params) {
242
+ const search = new URLSearchParams();
243
+ search.set("q", query);
244
+ if (params?.limit !== void 0) search.set("limit", String(params.limit));
245
+ if (params?.cursor !== void 0) search.set("cursor", params.cursor);
246
+ if (params?.category !== void 0) search.set("category", params.category);
247
+ return fetchClient.request(
248
+ "GET",
249
+ `/api/products/search?${search.toString()}`
250
+ );
251
+ }
252
+ },
253
+ checkout: {
254
+ create(details) {
255
+ return fetchClient.request("POST", "/api/checkout", details, {
256
+ keepIdentity: true
257
+ });
258
+ }
259
+ },
260
+ customers: {
261
+ register(params) {
262
+ return fetchClient.request("POST", "/api/auth/register", params);
263
+ },
264
+ login(email, password) {
265
+ return fetchClient.request("POST", "/api/auth/login", { email, password });
266
+ },
267
+ async logout() {
268
+ await fetchClient.request("POST", "/api/auth/logout");
269
+ },
270
+ refresh() {
271
+ return fetchClient.request(
272
+ "POST",
273
+ "/api/auth/refresh",
274
+ void 0,
275
+ { authRefresh: true }
276
+ );
277
+ },
278
+ getProfile() {
279
+ return fetchClient.request("GET", "/api/customers/me");
280
+ },
281
+ updateProfile(updates) {
282
+ return fetchClient.request("PATCH", "/api/customers/me", updates);
283
+ },
284
+ listAddresses() {
285
+ return fetchClient.request("GET", "/api/customers/me/addresses");
286
+ },
287
+ addAddress(address) {
288
+ return fetchClient.request("POST", "/api/customers/me/addresses", address);
289
+ },
290
+ async removeAddress(id) {
291
+ await fetchClient.request(
292
+ "DELETE",
293
+ `/api/customers/me/addresses/${encodeURIComponent(id)}`
294
+ );
295
+ },
296
+ async listOrders(params) {
297
+ const qs = params?.cursor !== void 0 ? `?cursor=${encodeURIComponent(params.cursor)}` : "";
298
+ const { data, meta } = await fetchClient.requestWithMeta("GET", `/api/orders${qs}`);
299
+ const nextCursor = typeof meta.nextCursor === "string" ? meta.nextCursor : null;
300
+ return { items: data ?? [], nextCursor };
301
+ },
302
+ getOrder
303
+ },
304
+ orders: {
305
+ get: getOrder,
306
+ async requestLink({ orderNumber, email }) {
307
+ await fetchClient.request("POST", "/api/orders/link", {
308
+ orderNumber,
309
+ email
310
+ });
311
+ }
312
+ },
313
+ recentlyViewed: {
314
+ async record(productId) {
315
+ await fetchClient.request("POST", "/api/recently-viewed", { productId });
316
+ },
317
+ list() {
318
+ return fetchClient.request("GET", "/api/recently-viewed");
319
+ }
320
+ },
321
+ reviews: {
322
+ getForProduct(productId, params) {
323
+ const search = new URLSearchParams();
324
+ if (params?.limit !== void 0) search.set("limit", String(params.limit));
325
+ if (params?.cursor !== void 0) search.set("cursor", params.cursor);
326
+ const qs = search.toString();
327
+ return fetchClient.request(
328
+ "GET",
329
+ `/api/products/${encodeURIComponent(productId)}/reviews${qs ? `?${qs}` : ""}`
330
+ );
331
+ },
332
+ submit(review) {
333
+ return fetchClient.request("POST", "/api/reviews", review);
334
+ }
335
+ },
336
+ alerts: {
337
+ subscribe(request) {
338
+ return fetchClient.request("POST", "/api/alerts/subscribe", request);
339
+ }
340
+ }
341
+ };
342
+ }
343
+
344
+ // src/auth/session.ts
345
+ var SESSION_HINT_KEY = "nymbal.auth.session";
346
+ var ACCESS_TOKEN_KEY = "nymbal.auth.accessToken";
347
+ function withStorage(use, fallback) {
348
+ try {
349
+ return typeof globalThis.localStorage === "undefined" ? fallback : use(globalThis.localStorage);
350
+ } catch {
351
+ return fallback;
352
+ }
353
+ }
354
+ function hasSessionHint() {
355
+ return withStorage((storage) => storage.getItem(SESSION_HINT_KEY) === "1", false);
356
+ }
357
+ function markSession() {
358
+ withStorage((storage) => storage.setItem(SESSION_HINT_KEY, "1"), void 0);
359
+ }
360
+ function forgetSession() {
361
+ withStorage((storage) => storage.removeItem(SESSION_HINT_KEY), void 0);
362
+ }
363
+ function adoptLegacyToken() {
364
+ withStorage((storage) => {
365
+ if (storage.getItem(ACCESS_TOKEN_KEY) === null) return;
366
+ storage.removeItem(ACCESS_TOKEN_KEY);
367
+ storage.setItem(SESSION_HINT_KEY, "1");
368
+ }, void 0);
369
+ }
370
+ function isSessionRefusal(err) {
371
+ return err instanceof NymbalApiError && (err.status === 401 || err.status === 403);
372
+ }
373
+ async function renewSession(adapter, tokenManager) {
374
+ try {
375
+ const result = await adapter.customers.refresh();
376
+ tokenManager.setTokens(result.accessToken, result.expiresIn);
377
+ } catch (err) {
378
+ console.warn("[NymbalClient] token refresh failed", err);
379
+ tokenManager.clear();
380
+ if (isSessionRefusal(err)) forgetSession();
381
+ }
382
+ }
383
+
384
+ // src/auth/token-manager.ts
385
+ function createTokenManager(options) {
386
+ const storageMode = options?.storage ?? "memory";
387
+ const storageKey = options?.storageKey ?? ACCESS_TOKEN_KEY;
388
+ let memoryToken = null;
389
+ let refreshTimer = null;
390
+ let refreshFn = null;
391
+ function getAccessToken() {
392
+ if (storageMode === "localStorage" && typeof globalThis.localStorage !== "undefined") {
393
+ return globalThis.localStorage.getItem(storageKey);
394
+ }
395
+ return memoryToken;
396
+ }
397
+ function setAccessToken(token) {
398
+ memoryToken = token;
399
+ if (storageMode === "localStorage" && typeof globalThis.localStorage !== "undefined") {
400
+ if (token) {
401
+ globalThis.localStorage.setItem(storageKey, token);
402
+ } else {
403
+ globalThis.localStorage.removeItem(storageKey);
404
+ }
405
+ }
406
+ }
407
+ function setTokens(accessToken, expiresIn) {
408
+ setAccessToken(accessToken);
409
+ markSession();
410
+ scheduleAutoRefresh(expiresIn);
411
+ }
412
+ function clear() {
413
+ setAccessToken(null);
414
+ if (refreshTimer !== null) {
415
+ clearTimeout(refreshTimer);
416
+ refreshTimer = null;
417
+ }
418
+ }
419
+ function scheduleAutoRefresh(expiresIn) {
420
+ if (refreshTimer !== null) {
421
+ clearTimeout(refreshTimer);
422
+ }
423
+ const delayMs = Math.max(1e3, expiresIn * 0.8 * 1e3);
424
+ refreshTimer = setTimeout(async () => {
425
+ if (!refreshFn) return;
426
+ try {
427
+ const result = await refreshFn();
428
+ if (result) setTokens(result.accessToken, result.expiresIn);
429
+ } catch (err) {
430
+ console.error("[TokenManager] auto-refresh failed, clearing session", err);
431
+ clear();
432
+ }
433
+ }, delayMs);
434
+ }
435
+ function scheduleRefresh(fn) {
436
+ refreshFn = fn;
437
+ }
438
+ function destroy() {
439
+ clear();
440
+ refreshFn = null;
441
+ }
442
+ return { getAccessToken, setTokens, clear, scheduleRefresh, destroy };
443
+ }
444
+
445
+ // src/client.ts
446
+ var STORE_NAMESPACES = [
447
+ "cart",
448
+ "product",
449
+ "productList",
450
+ "search",
451
+ "checkout",
452
+ "auth"
453
+ ];
454
+ var FACTORIES_KEY = /* @__PURE__ */ Symbol.for("nymbal.sdk.namespaces");
455
+ function factories() {
456
+ const holder = globalThis;
457
+ holder[FACTORIES_KEY] ??= {};
458
+ return holder[FACTORIES_KEY];
459
+ }
460
+ function defineNamespace(name, factory) {
461
+ factories()[name] = factory;
462
+ }
463
+ var STORE_BUILT = /* @__PURE__ */ Symbol.for("nymbal.client.onStoreBuilt");
464
+ function isStore(value) {
465
+ return typeof value === "object" && value !== null && typeof value.subscribe === "function" && typeof value.getState === "function";
466
+ }
467
+ var RESUME_SESSION = /* @__PURE__ */ Symbol.for("nymbal.client.resumeSession");
468
+ function resumeSession(client) {
469
+ const hook = client[RESUME_SESSION];
470
+ return typeof hook === "function" ? hook() : Promise.resolve();
471
+ }
472
+ function onStoreBuilt(client, listener) {
473
+ const hook = client[STORE_BUILT];
474
+ if (typeof hook === "function") return hook(listener);
475
+ for (const value of Object.values(client)) {
476
+ if (isStore(value)) listener(value);
477
+ }
478
+ return () => {
479
+ };
480
+ }
481
+ function createNymbalClient(options) {
482
+ if (options.tokenStorage !== "localStorage") adoptLegacyToken();
483
+ const tokenManager = createTokenManager({
484
+ ...options.tokenStorage !== void 0 && { storage: options.tokenStorage }
485
+ });
486
+ let adapter;
487
+ let fetchClient;
488
+ const renew = () => renewSession(adapter, tokenManager);
489
+ if (options.adapter) {
490
+ adapter = options.adapter;
491
+ } else {
492
+ fetchClient = new FetchClient({
493
+ baseUrl: options.baseUrl,
494
+ ...options.credentials !== void 0 && { credentials: options.credentials },
495
+ getAccessToken: () => tokenManager.getAccessToken(),
496
+ ...options.getCartToken !== void 0 && { getCartToken: options.getCartToken },
497
+ onUnauthorized: renew
498
+ });
499
+ adapter = createNymbalApiAdapter(fetchClient);
500
+ }
501
+ const customer = {
502
+ addresses: {
503
+ list: () => adapter.customers.listAddresses(),
504
+ create: (address) => adapter.customers.addAddress(address),
505
+ remove: (id) => adapter.customers.removeAddress(id)
506
+ },
507
+ orders: {
508
+ list: (opts) => adapter.customers.listOrders(opts),
509
+ get: (orderNumber, options2) => adapter.customers.getOrder(orderNumber, options2)
510
+ }
511
+ };
512
+ let report = null;
513
+ const capabilities = {
514
+ get() {
515
+ if (report === null) {
516
+ const reader = adapter.capabilities;
517
+ report = reader === void 0 ? Promise.reject(new Error("This client\u2019s adapter cannot read the store capabilities.")) : reader.get();
518
+ report.catch(() => {
519
+ report = null;
520
+ });
521
+ }
522
+ return report;
523
+ }
524
+ };
525
+ const noGuestOrders = () => Promise.reject(new Error("This client\u2019s adapter cannot open a guest\u2019s order."));
526
+ const orders = adapter.orders ?? { get: noGuestOrders, requestLink: noGuestOrders };
527
+ const recentlyViewedOf = () => {
528
+ const reader = adapter.recentlyViewed;
529
+ if (reader === void 0) {
530
+ throw new Error("This client\u2019s adapter cannot record or list recently viewed products.");
531
+ }
532
+ return reader;
533
+ };
534
+ const recentlyViewed = {
535
+ async record(productId) {
536
+ await recentlyViewedOf().record(productId);
537
+ },
538
+ async list() {
539
+ return recentlyViewedOf().list();
540
+ }
541
+ };
542
+ tokenManager.scheduleRefresh(() => fetchClient?.refreshSession() ?? renew());
543
+ const built = {};
544
+ const listeners = /* @__PURE__ */ new Set();
545
+ let destroyed = false;
546
+ function destroy() {
547
+ destroyed = true;
548
+ for (const store of Object.values(built)) store.destroy();
549
+ tokenManager.destroy();
550
+ }
551
+ const storeBuilt = (listener) => {
552
+ for (const store of Object.values(built)) listener(store);
553
+ listeners.add(listener);
554
+ return () => {
555
+ listeners.delete(listener);
556
+ };
557
+ };
558
+ const resume = () => tokenManager.getAccessToken() || !hasSessionHint() ? Promise.resolve() : context.restoreSession();
559
+ const client = Object.defineProperties(
560
+ { capabilities, recentlyViewed, customer, orders, adapter, destroy },
561
+ { [STORE_BUILT]: { value: storeBuilt }, [RESUME_SESSION]: { value: resume } }
562
+ );
563
+ const context = {
564
+ adapter,
565
+ tokenManager,
566
+ client,
567
+ restoreSession: () => fetchClient?.restoreSession() ?? renew()
568
+ };
569
+ function build(name) {
570
+ if (destroyed) throw new Error(`client.${name} was read after the client was destroyed`);
571
+ const factory = factories()[name];
572
+ if (factory === void 0) {
573
+ const module = name === "productList" ? "product-list" : name;
574
+ throw new Error(`client.${name} is not in this client: import '@nymbalhq/sdk/ns/${module}'`);
575
+ }
576
+ const store = built[name] = factory(context);
577
+ for (const listener of listeners) listener(store);
578
+ return store;
579
+ }
580
+ for (const name of STORE_NAMESPACES) {
581
+ Object.defineProperty(client, name, { get: () => built[name] ?? build(name) });
582
+ }
583
+ return client;
584
+ }
585
+
586
+ export {
587
+ NymbalApiError,
588
+ FetchClient,
589
+ createNymbalApiAdapter,
590
+ SESSION_HINT_KEY,
591
+ hasSessionHint,
592
+ forgetSession,
593
+ isSessionRefusal,
594
+ renewSession,
595
+ createTokenManager,
596
+ defineNamespace,
597
+ resumeSession,
598
+ onStoreBuilt,
599
+ createNymbalClient
600
+ };
601
+ //# sourceMappingURL=chunk-2EEZZXZX.js.map