@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.
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/dist/chunk-2EEZZXZX.js +601 -0
- package/dist/chunk-2EEZZXZX.js.map +1 -0
- package/dist/chunk-4GP43MKT.js +19 -0
- package/dist/chunk-4GP43MKT.js.map +1 -0
- package/dist/chunk-7LQGOJIT.js +133 -0
- package/dist/chunk-7LQGOJIT.js.map +1 -0
- package/dist/chunk-CJVA46FR.js +52 -0
- package/dist/chunk-CJVA46FR.js.map +1 -0
- package/dist/chunk-PDZKR65O.js +148 -0
- package/dist/chunk-PDZKR65O.js.map +1 -0
- package/dist/chunk-U6QF2DRY.js +30 -0
- package/dist/chunk-U6QF2DRY.js.map +1 -0
- package/dist/chunk-VYX72KGY.js +116 -0
- package/dist/chunk-VYX72KGY.js.map +1 -0
- package/dist/chunk-XS7RNJSY.js +118 -0
- package/dist/chunk-XS7RNJSY.js.map +1 -0
- package/dist/chunk-YHXC7DS5.js +92 -0
- package/dist/chunk-YHXC7DS5.js.map +1 -0
- package/dist/core-BviXjOsX.d.ts +560 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +21 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +4 -483
- package/dist/index.js +33 -1048
- package/dist/index.js.map +1 -1
- package/dist/ns/auth.d.ts +2 -0
- package/dist/ns/auth.js +4 -0
- package/dist/ns/auth.js.map +1 -0
- package/dist/ns/cart.d.ts +2 -0
- package/dist/ns/cart.js +4 -0
- package/dist/ns/cart.js.map +1 -0
- package/dist/ns/checkout.d.ts +2 -0
- package/dist/ns/checkout.js +5 -0
- package/dist/ns/checkout.js.map +1 -0
- package/dist/ns/product-list.d.ts +2 -0
- package/dist/ns/product-list.js +4 -0
- package/dist/ns/product-list.js.map +1 -0
- package/dist/ns/product.d.ts +2 -0
- package/dist/ns/product.js +4 -0
- package/dist/ns/product.js.map +1 -0
- package/dist/ns/search.d.ts +2 -0
- package/dist/ns/search.js +4 -0
- package/dist/ns/search.js.map +1 -0
- package/package.json +17 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapter/fetch-client.ts","../src/adapter/nymbal-api-adapter.ts","../src/auth/session.ts","../src/auth/token-manager.ts","../src/client.ts"],"sourcesContent":["export class NymbalApiError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n public readonly status: number,\n public readonly context?: Record<string, unknown>,\n ) {\n super(message)\n this.name = 'NymbalApiError'\n }\n}\n\nexport interface FetchClientOptions {\n baseUrl: string\n credentials?: RequestCredentials\n getAccessToken?: () => string | null\n getCartToken?: () => string | null\n onUnauthorized?: () => Promise<void>\n}\n\n/** Per request: headers sent beside the client's own. */\nexport interface RequestOptions {\n headers?: Record<string, string>\n /**\n * The token refresh itself (what `onUnauthorized` sends through this client): its 401 is thrown, never waited on or\n * sent again, so a refused refresh cannot wait on itself. An unmarked one that is refused is cut off after\n * 10 s with the original 401.\n */\n authRefresh?: boolean\n /**\n * Sent with a customer's bearer, never sent again without one: when the 401's refresh leaves no token, the 401 is\n * thrown. For a request the API would otherwise answer as a guest (a checkout begin).\n */\n keepIdentity?: boolean\n}\n\n/**\n * The longest a refused request waits on a refresh before its 401 is thrown: far beyond one healthy refresh round trip,\n * and the bound on a refresh that, unmarked, waits on itself.\n */\nconst REFRESH_WAIT_MS = 10_000\n\nconst CSRF_HEADER = 'x-csrf-token'\nconst UNSAFE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\ninterface Envelope<T> {\n data?: T\n meta?: Record<string, unknown>\n error?: { code: string; message: string; context?: Record<string, unknown> }\n}\n\nexport class FetchClient {\n private readonly baseUrl: string\n private readonly credentials: RequestCredentials\n private readonly getAccessToken: () => string | null\n private readonly getCartToken: (() => string | null) | undefined\n private readonly onUnauthorized: (() => Promise<void>) | undefined\n /** The refresh a refused request started, which every request refused meanwhile waits on rather than its own. */\n private refreshing: Promise<void> | null = null\n /** The token `GET /api/csrf` answered: the API's `nymbal.csrf` cookie, which this origin may not be able to read. */\n private csrfToken: Promise<string> | null = null\n /** A session restore's refresh, which every request sent meanwhile waits on so that none goes out as a guest. */\n private held: Promise<void> | null = null\n\n constructor(options: FetchClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.credentials = options.credentials ?? 'include'\n this.getAccessToken = options.getAccessToken ?? (() => null)\n this.getCartToken = options.getCartToken\n this.onUnauthorized = options.onUnauthorized\n }\n\n /** Starts the session's refresh, or joins the one in flight: a client sends one refresh at a time. */\n refreshSession(): Promise<void> {\n if (this.onUnauthorized === undefined) return Promise.resolve()\n return this.refreshing ?? this.refresh(this.onUnauthorized)\n }\n\n /**\n * As `refreshSession`, and every other request sent until the refresh settles waits for it, at most\n * `REFRESH_WAIT_MS`. Held from this call on, before any await: a page's first requests go in the same tick.\n */\n restoreSession(): Promise<void> {\n // A failed or unanswered refresh releases the held requests as well; they go with whatever token there is.\n const held = this.waitFor(this.refreshSession()).then(\n () => undefined,\n () => undefined,\n )\n this.held = held\n void held.then(() => {\n if (this.held === held) this.held = null\n })\n return held\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n return (await this.requestWithMeta<T>(method, path, body, options)).data\n }\n\n /** Like `request`, keeping the envelope's `meta` (pagination cursors live there). */\n async requestWithMeta<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<{ data: T; meta: Record<string, unknown> }> {\n if (this.held !== null && options.authRefresh !== true) await this.held\n // Read before the send's first await: the refresh, if any, in flight when this request was sent.\n const sentDuring = this.refreshing\n let { response, bearer } = await this.doFetch(method, path, body, options)\n\n // A stale token (the cookie was replaced or expired) is fetched again once; a second refusal is thrown.\n if (await this.isCsrfRejection(response)) {\n this.csrfToken = null\n ;({ response, bearer } = await this.doFetch(method, path, body, options))\n }\n\n if (response.status === 401 && this.onUnauthorized && options.authRefresh !== true) {\n const pending = this.refreshing\n const retry = async () => {\n // Sent as a customer, and the refresh left no token: sent again it would act as a guest.\n if (options.keepIdentity === true && bearer !== null && this.getAccessToken() === null) {\n return this.parseResponse<T>(response)\n }\n return this.parseResponse<T>((await this.doFetch(method, path, body, options)).response)\n }\n if (pending === null && this.getAccessToken() !== bearer) {\n // A refresh that ended after this was sent has replaced its token already.\n return retry()\n }\n if (pending !== null && pending === sentDuring) {\n // Sent during the refresh with the token it replaces: sent again once it has, and only if it did.\n if ((await this.waitFor(pending)) && this.getAccessToken() !== bearer) return retry()\n } else if (await this.waitFor(pending ?? this.refresh(this.onUnauthorized))) {\n // Every request refused with the old token waits on the one refresh and is sent again once.\n return retry()\n }\n }\n\n return this.parseResponse<T>(response)\n }\n\n /** Whether the refresh ended within `REFRESH_WAIT_MS`; a refresh that fails throws here, as it always has. */\n private async waitFor(refresh: Promise<void>): Promise<boolean> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const bound = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), REFRESH_WAIT_MS)\n })\n try {\n return await Promise.race([refresh.then(() => true as const), bound])\n } finally {\n clearTimeout(timer)\n }\n }\n\n private refresh(onUnauthorized: () => Promise<void>): Promise<void> {\n const refresh = Promise.resolve()\n .then(onUnauthorized)\n .finally(() => {\n this.refreshing = null\n })\n this.refreshing = refresh\n return refresh\n }\n\n private csrf(): Promise<string> {\n this.csrfToken ??= this.fetchCsrfToken().catch((err: unknown) => {\n this.csrfToken = null\n throw err\n })\n return this.csrfToken\n }\n\n private async fetchCsrfToken(): Promise<string> {\n const response = await globalThis.fetch(`${this.baseUrl}/api/csrf`, {\n method: 'GET',\n credentials: this.credentials,\n })\n const { data } = await this.parseResponse<{ token: string }>(response)\n return data.token\n }\n\n private async isCsrfRejection(response: Response): Promise<boolean> {\n if (response.status !== 403) return false\n const text = await response.clone().text()\n try {\n return (JSON.parse(text) as Envelope<unknown>).error?.code === 'csrf.invalid'\n } catch {\n return false\n }\n }\n\n private async doFetch(\n method: string,\n path: string,\n body: unknown,\n options: RequestOptions,\n ): Promise<{ response: Response; bearer: string | null }> {\n const headers: Record<string, string> = { ...options.headers }\n const cookies: string[] = []\n\n if (UNSAFE_METHODS.has(method.toUpperCase())) {\n const csrf = await this.csrf()\n headers[CSRF_HEADER] = csrf\n // Outside a browser there is no cookie jar to send the pair's cookie half; a browser drops this header.\n cookies.push(`nymbal.csrf=${csrf}`)\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const token = this.getAccessToken()\n if (token) {\n headers['Authorization'] = `Bearer ${token}`\n }\n\n const cartToken = this.getCartToken?.()\n if (cartToken) {\n cookies.unshift(`nymbal.cart=${cartToken}`)\n }\n if (cookies.length > 0) {\n headers['Cookie'] = cookies.join('; ')\n }\n\n const response = await globalThis.fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n credentials: this.credentials,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n })\n return { response, bearer: token }\n }\n\n private async parseResponse<T>(\n response: Response,\n ): Promise<{ data: T; meta: Record<string, unknown> }> {\n const text = await response.text()\n if (!text) {\n return { data: undefined as T, meta: {} }\n }\n\n const envelope = JSON.parse(text) as Envelope<T>\n\n if (envelope.error) {\n throw new NymbalApiError(\n envelope.error.code,\n envelope.error.message,\n response.status,\n envelope.error.context,\n )\n }\n\n return { data: envelope.data as T, meta: envelope.meta ?? {} }\n }\n}\n","import {\n ORDER_EMAIL_HEADER,\n ORDER_KEY_HEADER,\n type AlertKind,\n type Cart,\n type Customer,\n type CustomerAddress,\n type Order,\n type PublicReviewsResult,\n type ReviewResult,\n type ShopperOrder,\n type StoreCapabilities,\n} from '@nymbalhq/types'\nimport type { CommerceAdapter } from './commerce-adapter.js'\nimport type { FetchClient } from './fetch-client.js'\nimport type {\n DenormalisedProduct,\n ProductListParams,\n ProductListResult,\n ProductSearchParams,\n ProductSearchResult,\n CheckoutDetails,\n CheckoutResult,\n RegisterParams,\n AuthResult,\n NewAddress,\n OrderPage,\n} from '../types.js'\n\nexport function createNymbalApiAdapter(fetchClient: FetchClient): CommerceAdapter {\n // One read for a signed-in owner's order and a guest's: the key and the email, when given, go in headers.\n const getOrder = (orderNumber: string, { key, email }: { key?: string; email?: string } = {}) =>\n fetchClient.request<ShopperOrder>(\n 'GET',\n `/api/orders/${encodeURIComponent(orderNumber)}`,\n undefined,\n {\n headers: {\n ...(key !== undefined && { [ORDER_KEY_HEADER]: key }),\n ...(email !== undefined && { [ORDER_EMAIL_HEADER]: email }),\n },\n },\n )\n\n return {\n capabilities: {\n get() {\n return fetchClient.request<StoreCapabilities>('GET', '/api/capabilities')\n },\n },\n\n cart: {\n get() {\n return fetchClient.request<Cart>('GET', '/api/cart')\n },\n addItem(variantId: string, qty: number) {\n return fetchClient.request<Cart>('POST', '/api/cart/items', { variantId, qty })\n },\n removeItem(variantId: string) {\n return fetchClient.request<Cart>(\n 'DELETE',\n `/api/cart/items/${encodeURIComponent(variantId)}`,\n )\n },\n updateQuantity(variantId: string, qty: number) {\n return fetchClient.request<Cart>(\n 'PATCH',\n `/api/cart/items/${encodeURIComponent(variantId)}`,\n { qty },\n )\n },\n clear() {\n return fetchClient.request<Cart>('DELETE', '/api/cart')\n },\n },\n\n products: {\n getBySlug(slug: string) {\n return fetchClient.request<DenormalisedProduct>(\n 'GET',\n `/api/products/${encodeURIComponent(slug)}`,\n )\n },\n list(params?: ProductListParams) {\n const search = new URLSearchParams()\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n if (params?.category !== undefined) search.set('category', params.category)\n if (params?.sort !== undefined) search.set('sort', params.sort)\n const qs = search.toString()\n return fetchClient.request<ProductListResult>('GET', `/api/products${qs ? `?${qs}` : ''}`)\n },\n search(query: string, params?: ProductSearchParams) {\n const search = new URLSearchParams()\n search.set('q', query)\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n if (params?.category !== undefined) search.set('category', params.category)\n return fetchClient.request<ProductSearchResult>(\n 'GET',\n `/api/products/search?${search.toString()}`,\n )\n },\n },\n\n checkout: {\n create(details: CheckoutDetails) {\n // Placed as the signed-in customer or refused: never re-sent as a guest after a failed refresh.\n return fetchClient.request<CheckoutResult>('POST', '/api/checkout', details, {\n keepIdentity: true,\n })\n },\n },\n\n customers: {\n register(params: RegisterParams) {\n return fetchClient.request<AuthResult>('POST', '/api/auth/register', params)\n },\n login(email: string, password: string) {\n return fetchClient.request<AuthResult>('POST', '/api/auth/login', { email, password })\n },\n async logout() {\n await fetchClient.request<{ ok: boolean }>('POST', '/api/auth/logout')\n },\n refresh() {\n return fetchClient.request<{ accessToken: string; expiresIn: number }>(\n 'POST',\n '/api/auth/refresh',\n undefined,\n { authRefresh: true },\n )\n },\n getProfile() {\n return fetchClient.request<Customer>('GET', '/api/customers/me')\n },\n updateProfile(updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>) {\n return fetchClient.request<Customer>('PATCH', '/api/customers/me', updates)\n },\n listAddresses() {\n return fetchClient.request<CustomerAddress[]>('GET', '/api/customers/me/addresses')\n },\n addAddress(address: NewAddress) {\n return fetchClient.request<CustomerAddress>('POST', '/api/customers/me/addresses', address)\n },\n async removeAddress(id: string) {\n await fetchClient.request<{ ok: boolean }>(\n 'DELETE',\n `/api/customers/me/addresses/${encodeURIComponent(id)}`,\n )\n },\n async listOrders(params?: { cursor?: string }): Promise<OrderPage> {\n const qs =\n params?.cursor !== undefined ? `?cursor=${encodeURIComponent(params.cursor)}` : ''\n const { data, meta } = await fetchClient.requestWithMeta<Order[]>('GET', `/api/orders${qs}`)\n const nextCursor = typeof meta.nextCursor === 'string' ? meta.nextCursor : null\n return { items: data ?? [], nextCursor }\n },\n getOrder,\n },\n\n orders: {\n get: getOrder,\n async requestLink({ orderNumber, email }: { orderNumber: string; email: string }) {\n await fetchClient.request<{ sent: string }>('POST', '/api/orders/link', {\n orderNumber,\n email,\n })\n },\n },\n\n recentlyViewed: {\n async record(productId: string) {\n await fetchClient.request<void>('POST', '/api/recently-viewed', { productId })\n },\n list() {\n return fetchClient.request<{ items: DenormalisedProduct[] }>('GET', '/api/recently-viewed')\n },\n },\n\n reviews: {\n getForProduct(productId: string, params?: { limit?: number; cursor?: string }) {\n const search = new URLSearchParams()\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n const qs = search.toString()\n return fetchClient.request<PublicReviewsResult>(\n 'GET',\n `/api/products/${encodeURIComponent(productId)}/reviews${qs ? `?${qs}` : ''}`,\n )\n },\n submit(review: { productId: string; rating: number; title: string; body: string }) {\n return fetchClient.request<ReviewResult>('POST', '/api/reviews', review)\n },\n },\n\n alerts: {\n subscribe(request: { email: string; variantId: string; kind: AlertKind }) {\n return fetchClient.request<{ status: 'pending' }>('POST', '/api/alerts/subscribe', request)\n },\n },\n }\n}\n","import type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport { NymbalApiError } from '../adapter/fetch-client.js'\nimport type { TokenManager } from './token-manager.js'\n\n/**\n * Set in `localStorage` while the browser holds a session the refresh cookie can bring back. It holds no token: a page\n * load with no hint is a guest's, and restores nothing.\n */\nexport const SESSION_HINT_KEY = 'nymbal.auth.session'\n\n/** Where `tokenStorage: 'localStorage'` keeps the access token, and where every client kept it before. */\nexport const ACCESS_TOKEN_KEY = 'nymbal.auth.accessToken'\n\n/** Runs `use` on `localStorage`, or answers `fallback` where there is none or it throws (a locked-down window). */\nfunction withStorage<T>(use: (storage: Storage) => T, fallback: T): T {\n try {\n return typeof globalThis.localStorage === 'undefined' ? fallback : use(globalThis.localStorage)\n } catch {\n return fallback\n }\n}\n\nexport function hasSessionHint(): boolean {\n return withStorage((storage) => storage.getItem(SESSION_HINT_KEY) === '1', false)\n}\n\nexport function markSession(): void {\n withStorage((storage) => storage.setItem(SESSION_HINT_KEY, '1'), undefined)\n}\n\nexport function forgetSession(): void {\n withStorage((storage) => storage.removeItem(SESSION_HINT_KEY), undefined)\n}\n\n/** Removes an access token an earlier version kept in `localStorage`; the shopper it belonged to keeps their session. */\nexport function adoptLegacyToken(): void {\n withStorage((storage) => {\n if (storage.getItem(ACCESS_TOKEN_KEY) === null) return\n storage.removeItem(ACCESS_TOKEN_KEY)\n storage.setItem(SESSION_HINT_KEY, '1')\n }, undefined)\n}\n\n/** The API refused the session itself, as against a refresh that never reached it or that it failed to answer. */\nexport function isSessionRefusal(err: unknown): boolean {\n return err instanceof NymbalApiError && (err.status === 401 || err.status === 403)\n}\n\n/**\n * One refresh from the refresh cookie. It never throws: a failure leaves no token, and forgets the session only when\n * the API refused it, so an API that is down or restarting signs no one out for good.\n */\nexport async function renewSession(\n adapter: CommerceAdapter,\n tokenManager: TokenManager,\n): Promise<void> {\n try {\n const result = await adapter.customers.refresh()\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.warn('[NymbalClient] token refresh failed', err)\n tokenManager.clear()\n if (isSessionRefusal(err)) forgetSession()\n }\n}\n","import { ACCESS_TOKEN_KEY, markSession } from './session.js'\n\n/** A refresh: its token for the manager to hold, or nothing when it has set (or cleared) the token itself. */\ntype RefreshFn = () => Promise<{ accessToken: string; expiresIn: number } | void>\n\nexport interface TokenManagerOptions {\n storage?: 'memory' | 'localStorage'\n storageKey?: string\n}\n\nexport interface TokenManager {\n getAccessToken(): string | null\n setTokens(accessToken: string, expiresIn: number): void\n clear(): void\n scheduleRefresh(refreshFn: RefreshFn): void\n destroy(): void\n}\n\nexport function createTokenManager(options?: TokenManagerOptions): TokenManager {\n const storageMode = options?.storage ?? 'memory'\n const storageKey = options?.storageKey ?? ACCESS_TOKEN_KEY\n\n let memoryToken: string | null = null\n let refreshTimer: ReturnType<typeof setTimeout> | null = null\n let refreshFn: RefreshFn | null = null\n\n function getAccessToken(): string | null {\n if (storageMode === 'localStorage' && typeof globalThis.localStorage !== 'undefined') {\n return globalThis.localStorage.getItem(storageKey)\n }\n return memoryToken\n }\n\n function setAccessToken(token: string | null): void {\n memoryToken = token\n if (storageMode === 'localStorage' && typeof globalThis.localStorage !== 'undefined') {\n if (token) {\n globalThis.localStorage.setItem(storageKey, token)\n } else {\n globalThis.localStorage.removeItem(storageKey)\n }\n }\n }\n\n function setTokens(accessToken: string, expiresIn: number): void {\n setAccessToken(accessToken)\n markSession()\n scheduleAutoRefresh(expiresIn)\n }\n\n function clear(): void {\n setAccessToken(null)\n if (refreshTimer !== null) {\n clearTimeout(refreshTimer)\n refreshTimer = null\n }\n }\n\n function scheduleAutoRefresh(expiresIn: number): void {\n if (refreshTimer !== null) {\n clearTimeout(refreshTimer)\n }\n const delayMs = Math.max(1000, expiresIn * 0.8 * 1000)\n refreshTimer = setTimeout(async () => {\n if (!refreshFn) return\n try {\n const result = await refreshFn()\n if (result) setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.error('[TokenManager] auto-refresh failed, clearing session', err)\n clear()\n }\n }, delayMs)\n }\n\n function scheduleRefresh(fn: RefreshFn): void {\n refreshFn = fn\n }\n\n function destroy(): void {\n clear()\n refreshFn = null\n }\n\n return { getAccessToken, setTokens, clear, scheduleRefresh, destroy }\n}\n","import type { CommerceAdapter } from './adapter/commerce-adapter.js'\nimport { FetchClient } from './adapter/fetch-client.js'\nimport { createNymbalApiAdapter } from './adapter/nymbal-api-adapter.js'\nimport { adoptLegacyToken, hasSessionHint, renewSession } from './auth/session.js'\nimport { createTokenManager, type TokenManager } from './auth/token-manager.js'\nimport type { CartStore } from './store/cart-store.js'\nimport type { ProductStore } from './store/product-store.js'\nimport type { ProductListStore } from './store/product-list-store.js'\nimport type { SearchStore } from './store/search-store.js'\nimport type { CheckoutStore } from './store/checkout-store.js'\nimport type { AuthStore } from './store/auth-store.js'\nimport type { Store } from './store/create-store.js'\nimport type { CustomerAddress, ShopperOrder, StoreCapabilities } from '@nymbalhq/types'\nimport type { DenormalisedProduct, NewAddress, OrderPage } from './types.js'\n\nexport interface NymbalClientOptions {\n baseUrl: string\n adapter?: CommerceAdapter\n tokenStorage?: 'memory' | 'localStorage'\n credentials?: RequestCredentials\n getCartToken?: () => string | null\n}\n\n/** The signed-in customer's own account; every call sends the session's bearer token. */\nexport interface CustomerAccount {\n addresses: {\n list(): Promise<CustomerAddress[]>\n create(address: NewAddress): Promise<CustomerAddress>\n remove(id: string): Promise<void>\n }\n orders: {\n list(opts?: { cursor?: string }): Promise<OrderPage>\n /** `key`, the order's key from its checkout, reads it with no session; without one, only its owner can. */\n get(orderNumber: string, options?: { key?: string }): Promise<ShopperOrder>\n }\n}\n\n/**\n * A guest's orders, opened with the key from the checkout or the order email. Once the key has expired, the read is\n * refused with `orders.key_expired` and opens with the order's email beside the key; the email travels in a header,\n * never in a URL.\n */\nexport interface GuestOrders {\n get(orderNumber: string, options?: { key?: string; email?: string }): Promise<ShopperOrder>\n /** Asks the store to email the order's link to the order's own email; it answers the same whatever matched. */\n requestLink(request: { orderNumber: string; email: string }): Promise<void>\n}\n\n/** The store's capabilities report, read once per client: what it can do does not change while it runs. */\nexport interface StoreCapabilitiesReader {\n get(): Promise<StoreCapabilities>\n}\n\n/** The shopper's recently viewed products, kept by the store under the signed-in customer or the browser's visitor. */\nexport interface RecentlyViewed {\n /** Records a view of the product; the store ignores a product that is not active. */\n record(productId: string): Promise<void>\n /** The shopper's active products, newest first. */\n list(): Promise<{ items: DenormalisedProduct[] }>\n}\n\nexport interface NymbalClient {\n capabilities: StoreCapabilitiesReader\n recentlyViewed: RecentlyViewed\n cart: CartStore\n product: ProductStore\n productList: ProductListStore\n search: SearchStore\n checkout: CheckoutStore\n auth: AuthStore\n customer: CustomerAccount\n orders: GuestOrders\n adapter: CommerceAdapter\n destroy(): void\n}\n\n/** The client's store namespaces, each built on its first read by the factory its `@nymbalhq/sdk/ns/<module>` registers. */\nexport type StoreNamespace = 'cart' | 'product' | 'productList' | 'search' | 'checkout' | 'auth'\n\nconst STORE_NAMESPACES: StoreNamespace[] = [\n 'cart',\n 'product',\n 'productList',\n 'search',\n 'checkout',\n 'auth',\n]\n\n/** What a namespace's factory builds its store from: the client's adapter and token manager, and the client itself. */\nexport interface NamespaceContext {\n adapter: CommerceAdapter\n tokenManager: TokenManager\n client: NymbalClient\n /** The client's one session refresh, holding its other requests until it settles; it never throws. */\n restoreSession: () => Promise<void>\n}\n\ntype NamespaceFactory<K extends StoreNamespace> = (context: NamespaceContext) => NymbalClient[K]\n\ntype Factories = { [K in StoreNamespace]?: NamespaceFactory<K> }\n\n// On globalThis, like the payment drivers: a namespace module of one SDK copy registers for a client of another.\nconst FACTORIES_KEY = Symbol.for('nymbal.sdk.namespaces')\n\nfunction factories(): Factories {\n const holder = globalThis as Record<symbol, Factories | undefined>\n holder[FACTORIES_KEY] ??= {}\n return holder[FACTORIES_KEY]\n}\n\n/** Registers the factory of a store namespace; each `ns` module calls it on import. */\nexport function defineNamespace<K extends StoreNamespace>(\n name: K,\n factory: NamespaceFactory<K>,\n): void {\n factories()[name] = factory as Factories[K]\n}\n\n/** What `onStoreBuilt` hands its listener: any store, a hand-made client's included, reads and subscribes. */\ntype ObservedStore = Pick<Store<unknown>, 'getState' | 'subscribe'>\n\ntype ClientStore = ObservedStore & Pick<Store<unknown>, 'destroy'>\n\ntype StoreListener = (store: ObservedStore) => void\n\n// On a symbol, not in a module's registry, so a client made by another copy of the SDK still reports its stores.\nconst STORE_BUILT = Symbol.for('nymbal.client.onStoreBuilt')\n\ntype StoreBuiltHook = (listener: StoreListener) => () => void\n\nfunction isStore(value: unknown): value is ObservedStore {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as ObservedStore).subscribe === 'function' &&\n typeof (value as ObservedStore).getState === 'function'\n )\n}\n\n// On a symbol, as STORE_BUILT: the session's refresh is the client's, so a page starts it without the auth store.\nconst RESUME_SESSION = Symbol.for('nymbal.client.resumeSession')\n\ntype ResumeSessionHook = () => Promise<void>\n\n/**\n * `auth.restore()`'s refresh without the auth store, for a page that reads no profile: with the session hint and no\n * token held, the one refresh from the refresh cookie, which every other request the client sends meanwhile waits for.\n * A guest's sends nothing. An `auth.restore()` called while it is in flight shares it. Never throws.\n */\nexport function resumeSession(client: NymbalClient): Promise<void> {\n const hook = (client as unknown as Record<symbol, ResumeSessionHook | undefined>)[RESUME_SESSION]\n return typeof hook === 'function' ? hook() : Promise.resolve()\n}\n\n/**\n * Calls `listener` with each store namespace the client has built, and with each it builds later; returns the\n * unsubscribe. A client not made by `createNymbalClient` has its stores already: each is reported once, now.\n */\nexport function onStoreBuilt(client: NymbalClient, listener: StoreListener): () => void {\n const hook = (client as unknown as Record<symbol, StoreBuiltHook | undefined>)[STORE_BUILT]\n if (typeof hook === 'function') return hook(listener)\n for (const value of Object.values(client)) {\n if (isStore(value)) listener(value)\n }\n return () => {}\n}\n\nexport function createNymbalClient(options: NymbalClientOptions): NymbalClient {\n if (options.tokenStorage !== 'localStorage') adoptLegacyToken()\n const tokenManager = createTokenManager({\n ...(options.tokenStorage !== undefined && { storage: options.tokenStorage }),\n })\n\n let adapter: CommerceAdapter\n let fetchClient: FetchClient | undefined\n const renew = () => renewSession(adapter, tokenManager)\n\n if (options.adapter) {\n adapter = options.adapter\n } else {\n fetchClient = new FetchClient({\n baseUrl: options.baseUrl,\n ...(options.credentials !== undefined && { credentials: options.credentials }),\n getAccessToken: () => tokenManager.getAccessToken(),\n ...(options.getCartToken !== undefined && { getCartToken: options.getCartToken }),\n onUnauthorized: renew,\n })\n adapter = createNymbalApiAdapter(fetchClient)\n }\n\n const customer: CustomerAccount = {\n addresses: {\n list: () => adapter.customers.listAddresses(),\n create: (address) => adapter.customers.addAddress(address),\n remove: (id) => adapter.customers.removeAddress(id),\n },\n orders: {\n list: (opts) => adapter.customers.listOrders(opts),\n get: (orderNumber, options) => adapter.customers.getOrder(orderNumber, options),\n },\n }\n\n // A failed read is not kept, so the next get() asks again.\n let report: Promise<StoreCapabilities> | null = null\n const capabilities: StoreCapabilitiesReader = {\n get() {\n if (report === null) {\n const reader = adapter.capabilities\n report =\n reader === undefined\n ? Promise.reject(new Error('This client’s adapter cannot read the store capabilities.'))\n : reader.get()\n report.catch(() => {\n report = null\n })\n }\n return report\n },\n }\n\n // The adapter is the client's for its whole life, so whether it reads guests' orders is known here.\n const noGuestOrders = () =>\n Promise.reject(new Error('This client’s adapter cannot open a guest’s order.'))\n const orders: GuestOrders = adapter.orders ?? { get: noGuestOrders, requestLink: noGuestOrders }\n\n const recentlyViewedOf = (): NonNullable<CommerceAdapter['recentlyViewed']> => {\n const reader = adapter.recentlyViewed\n if (reader === undefined) {\n throw new Error('This client’s adapter cannot record or list recently viewed products.')\n }\n return reader\n }\n const recentlyViewed: RecentlyViewed = {\n async record(productId) {\n await recentlyViewedOf().record(productId)\n },\n async list() {\n return recentlyViewedOf().list()\n },\n }\n\n tokenManager.scheduleRefresh(() => fetchClient?.refreshSession() ?? renew())\n\n const built: { [K in StoreNamespace]?: ClientStore } = {}\n const listeners = new Set<StoreListener>()\n let destroyed = false\n\n function destroy(): void {\n destroyed = true\n for (const store of Object.values(built)) store.destroy()\n tokenManager.destroy()\n }\n\n const storeBuilt: StoreBuiltHook = (listener) => {\n for (const store of Object.values(built)) listener(store)\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n }\n const resume: ResumeSessionHook = () =>\n tokenManager.getAccessToken() || !hasSessionHint() ? Promise.resolve() : context.restoreSession()\n const client = Object.defineProperties(\n { capabilities, recentlyViewed, customer, orders, adapter, destroy },\n { [STORE_BUILT]: { value: storeBuilt }, [RESUME_SESSION]: { value: resume } },\n ) as NymbalClient\n const context: NamespaceContext = {\n adapter,\n tokenManager,\n client,\n restoreSession: () => fetchClient?.restoreSession() ?? renew(),\n }\n\n function build(name: StoreNamespace): ClientStore {\n // Building one now would leave a live store on a client whose destroy() has run.\n if (destroyed) throw new Error(`client.${name} was read after the client was destroyed`)\n const factory = factories()[name] as NamespaceFactory<StoreNamespace> | undefined\n if (factory === undefined) {\n const module = name === 'productList' ? 'product-list' : name\n throw new Error(`client.${name} is not in this client: import '@nymbalhq/sdk/ns/${module}'`)\n }\n const store = (built[name] = factory(context))\n for (const listener of listeners) listener(store)\n return store\n }\n\n // Defined properties are not enumerable: walking or spreading the client builds no store.\n for (const name of STORE_NAMESPACES) {\n Object.defineProperty(client, name, { get: () => built[name] ?? build(name) })\n }\n\n return client\n}\n"],"mappings":"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","names":["options"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/payment-drivers.ts
|
|
2
|
+
var DRIVERS_KEY = /* @__PURE__ */ Symbol.for("nymbal.paymentDrivers");
|
|
3
|
+
function drivers() {
|
|
4
|
+
const holder = globalThis;
|
|
5
|
+
holder[DRIVERS_KEY] ??= /* @__PURE__ */ new Map();
|
|
6
|
+
return holder[DRIVERS_KEY];
|
|
7
|
+
}
|
|
8
|
+
function registerPaymentDriver(name, driver) {
|
|
9
|
+
drivers().set(name, driver);
|
|
10
|
+
}
|
|
11
|
+
function getPaymentDriver(name) {
|
|
12
|
+
return drivers().get(name);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
registerPaymentDriver,
|
|
17
|
+
getPaymentDriver
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=chunk-4GP43MKT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/payment-drivers.ts"],"sourcesContent":["import type { Address } from '@nymbalhq/types'\n\n/** What a checkout already knows about the shopper, which a driver may hand its processor with the payment. */\nexport interface PaymentConfirmContext {\n email?: string\n billing?: Address\n}\n\n/** What a payment driver's `confirm` answers: paid now, accepted without a payment yet, or refused with a reason. */\nexport type PaymentConfirmation = { ok: true; confirmed: boolean } | { ok: false; error: string }\n\n/**\n * The browser half of a payments adapter, named by the `driver` of GET /api/capabilities' payments client. `mount`\n * renders whatever the shopper pays with inside `el`; `confirm` completes a payment the API left `requires_action`,\n * with the shopper's email and billing address when the checkout has them.\n */\nexport interface PaymentDriver {\n mount(el: HTMLElement, options: Record<string, string>): Promise<void> | void\n confirm(clientSecret: string, context?: PaymentConfirmContext): Promise<PaymentConfirmation>\n}\n\n// On globalThis, like the registered client: a page that loads two copies of the SDK still has one registry.\nconst DRIVERS_KEY = Symbol.for('nymbal.paymentDrivers')\n\nfunction drivers(): Map<string, PaymentDriver> {\n const holder = globalThis as Record<symbol, Map<string, PaymentDriver> | undefined>\n holder[DRIVERS_KEY] ??= new Map()\n return holder[DRIVERS_KEY]\n}\n\n/** Registers `driver` under `name`, replacing any driver of that name. */\nexport function registerPaymentDriver(name: string, driver: PaymentDriver): void {\n drivers().set(name, driver)\n}\n\nexport function getPaymentDriver(name: string): PaymentDriver | undefined {\n return drivers().get(name)\n}\n"],"mappings":";AAsBA,IAAM,cAAc,uBAAO,IAAI,uBAAuB;AAEtD,SAAS,UAAsC;AAC7C,QAAM,SAAS;AACf,SAAO,WAAW,MAAM,oBAAI,IAAI;AAChC,SAAO,OAAO,WAAW;AAC3B;AAGO,SAAS,sBAAsB,MAAc,QAA6B;AAC/E,UAAQ,EAAE,IAAI,MAAM,MAAM;AAC5B;AAEO,SAAS,iBAAiB,MAAyC;AACxE,SAAO,QAAQ,EAAE,IAAI,IAAI;AAC3B;","names":[]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStore
|
|
3
|
+
} from "./chunk-U6QF2DRY.js";
|
|
4
|
+
import {
|
|
5
|
+
defineNamespace
|
|
6
|
+
} from "./chunk-2EEZZXZX.js";
|
|
7
|
+
|
|
8
|
+
// src/store/cart-store.ts
|
|
9
|
+
function deriveItemCount(items) {
|
|
10
|
+
let count = 0;
|
|
11
|
+
for (const item of items) {
|
|
12
|
+
count += item.qty;
|
|
13
|
+
}
|
|
14
|
+
return count;
|
|
15
|
+
}
|
|
16
|
+
var INITIAL_STATE = {
|
|
17
|
+
items: [],
|
|
18
|
+
subtotalMinor: 0,
|
|
19
|
+
currency: "USD",
|
|
20
|
+
itemCount: 0,
|
|
21
|
+
loading: false,
|
|
22
|
+
error: null
|
|
23
|
+
};
|
|
24
|
+
function createCartStore(adapter) {
|
|
25
|
+
const store = createStore(INITIAL_STATE);
|
|
26
|
+
async function load() {
|
|
27
|
+
store.setState({ loading: true, error: null });
|
|
28
|
+
try {
|
|
29
|
+
const cart = await adapter.cart.get();
|
|
30
|
+
store.setState({
|
|
31
|
+
items: cart.items,
|
|
32
|
+
subtotalMinor: cart.subtotalMinor,
|
|
33
|
+
currency: cart.currency,
|
|
34
|
+
itemCount: deriveItemCount(cart.items),
|
|
35
|
+
loading: false
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
store.setState({
|
|
39
|
+
loading: false,
|
|
40
|
+
error: err instanceof Error ? err.message : String(err)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function addItem(variantId, qty = 1) {
|
|
45
|
+
store.setState({ loading: true, error: null });
|
|
46
|
+
try {
|
|
47
|
+
const cart = await adapter.cart.addItem(variantId, qty);
|
|
48
|
+
store.setState({
|
|
49
|
+
items: cart.items,
|
|
50
|
+
subtotalMinor: cart.subtotalMinor,
|
|
51
|
+
currency: cart.currency,
|
|
52
|
+
itemCount: deriveItemCount(cart.items),
|
|
53
|
+
loading: false
|
|
54
|
+
});
|
|
55
|
+
} catch (err) {
|
|
56
|
+
store.setState({
|
|
57
|
+
loading: false,
|
|
58
|
+
error: err instanceof Error ? err.message : String(err)
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function removeItem(variantId) {
|
|
63
|
+
store.setState({ loading: true, error: null });
|
|
64
|
+
try {
|
|
65
|
+
const cart = await adapter.cart.removeItem(variantId);
|
|
66
|
+
store.setState({
|
|
67
|
+
items: cart.items,
|
|
68
|
+
subtotalMinor: cart.subtotalMinor,
|
|
69
|
+
currency: cart.currency,
|
|
70
|
+
itemCount: deriveItemCount(cart.items),
|
|
71
|
+
loading: false
|
|
72
|
+
});
|
|
73
|
+
} catch (err) {
|
|
74
|
+
store.setState({
|
|
75
|
+
loading: false,
|
|
76
|
+
error: err instanceof Error ? err.message : String(err)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function updateQuantity(variantId, qty) {
|
|
81
|
+
store.setState({ loading: true, error: null });
|
|
82
|
+
try {
|
|
83
|
+
const cart = await adapter.cart.updateQuantity(variantId, qty);
|
|
84
|
+
store.setState({
|
|
85
|
+
items: cart.items,
|
|
86
|
+
subtotalMinor: cart.subtotalMinor,
|
|
87
|
+
currency: cart.currency,
|
|
88
|
+
itemCount: deriveItemCount(cart.items),
|
|
89
|
+
loading: false
|
|
90
|
+
});
|
|
91
|
+
} catch (err) {
|
|
92
|
+
store.setState({
|
|
93
|
+
loading: false,
|
|
94
|
+
error: err instanceof Error ? err.message : String(err)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function clear() {
|
|
99
|
+
store.setState({ loading: true, error: null });
|
|
100
|
+
try {
|
|
101
|
+
await adapter.cart.clear();
|
|
102
|
+
store.setState({
|
|
103
|
+
items: [],
|
|
104
|
+
subtotalMinor: 0,
|
|
105
|
+
itemCount: 0,
|
|
106
|
+
loading: false
|
|
107
|
+
});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
store.setState({
|
|
110
|
+
loading: false,
|
|
111
|
+
error: err instanceof Error ? err.message : String(err)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
getState: store.getState,
|
|
117
|
+
subscribe: store.subscribe,
|
|
118
|
+
addItem,
|
|
119
|
+
removeItem,
|
|
120
|
+
updateQuantity,
|
|
121
|
+
clear,
|
|
122
|
+
load,
|
|
123
|
+
destroy: store.destroy
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/ns/cart.ts
|
|
128
|
+
defineNamespace("cart", ({ adapter }) => createCartStore(adapter));
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
createCartStore
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=chunk-7LQGOJIT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/cart-store.ts","../src/ns/cart.ts"],"sourcesContent":["import type { CartItem } from '@nymbalhq/types'\nimport type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport type { CartState } from '../types.js'\nimport { createStore } from './create-store.js'\n\nfunction deriveItemCount(items: CartItem[]): number {\n let count = 0\n for (const item of items) {\n count += item.qty\n }\n return count\n}\n\nconst INITIAL_STATE: CartState = {\n items: [],\n subtotalMinor: 0,\n currency: 'USD',\n itemCount: 0,\n loading: false,\n error: null,\n}\n\nexport interface CartStore {\n getState(): CartState\n subscribe(listener: () => void): () => void\n addItem(variantId: string, qty?: number): Promise<void>\n removeItem(variantId: string): Promise<void>\n updateQuantity(variantId: string, qty: number): Promise<void>\n clear(): Promise<void>\n load(): Promise<void>\n destroy(): void\n}\n\nexport function createCartStore(adapter: CommerceAdapter): CartStore {\n const store = createStore<CartState>(INITIAL_STATE)\n\n async function load(): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const cart = await adapter.cart.get()\n store.setState({\n items: cart.items,\n subtotalMinor: cart.subtotalMinor,\n currency: cart.currency,\n itemCount: deriveItemCount(cart.items),\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function addItem(variantId: string, qty = 1): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const cart = await adapter.cart.addItem(variantId, qty)\n store.setState({\n items: cart.items,\n subtotalMinor: cart.subtotalMinor,\n currency: cart.currency,\n itemCount: deriveItemCount(cart.items),\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function removeItem(variantId: string): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const cart = await adapter.cart.removeItem(variantId)\n store.setState({\n items: cart.items,\n subtotalMinor: cart.subtotalMinor,\n currency: cart.currency,\n itemCount: deriveItemCount(cart.items),\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function updateQuantity(variantId: string, qty: number): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const cart = await adapter.cart.updateQuantity(variantId, qty)\n store.setState({\n items: cart.items,\n subtotalMinor: cart.subtotalMinor,\n currency: cart.currency,\n itemCount: deriveItemCount(cart.items),\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function clear(): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n await adapter.cart.clear()\n store.setState({\n items: [],\n subtotalMinor: 0,\n itemCount: 0,\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n addItem,\n removeItem,\n updateQuantity,\n clear,\n load,\n destroy: store.destroy,\n }\n}\n","import { defineNamespace } from '../client.js'\nimport { createCartStore } from '../store/cart-store.js'\n\ndefineNamespace('cart', ({ adapter }) => createCartStore(adapter))\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStore
|
|
3
|
+
} from "./chunk-U6QF2DRY.js";
|
|
4
|
+
import {
|
|
5
|
+
defineNamespace
|
|
6
|
+
} from "./chunk-2EEZZXZX.js";
|
|
7
|
+
|
|
8
|
+
// src/store/product-store.ts
|
|
9
|
+
var INITIAL_STATE = {
|
|
10
|
+
product: null,
|
|
11
|
+
selectedVariant: null,
|
|
12
|
+
loading: false,
|
|
13
|
+
error: null
|
|
14
|
+
};
|
|
15
|
+
function createProductStore(adapter) {
|
|
16
|
+
const store = createStore(INITIAL_STATE);
|
|
17
|
+
async function loadBySlug(slug) {
|
|
18
|
+
store.setState({ loading: true, error: null });
|
|
19
|
+
try {
|
|
20
|
+
const product = await adapter.products.getBySlug(slug);
|
|
21
|
+
const firstVariant = product.variants[0] ?? null;
|
|
22
|
+
store.setState({
|
|
23
|
+
product,
|
|
24
|
+
selectedVariant: firstVariant,
|
|
25
|
+
loading: false
|
|
26
|
+
});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
store.setState({
|
|
29
|
+
loading: false,
|
|
30
|
+
error: err instanceof Error ? err.message : String(err)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function selectVariant(variant) {
|
|
35
|
+
store.setState({ selectedVariant: variant });
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
getState: store.getState,
|
|
39
|
+
subscribe: store.subscribe,
|
|
40
|
+
loadBySlug,
|
|
41
|
+
selectVariant,
|
|
42
|
+
destroy: store.destroy
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/ns/product.ts
|
|
47
|
+
defineNamespace("product", ({ adapter }) => createProductStore(adapter));
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
createProductStore
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=chunk-CJVA46FR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/product-store.ts","../src/ns/product.ts"],"sourcesContent":["import type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport type { ProductState, DenormalisedVariant } from '../types.js'\nimport { createStore } from './create-store.js'\n\nconst INITIAL_STATE: ProductState = {\n product: null,\n selectedVariant: null,\n loading: false,\n error: null,\n}\n\nexport interface ProductStore {\n getState(): ProductState\n subscribe(listener: () => void): () => void\n loadBySlug(slug: string): Promise<void>\n selectVariant(variant: DenormalisedVariant | null): void\n destroy(): void\n}\n\nexport function createProductStore(adapter: CommerceAdapter): ProductStore {\n const store = createStore<ProductState>(INITIAL_STATE)\n\n async function loadBySlug(slug: string): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const product = await adapter.products.getBySlug(slug)\n const firstVariant = product.variants[0] ?? null\n store.setState({\n product,\n selectedVariant: firstVariant,\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n function selectVariant(variant: DenormalisedVariant | null): void {\n store.setState({ selectedVariant: variant })\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n loadBySlug,\n selectVariant,\n destroy: store.destroy,\n }\n}\n","import { defineNamespace } from '../client.js'\nimport { createProductStore } from '../store/product-store.js'\n\ndefineNamespace('product', ({ adapter }) => createProductStore(adapter))\n"],"mappings":";;;;;;;;AAIA,IAAM,gBAA8B;AAAA,EAClC,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,OAAO;AACT;AAUO,SAAS,mBAAmB,SAAwC;AACzE,QAAM,QAAQ,YAA0B,aAAa;AAErD,iBAAe,WAAW,MAA6B;AACrD,UAAM,SAAS,EAAE,SAAS,MAAM,OAAO,KAAK,CAAC;AAC7C,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,SAAS,UAAU,IAAI;AACrD,YAAM,eAAe,QAAQ,SAAS,CAAC,KAAK;AAC5C,YAAM,SAAS;AAAA,QACb;AAAA,QACA,iBAAiB;AAAA,QACjB,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,SAAS;AAAA,QACb,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,WAAS,cAAc,SAA2C;AAChE,UAAM,SAAS,EAAE,iBAAiB,QAAQ,CAAC;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,IACA,SAAS,MAAM;AAAA,EACjB;AACF;;;AChDA,gBAAgB,WAAW,CAAC,EAAE,QAAQ,MAAM,mBAAmB,OAAO,CAAC;","names":[]}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStore
|
|
3
|
+
} from "./chunk-U6QF2DRY.js";
|
|
4
|
+
import {
|
|
5
|
+
defineNamespace,
|
|
6
|
+
forgetSession,
|
|
7
|
+
hasSessionHint,
|
|
8
|
+
isSessionRefusal,
|
|
9
|
+
renewSession
|
|
10
|
+
} from "./chunk-2EEZZXZX.js";
|
|
11
|
+
|
|
12
|
+
// src/store/auth-store.ts
|
|
13
|
+
var INITIAL_STATE = {
|
|
14
|
+
customer: null,
|
|
15
|
+
isAuthenticated: false,
|
|
16
|
+
loading: false,
|
|
17
|
+
error: null
|
|
18
|
+
};
|
|
19
|
+
function createAuthStore(adapter, tokenManager, restoreSession = () => renewSession(adapter, tokenManager)) {
|
|
20
|
+
const store = createStore(INITIAL_STATE);
|
|
21
|
+
async function login(email, password) {
|
|
22
|
+
store.setState({ loading: true, error: null });
|
|
23
|
+
try {
|
|
24
|
+
const result = await adapter.customers.login(email, password);
|
|
25
|
+
tokenManager.setTokens(result.accessToken, result.expiresIn);
|
|
26
|
+
store.setState({
|
|
27
|
+
customer: result.customer,
|
|
28
|
+
isAuthenticated: true,
|
|
29
|
+
loading: false
|
|
30
|
+
});
|
|
31
|
+
} catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
store.setState({ loading: false, error: message });
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function register(params) {
|
|
38
|
+
store.setState({ loading: true, error: null });
|
|
39
|
+
try {
|
|
40
|
+
const result = await adapter.customers.register(params);
|
|
41
|
+
tokenManager.setTokens(result.accessToken, result.expiresIn);
|
|
42
|
+
store.setState({
|
|
43
|
+
customer: result.customer,
|
|
44
|
+
isAuthenticated: true,
|
|
45
|
+
loading: false
|
|
46
|
+
});
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
49
|
+
store.setState({ loading: false, error: message });
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function logout() {
|
|
54
|
+
store.setState({ loading: true, error: null });
|
|
55
|
+
try {
|
|
56
|
+
await adapter.customers.logout();
|
|
57
|
+
tokenManager.clear();
|
|
58
|
+
forgetSession();
|
|
59
|
+
store.setState({
|
|
60
|
+
customer: null,
|
|
61
|
+
isAuthenticated: false,
|
|
62
|
+
loading: false
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
tokenManager.clear();
|
|
66
|
+
forgetSession();
|
|
67
|
+
store.setState({
|
|
68
|
+
customer: null,
|
|
69
|
+
isAuthenticated: false,
|
|
70
|
+
loading: false,
|
|
71
|
+
error: err instanceof Error ? err.message : String(err)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function refreshToken() {
|
|
76
|
+
try {
|
|
77
|
+
const result = await adapter.customers.refresh();
|
|
78
|
+
tokenManager.setTokens(result.accessToken, result.expiresIn);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error("[AuthStore] token refresh failed, logging out", err);
|
|
81
|
+
tokenManager.clear();
|
|
82
|
+
if (isSessionRefusal(err)) forgetSession();
|
|
83
|
+
store.setState({
|
|
84
|
+
customer: null,
|
|
85
|
+
isAuthenticated: false
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function updateProfile(updates) {
|
|
90
|
+
store.setState({ loading: true, error: null });
|
|
91
|
+
try {
|
|
92
|
+
const customer = await adapter.customers.updateProfile(updates);
|
|
93
|
+
store.setState({ customer, loading: false });
|
|
94
|
+
} catch (err) {
|
|
95
|
+
store.setState({
|
|
96
|
+
loading: false,
|
|
97
|
+
error: err instanceof Error ? err.message : String(err)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function loadProfile() {
|
|
102
|
+
if (!tokenManager.getAccessToken()) return;
|
|
103
|
+
store.setState({ loading: true, error: null });
|
|
104
|
+
try {
|
|
105
|
+
const customer = await adapter.customers.getProfile();
|
|
106
|
+
store.setState({
|
|
107
|
+
customer,
|
|
108
|
+
isAuthenticated: true,
|
|
109
|
+
loading: false
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
store.setState({
|
|
113
|
+
loading: false,
|
|
114
|
+
error: err instanceof Error ? err.message : String(err)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function restore() {
|
|
119
|
+
if (tokenManager.getAccessToken()) return loadProfile();
|
|
120
|
+
if (!hasSessionHint()) return;
|
|
121
|
+
await restoreSession();
|
|
122
|
+
if (tokenManager.getAccessToken()) return loadProfile();
|
|
123
|
+
store.setState({ customer: null, isAuthenticated: false });
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
getState: store.getState,
|
|
127
|
+
subscribe: store.subscribe,
|
|
128
|
+
login,
|
|
129
|
+
register,
|
|
130
|
+
logout,
|
|
131
|
+
refreshToken,
|
|
132
|
+
updateProfile,
|
|
133
|
+
loadProfile,
|
|
134
|
+
restore,
|
|
135
|
+
destroy: store.destroy
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/ns/auth.ts
|
|
140
|
+
defineNamespace(
|
|
141
|
+
"auth",
|
|
142
|
+
({ adapter, tokenManager, restoreSession }) => createAuthStore(adapter, tokenManager, restoreSession)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
createAuthStore
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=chunk-PDZKR65O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/auth-store.ts","../src/ns/auth.ts"],"sourcesContent":["import type { Customer } from '@nymbalhq/types'\nimport type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport { forgetSession, hasSessionHint, isSessionRefusal, renewSession } from '../auth/session.js'\nimport type { TokenManager } from '../auth/token-manager.js'\nimport type { AuthState, RegisterParams } from '../types.js'\nimport { createStore } from './create-store.js'\n\nconst INITIAL_STATE: AuthState = {\n customer: null,\n isAuthenticated: false,\n loading: false,\n error: null,\n}\n\nexport interface AuthStore {\n getState(): AuthState\n subscribe(listener: () => void): () => void\n login(email: string, password: string): Promise<void>\n register(params: RegisterParams): Promise<void>\n logout(): Promise<void>\n refreshToken(): Promise<void>\n updateProfile(updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>): Promise<void>\n loadProfile(): Promise<void>\n /**\n * Brings a page load's session back: the profile with a token held; with none, one refresh from the refresh cookie\n * when this browser has signed in (the session hint), then the profile; a guest's sends nothing. Never throws.\n */\n restore(): Promise<void>\n destroy(): void\n}\n\nexport function createAuthStore(\n adapter: CommerceAdapter,\n tokenManager: TokenManager,\n restoreSession: () => Promise<void> = () => renewSession(adapter, tokenManager),\n): AuthStore {\n const store = createStore<AuthState>(INITIAL_STATE)\n\n async function login(email: string, password: string): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const result = await adapter.customers.login(email, password)\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n store.setState({\n customer: result.customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n store.setState({ loading: false, error: message })\n throw err\n }\n }\n\n async function register(params: RegisterParams): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const result = await adapter.customers.register(params)\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n store.setState({\n customer: result.customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n store.setState({ loading: false, error: message })\n throw err\n }\n }\n\n async function logout(): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n await adapter.customers.logout()\n tokenManager.clear()\n forgetSession()\n store.setState({\n customer: null,\n isAuthenticated: false,\n loading: false,\n })\n } catch (err) {\n tokenManager.clear()\n forgetSession()\n store.setState({\n customer: null,\n isAuthenticated: false,\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function refreshToken(): Promise<void> {\n try {\n const result = await adapter.customers.refresh()\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.error('[AuthStore] token refresh failed, logging out', err)\n tokenManager.clear()\n if (isSessionRefusal(err)) forgetSession()\n store.setState({\n customer: null,\n isAuthenticated: false,\n })\n }\n }\n\n async function updateProfile(\n updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>,\n ): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const customer = await adapter.customers.updateProfile(updates)\n store.setState({ customer, loading: false })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function loadProfile(): Promise<void> {\n if (!tokenManager.getAccessToken()) return\n store.setState({ loading: true, error: null })\n try {\n const customer = await adapter.customers.getProfile()\n store.setState({\n customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function restore(): Promise<void> {\n if (tokenManager.getAccessToken()) return loadProfile()\n if (!hasSessionHint()) return\n await restoreSession()\n if (tokenManager.getAccessToken()) return loadProfile()\n store.setState({ customer: null, isAuthenticated: false })\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n login,\n register,\n logout,\n refreshToken,\n updateProfile,\n loadProfile,\n restore,\n destroy: store.destroy,\n }\n}\n","import { defineNamespace } from '../client.js'\nimport { createAuthStore } from '../store/auth-store.js'\n\ndefineNamespace('auth', ({ adapter, tokenManager, restoreSession }) =>\n createAuthStore(adapter, tokenManager, restoreSession),\n)\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/store/create-store.ts
|
|
2
|
+
function createStore(initialState) {
|
|
3
|
+
let state = initialState;
|
|
4
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
function getState() {
|
|
6
|
+
return state;
|
|
7
|
+
}
|
|
8
|
+
function setState(partial) {
|
|
9
|
+
const next = typeof partial === "function" ? partial(state) : partial;
|
|
10
|
+
state = { ...state, ...next };
|
|
11
|
+
for (const listener of listeners) {
|
|
12
|
+
listener();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function subscribe(listener) {
|
|
16
|
+
listeners.add(listener);
|
|
17
|
+
return () => {
|
|
18
|
+
listeners.delete(listener);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function destroy() {
|
|
22
|
+
listeners.clear();
|
|
23
|
+
}
|
|
24
|
+
return { getState, setState, subscribe, destroy };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
createStore
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=chunk-U6QF2DRY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/create-store.ts"],"sourcesContent":["export type Listener = () => void\n\nexport interface Store<TState> {\n getState(): TState\n setState(partial: Partial<TState> | ((prev: TState) => Partial<TState>)): void\n subscribe(listener: Listener): () => void\n destroy(): void\n}\n\nexport function createStore<TState extends object>(\n initialState: TState,\n): Store<TState> {\n let state = initialState\n const listeners = new Set<Listener>()\n\n function getState(): TState {\n return state\n }\n\n function setState(partial: Partial<TState> | ((prev: TState) => Partial<TState>)): void {\n const next = typeof partial === 'function' ? partial(state) : partial\n state = { ...state, ...next }\n for (const listener of listeners) {\n listener()\n }\n }\n\n function subscribe(listener: Listener): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n }\n\n function destroy(): void {\n listeners.clear()\n }\n\n return { getState, setState, subscribe, destroy }\n}\n"],"mappings":";AASO,SAAS,YACd,cACe;AACf,MAAI,QAAQ;AACZ,QAAM,YAAY,oBAAI,IAAc;AAEpC,WAAS,WAAmB;AAC1B,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,SAAsE;AACtF,UAAM,OAAO,OAAO,YAAY,aAAa,QAAQ,KAAK,IAAI;AAC9D,YAAQ,EAAE,GAAG,OAAO,GAAG,KAAK;AAC5B,eAAW,YAAY,WAAW;AAChC,eAAS;AAAA,IACX;AAAA,EACF;AAEA,WAAS,UAAU,UAAgC;AACjD,cAAU,IAAI,QAAQ;AACtB,WAAO,MAAM;AACX,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,cAAU,MAAM;AAAA,EAClB;AAEA,SAAO,EAAE,UAAU,UAAU,WAAW,QAAQ;AAClD;","names":[]}
|