@doswiftly/storefront-sdk 4.0.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/README.md +430 -0
- package/dist/__tests__/unit/test-helpers.d.ts +46 -0
- package/dist/__tests__/unit/test-helpers.d.ts.map +1 -0
- package/dist/__tests__/unit/test-helpers.js +72 -0
- package/dist/core/auth/auth-client.d.ts +46 -0
- package/dist/core/auth/auth-client.d.ts.map +1 -0
- package/dist/core/auth/auth-client.js +82 -0
- package/dist/core/auth/cookie-config.d.ts +18 -0
- package/dist/core/auth/cookie-config.d.ts.map +1 -0
- package/dist/core/auth/cookie-config.js +18 -0
- package/dist/core/auth/handlers.d.ts +32 -0
- package/dist/core/auth/handlers.d.ts.map +1 -0
- package/dist/core/auth/handlers.js +127 -0
- package/dist/core/auth/routes.d.ts +21 -0
- package/dist/core/auth/routes.d.ts.map +1 -0
- package/dist/core/auth/routes.js +14 -0
- package/dist/core/auth/token-client.d.ts +26 -0
- package/dist/core/auth/token-client.d.ts.map +1 -0
- package/dist/core/auth/token-client.js +42 -0
- package/dist/core/auth/types.d.ts +53 -0
- package/dist/core/auth/types.d.ts.map +1 -0
- package/dist/core/auth/types.js +4 -0
- package/dist/core/cache.d.ts +54 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +82 -0
- package/dist/core/cart/cart-client.d.ts +57 -0
- package/dist/core/cart/cart-client.d.ts.map +1 -0
- package/dist/core/cart/cart-client.js +89 -0
- package/dist/core/cart/types.d.ts +110 -0
- package/dist/core/cart/types.d.ts.map +1 -0
- package/dist/core/cart/types.js +6 -0
- package/dist/core/client/compose.d.ts +9 -0
- package/dist/core/client/compose.d.ts.map +1 -0
- package/dist/core/client/compose.js +9 -0
- package/dist/core/client/create-client.d.ts +15 -0
- package/dist/core/client/create-client.d.ts.map +1 -0
- package/dist/core/client/create-client.js +85 -0
- package/dist/core/client/dedupe.d.ts +7 -0
- package/dist/core/client/dedupe.d.ts.map +1 -0
- package/dist/core/client/dedupe.js +16 -0
- package/dist/core/client/execute.d.ts +20 -0
- package/dist/core/client/execute.d.ts.map +1 -0
- package/dist/core/client/execute.js +48 -0
- package/dist/core/client/hash.d.ts +7 -0
- package/dist/core/client/hash.d.ts.map +1 -0
- package/dist/core/client/hash.js +21 -0
- package/dist/core/client/operation-name.d.ts +7 -0
- package/dist/core/client/operation-name.d.ts.map +1 -0
- package/dist/core/client/operation-name.js +10 -0
- package/dist/core/client/types.d.ts +126 -0
- package/dist/core/client/types.d.ts.map +1 -0
- package/dist/core/client/types.js +26 -0
- package/dist/core/errors.d.ts +43 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +43 -0
- package/dist/core/format.d.ts +92 -0
- package/dist/core/format.d.ts.map +1 -0
- package/dist/core/format.js +216 -0
- package/dist/core/helpers/assert-no-user-errors.d.ts +10 -0
- package/dist/core/helpers/assert-no-user-errors.d.ts.map +1 -0
- package/dist/core/helpers/assert-no-user-errors.js +16 -0
- package/dist/core/helpers/normalize-connection.d.ts +36 -0
- package/dist/core/helpers/normalize-connection.d.ts.map +1 -0
- package/dist/core/helpers/normalize-connection.js +21 -0
- package/dist/core/helpers/sanitize-html.d.ts +8 -0
- package/dist/core/helpers/sanitize-html.d.ts.map +1 -0
- package/dist/core/helpers/sanitize-html.js +35 -0
- package/dist/core/index.d.ts +59 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +68 -0
- package/dist/core/middleware/auth.d.ts +16 -0
- package/dist/core/middleware/auth.d.ts.map +1 -0
- package/dist/core/middleware/auth.js +22 -0
- package/dist/core/middleware/currency.d.ts +15 -0
- package/dist/core/middleware/currency.d.ts.map +1 -0
- package/dist/core/middleware/currency.js +21 -0
- package/dist/core/middleware/errors.d.ts +24 -0
- package/dist/core/middleware/errors.d.ts.map +1 -0
- package/dist/core/middleware/errors.js +77 -0
- package/dist/core/middleware/retry.d.ts +22 -0
- package/dist/core/middleware/retry.d.ts.map +1 -0
- package/dist/core/middleware/retry.js +58 -0
- package/dist/core/middleware/timeout.d.ts +19 -0
- package/dist/core/middleware/timeout.d.ts.map +1 -0
- package/dist/core/middleware/timeout.js +51 -0
- package/dist/core/operations/auth.d.ts +11 -0
- package/dist/core/operations/auth.d.ts.map +1 -0
- package/dist/core/operations/auth.js +112 -0
- package/dist/core/operations/cart.d.ts +15 -0
- package/dist/core/operations/cart.d.ts.map +1 -0
- package/dist/core/operations/cart.js +169 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/react/cookies.d.ts +28 -0
- package/dist/react/cookies.d.ts.map +1 -0
- package/dist/react/cookies.js +49 -0
- package/dist/react/helpers/create-store-context.d.ts +37 -0
- package/dist/react/helpers/create-store-context.d.ts.map +1 -0
- package/dist/react/helpers/create-store-context.js +47 -0
- package/dist/react/hooks/use-auth.d.ts +65 -0
- package/dist/react/hooks/use-auth.d.ts.map +1 -0
- package/dist/react/hooks/use-auth.js +168 -0
- package/dist/react/hooks/use-cart-manager.d.ts +30 -0
- package/dist/react/hooks/use-cart-manager.d.ts.map +1 -0
- package/dist/react/hooks/use-cart-manager.js +223 -0
- package/dist/react/hooks/use-currency.d.ts +11 -0
- package/dist/react/hooks/use-currency.d.ts.map +1 -0
- package/dist/react/hooks/use-currency.js +19 -0
- package/dist/react/hooks/use-debounced-value.d.ts +15 -0
- package/dist/react/hooks/use-debounced-value.d.ts.map +1 -0
- package/dist/react/hooks/use-debounced-value.js +25 -0
- package/dist/react/hooks/use-hydrated.d.ts +9 -0
- package/dist/react/hooks/use-hydrated.d.ts.map +1 -0
- package/dist/react/hooks/use-hydrated.js +14 -0
- package/dist/react/hooks/use-storefront-client.d.ts +6 -0
- package/dist/react/hooks/use-storefront-client.d.ts.map +1 -0
- package/dist/react/hooks/use-storefront-client.js +8 -0
- package/dist/react/index.d.ts +30 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +34 -0
- package/dist/react/providers/currency-provider.d.ts +14 -0
- package/dist/react/providers/currency-provider.d.ts.map +1 -0
- package/dist/react/providers/currency-provider.js +20 -0
- package/dist/react/providers/storefront-client-provider.d.ts +33 -0
- package/dist/react/providers/storefront-client-provider.d.ts.map +1 -0
- package/dist/react/providers/storefront-client-provider.js +57 -0
- package/dist/react/providers/storefront-provider.d.ts +42 -0
- package/dist/react/providers/storefront-provider.d.ts.map +1 -0
- package/dist/react/providers/storefront-provider.js +40 -0
- package/dist/react/server/get-storefront-client.d.ts +42 -0
- package/dist/react/server/get-storefront-client.d.ts.map +1 -0
- package/dist/react/server/get-storefront-client.js +44 -0
- package/dist/react/server/index.d.ts +2 -0
- package/dist/react/server/index.d.ts.map +1 -0
- package/dist/react/server/index.js +1 -0
- package/dist/react/stores/auth.store.d.ts +48 -0
- package/dist/react/stores/auth.store.d.ts.map +1 -0
- package/dist/react/stores/auth.store.js +67 -0
- package/dist/react/stores/currency.store.d.ts +29 -0
- package/dist/react/stores/currency.store.d.ts.map +1 -0
- package/dist/react/stores/currency.store.js +76 -0
- package/dist/react/stores/index.d.ts +8 -0
- package/dist/react/stores/index.d.ts.map +1 -0
- package/dist/react/stores/index.js +10 -0
- package/dist/react/stores/store-context.d.ts +27 -0
- package/dist/react/stores/store-context.d.ts.map +1 -0
- package/dist/react/stores/store-context.js +62 -0
- package/package.json +71 -0
- package/src/__tests__/contract/storefront-api.contract.test.ts +450 -0
- package/src/__tests__/unit/auth-client.test.ts +210 -0
- package/src/__tests__/unit/cart-client.test.ts +233 -0
- package/src/__tests__/unit/create-client.test.ts +356 -0
- package/src/__tests__/unit/helpers.test.ts +377 -0
- package/src/__tests__/unit/middleware.test.ts +374 -0
- package/src/__tests__/unit/test-helpers.ts +103 -0
- package/src/core/auth/auth-client.ts +123 -0
- package/src/core/auth/cookie-config.ts +23 -0
- package/src/core/auth/handlers.ts +168 -0
- package/src/core/auth/routes.ts +26 -0
- package/src/core/auth/token-client.ts +51 -0
- package/src/core/auth/types.ts +54 -0
- package/src/core/cache.ts +102 -0
- package/src/core/cart/cart-client.ts +150 -0
- package/src/core/cart/types.ts +104 -0
- package/src/core/client/compose.ts +15 -0
- package/src/core/client/create-client.ts +129 -0
- package/src/core/client/dedupe.ts +19 -0
- package/src/core/client/execute.ts +70 -0
- package/src/core/client/hash.ts +21 -0
- package/src/core/client/operation-name.ts +12 -0
- package/src/core/client/types.ts +171 -0
- package/src/core/errors.ts +67 -0
- package/src/core/format.ts +254 -0
- package/src/core/helpers/assert-no-user-errors.ts +21 -0
- package/src/core/helpers/normalize-connection.ts +48 -0
- package/src/core/helpers/sanitize-html.ts +42 -0
- package/src/core/index.ts +148 -0
- package/src/core/middleware/auth.ts +27 -0
- package/src/core/middleware/currency.ts +26 -0
- package/src/core/middleware/errors.ts +86 -0
- package/src/core/middleware/retry.ts +75 -0
- package/src/core/middleware/timeout.ts +61 -0
- package/src/core/operations/auth.ts +123 -0
- package/src/core/operations/cart.ts +185 -0
- package/src/index.ts +25 -0
- package/src/react/cookies.ts +54 -0
- package/src/react/helpers/create-store-context.ts +56 -0
- package/src/react/hooks/use-auth.ts +218 -0
- package/src/react/hooks/use-cart-manager.ts +236 -0
- package/src/react/hooks/use-currency.ts +23 -0
- package/src/react/hooks/use-debounced-value.ts +30 -0
- package/src/react/hooks/use-hydrated.ts +20 -0
- package/src/react/hooks/use-storefront-client.ts +12 -0
- package/src/react/index.ts +45 -0
- package/src/react/providers/currency-provider.tsx +30 -0
- package/src/react/providers/storefront-client-provider.tsx +90 -0
- package/src/react/providers/storefront-provider.tsx +71 -0
- package/src/react/server/get-storefront-client.ts +60 -0
- package/src/react/server/index.ts +1 -0
- package/src/react/stores/auth.store.ts +112 -0
- package/src/react/stores/currency.store.ts +113 -0
- package/src/react/stores/index.ts +17 -0
- package/src/react/stores/store-context.tsx +82 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# @doswiftly/storefront-sdk
|
|
2
|
+
|
|
3
|
+
Layered runtime SDK for DoSwiftly Commerce storefronts. Hydrogen-aligned architecture with **0 runtime dependencies** in core.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
@doswiftly/storefront-sdk
|
|
9
|
+
├── core (.) — Framework-agnostic: transport, middleware, CartClient, AuthClient,
|
|
10
|
+
│ cache, format utilities, sanitizeHtml, normalizeConnection,
|
|
11
|
+
│ auth cookie config/handlers/token client, route matching
|
|
12
|
+
├── react (./react) — React adapter: providers, Zustand stores (Context-based), hooks,
|
|
13
|
+
│ useHydrated, useDebouncedValue, createStoreContext
|
|
14
|
+
├── react/server — Server-side client factory
|
|
15
|
+
└── cache (./cache) — Cache strategy functions
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Core** works everywhere: Node.js, Edge Workers, Deno, Bun, CLI scripts — without React.
|
|
19
|
+
**React adapter** requires `react ^18 || ^19` and `zustand ^5` as peer dependencies.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @doswiftly/storefront-sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Core (framework-agnostic)
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import {
|
|
33
|
+
createStorefrontClient,
|
|
34
|
+
authMiddleware,
|
|
35
|
+
currencyMiddleware,
|
|
36
|
+
retryMiddleware,
|
|
37
|
+
timeoutMiddleware,
|
|
38
|
+
errorMiddleware,
|
|
39
|
+
CartClient,
|
|
40
|
+
AuthClient,
|
|
41
|
+
} from '@doswiftly/storefront-sdk';
|
|
42
|
+
|
|
43
|
+
const client = createStorefrontClient({
|
|
44
|
+
apiUrl: 'https://api.doswiftly.pl',
|
|
45
|
+
shopSlug: 'my-shop',
|
|
46
|
+
middleware: [
|
|
47
|
+
authMiddleware(() => getToken()),
|
|
48
|
+
currencyMiddleware(() => getCurrency()),
|
|
49
|
+
retryMiddleware({ maxRetries: 2 }),
|
|
50
|
+
timeoutMiddleware({ timeout: 5000 }),
|
|
51
|
+
errorMiddleware(), // ALWAYS LAST
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Query (deduplicated, cached)
|
|
56
|
+
const data = await client.query(ProductQuery, { handle: 'foo' }, cacheLong());
|
|
57
|
+
|
|
58
|
+
// Mutation (never cached, never retried)
|
|
59
|
+
const result = await client.mutate(CartCreateMutation, { input: {} });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### React (Next.js)
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// app/layout.tsx
|
|
66
|
+
import { StorefrontProvider } from '@doswiftly/storefront-sdk/react';
|
|
67
|
+
|
|
68
|
+
export default function Layout({ children }) {
|
|
69
|
+
return (
|
|
70
|
+
<StorefrontProvider
|
|
71
|
+
config={{ apiUrl: process.env.NEXT_PUBLIC_API_URL!, shopSlug: process.env.NEXT_PUBLIC_SHOP_SLUG! }}
|
|
72
|
+
shopData={shopData}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</StorefrontProvider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// Client Component
|
|
82
|
+
'use client';
|
|
83
|
+
import { useAuth, useCartManager, useAuthStore, useCurrencyStore, useAuthHydrated } from '@doswiftly/storefront-sdk/react';
|
|
84
|
+
|
|
85
|
+
const { login, logout } = useAuth({ onSetToken, onClearToken });
|
|
86
|
+
const { addItem, removeItem, lines, isLoading } = useCartManager();
|
|
87
|
+
const { isAuthenticated, customer } = useAuthStore();
|
|
88
|
+
const authHydrated = useAuthHydrated(); // true after persist rehydration
|
|
89
|
+
const { currency, setCurrency } = useCurrencyStore();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Export Paths
|
|
93
|
+
|
|
94
|
+
| Path | Description | Dependencies |
|
|
95
|
+
|------|-------------|-------------|
|
|
96
|
+
| `@doswiftly/storefront-sdk` | Core: transport, middleware, clients, errors, format, sanitize, auth handlers, route matching | **0** |
|
|
97
|
+
| `@doswiftly/storefront-sdk/react` | Providers, hooks, store hooks, useHydrated, useDebouncedValue, createStoreContext | react, zustand |
|
|
98
|
+
| `@doswiftly/storefront-sdk/react/server` | Server-side client factory | react |
|
|
99
|
+
| `@doswiftly/storefront-sdk/cache` | Cache strategy functions | **0** |
|
|
100
|
+
|
|
101
|
+
## Core API
|
|
102
|
+
|
|
103
|
+
### createStorefrontClient
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const client = createStorefrontClient({
|
|
107
|
+
apiUrl: string,
|
|
108
|
+
shopSlug: string,
|
|
109
|
+
middleware?: Middleware[],
|
|
110
|
+
defaultHeaders?: Record<string, string>,
|
|
111
|
+
fetch?: typeof globalThis.fetch, // custom fetch (polyfill, test mocks)
|
|
112
|
+
debug?: boolean, // log requests in dev
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
client.query<T, V>(document, variables?, cache?): Promise<T>
|
|
116
|
+
client.mutate<T, V>(document, variables?): Promise<T>
|
|
117
|
+
client.use(middleware): void // imperative middleware add
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Features: lazy pipeline compilation, same-tick request deduplication, TypedDocumentString support.
|
|
121
|
+
|
|
122
|
+
### Middleware Pipeline
|
|
123
|
+
|
|
124
|
+
Order matters: `auth → currency → [custom] → retry → timeout → errors (LAST)`
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import {
|
|
128
|
+
authMiddleware, // Authorization: Bearer {token}
|
|
129
|
+
currencyMiddleware, // X-Preferred-Currency header
|
|
130
|
+
retryMiddleware, // Exponential backoff (queries only, not mutations)
|
|
131
|
+
timeoutMiddleware, // AbortController, edge-safe (default 5s)
|
|
132
|
+
errorMiddleware, // Normalizes all errors → StorefrontError (ALWAYS LAST)
|
|
133
|
+
} from '@doswiftly/storefront-sdk';
|
|
134
|
+
|
|
135
|
+
// Custom middleware
|
|
136
|
+
const logMiddleware: Middleware = async (req, next) => {
|
|
137
|
+
console.log('Request:', req.operationName);
|
|
138
|
+
const response = await next(req);
|
|
139
|
+
console.log('Response:', response.status);
|
|
140
|
+
return response;
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### CartClient
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const cartClient = new CartClient(client);
|
|
148
|
+
|
|
149
|
+
const cart = await cartClient.create();
|
|
150
|
+
const updated = await cartClient.addItems(cartId, [{ merchandiseId: 'v-123', quantity: 1 }]);
|
|
151
|
+
await cartClient.updateItems(cartId, [{ id: 'line-1', quantity: 3 }]);
|
|
152
|
+
await cartClient.removeItems(cartId, ['line-1']);
|
|
153
|
+
await cartClient.updateDiscountCodes(cartId, ['SAVE10']);
|
|
154
|
+
await cartClient.updateNote(cartId, 'Gift message');
|
|
155
|
+
await cartClient.updateBuyerIdentity(cartId, { email: 'user@example.com' });
|
|
156
|
+
const existing = await cartClient.get(cartId);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Auto-throws `StorefrontError` with code `USER_ERROR` on validation failures.
|
|
160
|
+
|
|
161
|
+
### AuthClient
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const authClient = new AuthClient(client);
|
|
165
|
+
|
|
166
|
+
const { accessToken, expiresAt } = await authClient.login('user@example.com', 'pass');
|
|
167
|
+
await authClient.logout(accessToken);
|
|
168
|
+
const renewed = await authClient.renewToken(accessToken);
|
|
169
|
+
const { accessToken, customer } = await authClient.register({ email, password, firstName });
|
|
170
|
+
const customer = await authClient.getCustomer(accessToken);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### StorefrontError
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { StorefrontError, ErrorCodes } from '@doswiftly/storefront-sdk';
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await client.query(ProductQuery, { handle: 'missing' });
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof StorefrontError) {
|
|
182
|
+
err.code; // 'GRAPHQL_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'USER_ERROR' | ...
|
|
183
|
+
err.status; // HTTP status (0 for network errors)
|
|
184
|
+
err.graphqlErrors; // GraphQL-level errors
|
|
185
|
+
err.userErrors; // Field-level validation errors
|
|
186
|
+
err.hasUserErrors; // boolean
|
|
187
|
+
err.isNetworkError; // boolean
|
|
188
|
+
err.isTimeout; // boolean
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Format Utilities
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import {
|
|
197
|
+
formatPrice,
|
|
198
|
+
formatPriceRange,
|
|
199
|
+
formatAmount,
|
|
200
|
+
formatDate,
|
|
201
|
+
formatDateTime,
|
|
202
|
+
formatNumber,
|
|
203
|
+
formatPercentage,
|
|
204
|
+
getCurrencySymbol,
|
|
205
|
+
CURRENCY_SYMBOLS,
|
|
206
|
+
CURRENCY_LOCALES,
|
|
207
|
+
} from '@doswiftly/storefront-sdk';
|
|
208
|
+
|
|
209
|
+
formatPrice({ amount: '99.99', currencyCode: 'USD' }); // "$99.99"
|
|
210
|
+
formatPriceRange(minPrice, maxPrice); // "$10.00 - $50.00"
|
|
211
|
+
formatAmount('115.20', 'EUR'); // "115,20 €"
|
|
212
|
+
formatDate(new Date()); // "Dec 9, 2025"
|
|
213
|
+
formatPercentage(0.15); // "15%"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### HTML Sanitizer
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { sanitizeHtml } from '@doswiftly/storefront-sdk';
|
|
220
|
+
|
|
221
|
+
// Defense-in-depth: strips <script>, event handlers, javascript: URLs
|
|
222
|
+
const safe = sanitizeHtml(userHtml);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Connection Normalizer
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { normalizeConnection } from '@doswiftly/storefront-sdk';
|
|
229
|
+
|
|
230
|
+
// Relay connection → flat array
|
|
231
|
+
const { items, pageInfo, totalCount } = normalizeConnection(data.products);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Auth Cookie Config (Platform Contract)
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { AUTH_COOKIE_NAME, AUTH_COOKIE_DEFAULTS } from '@doswiftly/storefront-sdk';
|
|
238
|
+
|
|
239
|
+
// AUTH_COOKIE_NAME = 'customerAccessToken'
|
|
240
|
+
// AUTH_COOKIE_DEFAULTS = { name, path, sameSite, httpOnly, secure, maxAge }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Auth Cookie Handlers (API Route Factories)
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { createSetTokenHandler, createClearTokenHandler } from '@doswiftly/storefront-sdk';
|
|
247
|
+
|
|
248
|
+
// Next.js API route (2 lines):
|
|
249
|
+
// app/api/auth/set-token/route.ts
|
|
250
|
+
export const POST = createSetTokenHandler();
|
|
251
|
+
|
|
252
|
+
// app/api/auth/clear-token/route.ts
|
|
253
|
+
export const POST = createClearTokenHandler();
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Uses pure Web API (Request/Response) — 0 deps, framework-agnostic.
|
|
257
|
+
Security: origin validation, Content-Type check, CSRF via SameSite=Lax, httpOnly cookie.
|
|
258
|
+
|
|
259
|
+
### Auth Token Client (Client-side)
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { createAuthTokenClient } from '@doswiftly/storefront-sdk';
|
|
263
|
+
|
|
264
|
+
const { setToken, clearToken } = createAuthTokenClient();
|
|
265
|
+
await setToken(accessToken); // POST /api/auth/set-token
|
|
266
|
+
await clearToken(); // POST /api/auth/clear-token
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Route Matching
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { matchesRoute } from '@doswiftly/storefront-sdk';
|
|
273
|
+
|
|
274
|
+
// Supports exact and prefix matching
|
|
275
|
+
matchesRoute('/account/orders', ['/account']); // true
|
|
276
|
+
matchesRoute('/products', ['/account']); // false
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Cache Strategies
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { cacheLong, cacheShort, cacheNone, cachePrivate, cacheCustom } from '@doswiftly/storefront-sdk/cache';
|
|
283
|
+
|
|
284
|
+
cacheLong() // 1h + 23h stale-while-revalidate
|
|
285
|
+
cacheLong({ tags: ['product', slug] }) // with Next.js revalidation tags
|
|
286
|
+
cacheShort() // 1s + 9s swr
|
|
287
|
+
cacheNone() // no-store
|
|
288
|
+
cachePrivate() // private, 1s + 9s swr
|
|
289
|
+
cacheCustom({ maxAge: 300, swr: 600 }) // 5min + 10min swr
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## React Adapter
|
|
293
|
+
|
|
294
|
+
### Providers
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
// Convenience (recommended)
|
|
298
|
+
<StorefrontProvider config={{ apiUrl, shopSlug }} shopData={shop}>
|
|
299
|
+
{children}
|
|
300
|
+
</StorefrontProvider>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`StorefrontProvider` creates Zustand store instances via `useRef` and provides them through React Context. This eliminates module-level singleton issues with Turbopack/bundler module duplication.
|
|
304
|
+
|
|
305
|
+
### useAuth
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
const {
|
|
309
|
+
login, // (email, password) => Promise<LoginResult>
|
|
310
|
+
logout, // () => Promise<LogoutResult>
|
|
311
|
+
renewToken, // () => Promise<TokenRenewResult>
|
|
312
|
+
isLoggingIn, isLoggingOut, isRenewingToken, isLoading,
|
|
313
|
+
error,
|
|
314
|
+
} = useAuth({
|
|
315
|
+
onSetToken: async (token) => { /* set httpOnly cookie via server route */ },
|
|
316
|
+
onClearToken: async () => { /* clear httpOnly cookie */ },
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### useCartManager
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
const {
|
|
324
|
+
getCart, // () => Promise<Cart | null>
|
|
325
|
+
addItem, // (lines: CartLineInput[]) => Promise<Cart>
|
|
326
|
+
updateItem, // (lines: CartLineUpdateInput[]) => Promise<Cart>
|
|
327
|
+
removeItem, // (lineIds: string[]) => Promise<Cart>
|
|
328
|
+
updateDiscountCodes, updateNote, clearCart, getCartId,
|
|
329
|
+
isLoading, error,
|
|
330
|
+
} = useCartManager();
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Cart ID is persisted in a cookie (SSR/edge visible).
|
|
334
|
+
|
|
335
|
+
### useHydrated
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { useHydrated } from '@doswiftly/storefront-sdk/react';
|
|
339
|
+
|
|
340
|
+
const isHydrated = useHydrated();
|
|
341
|
+
// false during SSR and first client render, true after hydration
|
|
342
|
+
// Use to guard browser-only state (localStorage, cookies, window)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### useDebouncedValue
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { useDebouncedValue } from '@doswiftly/storefront-sdk/react';
|
|
349
|
+
|
|
350
|
+
const debouncedQuery = useDebouncedValue(query, 300);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### createStoreContext
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { createStoreContext } from '@doswiftly/storefront-sdk/react';
|
|
357
|
+
import { createStore } from 'zustand/vanilla';
|
|
358
|
+
|
|
359
|
+
// Define store factory + Context-based hooks (eliminates module-level singletons)
|
|
360
|
+
const { Provider: CartProvider, useStore: useCartStore, useApi: useCartStoreApi } =
|
|
361
|
+
createStoreContext<CartState>('CartStore');
|
|
362
|
+
|
|
363
|
+
// In layout:
|
|
364
|
+
const cartStore = useRef(createCartStore()).current;
|
|
365
|
+
<CartProvider store={cartStore}>{children}</CartProvider>
|
|
366
|
+
|
|
367
|
+
// In components:
|
|
368
|
+
const isOpen = useCartStore((s) => s.isOpen);
|
|
369
|
+
const api = useCartStoreApi(); // for .getState() in callbacks
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Zustand Stores (Context-based)
|
|
373
|
+
|
|
374
|
+
Stores use `createStore()` from `zustand/vanilla` + React Context pattern. All store hooks require `StorefrontProvider` wrapper.
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { useAuthStore, useAuthHydrated, useCurrencyStore } from '@doswiftly/storefront-sdk/react';
|
|
378
|
+
|
|
379
|
+
// Auth — state
|
|
380
|
+
const { isAuthenticated, customer, accessToken } = useAuthStore();
|
|
381
|
+
// Auth — with selector
|
|
382
|
+
const isAuthenticated = useAuthStore(s => s.isAuthenticated);
|
|
383
|
+
// Auth — persist hydration (replaces old isHydrated store field)
|
|
384
|
+
const authHydrated = useAuthHydrated(); // true after localStorage rehydration
|
|
385
|
+
|
|
386
|
+
// Currency
|
|
387
|
+
const { currency, baseCurrency, supportedCurrencies, setCurrency, isLoaded } = useCurrencyStore();
|
|
388
|
+
|
|
389
|
+
// For .getState() in callbacks (e.g. logout, renewToken)
|
|
390
|
+
import { useAuthStoreApi, useCurrencyStoreApi } from '@doswiftly/storefront-sdk/react';
|
|
391
|
+
const authStore = useAuthStoreApi();
|
|
392
|
+
const token = authStore.getState().accessToken;
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Server-side
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { getStorefrontClient } from '@doswiftly/storefront-sdk/react/server';
|
|
399
|
+
|
|
400
|
+
// Server Component or Route Handler
|
|
401
|
+
const client = getStorefrontClient({
|
|
402
|
+
apiUrl: process.env.API_URL!,
|
|
403
|
+
shopSlug: process.env.SHOP_SLUG!,
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Template Integration
|
|
408
|
+
|
|
409
|
+
Storefronts (Next.js templates) import SDK for **infrastructure** and own their **data-fetching layer**:
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
SDK provides: Template owns:
|
|
413
|
+
├── Transport + Middleware ├── codegen.ts + generated/graphql.ts
|
|
414
|
+
├── CartClient + AuthClient ├── lib/graphql/hooks.ts (React Query)
|
|
415
|
+
├── Providers + Zustand stores ├── lib/graphql/server.ts (React cache)
|
|
416
|
+
├── Format utilities ├── lib/graphql/fragments/
|
|
417
|
+
├── sanitizeHtml ├── stores/ (UI state via createStoreContext)
|
|
418
|
+
├── normalizeConnection ├── hooks/ (use-auth, use-cart-actions, etc.)
|
|
419
|
+
├── Auth handlers + token client ├── lib/auth/routes.ts (route config)
|
|
420
|
+
├── useHydrated + useDebouncedValue
|
|
421
|
+
├── createStoreContext └── components/providers/
|
|
422
|
+
├── AUTH_COOKIE_NAME + matchesRoute
|
|
423
|
+
└── Cache strategies
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Data-fetching hooks are generated locally via `@graphql-codegen/client-preset` (TypedDocumentString).
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
MIT
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test helpers — mock fetch factory, mock client builder.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Create a mock fetch that returns the specified GraphQL response.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createMockFetch(responseData: unknown, options?: {
|
|
8
|
+
errors?: Array<{
|
|
9
|
+
message: string;
|
|
10
|
+
}>;
|
|
11
|
+
status?: number;
|
|
12
|
+
delay?: number;
|
|
13
|
+
}): typeof globalThis.fetch;
|
|
14
|
+
/**
|
|
15
|
+
* Create a mock fetch that tracks all calls and returns specified response.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createSpyFetch(responseData: unknown, options?: {
|
|
18
|
+
errors?: Array<{
|
|
19
|
+
message: string;
|
|
20
|
+
}>;
|
|
21
|
+
status?: number;
|
|
22
|
+
}): {
|
|
23
|
+
fetch: typeof globalThis.fetch;
|
|
24
|
+
calls: {
|
|
25
|
+
url: string;
|
|
26
|
+
init: RequestInit;
|
|
27
|
+
}[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Create a mock fetch that fails with a network error.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createNetworkErrorFetch(errorMessage?: string): typeof globalThis.fetch;
|
|
33
|
+
/**
|
|
34
|
+
* Create a mock fetch that returns different responses on subsequent calls.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createSequenceFetch(responses: Array<{
|
|
37
|
+
data?: unknown;
|
|
38
|
+
errors?: Array<{
|
|
39
|
+
message: string;
|
|
40
|
+
}>;
|
|
41
|
+
status?: number;
|
|
42
|
+
}>): {
|
|
43
|
+
fetch: typeof globalThis.fetch;
|
|
44
|
+
callCount: () => number;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=test-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/test-helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,OAAO,EACrB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,UAAU,CAAC,KAAK,CAqBzB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,OAAO,EACrB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;WAqB0B,OAAO,UAAU,CAAC,KAAK;;aAnBxB,MAAM;cAAQ,WAAW;;EAoBpD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,SAAiB,GAAG,OAAO,UAAU,CAAC,KAAK,CAI9F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACzF;IAAE,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,MAAM,CAAA;CAAE,CAiB7D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test helpers — mock fetch factory, mock client builder.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Create a mock fetch that returns the specified GraphQL response.
|
|
6
|
+
*/
|
|
7
|
+
export function createMockFetch(responseData, options) {
|
|
8
|
+
const { errors, status = 200, delay: delayMs } = options ?? {};
|
|
9
|
+
return async (_url, _init) => {
|
|
10
|
+
if (delayMs) {
|
|
11
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
12
|
+
}
|
|
13
|
+
// Check for abort signal
|
|
14
|
+
if (_init?.signal?.aborted) {
|
|
15
|
+
throw new DOMException('The operation was aborted', 'AbortError');
|
|
16
|
+
}
|
|
17
|
+
const body = { data: responseData };
|
|
18
|
+
if (errors)
|
|
19
|
+
body.errors = errors;
|
|
20
|
+
return new Response(JSON.stringify(body), {
|
|
21
|
+
status,
|
|
22
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a mock fetch that tracks all calls and returns specified response.
|
|
28
|
+
*/
|
|
29
|
+
export function createSpyFetch(responseData, options) {
|
|
30
|
+
const calls = [];
|
|
31
|
+
const { errors, status = 200 } = options ?? {};
|
|
32
|
+
const fetchFn = async (url, init) => {
|
|
33
|
+
calls.push({ url: url.toString(), init: init });
|
|
34
|
+
if (init?.signal?.aborted) {
|
|
35
|
+
throw new DOMException('The operation was aborted', 'AbortError');
|
|
36
|
+
}
|
|
37
|
+
const body = { data: responseData };
|
|
38
|
+
if (errors)
|
|
39
|
+
body.errors = errors;
|
|
40
|
+
return new Response(JSON.stringify(body), {
|
|
41
|
+
status,
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
return { fetch: fetchFn, calls };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a mock fetch that fails with a network error.
|
|
49
|
+
*/
|
|
50
|
+
export function createNetworkErrorFetch(errorMessage = 'fetch failed') {
|
|
51
|
+
return async () => {
|
|
52
|
+
throw new TypeError(errorMessage);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a mock fetch that returns different responses on subsequent calls.
|
|
57
|
+
*/
|
|
58
|
+
export function createSequenceFetch(responses) {
|
|
59
|
+
let index = 0;
|
|
60
|
+
const fetchFn = async (_url, _init) => {
|
|
61
|
+
const response = responses[Math.min(index, responses.length - 1)];
|
|
62
|
+
index++;
|
|
63
|
+
const body = { data: response.data ?? null };
|
|
64
|
+
if (response.errors)
|
|
65
|
+
body.errors = response.errors;
|
|
66
|
+
return new Response(JSON.stringify(body), {
|
|
67
|
+
status: response.status ?? 200,
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
return { fetch: fetchFn, callCount: () => index };
|
|
72
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthClient — plain async API for customer authentication (no React).
|
|
3
|
+
*
|
|
4
|
+
* Wraps StorefrontClient.mutate/query with typed auth operations.
|
|
5
|
+
* Auto-throws on userErrors via assertNoUserErrors.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const authClient = new AuthClient(storefrontClient);
|
|
10
|
+
*
|
|
11
|
+
* const result = await authClient.login('user@example.com', 'password');
|
|
12
|
+
* console.log(result.accessToken); // JWT token
|
|
13
|
+
*
|
|
14
|
+
* const customer = await authClient.getCustomer(result.accessToken);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { StorefrontClient } from '../client/types';
|
|
18
|
+
import type { AuthResult, Customer, CustomerCreateInput } from './types';
|
|
19
|
+
export declare class AuthClient {
|
|
20
|
+
private readonly client;
|
|
21
|
+
constructor(client: StorefrontClient);
|
|
22
|
+
/**
|
|
23
|
+
* Login with email and password.
|
|
24
|
+
* Returns access token + expiry.
|
|
25
|
+
*/
|
|
26
|
+
login(email: string, password: string): Promise<AuthResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Logout — invalidate token on backend.
|
|
29
|
+
* Does not throw on failure (token may already be expired).
|
|
30
|
+
*/
|
|
31
|
+
logout(token: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Renew access token — extends expiry.
|
|
34
|
+
*/
|
|
35
|
+
renewToken(token: string): Promise<AuthResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Register new customer account.
|
|
38
|
+
* Returns access token + customer data.
|
|
39
|
+
*/
|
|
40
|
+
register(input: CustomerCreateInput): Promise<AuthResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Fetch customer data by access token.
|
|
43
|
+
*/
|
|
44
|
+
getCustomer(accessToken: string): Promise<Customer | null>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=auth-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../../../src/core/auth/auth-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAUzE,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,gBAAgB;IAErD;;;OAGG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiBjE;;;OAGG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1C;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiBpD;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC;IAmB/D;;OAEG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;CAOjE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthClient — plain async API for customer authentication (no React).
|
|
3
|
+
*
|
|
4
|
+
* Wraps StorefrontClient.mutate/query with typed auth operations.
|
|
5
|
+
* Auto-throws on userErrors via assertNoUserErrors.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const authClient = new AuthClient(storefrontClient);
|
|
10
|
+
*
|
|
11
|
+
* const result = await authClient.login('user@example.com', 'password');
|
|
12
|
+
* console.log(result.accessToken); // JWT token
|
|
13
|
+
*
|
|
14
|
+
* const customer = await authClient.getCustomer(result.accessToken);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { assertNoUserErrors } from '../helpers/assert-no-user-errors';
|
|
18
|
+
import { CUSTOMER_LOGIN, CUSTOMER_LOGOUT, CUSTOMER_TOKEN_RENEW, CUSTOMER_CREATE, CUSTOMER_QUERY, } from '../operations/auth';
|
|
19
|
+
export class AuthClient {
|
|
20
|
+
client;
|
|
21
|
+
constructor(client) {
|
|
22
|
+
this.client = client;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Login with email and password.
|
|
26
|
+
* Returns access token + expiry.
|
|
27
|
+
*/
|
|
28
|
+
async login(email, password) {
|
|
29
|
+
const data = await this.client.mutate(CUSTOMER_LOGIN, { input: { email, password } });
|
|
30
|
+
assertNoUserErrors(data.customerAccessTokenCreate);
|
|
31
|
+
const token = data.customerAccessTokenCreate.customerAccessToken;
|
|
32
|
+
return {
|
|
33
|
+
accessToken: token.accessToken,
|
|
34
|
+
expiresAt: token.expiresAt,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Logout — invalidate token on backend.
|
|
39
|
+
* Does not throw on failure (token may already be expired).
|
|
40
|
+
*/
|
|
41
|
+
async logout(token) {
|
|
42
|
+
try {
|
|
43
|
+
await this.client.mutate(CUSTOMER_LOGOUT, { customerAccessToken: token });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Silently ignore — token may already be expired
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Renew access token — extends expiry.
|
|
51
|
+
*/
|
|
52
|
+
async renewToken(token) {
|
|
53
|
+
const data = await this.client.mutate(CUSTOMER_TOKEN_RENEW, { customerAccessToken: token });
|
|
54
|
+
assertNoUserErrors(data.customerAccessTokenRenew);
|
|
55
|
+
const renewed = data.customerAccessTokenRenew.customerAccessToken;
|
|
56
|
+
return {
|
|
57
|
+
accessToken: renewed.accessToken,
|
|
58
|
+
expiresAt: renewed.expiresAt,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Register new customer account.
|
|
63
|
+
* Returns access token + customer data.
|
|
64
|
+
*/
|
|
65
|
+
async register(input) {
|
|
66
|
+
const data = await this.client.mutate(CUSTOMER_CREATE, { input });
|
|
67
|
+
assertNoUserErrors(data.customerCreate);
|
|
68
|
+
const token = data.customerCreate.customerAccessToken;
|
|
69
|
+
return {
|
|
70
|
+
accessToken: token.accessToken,
|
|
71
|
+
expiresAt: token.expiresAt,
|
|
72
|
+
customer: data.customerCreate.customer ?? undefined,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fetch customer data by access token.
|
|
77
|
+
*/
|
|
78
|
+
async getCustomer(accessToken) {
|
|
79
|
+
const data = await this.client.query(CUSTOMER_QUERY, { customerAccessToken: accessToken });
|
|
80
|
+
return data.customer;
|
|
81
|
+
}
|
|
82
|
+
}
|