@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,70 +0,0 @@
1
- /**
2
- * Native fetch transport — 0 runtime dependencies.
3
- *
4
- * Sends GraphQL POST request, parses response, returns typed GraphQLResponse.
5
- * Error normalization is handled by error middleware, NOT here.
6
- */
7
-
8
- import type { GraphQLRequest, GraphQLResponse, GraphQLErrorInfo } from './types';
9
-
10
- export interface ExecuteConfig {
11
- /** GraphQL endpoint URL */
12
- endpoint: string;
13
- /** Custom fetch implementation */
14
- fetch: typeof globalThis.fetch;
15
- /** Enable debug logging */
16
- debug: boolean;
17
- }
18
-
19
- /**
20
- * Create the innermost execute function for the middleware pipeline.
21
- */
22
- export function createExecute(config: ExecuteConfig) {
23
- const { endpoint, fetch: fetchFn, debug } = config;
24
-
25
- return async function execute(request: GraphQLRequest): Promise<GraphQLResponse> {
26
- const { query, variables, headers, signal, operationName } = request;
27
-
28
- if (debug) {
29
- console.log('[StorefrontSDK]', operationName ?? 'request', { variables });
30
- }
31
-
32
- const body: Record<string, unknown> = { query };
33
- if (variables && Object.keys(variables).length > 0) {
34
- body.variables = variables;
35
- }
36
- if (operationName) {
37
- body.operationName = operationName;
38
- }
39
-
40
- const response = await fetchFn(endpoint, {
41
- method: 'POST',
42
- headers: {
43
- 'Content-Type': 'application/json',
44
- Accept: 'application/json',
45
- ...headers,
46
- },
47
- body: JSON.stringify(body),
48
- signal,
49
- });
50
-
51
- const json = await response.json() as {
52
- data?: unknown;
53
- errors?: GraphQLErrorInfo[];
54
- };
55
-
56
- if (debug) {
57
- console.log('[StorefrontSDK]', operationName ?? 'response', {
58
- status: response.status,
59
- hasErrors: !!json.errors?.length,
60
- });
61
- }
62
-
63
- return {
64
- data: json.data as never,
65
- errors: json.errors,
66
- status: response.status,
67
- headers: response.headers,
68
- };
69
- };
70
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Stable query hashing for request deduplication.
3
- *
4
- * Sorts object keys so `{a:1,b:2}` and `{b:2,a:1}` produce the same hash.
5
- */
6
-
7
- function stableStringify(value: unknown): string {
8
- if (value === null || value === undefined) return '';
9
- if (typeof value !== 'object') return JSON.stringify(value);
10
- if (Array.isArray(value)) {
11
- return '[' + value.map(stableStringify).join(',') + ']';
12
- }
13
- const keys = Object.keys(value as Record<string, unknown>).sort();
14
- return '{' + keys.map(k => JSON.stringify(k) + ':' + stableStringify((value as Record<string, unknown>)[k])).join(',') + '}';
15
- }
16
-
17
- export function hashQuery(query: string, variables?: Record<string, unknown>): string {
18
- const queryPart = query.trim();
19
- const varsPart = variables ? stableStringify(variables) : '';
20
- return queryPart + '::' + varsPart;
21
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Auto-extract operation name from a GraphQL query string.
3
- *
4
- * Used for: X-Operation-Name header (debugging), cache tags, logging.
5
- */
6
-
7
- const OPERATION_RE = /(?:query|mutation|subscription)\s+(\w+)/;
8
-
9
- export function getOperationName(query: string): string {
10
- const match = query.match(OPERATION_RE);
11
- return match?.[1] ?? 'anonymous';
12
- }
@@ -1,171 +0,0 @@
1
- /**
2
- * Core types for Storefront SDK transport layer.
3
- *
4
- * Framework-agnostic — no React, no Zustand, 0 runtime dependencies.
5
- */
6
-
7
- // ---------------------------------------------------------------------------
8
- // TypedDocumentString (Saleor / client-preset pattern)
9
- // ---------------------------------------------------------------------------
10
-
11
- /**
12
- * A branded string carrying result & variable types.
13
- * Produced by graphql-codegen client-preset with `documentMode: 'string'`.
14
- *
15
- * SDK also accepts plain strings — TypedDocumentString is purely for DX.
16
- */
17
- export class TypedDocumentString<TResult = unknown, TVariables = unknown> extends String {
18
- /** Type-level brand — never used at runtime */
19
- __apiType?: (variables: TVariables) => TResult;
20
-
21
- constructor(value: string, public __meta__?: { hash: string }) {
22
- super(value);
23
- }
24
-
25
- override toString(): string {
26
- return this.valueOf();
27
- }
28
- }
29
-
30
- // ---------------------------------------------------------------------------
31
- // GraphQL request / response
32
- // ---------------------------------------------------------------------------
33
-
34
- export interface GraphQLRequest {
35
- /** GraphQL query or mutation string */
36
- query: string;
37
- /** Operation variables */
38
- variables?: Record<string, unknown>;
39
- /** Operation name (auto-extracted if omitted) */
40
- operationName?: string;
41
- /** Request headers (merged: default < middleware < request) */
42
- headers: Record<string, string>;
43
- /** AbortSignal for cancellation */
44
- signal?: AbortSignal;
45
- /** Whether this is a mutation (affects retry, cache) */
46
- isMutation: boolean;
47
- /** Cache strategy override */
48
- cache?: CacheStrategy;
49
- }
50
-
51
- export interface GraphQLResponse<T = unknown> {
52
- /** Parsed response data */
53
- data: T;
54
- /** GraphQL errors (if any) */
55
- errors?: GraphQLErrorInfo[];
56
- /** HTTP status code */
57
- status: number;
58
- /** Raw response headers */
59
- headers: Headers;
60
- }
61
-
62
- export interface GraphQLErrorInfo {
63
- message: string;
64
- locations?: Array<{ line: number; column: number }>;
65
- path?: Array<string | number>;
66
- extensions?: Record<string, unknown>;
67
- }
68
-
69
- // ---------------------------------------------------------------------------
70
- // User errors (cart mutations, auth mutations, etc.)
71
- // ---------------------------------------------------------------------------
72
-
73
- export interface UserError {
74
- message: string;
75
- field?: string[];
76
- code?: string;
77
- }
78
-
79
- // ---------------------------------------------------------------------------
80
- // Middleware
81
- // ---------------------------------------------------------------------------
82
-
83
- /**
84
- * Execute function — the innermost (or next) handler in the pipeline.
85
- */
86
- export type ExecuteFn = (request: GraphQLRequest) => Promise<GraphQLResponse>;
87
-
88
- /**
89
- * Middleware function — receives the request and the next handler.
90
- *
91
- * Can modify request, modify response, short-circuit, retry, etc.
92
- *
93
- * @example
94
- * ```typescript
95
- * const authMiddleware: Middleware = (req, next) => {
96
- * req.headers['Authorization'] = `Bearer ${getToken()}`;
97
- * return next(req);
98
- * };
99
- * ```
100
- */
101
- export type Middleware = (request: GraphQLRequest, next: ExecuteFn) => Promise<GraphQLResponse>;
102
-
103
- // ---------------------------------------------------------------------------
104
- // Cache strategy
105
- // ---------------------------------------------------------------------------
106
-
107
- export interface CacheOptions {
108
- /** Max age in seconds */
109
- maxAge: number;
110
- /** Stale-while-revalidate in seconds */
111
- staleWhileRevalidate?: number;
112
- /** Cache mode */
113
- mode: 'public' | 'private' | 'no-store';
114
- /** Cache tags (for Next.js revalidateTag) */
115
- tags?: string[];
116
- }
117
-
118
- export type CacheStrategy = CacheOptions;
119
-
120
- // ---------------------------------------------------------------------------
121
- // Client config
122
- // ---------------------------------------------------------------------------
123
-
124
- export interface StorefrontClientConfig {
125
- /** GraphQL API URL (e.g. 'https://api.doswiftly.pl') */
126
- apiUrl: string;
127
- /** Shop slug for multi-tenant routing */
128
- shopSlug: string;
129
- /** Default headers for all requests */
130
- defaultHeaders?: Record<string, string>;
131
- /** Middleware pipeline */
132
- middleware?: Middleware[];
133
- /** Custom fetch implementation (polyfill, test mocks, edge) */
134
- fetch?: typeof globalThis.fetch;
135
- /** Enable debug logging (request/response in dev) */
136
- debug?: boolean;
137
- }
138
-
139
- // ---------------------------------------------------------------------------
140
- // Client interface
141
- // ---------------------------------------------------------------------------
142
-
143
- export interface StorefrontClient {
144
- /**
145
- * Execute a typed GraphQL query.
146
- *
147
- * Accepts TypedDocumentString (from codegen) or plain string.
148
- */
149
- query<T = unknown, V = Record<string, unknown>>(
150
- document: TypedDocumentString<T, V> | string,
151
- variables?: V,
152
- cache?: CacheStrategy,
153
- ): Promise<T>;
154
-
155
- /**
156
- * Execute a typed GraphQL mutation.
157
- *
158
- * Mutations are never cached and never retried by retry middleware.
159
- */
160
- mutate<T = unknown, V = Record<string, unknown>>(
161
- document: TypedDocumentString<T, V> | string,
162
- variables?: V,
163
- ): Promise<T>;
164
-
165
- /**
166
- * Add middleware to the pipeline (imperative API).
167
- *
168
- * Invalidates the compiled pipeline — next request re-composes.
169
- */
170
- use(middleware: Middleware): void;
171
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * Currency configuration constants — platform contract.
3
- *
4
- * Used by:
5
- * - SDK currency store (client-side cookie read/write)
6
- * - SDK currency middleware (X-Preferred-Currency header)
7
- * - Server-side currency detection (SSR helpers)
8
- *
9
- * Single cookie for preferred currency persistence.
10
- */
11
- export const CURRENCY_COOKIE_NAME = 'preferred-currency';
12
- export const CURRENCY_COOKIE_MAX_AGE = 365 * 24 * 60 * 60; // 1 year
13
- export const CURRENCY_HEADER_NAME = 'X-Preferred-Currency';
@@ -1,67 +0,0 @@
1
- /**
2
- * StorefrontError — unified error class for all SDK errors.
3
- *
4
- * Normalizes GraphQL errors, network errors, user errors, and timeouts
5
- * into a single throwable type with structured data.
6
- */
7
-
8
- import type { GraphQLErrorInfo, UserError } from './client/types';
9
-
10
- export interface StorefrontErrorOptions {
11
- /** Error code for programmatic handling */
12
- code: string;
13
- /** Human-readable message */
14
- message: string;
15
- /** HTTP status code (0 for network errors) */
16
- status?: number;
17
- /** GraphQL-level errors from the response */
18
- graphqlErrors?: GraphQLErrorInfo[];
19
- /** User errors from mutations (field-level validation) */
20
- userErrors?: UserError[];
21
- /** Original error (if wrapping) */
22
- cause?: unknown;
23
- }
24
-
25
- export class StorefrontError extends Error {
26
- readonly code: string;
27
- readonly status: number;
28
- readonly graphqlErrors: GraphQLErrorInfo[];
29
- readonly userErrors: UserError[];
30
-
31
- constructor(options: StorefrontErrorOptions) {
32
- super(options.message, { cause: options.cause });
33
- this.name = 'StorefrontError';
34
- this.code = options.code;
35
- this.status = options.status ?? 0;
36
- this.graphqlErrors = options.graphqlErrors ?? [];
37
- this.userErrors = options.userErrors ?? [];
38
- }
39
-
40
- /** True if this error contains user errors (field-level validation) */
41
- get hasUserErrors(): boolean {
42
- return this.userErrors.length > 0;
43
- }
44
-
45
- /** True if this is a network error (no HTTP response) */
46
- get isNetworkError(): boolean {
47
- return this.code === 'NETWORK_ERROR';
48
- }
49
-
50
- /** True if the request timed out */
51
- get isTimeout(): boolean {
52
- return this.code === 'TIMEOUT';
53
- }
54
- }
55
-
56
- // ---------------------------------------------------------------------------
57
- // Error codes
58
- // ---------------------------------------------------------------------------
59
-
60
- export const ErrorCodes = {
61
- NETWORK_ERROR: 'NETWORK_ERROR',
62
- TIMEOUT: 'TIMEOUT',
63
- HTTP_ERROR: 'HTTP_ERROR',
64
- GRAPHQL_ERROR: 'GRAPHQL_ERROR',
65
- USER_ERROR: 'USER_ERROR',
66
- NO_DATA: 'NO_DATA',
67
- } as const;
@@ -1,254 +0,0 @@
1
- /**
2
- * Formatting Utilities
3
- *
4
- * Pure functions for formatting prices, dates, numbers.
5
- * 0 runtime dependencies — works in Node.js, Edge, Deno, Bun.
6
- */
7
-
8
- // ============================================================================
9
- // TYPES
10
- // ============================================================================
11
-
12
- export interface PriceMoney {
13
- amount: string;
14
- currencyCode: string;
15
- }
16
-
17
- // ============================================================================
18
- // CONSTANTS
19
- // ============================================================================
20
-
21
- /** Currency symbols mapping */
22
- export const CURRENCY_SYMBOLS: Record<string, string> = {
23
- PLN: 'zł',
24
- EUR: '€',
25
- USD: '$',
26
- GBP: '£',
27
- CHF: 'CHF',
28
- CZK: 'Kč',
29
- SEK: 'kr',
30
- NOK: 'kr',
31
- DKK: 'kr',
32
- JPY: '¥',
33
- CNY: '¥',
34
- AUD: 'A$',
35
- CAD: 'C$',
36
- };
37
-
38
- /** Currency locale mapping for proper formatting */
39
- export const CURRENCY_LOCALES: Record<string, string> = {
40
- PLN: 'pl-PL',
41
- EUR: 'de-DE',
42
- USD: 'en-US',
43
- GBP: 'en-GB',
44
- CHF: 'de-CH',
45
- CZK: 'cs-CZ',
46
- SEK: 'sv-SE',
47
- NOK: 'nb-NO',
48
- DKK: 'da-DK',
49
- JPY: 'ja-JP',
50
- CNY: 'zh-CN',
51
- AUD: 'en-AU',
52
- CAD: 'en-CA',
53
- };
54
-
55
- // ============================================================================
56
- // FORMATTER CACHE (avoids re-creating Intl objects on every call)
57
- // ============================================================================
58
-
59
- const numberFormatCache = new Map<string, Intl.NumberFormat>();
60
- const dateFormatCache = new Map<string, Intl.DateTimeFormat>();
61
-
62
- function getCurrencyFormatter(locale: string, currency: string): Intl.NumberFormat {
63
- const key = `${locale}:${currency}`;
64
- let fmt = numberFormatCache.get(key);
65
- if (!fmt) {
66
- fmt = new Intl.NumberFormat(locale, {
67
- style: 'currency',
68
- currency,
69
- minimumFractionDigits: 2,
70
- maximumFractionDigits: 2,
71
- });
72
- numberFormatCache.set(key, fmt);
73
- }
74
- return fmt;
75
- }
76
-
77
- function getNumberFormatter(locale: string): Intl.NumberFormat {
78
- let fmt = numberFormatCache.get(locale);
79
- if (!fmt) {
80
- fmt = new Intl.NumberFormat(locale);
81
- numberFormatCache.set(locale, fmt);
82
- }
83
- return fmt;
84
- }
85
-
86
- function getDateFormatter(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
87
- const key = `${locale}:${JSON.stringify(options)}`;
88
- let fmt = dateFormatCache.get(key);
89
- if (!fmt) {
90
- fmt = new Intl.DateTimeFormat(locale, options);
91
- dateFormatCache.set(key, fmt);
92
- }
93
- return fmt;
94
- }
95
-
96
- // ============================================================================
97
- // UTILITY FUNCTIONS
98
- // ============================================================================
99
-
100
- /**
101
- * Get currency symbol
102
- */
103
- export function getCurrencySymbol(code: string): string {
104
- return CURRENCY_SYMBOLS[code] || code;
105
- }
106
-
107
- // ============================================================================
108
- // PRICE FORMATTING
109
- // ============================================================================
110
-
111
- /**
112
- * Format price with currency symbol
113
- *
114
- * @example
115
- * ```typescript
116
- * formatPrice({ amount: "99.99", currencyCode: "USD" })
117
- * // => "$99.99"
118
- * ```
119
- */
120
- export function formatPrice(price: PriceMoney | null | undefined): string {
121
- if (!price) return '';
122
-
123
- const amount = parseFloat(price.amount);
124
- const code = price.currencyCode;
125
- const locale = CURRENCY_LOCALES[code] || 'en-US';
126
-
127
- try {
128
- return getCurrencyFormatter(locale, code).format(amount);
129
- } catch {
130
- const symbol = CURRENCY_SYMBOLS[code] || code;
131
- return `${amount.toFixed(2)} ${symbol}`;
132
- }
133
- }
134
-
135
- /**
136
- * Format price range
137
- *
138
- * @example
139
- * ```typescript
140
- * formatPriceRange(
141
- * { amount: "10.00", currencyCode: "USD" },
142
- * { amount: "50.00", currencyCode: "USD" }
143
- * )
144
- * // => "$10.00 - $50.00"
145
- * ```
146
- */
147
- export function formatPriceRange(
148
- minPrice: PriceMoney,
149
- maxPrice: PriceMoney,
150
- ): string {
151
- if (minPrice.amount === maxPrice.amount) {
152
- return formatPrice(minPrice);
153
- }
154
-
155
- return `${formatPrice(minPrice)} - ${formatPrice(maxPrice)}`;
156
- }
157
-
158
- /**
159
- * Format amount with currency
160
- *
161
- * @example
162
- * ```tsx
163
- * const formatted = formatAmount("115.20", "EUR");
164
- * // => "115,20 €"
165
- * ```
166
- */
167
- export function formatAmount(
168
- amount: string | number,
169
- currencyCode: string,
170
- ): string {
171
- const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
172
- const locale = CURRENCY_LOCALES[currencyCode] || 'en-US';
173
-
174
- try {
175
- return getCurrencyFormatter(locale, currencyCode).format(numAmount);
176
- } catch {
177
- const symbol = CURRENCY_SYMBOLS[currencyCode] || currencyCode;
178
- return `${numAmount.toFixed(2)} ${symbol}`;
179
- }
180
- }
181
-
182
- // ============================================================================
183
- // DATE FORMATTING
184
- // ============================================================================
185
-
186
- /**
187
- * Format date to locale string
188
- *
189
- * @example
190
- * ```typescript
191
- * formatDate(new Date())
192
- * // => "Dec 9, 2025"
193
- * ```
194
- */
195
- export function formatDate(date: Date | string): string {
196
- const d = typeof date === 'string' ? new Date(date) : date;
197
-
198
- return getDateFormatter('en-US', {
199
- year: 'numeric',
200
- month: 'short',
201
- day: 'numeric',
202
- }).format(d);
203
- }
204
-
205
- /**
206
- * Format date with time
207
- *
208
- * @example
209
- * ```typescript
210
- * formatDateTime(new Date())
211
- * // => "Dec 9, 2025, 10:30 PM"
212
- * ```
213
- */
214
- export function formatDateTime(date: Date | string): string {
215
- const d = typeof date === 'string' ? new Date(date) : date;
216
-
217
- return getDateFormatter('en-US', {
218
- year: 'numeric',
219
- month: 'short',
220
- day: 'numeric',
221
- hour: 'numeric',
222
- minute: '2-digit',
223
- }).format(d);
224
- }
225
-
226
- // ============================================================================
227
- // NUMBER FORMATTING
228
- // ============================================================================
229
-
230
- /**
231
- * Format number with thousands separator
232
- *
233
- * @example
234
- * ```typescript
235
- * formatNumber(1234567)
236
- * // => "1,234,567"
237
- * ```
238
- */
239
- export function formatNumber(num: number): string {
240
- return getNumberFormatter('en-US').format(num);
241
- }
242
-
243
- /**
244
- * Format percentage
245
- *
246
- * @example
247
- * ```typescript
248
- * formatPercentage(0.15)
249
- * // => "15%"
250
- * ```
251
- */
252
- export function formatPercentage(value: number): string {
253
- return `${Math.round(value * 100)}%`;
254
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Auto-throw on userErrors — used internally by CartClient and AuthClient.
3
- *
4
- * Frontend code doesn't need to manually check `if (userErrors.length)`.
5
- */
6
-
7
- import type { UserError } from '../client/types';
8
- import { StorefrontError, ErrorCodes } from '../errors';
9
-
10
- export function assertNoUserErrors(
11
- result: { userErrors?: UserError[] | null },
12
- ): void {
13
- if (result.userErrors && result.userErrors.length > 0) {
14
- const firstError = result.userErrors[0];
15
- throw new StorefrontError({
16
- code: ErrorCodes.USER_ERROR,
17
- message: firstError.message,
18
- userErrors: result.userErrors,
19
- });
20
- }
21
- }
@@ -1,48 +0,0 @@
1
- /**
2
- * Relay-style GraphQL connection normalizer.
3
- *
4
- * Converts `{ edges: [{ node }], pageInfo, totalCount }` → flat array.
5
- * Pure function, 0 deps, works everywhere.
6
- */
7
-
8
- export interface ConnectionEdge<T> {
9
- node: T;
10
- cursor: string;
11
- }
12
-
13
- export interface ConnectionPageInfo {
14
- hasNextPage: boolean;
15
- hasPreviousPage: boolean;
16
- startCursor?: string | null;
17
- endCursor?: string | null;
18
- }
19
-
20
- export interface Connection<T> {
21
- edges: ConnectionEdge<T>[];
22
- pageInfo: ConnectionPageInfo;
23
- totalCount?: number;
24
- }
25
-
26
- export interface NormalizedConnection<T> {
27
- items: T[];
28
- pageInfo: ConnectionPageInfo;
29
- totalCount?: number;
30
- }
31
-
32
- /**
33
- * Normalize a GraphQL Relay connection to a flat array.
34
- *
35
- * @example
36
- * ```typescript
37
- * const { items, pageInfo, totalCount } = normalizeConnection(data.products);
38
- * ```
39
- */
40
- export function normalizeConnection<T>(
41
- connection: Connection<T>,
42
- ): NormalizedConnection<T> {
43
- return {
44
- items: connection.edges.map((edge) => edge.node),
45
- pageInfo: connection.pageInfo,
46
- totalCount: connection.totalCount,
47
- };
48
- }