@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.
Files changed (206) hide show
  1. package/README.md +430 -0
  2. package/dist/__tests__/unit/test-helpers.d.ts +46 -0
  3. package/dist/__tests__/unit/test-helpers.d.ts.map +1 -0
  4. package/dist/__tests__/unit/test-helpers.js +72 -0
  5. package/dist/core/auth/auth-client.d.ts +46 -0
  6. package/dist/core/auth/auth-client.d.ts.map +1 -0
  7. package/dist/core/auth/auth-client.js +82 -0
  8. package/dist/core/auth/cookie-config.d.ts +18 -0
  9. package/dist/core/auth/cookie-config.d.ts.map +1 -0
  10. package/dist/core/auth/cookie-config.js +18 -0
  11. package/dist/core/auth/handlers.d.ts +32 -0
  12. package/dist/core/auth/handlers.d.ts.map +1 -0
  13. package/dist/core/auth/handlers.js +127 -0
  14. package/dist/core/auth/routes.d.ts +21 -0
  15. package/dist/core/auth/routes.d.ts.map +1 -0
  16. package/dist/core/auth/routes.js +14 -0
  17. package/dist/core/auth/token-client.d.ts +26 -0
  18. package/dist/core/auth/token-client.d.ts.map +1 -0
  19. package/dist/core/auth/token-client.js +42 -0
  20. package/dist/core/auth/types.d.ts +53 -0
  21. package/dist/core/auth/types.d.ts.map +1 -0
  22. package/dist/core/auth/types.js +4 -0
  23. package/dist/core/cache.d.ts +54 -0
  24. package/dist/core/cache.d.ts.map +1 -0
  25. package/dist/core/cache.js +82 -0
  26. package/dist/core/cart/cart-client.d.ts +57 -0
  27. package/dist/core/cart/cart-client.d.ts.map +1 -0
  28. package/dist/core/cart/cart-client.js +89 -0
  29. package/dist/core/cart/types.d.ts +110 -0
  30. package/dist/core/cart/types.d.ts.map +1 -0
  31. package/dist/core/cart/types.js +6 -0
  32. package/dist/core/client/compose.d.ts +9 -0
  33. package/dist/core/client/compose.d.ts.map +1 -0
  34. package/dist/core/client/compose.js +9 -0
  35. package/dist/core/client/create-client.d.ts +15 -0
  36. package/dist/core/client/create-client.d.ts.map +1 -0
  37. package/dist/core/client/create-client.js +85 -0
  38. package/dist/core/client/dedupe.d.ts +7 -0
  39. package/dist/core/client/dedupe.d.ts.map +1 -0
  40. package/dist/core/client/dedupe.js +16 -0
  41. package/dist/core/client/execute.d.ts +20 -0
  42. package/dist/core/client/execute.d.ts.map +1 -0
  43. package/dist/core/client/execute.js +48 -0
  44. package/dist/core/client/hash.d.ts +7 -0
  45. package/dist/core/client/hash.d.ts.map +1 -0
  46. package/dist/core/client/hash.js +21 -0
  47. package/dist/core/client/operation-name.d.ts +7 -0
  48. package/dist/core/client/operation-name.d.ts.map +1 -0
  49. package/dist/core/client/operation-name.js +10 -0
  50. package/dist/core/client/types.d.ts +126 -0
  51. package/dist/core/client/types.d.ts.map +1 -0
  52. package/dist/core/client/types.js +26 -0
  53. package/dist/core/errors.d.ts +43 -0
  54. package/dist/core/errors.d.ts.map +1 -0
  55. package/dist/core/errors.js +43 -0
  56. package/dist/core/format.d.ts +92 -0
  57. package/dist/core/format.d.ts.map +1 -0
  58. package/dist/core/format.js +216 -0
  59. package/dist/core/helpers/assert-no-user-errors.d.ts +10 -0
  60. package/dist/core/helpers/assert-no-user-errors.d.ts.map +1 -0
  61. package/dist/core/helpers/assert-no-user-errors.js +16 -0
  62. package/dist/core/helpers/normalize-connection.d.ts +36 -0
  63. package/dist/core/helpers/normalize-connection.d.ts.map +1 -0
  64. package/dist/core/helpers/normalize-connection.js +21 -0
  65. package/dist/core/helpers/sanitize-html.d.ts +8 -0
  66. package/dist/core/helpers/sanitize-html.d.ts.map +1 -0
  67. package/dist/core/helpers/sanitize-html.js +35 -0
  68. package/dist/core/index.d.ts +59 -0
  69. package/dist/core/index.d.ts.map +1 -0
  70. package/dist/core/index.js +68 -0
  71. package/dist/core/middleware/auth.d.ts +16 -0
  72. package/dist/core/middleware/auth.d.ts.map +1 -0
  73. package/dist/core/middleware/auth.js +22 -0
  74. package/dist/core/middleware/currency.d.ts +15 -0
  75. package/dist/core/middleware/currency.d.ts.map +1 -0
  76. package/dist/core/middleware/currency.js +21 -0
  77. package/dist/core/middleware/errors.d.ts +24 -0
  78. package/dist/core/middleware/errors.d.ts.map +1 -0
  79. package/dist/core/middleware/errors.js +77 -0
  80. package/dist/core/middleware/retry.d.ts +22 -0
  81. package/dist/core/middleware/retry.d.ts.map +1 -0
  82. package/dist/core/middleware/retry.js +58 -0
  83. package/dist/core/middleware/timeout.d.ts +19 -0
  84. package/dist/core/middleware/timeout.d.ts.map +1 -0
  85. package/dist/core/middleware/timeout.js +51 -0
  86. package/dist/core/operations/auth.d.ts +11 -0
  87. package/dist/core/operations/auth.d.ts.map +1 -0
  88. package/dist/core/operations/auth.js +112 -0
  89. package/dist/core/operations/cart.d.ts +15 -0
  90. package/dist/core/operations/cart.d.ts.map +1 -0
  91. package/dist/core/operations/cart.js +169 -0
  92. package/dist/index.d.ts +24 -0
  93. package/dist/index.d.ts.map +1 -0
  94. package/dist/index.js +24 -0
  95. package/dist/react/cookies.d.ts +28 -0
  96. package/dist/react/cookies.d.ts.map +1 -0
  97. package/dist/react/cookies.js +49 -0
  98. package/dist/react/helpers/create-store-context.d.ts +37 -0
  99. package/dist/react/helpers/create-store-context.d.ts.map +1 -0
  100. package/dist/react/helpers/create-store-context.js +47 -0
  101. package/dist/react/hooks/use-auth.d.ts +65 -0
  102. package/dist/react/hooks/use-auth.d.ts.map +1 -0
  103. package/dist/react/hooks/use-auth.js +168 -0
  104. package/dist/react/hooks/use-cart-manager.d.ts +30 -0
  105. package/dist/react/hooks/use-cart-manager.d.ts.map +1 -0
  106. package/dist/react/hooks/use-cart-manager.js +223 -0
  107. package/dist/react/hooks/use-currency.d.ts +11 -0
  108. package/dist/react/hooks/use-currency.d.ts.map +1 -0
  109. package/dist/react/hooks/use-currency.js +19 -0
  110. package/dist/react/hooks/use-debounced-value.d.ts +15 -0
  111. package/dist/react/hooks/use-debounced-value.d.ts.map +1 -0
  112. package/dist/react/hooks/use-debounced-value.js +25 -0
  113. package/dist/react/hooks/use-hydrated.d.ts +9 -0
  114. package/dist/react/hooks/use-hydrated.d.ts.map +1 -0
  115. package/dist/react/hooks/use-hydrated.js +14 -0
  116. package/dist/react/hooks/use-storefront-client.d.ts +6 -0
  117. package/dist/react/hooks/use-storefront-client.d.ts.map +1 -0
  118. package/dist/react/hooks/use-storefront-client.js +8 -0
  119. package/dist/react/index.d.ts +30 -0
  120. package/dist/react/index.d.ts.map +1 -0
  121. package/dist/react/index.js +34 -0
  122. package/dist/react/providers/currency-provider.d.ts +14 -0
  123. package/dist/react/providers/currency-provider.d.ts.map +1 -0
  124. package/dist/react/providers/currency-provider.js +20 -0
  125. package/dist/react/providers/storefront-client-provider.d.ts +33 -0
  126. package/dist/react/providers/storefront-client-provider.d.ts.map +1 -0
  127. package/dist/react/providers/storefront-client-provider.js +57 -0
  128. package/dist/react/providers/storefront-provider.d.ts +42 -0
  129. package/dist/react/providers/storefront-provider.d.ts.map +1 -0
  130. package/dist/react/providers/storefront-provider.js +40 -0
  131. package/dist/react/server/get-storefront-client.d.ts +42 -0
  132. package/dist/react/server/get-storefront-client.d.ts.map +1 -0
  133. package/dist/react/server/get-storefront-client.js +44 -0
  134. package/dist/react/server/index.d.ts +2 -0
  135. package/dist/react/server/index.d.ts.map +1 -0
  136. package/dist/react/server/index.js +1 -0
  137. package/dist/react/stores/auth.store.d.ts +48 -0
  138. package/dist/react/stores/auth.store.d.ts.map +1 -0
  139. package/dist/react/stores/auth.store.js +67 -0
  140. package/dist/react/stores/currency.store.d.ts +29 -0
  141. package/dist/react/stores/currency.store.d.ts.map +1 -0
  142. package/dist/react/stores/currency.store.js +76 -0
  143. package/dist/react/stores/index.d.ts +8 -0
  144. package/dist/react/stores/index.d.ts.map +1 -0
  145. package/dist/react/stores/index.js +10 -0
  146. package/dist/react/stores/store-context.d.ts +27 -0
  147. package/dist/react/stores/store-context.d.ts.map +1 -0
  148. package/dist/react/stores/store-context.js +62 -0
  149. package/package.json +71 -0
  150. package/src/__tests__/contract/storefront-api.contract.test.ts +450 -0
  151. package/src/__tests__/unit/auth-client.test.ts +210 -0
  152. package/src/__tests__/unit/cart-client.test.ts +233 -0
  153. package/src/__tests__/unit/create-client.test.ts +356 -0
  154. package/src/__tests__/unit/helpers.test.ts +377 -0
  155. package/src/__tests__/unit/middleware.test.ts +374 -0
  156. package/src/__tests__/unit/test-helpers.ts +103 -0
  157. package/src/core/auth/auth-client.ts +123 -0
  158. package/src/core/auth/cookie-config.ts +23 -0
  159. package/src/core/auth/handlers.ts +168 -0
  160. package/src/core/auth/routes.ts +26 -0
  161. package/src/core/auth/token-client.ts +51 -0
  162. package/src/core/auth/types.ts +54 -0
  163. package/src/core/cache.ts +102 -0
  164. package/src/core/cart/cart-client.ts +150 -0
  165. package/src/core/cart/types.ts +104 -0
  166. package/src/core/client/compose.ts +15 -0
  167. package/src/core/client/create-client.ts +129 -0
  168. package/src/core/client/dedupe.ts +19 -0
  169. package/src/core/client/execute.ts +70 -0
  170. package/src/core/client/hash.ts +21 -0
  171. package/src/core/client/operation-name.ts +12 -0
  172. package/src/core/client/types.ts +171 -0
  173. package/src/core/errors.ts +67 -0
  174. package/src/core/format.ts +254 -0
  175. package/src/core/helpers/assert-no-user-errors.ts +21 -0
  176. package/src/core/helpers/normalize-connection.ts +48 -0
  177. package/src/core/helpers/sanitize-html.ts +42 -0
  178. package/src/core/index.ts +148 -0
  179. package/src/core/middleware/auth.ts +27 -0
  180. package/src/core/middleware/currency.ts +26 -0
  181. package/src/core/middleware/errors.ts +86 -0
  182. package/src/core/middleware/retry.ts +75 -0
  183. package/src/core/middleware/timeout.ts +61 -0
  184. package/src/core/operations/auth.ts +123 -0
  185. package/src/core/operations/cart.ts +185 -0
  186. package/src/index.ts +25 -0
  187. package/src/react/cookies.ts +54 -0
  188. package/src/react/helpers/create-store-context.ts +56 -0
  189. package/src/react/hooks/use-auth.ts +218 -0
  190. package/src/react/hooks/use-cart-manager.ts +236 -0
  191. package/src/react/hooks/use-currency.ts +23 -0
  192. package/src/react/hooks/use-debounced-value.ts +30 -0
  193. package/src/react/hooks/use-hydrated.ts +20 -0
  194. package/src/react/hooks/use-storefront-client.ts +12 -0
  195. package/src/react/index.ts +45 -0
  196. package/src/react/providers/currency-provider.tsx +30 -0
  197. package/src/react/providers/storefront-client-provider.tsx +90 -0
  198. package/src/react/providers/storefront-provider.tsx +71 -0
  199. package/src/react/server/get-storefront-client.ts +60 -0
  200. package/src/react/server/index.ts +1 -0
  201. package/src/react/stores/auth.store.ts +112 -0
  202. package/src/react/stores/currency.store.ts +113 -0
  203. package/src/react/stores/index.ts +17 -0
  204. package/src/react/stores/store-context.tsx +82 -0
  205. package/tsconfig.json +20 -0
  206. package/vitest.config.ts +14 -0
@@ -0,0 +1,169 @@
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
+ // Fragments
9
+ // ---------------------------------------------------------------------------
10
+ const MONEY_FRAGMENT = `
11
+ fragment Money on Money {
12
+ amount
13
+ currencyCode
14
+ }
15
+ `;
16
+ const CART_COST_FRAGMENT = `
17
+ fragment CartCost on CartCost {
18
+ totalAmount { ...Money }
19
+ subtotalAmount { ...Money }
20
+ totalTaxAmount { ...Money }
21
+ totalDutyAmount { ...Money }
22
+ }
23
+ `;
24
+ const CART_LINE_FRAGMENT = `
25
+ fragment CartLineFields on CartLine {
26
+ id
27
+ quantity
28
+ merchandise {
29
+ id
30
+ title
31
+ sku
32
+ image { url altText }
33
+ price { ...Money }
34
+ compareAtPrice { ...Money }
35
+ }
36
+ cost {
37
+ totalAmount { ...Money }
38
+ amountPerQuantity { ...Money }
39
+ compareAtAmountPerQuantity { ...Money }
40
+ }
41
+ attributes { key value }
42
+ productId
43
+ productTitle
44
+ productHandle
45
+ }
46
+ `;
47
+ const CART_FRAGMENT = `
48
+ fragment CartFields on Cart {
49
+ id
50
+ checkoutUrl
51
+ totalQuantity
52
+ note
53
+ createdAt
54
+ updatedAt
55
+ cost { ...CartCost }
56
+ lines(first: 100) {
57
+ edges {
58
+ node { ...CartLineFields }
59
+ }
60
+ }
61
+ buyerIdentity {
62
+ email
63
+ phone
64
+ countryCode
65
+ }
66
+ discountCodes {
67
+ code
68
+ applicable
69
+ }
70
+ discountAllocations {
71
+ discountedAmount { ...Money }
72
+ }
73
+ attributes { key value }
74
+ }
75
+ ${MONEY_FRAGMENT}
76
+ ${CART_COST_FRAGMENT}
77
+ ${CART_LINE_FRAGMENT}
78
+ `;
79
+ const USER_ERROR_FRAGMENT = `
80
+ fragment UserErrorFields on UserError {
81
+ message
82
+ field
83
+ code
84
+ }
85
+ `;
86
+ // ---------------------------------------------------------------------------
87
+ // Queries
88
+ // ---------------------------------------------------------------------------
89
+ export const CART_QUERY = `
90
+ query Cart($id: ID!) {
91
+ cart(id: $id) {
92
+ ...CartFields
93
+ }
94
+ }
95
+ ${CART_FRAGMENT}
96
+ `;
97
+ // ---------------------------------------------------------------------------
98
+ // Mutations
99
+ // ---------------------------------------------------------------------------
100
+ export const CART_CREATE = `
101
+ mutation CartCreate($input: CartCreateInput) {
102
+ cartCreate(input: $input) {
103
+ cart { ...CartFields }
104
+ userErrors { ...UserErrorFields }
105
+ }
106
+ }
107
+ ${CART_FRAGMENT}
108
+ ${USER_ERROR_FRAGMENT}
109
+ `;
110
+ export const CART_LINES_ADD = `
111
+ mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
112
+ cartLinesAdd(cartId: $cartId, lines: $lines) {
113
+ cart { ...CartFields }
114
+ userErrors { ...UserErrorFields }
115
+ }
116
+ }
117
+ ${CART_FRAGMENT}
118
+ ${USER_ERROR_FRAGMENT}
119
+ `;
120
+ export const CART_LINES_UPDATE = `
121
+ mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
122
+ cartLinesUpdate(cartId: $cartId, lines: $lines) {
123
+ cart { ...CartFields }
124
+ userErrors { ...UserErrorFields }
125
+ }
126
+ }
127
+ ${CART_FRAGMENT}
128
+ ${USER_ERROR_FRAGMENT}
129
+ `;
130
+ export const CART_LINES_REMOVE = `
131
+ mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
132
+ cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
133
+ cart { ...CartFields }
134
+ userErrors { ...UserErrorFields }
135
+ }
136
+ }
137
+ ${CART_FRAGMENT}
138
+ ${USER_ERROR_FRAGMENT}
139
+ `;
140
+ export const CART_DISCOUNT_CODES_UPDATE = `
141
+ mutation CartDiscountCodesUpdate($cartId: ID!, $discountCodes: [String!]!) {
142
+ cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
143
+ cart { ...CartFields }
144
+ userErrors { ...UserErrorFields }
145
+ }
146
+ }
147
+ ${CART_FRAGMENT}
148
+ ${USER_ERROR_FRAGMENT}
149
+ `;
150
+ export const CART_NOTE_UPDATE = `
151
+ mutation CartNoteUpdate($cartId: ID!, $note: String!) {
152
+ cartNoteUpdate(cartId: $cartId, note: $note) {
153
+ cart { ...CartFields }
154
+ userErrors { ...UserErrorFields }
155
+ }
156
+ }
157
+ ${CART_FRAGMENT}
158
+ ${USER_ERROR_FRAGMENT}
159
+ `;
160
+ export const CART_BUYER_IDENTITY_UPDATE = `
161
+ mutation CartBuyerIdentityUpdate($cartId: ID!, $buyerIdentity: CartBuyerIdentityInput!) {
162
+ cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) {
163
+ cart { ...CartFields }
164
+ userErrors { ...UserErrorFields }
165
+ }
166
+ }
167
+ ${CART_FRAGMENT}
168
+ ${USER_ERROR_FRAGMENT}
169
+ `;
@@ -0,0 +1,24 @@
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
+ export * from './core';
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,cAAc,QAAQ,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
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
+ // Re-export everything from core
24
+ export * from './core';
@@ -0,0 +1,28 @@
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
+ * Get cookie value by name (client-side only).
9
+ */
10
+ export declare function getCookie(name: string): string | null;
11
+ /**
12
+ * Set cookie (client-side only).
13
+ */
14
+ export declare function setCookie(name: string, value: string, options?: {
15
+ maxAge?: number;
16
+ path?: string;
17
+ sameSite?: string;
18
+ }): void;
19
+ /**
20
+ * Delete cookie (client-side only).
21
+ */
22
+ export declare function deleteCookie(name: string, path?: string): void;
23
+ /**
24
+ * Get preferred currency from cookie (async — works with Next.js cookies()).
25
+ * Falls back to document.cookie on client.
26
+ */
27
+ export declare function getCurrencyFromCookieAsync(): Promise<string | null>;
28
+ //# sourceMappingURL=cookies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/react/cookies.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIrD;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,IAAI,CAIN;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,IAAI,CAG3D;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYzE"}
@@ -0,0 +1,49 @@
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
+ * Get cookie value by name (client-side only).
9
+ */
10
+ export function getCookie(name) {
11
+ if (typeof document === 'undefined')
12
+ return null;
13
+ const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
14
+ return match ? decodeURIComponent(match[1]) : null;
15
+ }
16
+ /**
17
+ * Set cookie (client-side only).
18
+ */
19
+ export function setCookie(name, value, options = {}) {
20
+ if (typeof document === 'undefined')
21
+ return;
22
+ const { maxAge = 365 * 24 * 60 * 60, path = '/', sameSite = 'lax' } = options;
23
+ document.cookie = `${name}=${encodeURIComponent(value)};max-age=${maxAge};path=${path};samesite=${sameSite}`;
24
+ }
25
+ /**
26
+ * Delete cookie (client-side only).
27
+ */
28
+ export function deleteCookie(name, path = '/') {
29
+ if (typeof document === 'undefined')
30
+ return;
31
+ document.cookie = `${name}=;max-age=0;path=${path}`;
32
+ }
33
+ /**
34
+ * Get preferred currency from cookie (async — works with Next.js cookies()).
35
+ * Falls back to document.cookie on client.
36
+ */
37
+ export async function getCurrencyFromCookieAsync() {
38
+ // Server-side: try Next.js cookies()
39
+ try {
40
+ const { cookies } = await import('next/headers');
41
+ const cookieStore = await cookies();
42
+ return cookieStore.get('preferred-currency')?.value ?? null;
43
+ }
44
+ catch {
45
+ // Not in a server context or next not available
46
+ }
47
+ // Client-side fallback
48
+ return getCookie('preferred-currency');
49
+ }
@@ -0,0 +1,37 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type StoreApi } from 'zustand';
3
+ /**
4
+ * Create a Context-based Zustand store pattern.
5
+ *
6
+ * Returns Provider, useStore hook, and useApi hook — eliminates
7
+ * module-level singleton boilerplate (Turbopack duplication bug).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const { Provider: CartProvider, useStore: useCartStore, useApi: useCartStoreApi } =
12
+ * createStoreContext<CartState>('CartStore');
13
+ *
14
+ * // In layout:
15
+ * const cartStore = useRef(createCartStore());
16
+ * <CartProvider store={cartStore.current}>{children}</CartProvider>
17
+ *
18
+ * // In components:
19
+ * const isOpen = useCartStore((s) => s.isOpen);
20
+ * const api = useCartStoreApi(); // for .getState() in callbacks
21
+ * ```
22
+ */
23
+ export declare function createStoreContext<T>(displayName: string): {
24
+ readonly Provider: {
25
+ ({ store, children }: {
26
+ store: StoreApi<T>;
27
+ children: ReactNode;
28
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<StoreApi<T> | null>>;
29
+ displayName: string;
30
+ };
31
+ readonly useStore: {
32
+ (): T;
33
+ <U>(selector: (s: T) => U): U;
34
+ };
35
+ readonly useApi: () => StoreApi<T>;
36
+ };
37
+ //# sourceMappingURL=create-store-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-store-context.d.ts","sourceRoot":"","sources":["../../../src/react/helpers/create-store-context.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjF,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM;;8BAIhB;YAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;YAAC,QAAQ,EAAE,SAAS,CAAA;SAAE;;;;YAMzD,CAAC;SACJ,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC;;2BAU/B,QAAQ,CAAC,CAAC,CAAC;EAS/B"}
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { createContext, useContext, createElement } from 'react';
3
+ import { useStore } from 'zustand';
4
+ /**
5
+ * Create a Context-based Zustand store pattern.
6
+ *
7
+ * Returns Provider, useStore hook, and useApi hook — eliminates
8
+ * module-level singleton boilerplate (Turbopack duplication bug).
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const { Provider: CartProvider, useStore: useCartStore, useApi: useCartStoreApi } =
13
+ * createStoreContext<CartState>('CartStore');
14
+ *
15
+ * // In layout:
16
+ * const cartStore = useRef(createCartStore());
17
+ * <CartProvider store={cartStore.current}>{children}</CartProvider>
18
+ *
19
+ * // In components:
20
+ * const isOpen = useCartStore((s) => s.isOpen);
21
+ * const api = useCartStoreApi(); // for .getState() in callbacks
22
+ * ```
23
+ */
24
+ export function createStoreContext(displayName) {
25
+ const StoreContext = createContext(null);
26
+ StoreContext.displayName = displayName;
27
+ function Provider({ store, children }) {
28
+ return createElement(StoreContext.Provider, { value: store }, children);
29
+ }
30
+ Provider.displayName = `${displayName}Provider`;
31
+ function useStoreHook(selector) {
32
+ const store = useContext(StoreContext);
33
+ if (!store) {
34
+ throw new Error(`${displayName}: useStore must be used within its Provider`);
35
+ }
36
+ // eslint-disable-next-line react-hooks/rules-of-hooks
37
+ return selector ? useStore(store, selector) : useStore(store);
38
+ }
39
+ function useApi() {
40
+ const store = useContext(StoreContext);
41
+ if (!store) {
42
+ throw new Error(`${displayName}: useApi must be used within its Provider`);
43
+ }
44
+ return store;
45
+ }
46
+ return { Provider, useStore: useStoreHook, useApi };
47
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * useAuth — wraps AuthClient + updates auth store.
3
+ *
4
+ * Centralizes login/logout/renew with dual-layer persistence:
5
+ * - httpOnly cookie (for SSR/middleware) — via onSetToken/onClearToken callbacks
6
+ * - Zustand store (for client-side state)
7
+ *
8
+ * Does NOT use React Query — plain async + store updates.
9
+ * Template can wrap in useMutation() if React Query features are needed.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const { login, logout, isLoading } = useAuth();
14
+ *
15
+ * const result = await login('user@example.com', 'password');
16
+ * if (result.success) router.push('/account');
17
+ * ```
18
+ */
19
+ export interface UseAuthOptions {
20
+ /**
21
+ * Called after successful login/renewToken with the new access token.
22
+ * Use to set httpOnly cookie via API route.
23
+ */
24
+ onSetToken?: (token: string) => Promise<void>;
25
+ /**
26
+ * Called after logout to clear httpOnly cookie.
27
+ */
28
+ onClearToken?: () => Promise<void>;
29
+ }
30
+ export interface LoginResult {
31
+ success: boolean;
32
+ userErrors: Array<{
33
+ message: string;
34
+ field?: string[];
35
+ }>;
36
+ accessToken?: string;
37
+ expiresAt?: string;
38
+ }
39
+ export interface LogoutResult {
40
+ success: boolean;
41
+ userErrors: Array<{
42
+ message: string;
43
+ field?: string[];
44
+ }>;
45
+ }
46
+ export interface TokenRenewResult {
47
+ success: boolean;
48
+ userErrors: Array<{
49
+ message: string;
50
+ field?: string[];
51
+ }>;
52
+ accessToken?: string;
53
+ expiresAt?: string;
54
+ }
55
+ export declare function useAuth(options?: UseAuthOptions): {
56
+ login: (email: string, password: string) => Promise<LoginResult>;
57
+ logout: () => Promise<LogoutResult>;
58
+ renewToken: () => Promise<TokenRenewResult>;
59
+ isLoggingIn: boolean;
60
+ isLoggingOut: boolean;
61
+ isRenewingToken: boolean;
62
+ isLoading: boolean;
63
+ error: string | null;
64
+ };
65
+ //# sourceMappingURL=use-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-auth.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AASH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB;mBAUV,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,WAAW,CAAC;kBA2DlD,OAAO,CAAC,YAAY,CAAC;sBA6BjB,OAAO,CAAC,gBAAgB,CAAC;;;;;;EA8DnE"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * useAuth — wraps AuthClient + updates auth store.
3
+ *
4
+ * Centralizes login/logout/renew with dual-layer persistence:
5
+ * - httpOnly cookie (for SSR/middleware) — via onSetToken/onClearToken callbacks
6
+ * - Zustand store (for client-side state)
7
+ *
8
+ * Does NOT use React Query — plain async + store updates.
9
+ * Template can wrap in useMutation() if React Query features are needed.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const { login, logout, isLoading } = useAuth();
14
+ *
15
+ * const result = await login('user@example.com', 'password');
16
+ * if (result.success) router.push('/account');
17
+ * ```
18
+ */
19
+ 'use client';
20
+ import { useState, useCallback } from 'react';
21
+ import { useStorefrontClientContext } from '../providers/storefront-client-provider';
22
+ import { useAuthStore, useAuthStoreApi } from '../stores/store-context';
23
+ import { StorefrontError } from '../../core/errors';
24
+ export function useAuth(options = {}) {
25
+ const { authClient } = useStorefrontClientContext();
26
+ const { setAuth, clearAuth } = useAuthStore();
27
+ const authStore = useAuthStoreApi();
28
+ const [isLoggingIn, setIsLoggingIn] = useState(false);
29
+ const [isLoggingOut, setIsLoggingOut] = useState(false);
30
+ const [isRenewingToken, setIsRenewingToken] = useState(false);
31
+ const [error, setError] = useState(null);
32
+ const login = useCallback(async (email, password) => {
33
+ setError(null);
34
+ setIsLoggingIn(true);
35
+ try {
36
+ const result = await authClient.login(email, password);
37
+ // Set httpOnly cookie
38
+ if (options.onSetToken) {
39
+ await options.onSetToken(result.accessToken);
40
+ }
41
+ // Fetch customer data and set store
42
+ try {
43
+ const customer = await authClient.getCustomer(result.accessToken);
44
+ if (customer) {
45
+ setAuth({
46
+ id: customer.id,
47
+ email: customer.email,
48
+ firstName: customer.firstName ?? undefined,
49
+ lastName: customer.lastName ?? undefined,
50
+ phone: customer.phone ?? undefined,
51
+ }, result.accessToken);
52
+ }
53
+ else {
54
+ setAuth({ id: '', email }, result.accessToken);
55
+ }
56
+ }
57
+ catch {
58
+ // Customer fetch failed — store minimal data
59
+ setAuth({ id: '', email }, result.accessToken);
60
+ }
61
+ return {
62
+ success: true,
63
+ userErrors: [],
64
+ accessToken: result.accessToken,
65
+ expiresAt: result.expiresAt,
66
+ };
67
+ }
68
+ catch (err) {
69
+ if (err instanceof StorefrontError && err.hasUserErrors) {
70
+ return {
71
+ success: false,
72
+ userErrors: err.userErrors.map((e) => ({
73
+ message: e.message,
74
+ field: e.field,
75
+ })),
76
+ };
77
+ }
78
+ const message = err instanceof Error ? err.message : 'Login failed';
79
+ setError(message);
80
+ return { success: false, userErrors: [{ message }] };
81
+ }
82
+ finally {
83
+ setIsLoggingIn(false);
84
+ }
85
+ }, [authClient, setAuth, options]);
86
+ const logout = useCallback(async () => {
87
+ setError(null);
88
+ setIsLoggingOut(true);
89
+ try {
90
+ const token = authStore.getState().accessToken;
91
+ if (token) {
92
+ await authClient.logout(token);
93
+ }
94
+ // Clear httpOnly cookie
95
+ if (options.onClearToken) {
96
+ await options.onClearToken();
97
+ }
98
+ clearAuth();
99
+ return { success: true, userErrors: [] };
100
+ }
101
+ catch (err) {
102
+ // Even on error, clear local state
103
+ clearAuth();
104
+ const message = err instanceof Error ? err.message : 'Logout failed';
105
+ setError(message);
106
+ return { success: false, userErrors: [{ message }] };
107
+ }
108
+ finally {
109
+ setIsLoggingOut(false);
110
+ }
111
+ }, [authClient, clearAuth, options, authStore]);
112
+ const renewToken = useCallback(async () => {
113
+ setError(null);
114
+ setIsRenewingToken(true);
115
+ try {
116
+ const token = authStore.getState().accessToken;
117
+ if (!token) {
118
+ return {
119
+ success: false,
120
+ userErrors: [{ message: 'No token to renew' }],
121
+ };
122
+ }
123
+ const result = await authClient.renewToken(token);
124
+ // Update httpOnly cookie
125
+ if (options.onSetToken) {
126
+ await options.onSetToken(result.accessToken);
127
+ }
128
+ // Update store (keep customer data, update token)
129
+ const currentCustomer = authStore.getState().customer;
130
+ if (currentCustomer) {
131
+ setAuth(currentCustomer, result.accessToken);
132
+ }
133
+ return {
134
+ success: true,
135
+ userErrors: [],
136
+ accessToken: result.accessToken,
137
+ expiresAt: result.expiresAt,
138
+ };
139
+ }
140
+ catch (err) {
141
+ if (err instanceof StorefrontError && err.hasUserErrors) {
142
+ return {
143
+ success: false,
144
+ userErrors: err.userErrors.map((e) => ({
145
+ message: e.message,
146
+ field: e.field,
147
+ })),
148
+ };
149
+ }
150
+ const message = err instanceof Error ? err.message : 'Token renewal failed';
151
+ setError(message);
152
+ return { success: false, userErrors: [{ message }] };
153
+ }
154
+ finally {
155
+ setIsRenewingToken(false);
156
+ }
157
+ }, [authClient, setAuth, options, authStore]);
158
+ return {
159
+ login,
160
+ logout,
161
+ renewToken,
162
+ isLoggingIn,
163
+ isLoggingOut,
164
+ isRenewingToken,
165
+ isLoading: isLoggingIn || isLoggingOut || isRenewingToken,
166
+ error,
167
+ };
168
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * useCartManager — wraps CartClient with cart ID persistence + loading states.
3
+ *
4
+ * Auto-creates cart on first add. CartId persisted in cookie (SSR/edge visible).
5
+ * Does NOT use React Query — plain async + useState.
6
+ * Template wraps in useMutation() if React Query features are needed.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const { addItem, updateQuantity, removeItem, isLoading } = useCartManager();
11
+ *
12
+ * await addItem([{ merchandiseId: 'variant-123', quantity: 1 }]);
13
+ * ```
14
+ */
15
+ import type { Cart, CartLineInput, CartLineUpdateInput } from '../../core/cart/types';
16
+ export declare function useCartManager(): {
17
+ getCart: () => Promise<Cart | null>;
18
+ addItem: (lines: CartLineInput[], options?: {
19
+ forceNewCart?: boolean;
20
+ }) => Promise<Cart>;
21
+ updateItem: (lines: CartLineUpdateInput[]) => Promise<Cart>;
22
+ removeItem: (lineIds: string[]) => Promise<Cart>;
23
+ updateDiscountCodes: (codes: string[]) => Promise<Cart>;
24
+ updateNote: (note: string) => Promise<Cart>;
25
+ clearCart: () => void;
26
+ getCartId: () => string | null;
27
+ isLoading: boolean;
28
+ error: string | null;
29
+ };
30
+ //# sourceMappingURL=use-cart-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-cart-manager.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,mBAAmB,EAA2C,MAAM,uBAAuB,CAAC;AAwB/H,wBAAgB,cAAc;mBAiCU,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;qBAoBjD,aAAa,EAAE,YACZ;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KACnC,OAAO,CAAC,IAAI,CAAC;wBA0BP,mBAAmB,EAAE,KAC3B,OAAO,CAAC,IAAI,CAAC;0BAuB+B,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAAC;iCAuBjB,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAAC;uBAoBlC,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;;qBA2BhC,MAAM,GAAG,IAAI;;;EAiBhD"}