@doswiftly/storefront-sdk 4.3.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +6 -14
  2. package/dist/core/cart/types.d.ts +53 -20
  3. package/dist/core/cart/types.d.ts.map +1 -1
  4. package/dist/core/cart/types.js +3 -0
  5. package/dist/core/image.d.ts +4 -46
  6. package/dist/core/image.d.ts.map +1 -1
  7. package/dist/core/image.js +4 -65
  8. package/dist/core/index.d.ts +1 -1
  9. package/dist/core/index.d.ts.map +1 -1
  10. package/dist/core/index.js +0 -2
  11. package/dist/core/operations/cart.d.ts +15 -9
  12. package/dist/core/operations/cart.d.ts.map +1 -1
  13. package/dist/core/operations/cart.js +130 -58
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/package.json +9 -4
  17. package/src/__tests__/contract/storefront-api.contract.test.ts +0 -450
  18. package/src/__tests__/unit/auth-client.test.ts +0 -210
  19. package/src/__tests__/unit/bot-protection.test.ts +0 -461
  20. package/src/__tests__/unit/cart-client.test.ts +0 -233
  21. package/src/__tests__/unit/cart-store.test.ts +0 -349
  22. package/src/__tests__/unit/create-client.test.ts +0 -356
  23. package/src/__tests__/unit/helpers.test.ts +0 -377
  24. package/src/__tests__/unit/middleware.test.ts +0 -374
  25. package/src/__tests__/unit/test-helpers.ts +0 -103
  26. package/src/core/auth/auth-client.ts +0 -123
  27. package/src/core/auth/cookie-config.ts +0 -23
  28. package/src/core/auth/handlers.ts +0 -168
  29. package/src/core/auth/routes.ts +0 -26
  30. package/src/core/auth/token-client.ts +0 -51
  31. package/src/core/auth/types.ts +0 -54
  32. package/src/core/bot-protection/abstract-manager.ts +0 -185
  33. package/src/core/bot-protection/create-manager.ts +0 -37
  34. package/src/core/bot-protection/eucaptcha-manager.ts +0 -88
  35. package/src/core/bot-protection/fallback-manager.ts +0 -43
  36. package/src/core/bot-protection/turnstile-manager.ts +0 -92
  37. package/src/core/bot-protection/types/eucaptcha.d.ts +0 -28
  38. package/src/core/bot-protection/types/turnstile.d.ts +0 -33
  39. package/src/core/cache.ts +0 -102
  40. package/src/core/cart/cart-client.ts +0 -150
  41. package/src/core/cart/cookie-config.ts +0 -13
  42. package/src/core/cart/types.ts +0 -104
  43. package/src/core/client/compose.ts +0 -15
  44. package/src/core/client/create-client.ts +0 -129
  45. package/src/core/client/dedupe.ts +0 -19
  46. package/src/core/client/execute.ts +0 -70
  47. package/src/core/client/hash.ts +0 -21
  48. package/src/core/client/operation-name.ts +0 -12
  49. package/src/core/client/types.ts +0 -171
  50. package/src/core/currency/cookie-config.ts +0 -13
  51. package/src/core/errors.ts +0 -67
  52. package/src/core/format.ts +0 -254
  53. package/src/core/helpers/assert-no-user-errors.ts +0 -21
  54. package/src/core/helpers/normalize-connection.ts +0 -48
  55. package/src/core/helpers/sanitize-html.ts +0 -42
  56. package/src/core/image.ts +0 -103
  57. package/src/core/index.ts +0 -180
  58. package/src/core/language/cookie-config.ts +0 -13
  59. package/src/core/middleware/auth.ts +0 -27
  60. package/src/core/middleware/bot-protection.ts +0 -140
  61. package/src/core/middleware/currency.ts +0 -27
  62. package/src/core/middleware/errors.ts +0 -86
  63. package/src/core/middleware/language.ts +0 -30
  64. package/src/core/middleware/retry.ts +0 -75
  65. package/src/core/middleware/timeout.ts +0 -61
  66. package/src/core/operations/auth.ts +0 -123
  67. package/src/core/operations/cart.ts +0 -185
  68. package/src/index.ts +0 -25
  69. package/src/react/bot-protection/bot-protection-context.ts +0 -17
  70. package/src/react/bot-protection/bot-protection-widget.tsx +0 -46
  71. package/src/react/cookies.ts +0 -89
  72. package/src/react/helpers/create-store-context.ts +0 -56
  73. package/src/react/hooks/use-auth.ts +0 -218
  74. package/src/react/hooks/use-bot-protection.ts +0 -31
  75. package/src/react/hooks/use-cart-manager.ts +0 -236
  76. package/src/react/hooks/use-currency.ts +0 -23
  77. package/src/react/hooks/use-debounced-value.ts +0 -30
  78. package/src/react/hooks/use-hydrated.ts +0 -20
  79. package/src/react/hooks/use-storefront-client.ts +0 -12
  80. package/src/react/index.ts +0 -71
  81. package/src/react/providers/currency-provider.tsx +0 -30
  82. package/src/react/providers/language-provider.tsx +0 -34
  83. package/src/react/providers/storefront-client-provider.tsx +0 -107
  84. package/src/react/providers/storefront-provider.tsx +0 -99
  85. package/src/react/server/get-storefront-client.ts +0 -60
  86. package/src/react/server/index.ts +0 -1
  87. package/src/react/stores/auth.store.ts +0 -112
  88. package/src/react/stores/cart.context.ts +0 -10
  89. package/src/react/stores/cart.store.ts +0 -254
  90. package/src/react/stores/currency.store.ts +0 -93
  91. package/src/react/stores/index.ts +0 -17
  92. package/src/react/stores/language.store.ts +0 -90
  93. package/src/react/stores/store-context.tsx +0 -103
  94. package/src/react/types/shop-config.ts +0 -22
  95. package/tsconfig.json +0 -20
  96. package/vitest.config.ts +0 -14
@@ -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
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * Timeout middleware — aborts request after timeout via AbortController.
3
- *
4
- * Edge-safe (no Node.js APIs). Default: 5 seconds.
5
- *
6
- * @example
7
- * ```typescript
8
- * import { timeoutMiddleware } from '@doswiftly/storefront-sdk';
9
- *
10
- * client.use(timeoutMiddleware({ timeout: 10000 })); // 10s
11
- * ```
12
- */
13
-
14
- import type { Middleware } from '../client/types';
15
- import { StorefrontError, ErrorCodes } from '../errors';
16
-
17
- export interface TimeoutOptions {
18
- /** Timeout in milliseconds (default: 5000) */
19
- timeout?: number;
20
- }
21
-
22
- export function timeoutMiddleware(options: TimeoutOptions = {}): Middleware {
23
- const { timeout = 5000 } = options;
24
-
25
- return async (request, next) => {
26
- const controller = new AbortController();
27
-
28
- // If request already has a signal, link them
29
- if (request.signal) {
30
- if (request.signal.aborted) {
31
- controller.abort(request.signal.reason);
32
- } else {
33
- request.signal.addEventListener('abort', () => {
34
- controller.abort(request.signal!.reason);
35
- }, { once: true });
36
- }
37
- }
38
-
39
- request.signal = controller.signal;
40
-
41
- const timer = setTimeout(() => {
42
- controller.abort(new Error(`Request timed out after ${timeout}ms`));
43
- }, timeout);
44
-
45
- try {
46
- const response = await next(request);
47
- return response;
48
- } catch (error) {
49
- if (controller.signal.aborted) {
50
- throw new StorefrontError({
51
- code: ErrorCodes.TIMEOUT,
52
- message: `Request timed out after ${timeout}ms`,
53
- cause: error,
54
- });
55
- }
56
- throw error;
57
- } finally {
58
- clearTimeout(timer);
59
- }
60
- };
61
- }
@@ -1,123 +0,0 @@
1
- /**
2
- * Auth/Customer GraphQL operations — manual query strings (no codegen).
3
- *
4
- * These match the backend storefront-graphql schema exactly.
5
- */
6
-
7
- // ---------------------------------------------------------------------------
8
- // Fragments
9
- // ---------------------------------------------------------------------------
10
-
11
- const CUSTOMER_ACCESS_TOKEN_FRAGMENT = `
12
- fragment CustomerAccessTokenFields on CustomerAccessToken {
13
- accessToken
14
- expiresAt
15
- }
16
- `;
17
-
18
- const USER_ERROR_FRAGMENT = `
19
- fragment UserErrorFields on UserError {
20
- message
21
- field
22
- code
23
- }
24
- `;
25
-
26
- const CUSTOMER_FRAGMENT = `
27
- fragment CustomerFields on Customer {
28
- id
29
- email
30
- firstName
31
- lastName
32
- displayName
33
- phone
34
- emailVerified
35
- emailMarketingState
36
- defaultAddress {
37
- id
38
- address1
39
- address2
40
- city
41
- company
42
- country
43
- countryCode
44
- firstName
45
- lastName
46
- phone
47
- province
48
- provinceCode
49
- zip
50
- isDefault
51
- }
52
- ordersCount
53
- totalSpent {
54
- amount
55
- currencyCode
56
- }
57
- createdAt
58
- updatedAt
59
- }
60
- `;
61
-
62
- // ---------------------------------------------------------------------------
63
- // Mutations
64
- // ---------------------------------------------------------------------------
65
-
66
- export const CUSTOMER_LOGIN = `
67
- mutation CustomerLogin($input: CustomerAccessTokenCreateInput!) {
68
- customerAccessTokenCreate(input: $input) {
69
- customerAccessToken { ...CustomerAccessTokenFields }
70
- userErrors { ...UserErrorFields }
71
- }
72
- }
73
- ${CUSTOMER_ACCESS_TOKEN_FRAGMENT}
74
- ${USER_ERROR_FRAGMENT}
75
- `;
76
-
77
- export const CUSTOMER_LOGOUT = `
78
- mutation CustomerLogout($customerAccessToken: String!) {
79
- customerAccessTokenDelete(customerAccessToken: $customerAccessToken) {
80
- deletedAccessToken
81
- deletedCustomerAccessTokenId
82
- userErrors { ...UserErrorFields }
83
- }
84
- }
85
- ${USER_ERROR_FRAGMENT}
86
- `;
87
-
88
- export const CUSTOMER_TOKEN_RENEW = `
89
- mutation CustomerTokenRenew($customerAccessToken: String!) {
90
- customerAccessTokenRenew(customerAccessToken: $customerAccessToken) {
91
- customerAccessToken { ...CustomerAccessTokenFields }
92
- userErrors { ...UserErrorFields }
93
- }
94
- }
95
- ${CUSTOMER_ACCESS_TOKEN_FRAGMENT}
96
- ${USER_ERROR_FRAGMENT}
97
- `;
98
-
99
- export const CUSTOMER_CREATE = `
100
- mutation CustomerCreate($input: CustomerCreateInput!) {
101
- customerCreate(input: $input) {
102
- customer { ...CustomerFields }
103
- customerAccessToken { ...CustomerAccessTokenFields }
104
- userErrors { ...UserErrorFields }
105
- }
106
- }
107
- ${CUSTOMER_FRAGMENT}
108
- ${CUSTOMER_ACCESS_TOKEN_FRAGMENT}
109
- ${USER_ERROR_FRAGMENT}
110
- `;
111
-
112
- // ---------------------------------------------------------------------------
113
- // Queries
114
- // ---------------------------------------------------------------------------
115
-
116
- export const CUSTOMER_QUERY = `
117
- query Customer($customerAccessToken: String!) {
118
- customer(customerAccessToken: $customerAccessToken) {
119
- ...CustomerFields
120
- }
121
- }
122
- ${CUSTOMER_FRAGMENT}
123
- `;
@@ -1,185 +0,0 @@
1
- /**
2
- * Cart GraphQL operations — manual query strings (no codegen).
3
- *
4
- * These match the backend storefront-graphql schema exactly.
5
- * Cart mutations always return full Cart + userErrors.
6
- */
7
-
8
- // ---------------------------------------------------------------------------
9
- // Fragments
10
- // ---------------------------------------------------------------------------
11
-
12
- const MONEY_FRAGMENT = `
13
- fragment Money on Money {
14
- amount
15
- currencyCode
16
- }
17
- `;
18
-
19
- const CART_COST_FRAGMENT = `
20
- fragment CartCost on CartCost {
21
- totalAmount { ...Money }
22
- subtotalAmount { ...Money }
23
- totalTaxAmount { ...Money }
24
- totalDutyAmount { ...Money }
25
- }
26
- `;
27
-
28
- const CART_LINE_FRAGMENT = `
29
- fragment CartLineFields on CartLine {
30
- id
31
- quantity
32
- merchandise {
33
- id
34
- title
35
- sku
36
- image { url altText }
37
- price { ...Money }
38
- compareAtPrice { ...Money }
39
- }
40
- cost {
41
- totalAmount { ...Money }
42
- amountPerQuantity { ...Money }
43
- compareAtAmountPerQuantity { ...Money }
44
- }
45
- attributes { key value }
46
- productId
47
- productTitle
48
- productHandle
49
- }
50
- `;
51
-
52
- const CART_FRAGMENT = `
53
- fragment CartFields on Cart {
54
- id
55
- checkoutUrl
56
- totalQuantity
57
- note
58
- createdAt
59
- updatedAt
60
- cost { ...CartCost }
61
- lines(first: 100) {
62
- edges {
63
- node { ...CartLineFields }
64
- }
65
- }
66
- buyerIdentity {
67
- email
68
- phone
69
- countryCode
70
- }
71
- discountCodes {
72
- code
73
- applicable
74
- }
75
- discountAllocations {
76
- discountedAmount { ...Money }
77
- }
78
- attributes { key value }
79
- }
80
- ${MONEY_FRAGMENT}
81
- ${CART_COST_FRAGMENT}
82
- ${CART_LINE_FRAGMENT}
83
- `;
84
-
85
- const USER_ERROR_FRAGMENT = `
86
- fragment UserErrorFields on UserError {
87
- message
88
- field
89
- code
90
- }
91
- `;
92
-
93
- // ---------------------------------------------------------------------------
94
- // Queries
95
- // ---------------------------------------------------------------------------
96
-
97
- export const CART_QUERY = `
98
- query Cart($id: ID!) {
99
- cart(id: $id) {
100
- ...CartFields
101
- }
102
- }
103
- ${CART_FRAGMENT}
104
- `;
105
-
106
- // ---------------------------------------------------------------------------
107
- // Mutations
108
- // ---------------------------------------------------------------------------
109
-
110
- export const CART_CREATE = `
111
- mutation CartCreate($input: CartCreateInput) {
112
- cartCreate(input: $input) {
113
- cart { ...CartFields }
114
- userErrors { ...UserErrorFields }
115
- }
116
- }
117
- ${CART_FRAGMENT}
118
- ${USER_ERROR_FRAGMENT}
119
- `;
120
-
121
- export const CART_LINES_ADD = `
122
- mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
123
- cartLinesAdd(cartId: $cartId, lines: $lines) {
124
- cart { ...CartFields }
125
- userErrors { ...UserErrorFields }
126
- }
127
- }
128
- ${CART_FRAGMENT}
129
- ${USER_ERROR_FRAGMENT}
130
- `;
131
-
132
- export const CART_LINES_UPDATE = `
133
- mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
134
- cartLinesUpdate(cartId: $cartId, lines: $lines) {
135
- cart { ...CartFields }
136
- userErrors { ...UserErrorFields }
137
- }
138
- }
139
- ${CART_FRAGMENT}
140
- ${USER_ERROR_FRAGMENT}
141
- `;
142
-
143
- export const CART_LINES_REMOVE = `
144
- mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
145
- cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
146
- cart { ...CartFields }
147
- userErrors { ...UserErrorFields }
148
- }
149
- }
150
- ${CART_FRAGMENT}
151
- ${USER_ERROR_FRAGMENT}
152
- `;
153
-
154
- export const CART_DISCOUNT_CODES_UPDATE = `
155
- mutation CartDiscountCodesUpdate($cartId: ID!, $discountCodes: [String!]!) {
156
- cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
157
- cart { ...CartFields }
158
- userErrors { ...UserErrorFields }
159
- }
160
- }
161
- ${CART_FRAGMENT}
162
- ${USER_ERROR_FRAGMENT}
163
- `;
164
-
165
- export const CART_NOTE_UPDATE = `
166
- mutation CartNoteUpdate($cartId: ID!, $note: String!) {
167
- cartNoteUpdate(cartId: $cartId, note: $note) {
168
- cart { ...CartFields }
169
- userErrors { ...UserErrorFields }
170
- }
171
- }
172
- ${CART_FRAGMENT}
173
- ${USER_ERROR_FRAGMENT}
174
- `;
175
-
176
- export const CART_BUYER_IDENTITY_UPDATE = `
177
- mutation CartBuyerIdentityUpdate($cartId: ID!, $buyerIdentity: CartBuyerIdentityInput!) {
178
- cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) {
179
- cart { ...CartFields }
180
- userErrors { ...UserErrorFields }
181
- }
182
- }
183
- ${CART_FRAGMENT}
184
- ${USER_ERROR_FRAGMENT}
185
- `;
package/src/index.ts DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * @doswiftly/storefront-sdk v4.0.0
3
- *
4
- * Layered SDK — Hydrogen-aligned architecture.
5
- *
6
- * Entry points:
7
- * "." → Core (framework-agnostic, 0 deps)
8
- * "./react" → React adapter (providers, stores, hooks)
9
- * "./react/stores" → Zustand stores (direct import)
10
- * "./react/server" → Server-side client factory
11
- * "./cache" → Cache strategies
12
- *
13
- * @example Core (CLI, Edge, Remix, Astro — no React)
14
- * ```typescript
15
- * import { createStorefrontClient, CartClient, errorMiddleware } from '@doswiftly/storefront-sdk';
16
- * ```
17
- *
18
- * @example React
19
- * ```tsx
20
- * import { StorefrontProvider, useAuth, useCartManager } from '@doswiftly/storefront-sdk/react';
21
- * ```
22
- */
23
-
24
- // Re-export everything from core
25
- export * from './core';
@@ -1,17 +0,0 @@
1
- /**
2
- * Bot protection React context.
3
- *
4
- * Provides access to the BotProtectionTokenProvider manager
5
- * for the useBotProtection() escape hatch hook.
6
- */
7
-
8
- 'use client';
9
-
10
- import { createContext } from 'react';
11
- import type { BotProtectionTokenProvider } from '../../core/middleware/bot-protection';
12
-
13
- export interface BotProtectionContextValue {
14
- manager: BotProtectionTokenProvider | null;
15
- }
16
-
17
- export const BotProtectionContext = createContext<BotProtectionContextValue | null>(null);
@@ -1,46 +0,0 @@
1
- /**
2
- * BotProtectionWidget — invisible React wrapper that mounts the bot protection manager.
3
- *
4
- * Renders a hidden div and mounts/unmounts the manager lifecycle.
5
- * Triggers lazy script preload on mount.
6
- */
7
-
8
- 'use client';
9
-
10
- import React, { useEffect, useRef } from 'react';
11
- import type { BotProtectionTokenProvider } from '../../core/middleware/bot-protection';
12
- import type { AbstractBotProtectionManager } from '../../core/bot-protection/abstract-manager';
13
-
14
- interface BotProtectionWidgetProps {
15
- manager: BotProtectionTokenProvider | null;
16
- }
17
-
18
- export function BotProtectionWidget({ manager }: BotProtectionWidgetProps) {
19
- const containerRef = useRef<HTMLDivElement>(null);
20
- const mountedRef = useRef(false);
21
-
22
- useEffect(() => {
23
- if (!manager || !containerRef.current || mountedRef.current) return;
24
-
25
- mountedRef.current = true;
26
-
27
- // Trigger lazy script preload + mount container
28
- const mgr = manager as AbstractBotProtectionManager;
29
- if (typeof mgr.loadScript === 'function') {
30
- mgr.loadScript().catch(() => {
31
- // Script load failure is handled gracefully in execute()
32
- });
33
- }
34
- if (typeof mgr.mount === 'function') {
35
- mgr.mount(containerRef.current);
36
- }
37
-
38
- return () => {
39
- mountedRef.current = false;
40
- };
41
- }, [manager]);
42
-
43
- if (!manager) return null;
44
-
45
- return <div ref={containerRef} style={{ display: 'none' }} aria-hidden="true" />;
46
- }
@@ -1,89 +0,0 @@
1
- /**
2
- * Cookie utilities for SDK consumers.
3
- *
4
- * Simple, framework-agnostic cookie read/write for client-side.
5
- * For Next.js server-side cookies, use `cookies()` from next/headers.
6
- */
7
-
8
- /**
9
- * Get cookie value by name (client-side only).
10
- */
11
- export function getCookie(name: string): string | null {
12
- if (typeof document === 'undefined') return null;
13
- const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
14
- return match ? decodeURIComponent(match[1]) : null;
15
- }
16
-
17
- /**
18
- * Set cookie (client-side only).
19
- *
20
- * `secure` defaults to auto-detect from `location.protocol` (true on HTTPS).
21
- */
22
- export function setCookie(
23
- name: string,
24
- value: string,
25
- options: { maxAge?: number; path?: string; sameSite?: string; secure?: boolean } = {},
26
- ): void {
27
- if (typeof document === 'undefined') return;
28
- const { maxAge = 365 * 24 * 60 * 60, path = '/', sameSite = 'lax' } = options;
29
- const secure = options.secure ??
30
- (typeof location !== 'undefined' && location.protocol === 'https:');
31
- const securePart = secure ? ';secure' : '';
32
- document.cookie = `${name}=${encodeURIComponent(value)};max-age=${maxAge};path=${path};samesite=${sameSite}${securePart}`;
33
- }
34
-
35
- /**
36
- * Delete cookie (client-side only).
37
- */
38
- export function deleteCookie(name: string, path = '/'): void {
39
- if (typeof document === 'undefined') return;
40
- document.cookie = `${name}=;max-age=0;path=${path}`;
41
- }
42
-
43
- import { CURRENCY_COOKIE_NAME } from '../core/currency/cookie-config';
44
- import { CART_COOKIE_NAME } from '../core/cart/cookie-config';
45
-
46
- /**
47
- * Get preferred currency from cookie (async — works with Next.js cookies()).
48
- * Falls back to document.cookie on client.
49
- */
50
- export async function getCurrencyFromCookieAsync(): Promise<string | null> {
51
- // Server-side: try Next.js cookies()
52
- try {
53
- const { cookies } = await import('next/headers');
54
- const cookieStore = await cookies();
55
- return cookieStore.get(CURRENCY_COOKIE_NAME)?.value ?? null;
56
- } catch {
57
- // Not in a server context or next not available
58
- }
59
-
60
- // Client-side fallback
61
- return getCookie(CURRENCY_COOKIE_NAME);
62
- }
63
-
64
- /**
65
- * Get cart ID from cookie (async — works with Next.js cookies()).
66
- * Falls back to document.cookie on client.
67
- *
68
- * Use in Server Components for SSR cart badge:
69
- * ```typescript
70
- * const cartId = await getCartIdFromCookieAsync();
71
- * if (cartId) {
72
- * const cart = await fetchCart(cartId);
73
- * // Render cart badge with real totalQuantity — no skeleton needed
74
- * }
75
- * ```
76
- */
77
- export async function getCartIdFromCookieAsync(): Promise<string | null> {
78
- // Server-side: try Next.js cookies()
79
- try {
80
- const { cookies } = await import('next/headers');
81
- const cookieStore = await cookies();
82
- return cookieStore.get(CART_COOKIE_NAME)?.value ?? null;
83
- } catch {
84
- // Not in a server context or next not available
85
- }
86
-
87
- // Client-side fallback
88
- return getCookie(CART_COOKIE_NAME);
89
- }
@@ -1,56 +0,0 @@
1
- 'use client';
2
-
3
- import { createContext, useContext, createElement, type ReactNode } from 'react';
4
- import { useStore, type StoreApi } from 'zustand';
5
-
6
- /**
7
- * Create a Context-based Zustand store pattern.
8
- *
9
- * Returns Provider, useStore hook, and useApi hook — eliminates
10
- * module-level singleton boilerplate (Turbopack duplication bug).
11
- *
12
- * @example
13
- * ```ts
14
- * const { Provider: CartProvider, useStore: useCartStore, useApi: useCartStoreApi } =
15
- * createStoreContext<CartState>('CartStore');
16
- *
17
- * // In layout:
18
- * const cartStore = useRef(createCartStore());
19
- * <CartProvider store={cartStore.current}>{children}</CartProvider>
20
- *
21
- * // In components:
22
- * const isOpen = useCartStore((s) => s.isOpen);
23
- * const api = useCartStoreApi(); // for .getState() in callbacks
24
- * ```
25
- */
26
- export function createStoreContext<T>(displayName: string) {
27
- const StoreContext = createContext<StoreApi<T> | null>(null);
28
- StoreContext.displayName = displayName;
29
-
30
- function Provider({ store, children }: { store: StoreApi<T>; children: ReactNode }) {
31
- return createElement(StoreContext.Provider, { value: store }, children);
32
- }
33
- Provider.displayName = `${displayName}Provider`;
34
-
35
- // Overloads: no selector → full state, with selector → selected slice
36
- function useStoreHook(): T;
37
- function useStoreHook<U>(selector: (s: T) => U): U;
38
- function useStoreHook<U>(selector?: (s: T) => U) {
39
- const store = useContext(StoreContext);
40
- if (!store) {
41
- throw new Error(`${displayName}: useStore must be used within its Provider`);
42
- }
43
- // eslint-disable-next-line react-hooks/rules-of-hooks
44
- return selector ? useStore(store, selector) : useStore(store);
45
- }
46
-
47
- function useApi(): StoreApi<T> {
48
- const store = useContext(StoreContext);
49
- if (!store) {
50
- throw new Error(`${displayName}: useApi must be used within its Provider`);
51
- }
52
- return store;
53
- }
54
-
55
- return { Provider, useStore: useStoreHook, useApi } as const;
56
- }