@doswiftly/storefront-sdk 4.4.0 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/core/cart/types.d.ts +75 -20
  2. package/dist/core/cart/types.d.ts.map +1 -1
  3. package/dist/core/cart/types.js +3 -0
  4. package/dist/core/image.d.ts +24 -2
  5. package/dist/core/image.d.ts.map +1 -1
  6. package/dist/core/image.js +145 -2
  7. package/dist/core/index.d.ts +1 -1
  8. package/dist/core/index.d.ts.map +1 -1
  9. package/dist/core/index.js +2 -0
  10. package/dist/core/operations/cart.d.ts +15 -9
  11. package/dist/core/operations/cart.d.ts.map +1 -1
  12. package/dist/core/operations/cart.js +131 -58
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/package.json +19 -14
  16. package/src/__tests__/contract/storefront-api.contract.test.ts +0 -450
  17. package/src/__tests__/unit/auth-client.test.ts +0 -210
  18. package/src/__tests__/unit/bot-protection.test.ts +0 -461
  19. package/src/__tests__/unit/cart-client.test.ts +0 -233
  20. package/src/__tests__/unit/cart-store.test.ts +0 -349
  21. package/src/__tests__/unit/create-client.test.ts +0 -356
  22. package/src/__tests__/unit/helpers.test.ts +0 -377
  23. package/src/__tests__/unit/middleware.test.ts +0 -374
  24. package/src/__tests__/unit/test-helpers.ts +0 -103
  25. package/src/core/auth/auth-client.ts +0 -123
  26. package/src/core/auth/cookie-config.ts +0 -23
  27. package/src/core/auth/handlers.ts +0 -168
  28. package/src/core/auth/routes.ts +0 -26
  29. package/src/core/auth/token-client.ts +0 -51
  30. package/src/core/auth/types.ts +0 -54
  31. package/src/core/bot-protection/abstract-manager.ts +0 -185
  32. package/src/core/bot-protection/create-manager.ts +0 -37
  33. package/src/core/bot-protection/eucaptcha-manager.ts +0 -88
  34. package/src/core/bot-protection/fallback-manager.ts +0 -43
  35. package/src/core/bot-protection/turnstile-manager.ts +0 -92
  36. package/src/core/bot-protection/types/eucaptcha.d.ts +0 -28
  37. package/src/core/bot-protection/types/turnstile.d.ts +0 -33
  38. package/src/core/cache.ts +0 -102
  39. package/src/core/cart/cart-client.ts +0 -150
  40. package/src/core/cart/cookie-config.ts +0 -13
  41. package/src/core/cart/types.ts +0 -104
  42. package/src/core/client/compose.ts +0 -15
  43. package/src/core/client/create-client.ts +0 -129
  44. package/src/core/client/dedupe.ts +0 -19
  45. package/src/core/client/execute.ts +0 -70
  46. package/src/core/client/hash.ts +0 -21
  47. package/src/core/client/operation-name.ts +0 -12
  48. package/src/core/client/types.ts +0 -171
  49. package/src/core/currency/cookie-config.ts +0 -13
  50. package/src/core/errors.ts +0 -67
  51. package/src/core/format.ts +0 -254
  52. package/src/core/helpers/assert-no-user-errors.ts +0 -21
  53. package/src/core/helpers/normalize-connection.ts +0 -48
  54. package/src/core/helpers/sanitize-html.ts +0 -42
  55. package/src/core/image.ts +0 -22
  56. package/src/core/index.ts +0 -174
  57. package/src/core/language/cookie-config.ts +0 -13
  58. package/src/core/middleware/auth.ts +0 -27
  59. package/src/core/middleware/bot-protection.ts +0 -140
  60. package/src/core/middleware/currency.ts +0 -27
  61. package/src/core/middleware/errors.ts +0 -86
  62. package/src/core/middleware/language.ts +0 -30
  63. package/src/core/middleware/retry.ts +0 -75
  64. package/src/core/middleware/timeout.ts +0 -61
  65. package/src/core/operations/auth.ts +0 -123
  66. package/src/core/operations/cart.ts +0 -185
  67. package/src/index.ts +0 -25
  68. package/src/react/bot-protection/bot-protection-context.ts +0 -17
  69. package/src/react/bot-protection/bot-protection-widget.tsx +0 -46
  70. package/src/react/cookies.ts +0 -89
  71. package/src/react/helpers/create-store-context.ts +0 -56
  72. package/src/react/hooks/use-auth.ts +0 -218
  73. package/src/react/hooks/use-bot-protection.ts +0 -31
  74. package/src/react/hooks/use-cart-manager.ts +0 -236
  75. package/src/react/hooks/use-currency.ts +0 -23
  76. package/src/react/hooks/use-debounced-value.ts +0 -30
  77. package/src/react/hooks/use-hydrated.ts +0 -20
  78. package/src/react/hooks/use-storefront-client.ts +0 -12
  79. package/src/react/index.ts +0 -71
  80. package/src/react/providers/currency-provider.tsx +0 -30
  81. package/src/react/providers/language-provider.tsx +0 -34
  82. package/src/react/providers/storefront-client-provider.tsx +0 -107
  83. package/src/react/providers/storefront-provider.tsx +0 -99
  84. package/src/react/server/get-storefront-client.ts +0 -60
  85. package/src/react/server/index.ts +0 -1
  86. package/src/react/stores/auth.store.ts +0 -112
  87. package/src/react/stores/cart.context.ts +0 -10
  88. package/src/react/stores/cart.store.ts +0 -254
  89. package/src/react/stores/currency.store.ts +0 -93
  90. package/src/react/stores/index.ts +0 -17
  91. package/src/react/stores/language.store.ts +0 -90
  92. package/src/react/stores/store-context.tsx +0 -103
  93. package/src/react/types/shop-config.ts +0 -22
  94. package/tsconfig.json +0 -20
  95. package/vitest.config.ts +0 -14
@@ -1,42 +0,0 @@
1
- /**
2
- * Lightweight HTML sanitizer for defense-in-depth.
3
- * Primary sanitization happens on the backend (sanitize-html library).
4
- * This strips dangerous tags/attributes as an extra safety layer.
5
- * Works in both Node.js and Edge runtimes (no external dependencies).
6
- */
7
-
8
- const DANGEROUS_TAG_PATTERNS = [
9
- // Tags with content that must be fully removed
10
- /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi,
11
- /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe\s*>/gi,
12
- /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object\s*>/gi,
13
- /<form\b[^<]*(?:(?!<\/form>)<[^<]*)*<\/form\s*>/gi,
14
- /<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style\s*>/gi,
15
- // Self-closing / void dangerous tags
16
- /<embed\b[^>]*\/?>/gi,
17
- /<link\b[^>]*\/?>/gi,
18
- /<meta\b[^>]*\/?>/gi,
19
- /<base\b[^>]*\/?>/gi,
20
- /<applet\b[^<]*(?:(?!<\/applet>)<[^<]*)*<\/applet\s*>/gi,
21
- ];
22
-
23
- // Event handler attributes (onclick, onerror, onload, etc.)
24
- const EVENT_HANDLER_PATTERN = /\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi;
25
-
26
- // javascript: protocol in href/src/action attributes
27
- const JS_PROTOCOL_PATTERN = /(href|src|action)\s*=\s*["']\s*javascript\s*:/gi;
28
-
29
- export function sanitizeHtml(html: string): string {
30
- if (!html) return html;
31
-
32
- let result = html;
33
-
34
- for (const pattern of DANGEROUS_TAG_PATTERNS) {
35
- result = result.replace(pattern, '');
36
- }
37
-
38
- result = result.replace(EVENT_HANDLER_PATTERN, '');
39
- result = result.replace(JS_PROTOCOL_PATTERN, '$1="');
40
-
41
- return result;
42
- }
package/src/core/image.ts DELETED
@@ -1,22 +0,0 @@
1
- /**
2
- * Image types for DoSwiftly storefronts.
3
- *
4
- * GraphQL API returns ready-to-use CDN URLs with transform query params.
5
- * No client-side loader needed — use `url(transform: { maxWidth: 800 })` in queries.
6
- */
7
-
8
- /**
9
- * Image data from GraphQL API (matches Image type in storefront schema).
10
- */
11
- export interface ImageData {
12
- /** Image URL from GraphQL (ready-to-use CDN URL, optionally with transform query params) */
13
- url: string;
14
- /** Alt text for accessibility + SEO */
15
- altText?: string | null;
16
- /** Original image width in pixels */
17
- width?: number | null;
18
- /** Original image height in pixels */
19
- height?: number | null;
20
- /** Image ID */
21
- id?: string | null;
22
- }
package/src/core/index.ts DELETED
@@ -1,174 +0,0 @@
1
- /**
2
- * @doswiftly/storefront-sdk — Core (framework-agnostic)
3
- *
4
- * 100% framework-agnostic, 0 runtime dependencies.
5
- * Works in Node.js, Edge Workers, Deno, Bun, CLI scripts — without React.
6
- *
7
- * @example
8
- * ```typescript
9
- * import {
10
- * createStorefrontClient,
11
- * authMiddleware,
12
- * currencyMiddleware,
13
- * retryMiddleware,
14
- * timeoutMiddleware,
15
- * errorMiddleware,
16
- * CartClient,
17
- * AuthClient,
18
- * } from '@doswiftly/storefront-sdk';
19
- *
20
- * const client = createStorefrontClient({
21
- * apiUrl: 'https://api.doswiftly.pl',
22
- * shopSlug: 'my-shop',
23
- * middleware: [
24
- * authMiddleware(() => getToken()),
25
- * currencyMiddleware(() => getCurrency()),
26
- * retryMiddleware({ maxRetries: 2 }),
27
- * timeoutMiddleware({ timeout: 5000 }),
28
- * errorMiddleware(),
29
- * ],
30
- * });
31
- *
32
- * const cartClient = new CartClient(client);
33
- * const cart = await cartClient.create();
34
- * ```
35
- */
36
-
37
- // Client factory
38
- export { createStorefrontClient } from './client/create-client';
39
-
40
- // Client types
41
- export type {
42
- StorefrontClient,
43
- StorefrontClientConfig,
44
- Middleware,
45
- ExecuteFn,
46
- GraphQLRequest,
47
- GraphQLResponse,
48
- GraphQLErrorInfo,
49
- UserError,
50
- CacheStrategy,
51
- CacheOptions,
52
- TypedDocumentString,
53
- } from './client/types';
54
-
55
- // Middleware
56
- export { authMiddleware } from './middleware/auth';
57
- export { currencyMiddleware } from './middleware/currency';
58
- export { languageMiddleware } from './middleware/language';
59
- export {
60
- botProtectionMiddleware,
61
- BOT_PROTECTION_HEADER,
62
- type BotProtectionTokenProvider,
63
- type BotProtectionConfig,
64
- type BotProtectionProviderConfig,
65
- type BotProtectionMiddlewareOptions,
66
- type FailStrategy,
67
- } from './middleware/bot-protection';
68
- export { retryMiddleware, type RetryOptions } from './middleware/retry';
69
- export { timeoutMiddleware, type TimeoutOptions } from './middleware/timeout';
70
- export { errorMiddleware } from './middleware/errors';
71
-
72
- // Bot protection managers (framework-agnostic, DOM APIs)
73
- export { createBotProtectionManager } from './bot-protection/create-manager';
74
- export { FallbackBotProtectionManager } from './bot-protection/fallback-manager';
75
-
76
- // Errors
77
- export { StorefrontError, ErrorCodes, type StorefrontErrorOptions } from './errors';
78
-
79
- // Cache strategies
80
- export {
81
- cacheNone,
82
- cacheShort,
83
- cacheLong,
84
- cachePrivate,
85
- cacheCustom,
86
- generateCacheControlHeader,
87
- type CacheOverrides,
88
- } from './cache';
89
-
90
- // Cart client
91
- export { CartClient } from './cart/cart-client';
92
- export type {
93
- Cart,
94
- CartLine,
95
- CartLineMerchandise,
96
- CartLineCost,
97
- CartCost,
98
- CartBuyerIdentity,
99
- CartDiscountCode,
100
- CartDiscountAllocation,
101
- CartLineInput,
102
- CartLineUpdateInput,
103
- CartCreateInput,
104
- CartBuyerIdentityInput,
105
- Money,
106
- } from './cart/types';
107
-
108
- // Auth client
109
- export { AuthClient } from './auth/auth-client';
110
- export type {
111
- Customer,
112
- CustomerAccessToken,
113
- MailingAddress,
114
- AuthResult,
115
- CustomerCreateInput,
116
- } from './auth/types';
117
-
118
- // Helpers
119
- export { assertNoUserErrors } from './helpers/assert-no-user-errors';
120
- export { sanitizeHtml } from './helpers/sanitize-html';
121
- export {
122
- normalizeConnection,
123
- type Connection,
124
- type ConnectionEdge,
125
- type ConnectionPageInfo,
126
- type NormalizedConnection,
127
- } from './helpers/normalize-connection';
128
-
129
- // Format utilities
130
- export {
131
- formatPrice,
132
- formatPriceRange,
133
- formatAmount,
134
- formatDate,
135
- formatDateTime,
136
- formatNumber,
137
- formatPercentage,
138
- getCurrencySymbol,
139
- CURRENCY_SYMBOLS,
140
- CURRENCY_LOCALES,
141
- type PriceMoney,
142
- } from './format';
143
-
144
- // Auth cookie config (platform contract)
145
- export {
146
- AUTH_COOKIE_NAME,
147
- AUTH_COOKIE_DEFAULTS,
148
- type AuthCookieConfig,
149
- } from './auth/cookie-config';
150
-
151
- // Language config
152
- export { LANGUAGE_COOKIE_NAME, LANGUAGE_COOKIE_MAX_AGE, LANGUAGE_HEADER_NAME } from './language/cookie-config';
153
-
154
- // Currency config
155
- export { CURRENCY_COOKIE_NAME, CURRENCY_COOKIE_MAX_AGE, CURRENCY_HEADER_NAME } from './currency/cookie-config';
156
-
157
- // Cart cookie config
158
- export { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from './cart/cookie-config';
159
-
160
- // Auth route matching
161
- export { matchesRoute, type RouteProtectionConfig } from './auth/routes';
162
-
163
- // Auth cookie handlers (API route factories)
164
- export { createSetTokenHandler, createClearTokenHandler } from './auth/handlers';
165
-
166
- // Auth token client (client-side fetch helpers)
167
- export { createAuthTokenClient, type AuthTokenClient } from './auth/token-client';
168
-
169
- // Image types (loaders removed — GraphQL returns ready-to-use CDN URLs with transform params)
170
- export { type ImageData } from './image';
171
-
172
- // Utilities
173
- export { getOperationName } from './client/operation-name';
174
- export { hashQuery } from './client/hash';
@@ -1,13 +0,0 @@
1
- /**
2
- * Language configuration constants — platform contract.
3
- *
4
- * Used by:
5
- * - SDK language store (client-side cookie read/write)
6
- * - SDK language middleware (X-Lang header)
7
- * - next-intl middleware `localeCookie.name` (server-side locale detection)
8
- *
9
- * Single cookie replaces both next-intl's NEXT_LOCALE and SDK's preferred-language.
10
- */
11
- export const LANGUAGE_COOKIE_NAME = 'preferred-language';
12
- export const LANGUAGE_COOKIE_MAX_AGE = 365 * 24 * 60 * 60; // 1 year
13
- export const LANGUAGE_HEADER_NAME = 'X-Lang';
@@ -1,27 +0,0 @@
1
- /**
2
- * Auth middleware — adds Authorization header from token getter.
3
- *
4
- * Token is resolved lazily (on each request) so it picks up
5
- * store changes, token renewals, etc.
6
- *
7
- * @example
8
- * ```typescript
9
- * import { authMiddleware } from '@doswiftly/storefront-sdk';
10
- *
11
- * client.use(authMiddleware(() => authStore.getState().accessToken));
12
- * ```
13
- */
14
-
15
- import type { Middleware } from '../client/types';
16
-
17
- export function authMiddleware(
18
- getToken: () => string | null | undefined,
19
- ): Middleware {
20
- return (request, next) => {
21
- const token = getToken();
22
- if (token) {
23
- request.headers['Authorization'] = `Bearer ${token}`;
24
- }
25
- return next(request);
26
- };
27
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Bot protection middleware — vendor-agnostic token injection.
3
- *
4
- * Intercepts protected mutations and adds a bot verification token
5
- * via the X-Bot-Protection-Token header. Token acquisition is delegated
6
- * to a BotProtectionTokenProvider (EU CAPTCHA, Turnstile, etc.).
7
- *
8
- * Fail strategy is per-operation: 'open' = continue without token,
9
- * 'closed' = throw if token unavailable.
10
- */
11
-
12
- import type { Middleware } from '../client/types';
13
- import { StorefrontError } from '../errors';
14
-
15
- // ---------------------------------------------------------------------------
16
- // Token provider interface (implemented by managers)
17
- // ---------------------------------------------------------------------------
18
-
19
- /** Vendor-agnostic token provider interface */
20
- export interface BotProtectionTokenProvider {
21
- /**
22
- * Get a fresh bot protection token.
23
- * Handles: in-flight deduplication, timeout, singleton script loading.
24
- *
25
- * @param options.action - Operation name (e.g. 'CustomerCreate') for action validation
26
- * @param options.timeoutMs - Timeout in ms (default: 10000)
27
- * @returns Token string or null if unavailable (adblock, timeout, error)
28
- */
29
- execute(options?: { action?: string; timeoutMs?: number }): Promise<string | null>;
30
-
31
- /** Cleanup widget and script resources */
32
- destroy(): void;
33
- }
34
-
35
- // ---------------------------------------------------------------------------
36
- // Configuration types
37
- // ---------------------------------------------------------------------------
38
-
39
- /** Single provider config (from shop query) */
40
- export interface BotProtectionProviderConfig {
41
- /** Provider identifier: 'eucaptcha' | 'turnstile' | 'recaptcha' | extensible */
42
- provider: string;
43
- /** Site key for the bot protection widget */
44
- siteKey: string;
45
- /** Script URL — configurable for region-specific / enterprise overrides */
46
- scriptUrl: string;
47
- }
48
-
49
- /** Bot protection configuration from shop query */
50
- export interface BotProtectionConfig {
51
- /** Primary provider */
52
- primary: BotProtectionProviderConfig;
53
- /** Fallback provider (runtime: if primary fails -> fallback -> fail-open) */
54
- fallback?: BotProtectionProviderConfig | null;
55
- /** GraphQL operation names requiring verification */
56
- protectedOperations: string[];
57
- }
58
-
59
- /** Fail strategy per request */
60
- export type FailStrategy = 'open' | 'closed';
61
-
62
- // ---------------------------------------------------------------------------
63
- // Middleware options
64
- // ---------------------------------------------------------------------------
65
-
66
- export interface BotProtectionMiddlewareOptions {
67
- /** Token provider instance (manager or fallback chain) */
68
- tokenProvider: BotProtectionTokenProvider;
69
- /** GraphQL operation names that require bot protection */
70
- protectedOperations: string[];
71
- /** Default fail strategy when token acquisition fails (default: 'open') */
72
- defaultFailStrategy?: FailStrategy;
73
- /** Override fail strategy per operation (e.g. CheckoutComplete = closed) */
74
- failStrategyOverrides?: Record<string, FailStrategy>;
75
- }
76
-
77
- // ---------------------------------------------------------------------------
78
- // Constants
79
- // ---------------------------------------------------------------------------
80
-
81
- /** Header name for bot protection token */
82
- export const BOT_PROTECTION_HEADER = 'X-Bot-Protection-Token';
83
-
84
- // ---------------------------------------------------------------------------
85
- // Middleware factory
86
- // ---------------------------------------------------------------------------
87
-
88
- /**
89
- * Creates bot protection middleware for the SDK pipeline.
90
- *
91
- * Only intercepts mutations whose operationName is in protectedOperations.
92
- * Token is fetched fresh on every call (single-use tokens, retry-safe).
93
- *
94
- * Pipeline position: AFTER auth/currency, BEFORE retry/timeout/errors.
95
- * Retry middleware re-executes the full chain, so each attempt gets a fresh token.
96
- */
97
- export function botProtectionMiddleware(options: BotProtectionMiddlewareOptions): Middleware {
98
- const {
99
- tokenProvider,
100
- protectedOperations,
101
- defaultFailStrategy = 'open',
102
- failStrategyOverrides = {},
103
- } = options;
104
-
105
- const protectedSet = new Set(protectedOperations);
106
-
107
- return async (request, next) => {
108
- // Only intercept mutations
109
- if (!request.isMutation) return next(request);
110
-
111
- // Only intercept protected operations
112
- const opName = request.operationName;
113
- if (!opName || !protectedSet.has(opName)) {
114
- return next(request);
115
- }
116
-
117
- // Fetch fresh token — action param enables backend action validation
118
- const token = await tokenProvider.execute({
119
- action: opName,
120
- timeoutMs: 10000,
121
- });
122
-
123
- if (token) {
124
- request.headers[BOT_PROTECTION_HEADER] = token;
125
- } else {
126
- // Fail strategy: open = continue without token, closed = throw
127
- const strategy = failStrategyOverrides[opName] ?? defaultFailStrategy;
128
- if (strategy === 'closed') {
129
- throw new StorefrontError({
130
- code: 'BOT_PROTECTION_REQUIRED',
131
- message: 'Bot protection verification failed. Please try again.',
132
- status: 0,
133
- });
134
- }
135
- // fail-open: continue without token — backend guard may still reject
136
- }
137
-
138
- return next(request);
139
- };
140
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Currency middleware — adds X-Preferred-Currency header.
3
- *
4
- * Currency is resolved lazily so currency switches are reflected immediately.
5
- *
6
- * @example
7
- * ```typescript
8
- * import { currencyMiddleware } from '@doswiftly/storefront-sdk';
9
- *
10
- * client.use(currencyMiddleware(() => currencyStore.getState().currency));
11
- * ```
12
- */
13
-
14
- import type { Middleware } from '../client/types';
15
- import { CURRENCY_HEADER_NAME } from '../currency/cookie-config';
16
-
17
- export function currencyMiddleware(
18
- getCurrency: () => string | null | undefined,
19
- ): Middleware {
20
- return (request, next) => {
21
- const currency = getCurrency();
22
- if (currency) {
23
- request.headers[CURRENCY_HEADER_NAME] = currency;
24
- }
25
- return next(request);
26
- };
27
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * Error normalization middleware — ALWAYS LAST in the pipeline.
3
- *
4
- * Catches all errors from downstream middleware and the transport,
5
- * normalizes them into StorefrontError instances.
6
- *
7
- * Handles:
8
- * - Network errors (fetch failures)
9
- * - HTTP errors (non-200 responses)
10
- * - GraphQL errors (errors array in response)
11
- * - Abort/timeout errors
12
- * - Missing data in response
13
- *
14
- * @example
15
- * ```typescript
16
- * import { errorMiddleware } from '@doswiftly/storefront-sdk';
17
- *
18
- * // Always add as the LAST middleware
19
- * client.use(errorMiddleware());
20
- * ```
21
- */
22
-
23
- import type { Middleware } from '../client/types';
24
- import { StorefrontError, ErrorCodes } from '../errors';
25
-
26
- export function errorMiddleware(): Middleware {
27
- return async (request, next) => {
28
- let response;
29
-
30
- try {
31
- response = await next(request);
32
- } catch (error) {
33
- // Already a StorefrontError — re-throw as-is
34
- if (error instanceof StorefrontError) {
35
- throw error;
36
- }
37
-
38
- // AbortError (timeout handled by timeout middleware, manual abort here)
39
- if (error instanceof Error && error.name === 'AbortError') {
40
- throw new StorefrontError({
41
- code: ErrorCodes.TIMEOUT,
42
- message: 'Request was aborted',
43
- cause: error,
44
- });
45
- }
46
-
47
- // Network error (fetch failed — DNS, connection refused, etc.)
48
- throw new StorefrontError({
49
- code: ErrorCodes.NETWORK_ERROR,
50
- message: error instanceof Error ? error.message : 'Network request failed',
51
- cause: error,
52
- });
53
- }
54
-
55
- // HTTP error (non-2xx response)
56
- if (response.status >= 400) {
57
- throw new StorefrontError({
58
- code: ErrorCodes.HTTP_ERROR,
59
- message: `HTTP ${response.status}`,
60
- status: response.status,
61
- graphqlErrors: response.errors ?? [],
62
- });
63
- }
64
-
65
- // GraphQL errors in the response
66
- if (response.errors?.length) {
67
- throw new StorefrontError({
68
- code: ErrorCodes.GRAPHQL_ERROR,
69
- message: response.errors[0].message,
70
- status: response.status,
71
- graphqlErrors: response.errors,
72
- });
73
- }
74
-
75
- // No data returned
76
- if (response.data === undefined || response.data === null) {
77
- throw new StorefrontError({
78
- code: ErrorCodes.NO_DATA,
79
- message: 'No data returned from GraphQL',
80
- status: response.status,
81
- });
82
- }
83
-
84
- return response;
85
- };
86
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Language middleware — adds X-Lang header.
3
- *
4
- * Language is resolved lazily so language switches are reflected immediately.
5
- * CRITICAL: does NOT send header when language=null (store not yet initialized)
6
- * — without this, backend returns default language, React Query caches it,
7
- * and after setLanguage() the cache is stale.
8
- *
9
- * @example
10
- * ```typescript
11
- * import { languageMiddleware } from '@doswiftly/storefront-sdk';
12
- *
13
- * client.use(languageMiddleware(() => languageStore.getState().language));
14
- * ```
15
- */
16
-
17
- import type { Middleware } from '../client/types';
18
- import { LANGUAGE_HEADER_NAME } from '../language/cookie-config';
19
-
20
- export function languageMiddleware(
21
- getLanguage: () => string | null | undefined,
22
- ): Middleware {
23
- return (request, next) => {
24
- const language = getLanguage();
25
- if (language) {
26
- request.headers[LANGUAGE_HEADER_NAME] = language;
27
- }
28
- return next(request);
29
- };
30
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Retry middleware — retries on network errors and 5xx responses.
3
- *
4
- * ONLY retries queries, NEVER mutations (to prevent double-charges, etc.).
5
- * Uses exponential backoff with jitter.
6
- *
7
- * @example
8
- * ```typescript
9
- * import { retryMiddleware } from '@doswiftly/storefront-sdk';
10
- *
11
- * client.use(retryMiddleware({ maxRetries: 2 }));
12
- * ```
13
- */
14
-
15
- import type { Middleware } from '../client/types';
16
- import { StorefrontError, ErrorCodes } from '../errors';
17
-
18
- export interface RetryOptions {
19
- /** Maximum number of retries (default: 2) */
20
- maxRetries?: number;
21
- /** Initial delay in ms (default: 300) */
22
- initialDelay?: number;
23
- }
24
-
25
- export function retryMiddleware(options: RetryOptions = {}): Middleware {
26
- const { maxRetries = 2, initialDelay = 300 } = options;
27
-
28
- return async (request, next) => {
29
- // Never retry mutations
30
- if (request.isMutation) {
31
- return next(request);
32
- }
33
-
34
- let lastError: unknown;
35
-
36
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
37
- try {
38
- const response = await next(request);
39
-
40
- // Retry on 5xx server errors
41
- if (response.status >= 500 && attempt < maxRetries) {
42
- await delay(initialDelay * Math.pow(2, attempt));
43
- continue;
44
- }
45
-
46
- return response;
47
- } catch (error) {
48
- lastError = error;
49
-
50
- // Don't retry on non-retriable errors
51
- if (error instanceof StorefrontError) {
52
- if (error.code === ErrorCodes.TIMEOUT) {
53
- // Timeouts are retriable
54
- } else if (error.status >= 400 && error.status < 500) {
55
- // 4xx errors are not retriable
56
- throw error;
57
- }
58
- }
59
-
60
- if (attempt < maxRetries) {
61
- await delay(initialDelay * Math.pow(2, attempt));
62
- continue;
63
- }
64
- }
65
- }
66
-
67
- throw lastError;
68
- };
69
- }
70
-
71
- function delay(ms: number): Promise<void> {
72
- // Add jitter: ±25%
73
- const jitter = ms * 0.25 * (Math.random() * 2 - 1);
74
- return new Promise(resolve => setTimeout(resolve, ms + jitter));
75
- }