@doswiftly/storefront-sdk 18.0.0 → 18.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## 18.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ff03fd3: Add `<CartManagerProvider>` + `useCartManagerContext()` so a single cart manager can be shared across a checkout tree, plus optional lifecycle callbacks on `useCartManager` and the provider.
8
+
9
+ **Why** — `useCartManager` keeps per-mount state (loading status, recovery coordinator, cart-expired listeners). Calling it in several components creates independent managers, so a checkout that wants one loading indicator, one recovery queue, and one cart-expired subscription had to hand-roll a React Context wrapper. This ships that wrapper as a first-class, opt-in provider, matching the provider-first pattern already used by `<StorefrontProvider>`, `<CurrencyProvider>`, and `<CartProvider>`.
10
+
11
+ **Additive (backward-compatible)**:
12
+ 1. `<CartManagerProvider>` creates one `useCartManager` instance and exposes it through Context; `useCartManagerContext()` reads it (throws when used outside the provider). The provider must be rendered inside `<StorefrontProvider>`.
13
+ 2. New optional lifecycle callbacks, accepted both by `useCartManager(options)` and as `<CartManagerProvider>` props:
14
+ - `onMutationStart(operation)` — fired when a mutation starts.
15
+ - `onMutationSuccess(operation)` — fired after it resolves.
16
+ - `onMutationError(operation, error)` — fired with a buyer-surfaceable error (its message comes from the backend). Cart expiry and session loss are delivered through the dedicated `onExpired` / session-expired channels and do NOT trigger `onMutationError`, so `toast.error(error.message)` is safe to wire.
17
+
18
+ A transient missing-cart error that the manager transparently recovers from resolves as success. Callbacks are invoked defensively — a throwing callback never rejects the underlying mutation (it is logged via `console.warn`).
19
+
20
+ 3. New exported types: `CartManagerProviderProps`, `CartManagerLifecycleCallbacks`, `UseCartManagerOptions`.
21
+
22
+ **Usage example**:
23
+
24
+ ```tsx
25
+ "use client";
26
+ import { useRouter } from "next/navigation";
27
+ import { toast } from "sonner";
28
+ import {
29
+ CartManagerProvider,
30
+ useCartManagerContext,
31
+ } from "@doswiftly/storefront-sdk/react";
32
+
33
+ function Checkout({ initialCartId }: { initialCartId: string | null }) {
34
+ const router = useRouter();
35
+ return (
36
+ <CartManagerProvider
37
+ initialCartId={initialCartId}
38
+ onMutationSuccess={() => router.refresh()}
39
+ onMutationError={(operation, error) => toast.error(error.message)}
40
+ >
41
+ <CheckoutForm />
42
+ </CartManagerProvider>
43
+ );
44
+ }
45
+
46
+ function CheckoutForm() {
47
+ // one shared manager for the whole form
48
+ const { addItem, complete, status } = useCartManagerContext();
49
+ // ...
50
+ }
51
+ ```
52
+
53
+ **Migration checklist** — none required; everything is opt-in.
54
+ - [ ] Optional: replace a hand-rolled `useCartManager` Context wrapper with `<CartManagerProvider>` + `useCartManagerContext()`.
55
+ - [ ] Optional: move per-call toast / refresh side-effects into `onMutationSuccess` / `onMutationError` on the provider.
56
+ - [ ] Keep calling `useCartManager()` directly where you want independent managers (e.g. multi-cart B2B, an admin "view this cart" panel).
57
+
58
+ **Standards reference** — provider-first sharing of a stateful instance with listeners and async initialisation is the established React-ecosystem pattern (`QueryClientProvider`, Stripe `<Elements>`), and mirrors the provider-first cart APIs of leading commerce SDKs.
59
+
3
60
  ## 18.0.0
4
61
 
5
62
  ### Major Changes
package/README.md CHANGED
@@ -493,6 +493,37 @@ export function CartButton() {
493
493
 
494
494
  Methods returning `CartMutationOutcome` are: `addItem`, `updateBuyerIdentity`, `setShippingAddress`, `updateDiscountCodes`, `updateNote` (auto-replay) and `updateItem`, `removeItem` (bail + `onExpired`). Use `clearCart` to reset locally without backend call.
495
495
 
496
+ ### React: `<CartManagerProvider>` (shared instance)
497
+
498
+ `useCartManager` keeps per-mount state, so calling it in several components creates independent managers. To share one source of truth across a checkout — one loading state, one recovery queue, one `cart-expired` subscription — wrap the subtree in `<CartManagerProvider>` (inside `<StorefrontProvider>`) and read it with `useCartManagerContext()`. The provider also takes lifecycle callbacks so cross-cutting side-effects live in one place:
499
+
500
+ ```tsx
501
+ 'use client';
502
+ import { useRouter } from 'next/navigation';
503
+ import { toast } from 'sonner';
504
+ import { CartManagerProvider, useCartManagerContext } from '@doswiftly/storefront-sdk/react';
505
+
506
+ function Checkout({ initialCartId }: { initialCartId: string | null }) {
507
+ const router = useRouter();
508
+ return (
509
+ <CartManagerProvider
510
+ initialCartId={initialCartId}
511
+ onMutationSuccess={() => router.refresh()}
512
+ onMutationError={(operation, error) => toast.error(error.message)}
513
+ >
514
+ <CheckoutForm />
515
+ </CartManagerProvider>
516
+ );
517
+ }
518
+
519
+ function CheckoutForm() {
520
+ const { addItem, complete, status } = useCartManagerContext();
521
+ // ...
522
+ }
523
+ ```
524
+
525
+ For deliberately independent managers (multi-cart B2B, an admin "view this cart" panel) call `useCartManager()` directly instead.
526
+
496
527
  ### AuthClient
497
528
 
498
529
  ```typescript
@@ -119,11 +119,40 @@ export type CartManagerStatus = {
119
119
  type: 'success';
120
120
  operation: CartManagerOperation;
121
121
  };
122
+ /**
123
+ * Optional lifecycle callbacks fired around every cart and checkout operation
124
+ * the hook drives. Centralise cross-cutting side-effects — a global loading
125
+ * indicator, a toast on failure, a router refresh on success — in one place
126
+ * instead of wrapping each call site.
127
+ *
128
+ * `onMutationError` fires only for failures you can surface to the buyer: its
129
+ * `error` carries a backend-translated message. Cart expiry and session loss
130
+ * are delivered through their own dedicated channels (`onExpired` and the
131
+ * session-expired event) — they carry SDK-internal messages and do NOT trigger
132
+ * `onMutationError`. A transient missing-cart error that the manager recovers
133
+ * from (by recreating the cart) resolves as success, so it never reaches the
134
+ * error callback either.
135
+ *
136
+ * Callbacks are invoked defensively: a throwing callback never rejects the
137
+ * underlying mutation.
138
+ */
139
+ export interface CartManagerLifecycleCallbacks {
140
+ /** Fired when an operation starts, before the request is sent. */
141
+ onMutationStart?: (operation: CartManagerOperation) => void;
142
+ /** Fired after an operation resolves successfully. */
143
+ onMutationSuccess?: (operation: CartManagerOperation) => void;
144
+ /**
145
+ * Fired when an operation fails with a buyer-surfaceable error. Cart expiry
146
+ * and session loss are routed to `onExpired` / the session-expired event
147
+ * instead, not here.
148
+ */
149
+ onMutationError?: (operation: CartManagerOperation, error: Error) => void;
150
+ }
122
151
  /**
123
152
  * Optional configuration for `useCartManager`. All fields additive — calling
124
153
  * the hook with no arguments preserves the original cookie-driven behaviour.
125
154
  */
126
- export interface UseCartManagerOptions {
155
+ export interface UseCartManagerOptions extends CartManagerLifecycleCallbacks {
127
156
  /**
128
157
  * Server-known cart-id seed used when the `cart-id` cookie is empty on
129
158
  * mount. Cookie wins when present; the seed only fills the gap on the
@@ -1 +1 @@
1
- {"version":3,"file":"use-cart-manager.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAMH,OAAO,KAAK,EACV,IAAI,EACJ,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,EAC7B,4BAA4B,EAC5B,8BAA8B,EAC9B,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,kBAAkB,EAClB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC5F,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AAGvC;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,YAAY,GACZ,YAAY,GACZ,qBAAqB,GACrB,oBAAoB,GACpB,mBAAmB,GACnB,qBAAqB,GACrB,YAAY,GACZ,kBAAkB,GAClB,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,GACvB,eAAe,GACf,gBAAgB,GAChB,yBAAyB,GACzB,UAAU,GACV,eAAe,CAAC;AAEpB;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,oBAAoB,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,oBAAoB,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IAEnC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAG/B,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClE,mBAAmB,EAAE,CAAC,aAAa,EAAE,sBAAsB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC7F,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChF,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC3D,gBAAgB,EAAE,CAAC,UAAU,EAAE,kBAAkB,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAGrF,UAAU,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC3E,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChE,iBAAiB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC/E,oBAAoB,EAAE,CACpB,KAAK,EAAE,IAAI,CAAC,6BAA6B,EAAE,QAAQ,CAAC,KACjD,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,mBAAmB,EAAE,CACnB,KAAK,EAAE,IAAI,CAAC,4BAA4B,EAAE,QAAQ,CAAC,KAChD,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,qBAAqB,EAAE,CACrB,KAAK,CAAC,EAAE,IAAI,CAAC,8BAA8B,EAAE,QAAQ,CAAC,KACnD,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,aAAa,EAAE,CACb,KAAK,EAAE,IAAI,CAAC,sBAAsB,EAAE,QAAQ,CAAC,KAC1C,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,cAAc,EAAE,CACd,KAAK,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,KAC3C,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,uBAAuB,EAAE,CACvB,KAAK,EAAE,IAAI,CAAC,gCAAgC,EAAE,QAAQ,CAAC,KACpD,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAElC;;;;;;OAMG;IACH,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEtF;;;;;OAKG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAGtE,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAGvE,MAAM,EAAE,iBAAiB,CAAC;IAG1B,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,oBAAoB,CA0WpF"}
1
+ {"version":3,"file":"use-cart-manager.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart-manager.ts"],"names":[],"mappings":"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"}
@@ -86,15 +86,48 @@
86
86
  * ```
87
87
  */
88
88
  'use client';
89
- import { useCallback, useMemo, useState } from 'react';
89
+ import { useCallback, useMemo, useRef, useState } from 'react';
90
90
  import { useStorefrontClientContext } from '../providers/storefront-client-provider';
91
- import { createCartRecoveryRunner, recreateWithInput, } from '../../core/cart/cart-recovery';
91
+ import { createCartRecoveryRunner, recreateWithInput, CartRecoveryNotPossibleError, CartSessionRequiredError, } from '../../core/cart/cart-recovery';
92
92
  import { createBrowserCartCookieStore } from '../cookies';
93
+ /**
94
+ * Invoke an optional lifecycle callback without letting a thrown callback
95
+ * reject the cart mutation it wraps. A misbehaving UI side-effect (toast,
96
+ * navigation) must never corrupt cart state or surface as a failed mutation —
97
+ * but the throw is logged so the consumer bug stays debuggable.
98
+ */
99
+ function safeInvoke(run) {
100
+ try {
101
+ run();
102
+ }
103
+ catch (err) {
104
+ console.warn('[storefront-sdk] cart manager lifecycle callback threw', err);
105
+ }
106
+ }
107
+ /**
108
+ * Cart expiry (`CartRecoveryNotPossibleError`) and session loss
109
+ * (`CartSessionRequiredError`) are surfaced through their own dedicated
110
+ * channels (`onExpired` / the session-expired event) and carry SDK-internal
111
+ * English messages. They are NOT routed to `onMutationError`, whose error is
112
+ * meant to be surfaceable to the buyer.
113
+ */
114
+ function isDedicatedChannelError(err) {
115
+ return err instanceof CartRecoveryNotPossibleError || err instanceof CartSessionRequiredError;
116
+ }
93
117
  export function useCartManager(options) {
94
118
  const { cartClient } = useStorefrontClientContext();
95
119
  const [status, setStatus] = useState({ type: 'idle' });
96
120
  // Cookie store is stateless — keep one instance per hook mount.
97
121
  const cookieStore = useMemo(() => createBrowserCartCookieStore(), []);
122
+ // Latest-callback ref: the mutation wrapper reads `lifecycleRef.current` so it
123
+ // always fires the current callbacks while keeping its own deps array empty
124
+ // (a stable `wrapMutation` avoids rebuilding every mutation on each render).
125
+ const lifecycleRef = useRef({});
126
+ lifecycleRef.current = {
127
+ onMutationStart: options?.onMutationStart,
128
+ onMutationSuccess: options?.onMutationSuccess,
129
+ onMutationError: options?.onMutationError,
130
+ };
98
131
  // Normalize seed so the runner identity is stable across `undefined` /
99
132
  // `null` props and only flips when the storefront actually swaps the seed.
100
133
  const initialCartId = options?.initialCartId ?? null;
@@ -113,14 +146,19 @@ export function useCartManager(options) {
113
146
  // recovery semantics to the runner (which fires onExpired separately).
114
147
  const wrapMutation = useCallback(async (operation, run, failureFallbackMessage) => {
115
148
  setStatus({ type: 'loading', operation });
149
+ safeInvoke(() => lifecycleRef.current.onMutationStart?.(operation));
116
150
  try {
117
151
  const result = await run();
118
152
  setStatus({ type: 'success', operation });
153
+ safeInvoke(() => lifecycleRef.current.onMutationSuccess?.(operation));
119
154
  return result;
120
155
  }
121
156
  catch (err) {
122
157
  const error = err instanceof Error ? err : new Error(failureFallbackMessage);
123
158
  setStatus({ type: 'error', operation, error });
159
+ if (!isDedicatedChannelError(error)) {
160
+ safeInvoke(() => lifecycleRef.current.onMutationError?.(operation, error));
161
+ }
124
162
  throw err;
125
163
  }
126
164
  }, []);
@@ -206,6 +244,7 @@ export function useCartManager(options) {
206
244
  */
207
245
  const complete = useCallback(async (input) => {
208
246
  setStatus({ type: 'loading', operation: 'complete' });
247
+ safeInvoke(() => lifecycleRef.current.onMutationStart?.('complete'));
209
248
  try {
210
249
  const result = await runner.execute({
211
250
  name: 'complete',
@@ -213,11 +252,15 @@ export function useCartManager(options) {
213
252
  });
214
253
  cookieStore.clear();
215
254
  setStatus({ type: 'idle' });
255
+ safeInvoke(() => lifecycleRef.current.onMutationSuccess?.('complete'));
216
256
  return result;
217
257
  }
218
258
  catch (err) {
219
259
  const error = err instanceof Error ? err : new Error('Failed to complete cart');
220
260
  setStatus({ type: 'error', operation: 'complete', error });
261
+ if (!isDedicatedChannelError(error)) {
262
+ safeInvoke(() => lifecycleRef.current.onMutationError?.('complete', error));
263
+ }
221
264
  throw err;
222
265
  }
223
266
  }, [runner, cartClient, cookieStore]);
@@ -237,7 +280,11 @@ export function useCartManager(options) {
237
280
  // Backward-compatible derived selectors over the tagged-union status.
238
281
  const isLoading = status.type === 'loading';
239
282
  const error = status.type === 'error' ? status.error.message : null;
240
- return {
283
+ // Memoize the aggregate so the object identity changes only when `status`
284
+ // flips (the sole reactive field) — every method is already useCallback-stable.
285
+ // Keeps `<CartManagerProvider>`'s Context value referentially stable across
286
+ // unrelated re-renders, matching the sibling StorefrontClientProvider.
287
+ return useMemo(() => ({
241
288
  getCart,
242
289
  getCartId,
243
290
  addItem,
@@ -262,5 +309,30 @@ export function useCartManager(options) {
262
309
  status,
263
310
  isLoading,
264
311
  error,
265
- };
312
+ }), [
313
+ getCart,
314
+ getCartId,
315
+ addItem,
316
+ updateBuyerIdentity,
317
+ setShippingAddress,
318
+ updateDiscountCodes,
319
+ updateNote,
320
+ updateAttributes,
321
+ updateItem,
322
+ removeItem,
323
+ setBillingAddress,
324
+ selectShippingMethod,
325
+ selectPaymentMethod,
326
+ clearPaymentSelection,
327
+ applyGiftCard,
328
+ removeGiftCard,
329
+ updateGiftCardRecipient,
330
+ complete,
331
+ createPayment,
332
+ clearCart,
333
+ onExpired,
334
+ status,
335
+ isLoading,
336
+ error,
337
+ ]);
266
338
  }
@@ -15,6 +15,7 @@ export { StorefrontProvider, type StorefrontProviderProps } from './providers/st
15
15
  export { StorefrontClientProvider, type StorefrontClientProviderProps } from './providers/storefront-client-provider';
16
16
  export { CurrencyProvider, type CurrencyProviderProps } from './providers/currency-provider';
17
17
  export { LanguageProvider, type LanguageProviderProps } from './providers/language-provider';
18
+ export { CartManagerProvider, useCartManagerContext, type CartManagerProviderProps } from './providers/cart-manager-provider';
18
19
  export { useAuth, type UseAuthOptions, type LoginResult, type LogoutResult, type TokenRefreshResult } from './hooks/use-auth';
19
20
  export { useLogin, type UseLoginOptions, type UseLoginReturn } from './hooks/use-login';
20
21
  export { useLogout, type UseLogoutOptions, type UseLogoutReturn } from './hooks/use-logout';
@@ -22,7 +23,7 @@ export { useRefreshToken, type UseRefreshTokenOptions, type UseRefreshTokenRetur
22
23
  export { useSessionRefresh, type UseSessionRefreshOptions } from './hooks/use-session-refresh';
23
24
  export { useSessionExpired } from './hooks/use-session-expired';
24
25
  export type { SessionExpiredEvent, SessionExpiredReason, SessionExpiredEmitter } from '../core/auth/session-events';
25
- export { useCartManager, type CartManagerOperation, type CartManagerStatus, type UseCartManagerResult } from './hooks/use-cart-manager';
26
+ export { useCartManager, type CartManagerOperation, type CartManagerStatus, type UseCartManagerResult, type UseCartManagerOptions, type CartManagerLifecycleCallbacks } from './hooks/use-cart-manager';
26
27
  export { useCart, type UseCartOptions, type UseCartResult, type ServerCartOperation } from './hooks/use-cart';
27
28
  export { useStorefrontClient } from './hooks/use-storefront-client';
28
29
  export { useCurrency } from './hooks/use-currency';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,7 @@ export { StorefrontProvider } from './providers/storefront-provider';
16
16
  export { StorefrontClientProvider } from './providers/storefront-client-provider';
17
17
  export { CurrencyProvider } from './providers/currency-provider';
18
18
  export { LanguageProvider } from './providers/language-provider';
19
+ export { CartManagerProvider, useCartManagerContext } from './providers/cart-manager-provider';
19
20
  // Hooks
20
21
  export { useAuth } from './hooks/use-auth';
21
22
  export { useLogin } from './hooks/use-login';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * CartManagerProvider — shares a single `useCartManager` instance across the tree.
3
+ *
4
+ * `useCartManager` owns per-mount state (status, recovery coordinator, cart-expired
5
+ * listeners), so calling it in several components creates independent managers
6
+ * with separate loading state and separate recovery queues. Wrap the checkout
7
+ * subtree in `<CartManagerProvider>` and read the shared instance with
8
+ * `useCartManagerContext()` to get one source of truth: one loading indicator,
9
+ * one recovery coordinator, one cart-expired subscription.
10
+ *
11
+ * Must be rendered inside `<StorefrontProvider>` — it builds on the storefront
12
+ * client from context. For deliberately independent managers (e.g. a multi-cart
13
+ * admin view) call `useCartManager()` directly instead of using this provider.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * // app/checkout/page.tsx — Server Component resolves the cart-id
18
+ * <CartManagerProvider
19
+ * initialCartId={cartId}
20
+ * onMutationError={(operation, error) => toast.error(error.message)}
21
+ * onMutationSuccess={() => router.refresh()}
22
+ * >
23
+ * <CheckoutForm />
24
+ * </CartManagerProvider>
25
+ *
26
+ * // CheckoutForm.tsx
27
+ * const { addItem, complete, status } = useCartManagerContext();
28
+ * ```
29
+ */
30
+ import { type ReactNode } from 'react';
31
+ import { type CartManagerLifecycleCallbacks, type UseCartManagerResult } from '../hooks/use-cart-manager';
32
+ export interface CartManagerProviderProps extends CartManagerLifecycleCallbacks {
33
+ /**
34
+ * Server-known cart-id seed forwarded to `useCartManager`. Used only when the
35
+ * `cart-id` cookie is empty on mount — the cookie always wins when present.
36
+ */
37
+ initialCartId?: string | null;
38
+ children: ReactNode;
39
+ }
40
+ /**
41
+ * Creates one `useCartManager` instance and exposes it to descendants via
42
+ * Context. Lifecycle callbacks are forwarded to the underlying hook.
43
+ */
44
+ export declare function CartManagerProvider({ initialCartId, onMutationStart, onMutationSuccess, onMutationError, children, }: CartManagerProviderProps): import("react/jsx-runtime").JSX.Element;
45
+ /**
46
+ * Read the shared cart manager provided by the nearest `<CartManagerProvider>`.
47
+ * Throws when called outside a provider.
48
+ */
49
+ export declare function useCartManagerContext(): UseCartManagerResult;
50
+ //# sourceMappingURL=cart-manager-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart-manager-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/cart-manager-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAKnC,MAAM,WAAW,wBAAyB,SAAQ,6BAA6B;IAC7E;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,QAAQ,GACT,EAAE,wBAAwB,2CAS1B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,oBAAoB,CAM5D"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * CartManagerProvider — shares a single `useCartManager` instance across the tree.
3
+ *
4
+ * `useCartManager` owns per-mount state (status, recovery coordinator, cart-expired
5
+ * listeners), so calling it in several components creates independent managers
6
+ * with separate loading state and separate recovery queues. Wrap the checkout
7
+ * subtree in `<CartManagerProvider>` and read the shared instance with
8
+ * `useCartManagerContext()` to get one source of truth: one loading indicator,
9
+ * one recovery coordinator, one cart-expired subscription.
10
+ *
11
+ * Must be rendered inside `<StorefrontProvider>` — it builds on the storefront
12
+ * client from context. For deliberately independent managers (e.g. a multi-cart
13
+ * admin view) call `useCartManager()` directly instead of using this provider.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * // app/checkout/page.tsx — Server Component resolves the cart-id
18
+ * <CartManagerProvider
19
+ * initialCartId={cartId}
20
+ * onMutationError={(operation, error) => toast.error(error.message)}
21
+ * onMutationSuccess={() => router.refresh()}
22
+ * >
23
+ * <CheckoutForm />
24
+ * </CartManagerProvider>
25
+ *
26
+ * // CheckoutForm.tsx
27
+ * const { addItem, complete, status } = useCartManagerContext();
28
+ * ```
29
+ */
30
+ 'use client';
31
+ import { jsx as _jsx } from "react/jsx-runtime";
32
+ import { createContext, useContext } from 'react';
33
+ import { useCartManager, } from '../hooks/use-cart-manager';
34
+ const CartManagerContext = createContext(null);
35
+ CartManagerContext.displayName = 'CartManagerContext';
36
+ /**
37
+ * Creates one `useCartManager` instance and exposes it to descendants via
38
+ * Context. Lifecycle callbacks are forwarded to the underlying hook.
39
+ */
40
+ export function CartManagerProvider({ initialCartId, onMutationStart, onMutationSuccess, onMutationError, children, }) {
41
+ const manager = useCartManager({
42
+ initialCartId,
43
+ onMutationStart,
44
+ onMutationSuccess,
45
+ onMutationError,
46
+ });
47
+ return _jsx(CartManagerContext.Provider, { value: manager, children: children });
48
+ }
49
+ /**
50
+ * Read the shared cart manager provided by the nearest `<CartManagerProvider>`.
51
+ * Throws when called outside a provider.
52
+ */
53
+ export function useCartManagerContext() {
54
+ const ctx = useContext(CartManagerContext);
55
+ if (!ctx) {
56
+ throw new Error('useCartManagerContext must be used within CartManagerProvider');
57
+ }
58
+ return ctx;
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-sdk",
3
- "version": "18.0.0",
3
+ "version": "18.1.0",
4
4
  "description": "Storefront runtime SDK for DoSwiftly Commerce — layered transport, middleware pipeline, React providers, Zustand stores, cache strategies. 0 runtime dependencies in core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,