@doswiftly/storefront-sdk 14.0.0 → 15.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.
@@ -12,12 +12,42 @@
12
12
  * Drift detection: PreToolUse hook validuje strings przeciwko storefront-operations/schema.graphql.
13
13
  */
14
14
  export declare const CART_QUERY: string;
15
+ /**
16
+ * cartAvailableShippingMethods — cart-aware shipping discovery for the
17
+ * checkout shipping step. Backend uses the cart's contents (subtotal,
18
+ * physical-item weight) and surfaces a `DIGITAL_ONLY_NO_SHIPPING` userError
19
+ * when the cart has no shippable items. Each method carries `deliveryType`
20
+ * so the storefront can render a pickup-point / locker picker without
21
+ * inferring the type from the method name.
22
+ */
23
+ export declare const CART_AVAILABLE_SHIPPING_METHODS_QUERY: string;
24
+ /**
25
+ * availablePaymentMethods — shop-level list of active payment methods
26
+ * (default + sorted by merchant position). Independent of cart contents
27
+ * today; storefront fetches once for the checkout payment step.
28
+ */
29
+ export declare const AVAILABLE_PAYMENT_METHODS_QUERY: string;
30
+ /**
31
+ * orderByToken — guest order lookup by opaque access token (returned by
32
+ * `cartComplete.order.accessToken`). Optional `email` adds defense-in-depth:
33
+ * if supplied it must match the order's buyer email case-insensitively, else
34
+ * the query returns `null` (same shape as an invalid token). Rate-limited by
35
+ * the backend (5/min per IP+shop); no cache (`Cache-Control: no-store`).
36
+ */
37
+ export declare const ORDER_BY_TOKEN_QUERY: string;
15
38
  export declare const CART_CREATE: string;
16
39
  export declare const CART_ADD_LINES: string;
17
40
  export declare const CART_UPDATE_LINES: string;
18
41
  export declare const CART_REMOVE_LINES: string;
19
42
  export declare const CART_DISCOUNT_CODES_UPDATE: string;
20
43
  export declare const CART_UPDATE_NOTE: string;
44
+ /**
45
+ * cartUpdateAttributes — replaces (NOT merges) the cart's custom `[{ key, value }]`
46
+ * attribute pairs. Free-form metadata visible to the merchant in admin: delivery
47
+ * instructions, gift-wrap flags, B2B PO numbers. Backend limit: 250 pairs / 255-char
48
+ * keys (rejected with `CART_ATTRIBUTES_LIMIT_EXCEEDED` userError).
49
+ */
50
+ export declare const CART_UPDATE_ATTRIBUTES: string;
21
51
  export declare const CART_UPDATE_BUYER_IDENTITY: string;
22
52
  export declare const CART_SET_SHIPPING_ADDRESS: string;
23
53
  export declare const CART_SET_BILLING_ADDRESS: string;
@@ -1 +1 @@
1
- {"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAsSH,eAAO,MAAM,UAAU,QAOrB,CAAC;AAMH,eAAO,MAAM,WAAW,QAWtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAW3B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAMH,eAAO,MAAM,yBAAyB,QAWpC,CAAC;AAEH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH,eAAO,MAAM,2BAA2B,QAWtC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAW/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,QAWhC,CAAC;AAEH,eAAO,MAAM,+BAA+B,QAW1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAWxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QASzB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,QAoBtC,CAAC"}
1
+ {"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0XH,eAAO,MAAM,UAAU,QAOrB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,qCAAqC,QAehD,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,QAO1C,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAO/B,CAAC;AAMH,eAAO,MAAM,WAAW,QAWtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAW3B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAWjC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAMH,eAAO,MAAM,yBAAyB,QAWpC,CAAC;AAEH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH,eAAO,MAAM,2BAA2B,QAWtC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAW/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,QAWhC,CAAC;AAEH,eAAO,MAAM,+BAA+B,QAW1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAWxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QASzB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,QAoBtC,CAAC"}
@@ -138,6 +138,14 @@ const CART_DISCOUNT_ALLOCATION_FRAGMENT = `
138
138
  amount { ...Money }
139
139
  }
140
140
  `;
141
+ const PICKUP_POINT_FRAGMENT = `
142
+ fragment PickupPoint on PickupPoint {
143
+ provider
144
+ pointId
145
+ name
146
+ address
147
+ }
148
+ `;
141
149
  const MAILING_ADDRESS_FRAGMENT = `
142
150
  fragment MailingAddress on MailingAddress {
143
151
  id
@@ -155,7 +163,11 @@ const MAILING_ADDRESS_FRAGMENT = `
155
163
  postalCode
156
164
  phone
157
165
  isDefault
166
+ taxId
167
+ vatNumber
168
+ pickupPoint { ...PickupPoint }
158
169
  }
170
+ ${PICKUP_POINT_FRAGMENT}
159
171
  `;
160
172
  const CART_SHIPPING_METHOD_FRAGMENT = `
161
173
  fragment CartShippingMethod on CartShippingMethod {
@@ -166,6 +178,7 @@ const CART_SHIPPING_METHOD_FRAGMENT = `
166
178
  `;
167
179
  const CART_APPLIED_GIFT_CARD_FRAGMENT = `
168
180
  fragment CartAppliedGiftCard on CartAppliedGiftCard {
181
+ id
169
182
  maskedCode
170
183
  lastCharacters
171
184
  appliedAmount { ...Money }
@@ -263,6 +276,70 @@ const CART_WARNING_FRAGMENT = `
263
276
  target
264
277
  }
265
278
  `;
279
+ const SHIPPING_CARRIER_FRAGMENT = `
280
+ fragment ShippingCarrier on ShippingCarrier {
281
+ id
282
+ name
283
+ logoUrl
284
+ serviceCode
285
+ }
286
+ `;
287
+ const DELIVERY_ESTIMATE_FRAGMENT = `
288
+ fragment DeliveryEstimate on DeliveryEstimate {
289
+ minDays
290
+ maxDays
291
+ description
292
+ }
293
+ `;
294
+ const FREE_SHIPPING_PROGRESS_FRAGMENT = `
295
+ fragment FreeShippingProgress on FreeShippingProgress {
296
+ qualifies
297
+ currentAmount { ...Money }
298
+ threshold { ...Money }
299
+ remaining { ...Money }
300
+ progressPercent
301
+ message
302
+ }
303
+ ${MONEY_FRAGMENT}
304
+ `;
305
+ const AVAILABLE_SHIPPING_METHOD_FRAGMENT = `
306
+ fragment AvailableShippingMethod on AvailableShippingMethod {
307
+ id
308
+ name
309
+ description
310
+ deliveryType
311
+ carrier { ...ShippingCarrier }
312
+ price { ...Money }
313
+ isFree
314
+ estimatedDelivery { ...DeliveryEstimate }
315
+ freeShippingProgress { ...FreeShippingProgress }
316
+ sortOrder
317
+ }
318
+ ${SHIPPING_CARRIER_FRAGMENT}
319
+ ${MONEY_FRAGMENT}
320
+ ${DELIVERY_ESTIMATE_FRAGMENT}
321
+ ${FREE_SHIPPING_PROGRESS_FRAGMENT}
322
+ `;
323
+ const PAYMENT_METHOD_FRAGMENT = `
324
+ fragment PaymentMethod on PaymentMethod {
325
+ id
326
+ name
327
+ provider
328
+ type
329
+ icon
330
+ description
331
+ isDefault
332
+ supportedCurrencies
333
+ position
334
+ }
335
+ `;
336
+ const AVAILABLE_PAYMENT_METHODS_FRAGMENT = `
337
+ fragment AvailablePaymentMethods on AvailablePaymentMethods {
338
+ methods { ...PaymentMethod }
339
+ defaultMethod { ...PaymentMethod }
340
+ }
341
+ ${PAYMENT_METHOD_FRAGMENT}
342
+ `;
266
343
  const PAYMENT_SESSION_FRAGMENT = `
267
344
  fragment PaymentSession on PaymentSession {
268
345
  id
@@ -286,6 +363,58 @@ export const CART_QUERY = composeOperation(`
286
363
  }
287
364
  ${CART_FRAGMENT}
288
365
  `);
366
+ /**
367
+ * cartAvailableShippingMethods — cart-aware shipping discovery for the
368
+ * checkout shipping step. Backend uses the cart's contents (subtotal,
369
+ * physical-item weight) and surfaces a `DIGITAL_ONLY_NO_SHIPPING` userError
370
+ * when the cart has no shippable items. Each method carries `deliveryType`
371
+ * so the storefront can render a pickup-point / locker picker without
372
+ * inferring the type from the method name.
373
+ */
374
+ export const CART_AVAILABLE_SHIPPING_METHODS_QUERY = composeOperation(`
375
+ query CartAvailableShippingMethods($cartId: ID!, $address: ShippingAddressInput!) {
376
+ cart(id: $cartId) {
377
+ id
378
+ requiresShipping
379
+ availableShippingMethods(address: $address) {
380
+ methods { ...AvailableShippingMethod }
381
+ freeShippingProgress { ...FreeShippingProgress }
382
+ userErrors { ...UserError }
383
+ }
384
+ }
385
+ }
386
+ ${AVAILABLE_SHIPPING_METHOD_FRAGMENT}
387
+ ${FREE_SHIPPING_PROGRESS_FRAGMENT}
388
+ ${USER_ERROR_FRAGMENT}
389
+ `);
390
+ /**
391
+ * availablePaymentMethods — shop-level list of active payment methods
392
+ * (default + sorted by merchant position). Independent of cart contents
393
+ * today; storefront fetches once for the checkout payment step.
394
+ */
395
+ export const AVAILABLE_PAYMENT_METHODS_QUERY = composeOperation(`
396
+ query AvailablePaymentMethods {
397
+ availablePaymentMethods {
398
+ ...AvailablePaymentMethods
399
+ }
400
+ }
401
+ ${AVAILABLE_PAYMENT_METHODS_FRAGMENT}
402
+ `);
403
+ /**
404
+ * orderByToken — guest order lookup by opaque access token (returned by
405
+ * `cartComplete.order.accessToken`). Optional `email` adds defense-in-depth:
406
+ * if supplied it must match the order's buyer email case-insensitively, else
407
+ * the query returns `null` (same shape as an invalid token). Rate-limited by
408
+ * the backend (5/min per IP+shop); no cache (`Cache-Control: no-store`).
409
+ */
410
+ export const ORDER_BY_TOKEN_QUERY = composeOperation(`
411
+ query OrderByToken($token: String!, $email: String) {
412
+ orderByToken(token: $token, email: $email) {
413
+ ...Order
414
+ }
415
+ }
416
+ ${ORDER_FRAGMENT}
417
+ `);
289
418
  // ---------------------------------------------------------------------------
290
419
  // Mutations
291
420
  // ---------------------------------------------------------------------------
@@ -361,6 +490,24 @@ export const CART_UPDATE_NOTE = composeOperation(`
361
490
  ${USER_ERROR_FRAGMENT}
362
491
  ${CART_WARNING_FRAGMENT}
363
492
  `);
493
+ /**
494
+ * cartUpdateAttributes — replaces (NOT merges) the cart's custom `[{ key, value }]`
495
+ * attribute pairs. Free-form metadata visible to the merchant in admin: delivery
496
+ * instructions, gift-wrap flags, B2B PO numbers. Backend limit: 250 pairs / 255-char
497
+ * keys (rejected with `CART_ATTRIBUTES_LIMIT_EXCEEDED` userError).
498
+ */
499
+ export const CART_UPDATE_ATTRIBUTES = composeOperation(`
500
+ mutation CartUpdateAttributes($id: ID!, $attributes: [CartAttributeInput!]!) {
501
+ cartUpdateAttributes(id: $id, attributes: $attributes) {
502
+ cart { ...Cart }
503
+ userErrors { ...UserError }
504
+ warnings { ...CartWarning }
505
+ }
506
+ }
507
+ ${CART_FRAGMENT}
508
+ ${USER_ERROR_FRAGMENT}
509
+ ${CART_WARNING_FRAGMENT}
510
+ `);
364
511
  export const CART_UPDATE_BUYER_IDENTITY = composeOperation(`
365
512
  mutation CartUpdateBuyerIdentity($id: ID!, $buyerIdentity: CartBuyerIdentityInput!) {
366
513
  cartUpdateBuyerIdentity(id: $id, buyerIdentity: $buyerIdentity) {
@@ -0,0 +1,78 @@
1
+ /**
2
+ * useCart — server-driven cart hook bound to an explicit `cartId`.
3
+ *
4
+ * Sister of {@link useCartManager}: same `CartClient` actions, different
5
+ * lifecycle. `useCartManager` reads the cart id from the `cart-id` cookie and
6
+ * runs auto-recovery on stale carts (cookie-first flow — browser-rendered
7
+ * storefront). `useCart` takes the cart id as a prop and never touches the
8
+ * cookie, which is what SSR, deep-link recovery, and admin "view this cart"
9
+ * UIs need.
10
+ *
11
+ * State management follows the platform's store pattern: a vanilla Zustand
12
+ * store is created per hook mount via `useMemo` (component-scoped, no
13
+ * module-level singleton — Inv-3 of the SDK), and React subscribes through
14
+ * `useStore`. Changing `cartId` recreates the store; refetches are explicit
15
+ * (`refetch()` or any mutation triggers one).
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * // Server component / route handler hands the cartId to the client tree.
20
+ * function CheckoutPage({ cartId }: { cartId: string }) {
21
+ * const { cart, isLoading, error, addItems, updateAttributes } = useCart(cartId);
22
+ *
23
+ * if (isLoading) return <Spinner />;
24
+ * if (error) return <ErrorBanner error={error} />;
25
+ * if (!cart) return null;
26
+ *
27
+ * return <CheckoutForm cart={cart} onAdd={addItems} onAttrs={updateAttributes} />;
28
+ * }
29
+ * ```
30
+ */
31
+ import type { CartMutationOutcome } from '../../core/cart/cart-client';
32
+ import type { Cart, CartAddressInput, CartAttributeInput, CartBuyerIdentityInput, CartLineInput, CartLineUpdateInput } from '../../core/cart/types';
33
+ /** Names of mutations exposed by the hook — useful for telemetry / spinners. */
34
+ export type ServerCartOperation = 'fetch' | 'addItems' | 'updateItems' | 'removeItems' | 'updateBuyerIdentity' | 'setShippingAddress' | 'updateDiscountCodes' | 'updateNote' | 'updateAttributes';
35
+ export interface UseCartOptions {
36
+ /**
37
+ * Fetch the cart automatically when the hook mounts. Set to `false` when the
38
+ * server has already pre-rendered the cart and you want to seed the store
39
+ * instead of double-fetching. Default `true`.
40
+ */
41
+ autoFetch?: boolean;
42
+ /**
43
+ * Pre-loaded cart used as the initial store value — e.g. the cart already
44
+ * resolved on the server during SSR. Combine with `autoFetch: false` to avoid
45
+ * a duplicate round-trip on hydration.
46
+ */
47
+ initialCart?: Cart | null;
48
+ }
49
+ export interface UseCartResult {
50
+ /** Currently loaded cart, or null before the first fetch / when not found. */
51
+ cart: Cart | null;
52
+ /** True while any operation (fetch or mutation) is in flight. */
53
+ isLoading: boolean;
54
+ /** Last error from any operation, or null. */
55
+ error: Error | null;
56
+ /** Which operation produced the current `isLoading` / `error`, if any. */
57
+ operation: ServerCartOperation | null;
58
+ /** Re-fetch the cart from the backend. Resolves with the fresh cart or null. */
59
+ refetch: () => Promise<Cart | null>;
60
+ /** Append line items to the cart. Resolves with `{ cart, warnings }`. */
61
+ addItems: (lines: CartLineInput[]) => Promise<CartMutationOutcome>;
62
+ /** Update line items (quantity, attributes) by their line IDs. */
63
+ updateItems: (lines: CartLineUpdateInput[]) => Promise<CartMutationOutcome>;
64
+ /** Remove line items by their line IDs. */
65
+ removeItems: (lineIds: string[]) => Promise<CartMutationOutcome>;
66
+ /** Update buyer identity (email, phone, country, customer link, language). */
67
+ updateBuyerIdentity: (buyerIdentity: CartBuyerIdentityInput) => Promise<CartMutationOutcome>;
68
+ /** Set the shipping address (full replace). */
69
+ setShippingAddress: (address: CartAddressInput) => Promise<CartMutationOutcome>;
70
+ /** Apply discount codes (replace-all — pass `[]` to clear). */
71
+ updateDiscountCodes: (codes: string[]) => Promise<CartMutationOutcome>;
72
+ /** Update the cart note / gift message. */
73
+ updateNote: (note: string) => Promise<CartMutationOutcome>;
74
+ /** Replace the cart's custom `{ key, value }` attribute pairs. */
75
+ updateAttributes: (attributes: CartAttributeInput[]) => Promise<CartMutationOutcome>;
76
+ }
77
+ export declare function useCart(cartId: string, options?: UseCartOptions): UseCartResult;
78
+ //# sourceMappingURL=use-cart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-cart.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH,OAAO,KAAK,EAAc,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,KAAK,EACV,IAAI,EACJ,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,EACb,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAM/B,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAC3B,OAAO,GACP,UAAU,GACV,aAAa,GACb,aAAa,GACb,qBAAqB,GACrB,oBAAoB,GACpB,qBAAqB,GACrB,YAAY,GACZ,kBAAkB,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,iEAAiE;IACjE,SAAS,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,0EAA0E;IAC1E,SAAS,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAEtC,gFAAgF;IAChF,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,yEAAyE;IACzE,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnE,kEAAkE;IAClE,WAAW,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5E,2CAA2C;IAC3C,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACjE,8EAA8E;IAC9E,mBAAmB,EAAE,CAAC,aAAa,EAAE,sBAAsB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC7F,+CAA+C;IAC/C,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChF,+DAA+D;IAC/D,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvE,2CAA2C;IAC3C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC3D,kEAAkE;IAClE,gBAAgB,EAAE,CAAC,UAAU,EAAE,kBAAkB,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACtF;AAoFD,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CA4CnF"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * useCart — server-driven cart hook bound to an explicit `cartId`.
3
+ *
4
+ * Sister of {@link useCartManager}: same `CartClient` actions, different
5
+ * lifecycle. `useCartManager` reads the cart id from the `cart-id` cookie and
6
+ * runs auto-recovery on stale carts (cookie-first flow — browser-rendered
7
+ * storefront). `useCart` takes the cart id as a prop and never touches the
8
+ * cookie, which is what SSR, deep-link recovery, and admin "view this cart"
9
+ * UIs need.
10
+ *
11
+ * State management follows the platform's store pattern: a vanilla Zustand
12
+ * store is created per hook mount via `useMemo` (component-scoped, no
13
+ * module-level singleton — Inv-3 of the SDK), and React subscribes through
14
+ * `useStore`. Changing `cartId` recreates the store; refetches are explicit
15
+ * (`refetch()` or any mutation triggers one).
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * // Server component / route handler hands the cartId to the client tree.
20
+ * function CheckoutPage({ cartId }: { cartId: string }) {
21
+ * const { cart, isLoading, error, addItems, updateAttributes } = useCart(cartId);
22
+ *
23
+ * if (isLoading) return <Spinner />;
24
+ * if (error) return <ErrorBanner error={error} />;
25
+ * if (!cart) return null;
26
+ *
27
+ * return <CheckoutForm cart={cart} onAdd={addItems} onAttrs={updateAttributes} />;
28
+ * }
29
+ * ```
30
+ */
31
+ 'use client';
32
+ import { useEffect, useMemo } from 'react';
33
+ import { createStore } from 'zustand/vanilla';
34
+ import { useStore } from 'zustand';
35
+ import { useStorefrontClientContext } from '../providers/storefront-client-provider';
36
+ function createServerCartStore(opts) {
37
+ const { cartClient, cartId, initialCart } = opts;
38
+ const store = createStore(() => ({
39
+ cart: initialCart ?? null,
40
+ isLoading: false,
41
+ error: null,
42
+ operation: null,
43
+ }));
44
+ async function run(operation, exec) {
45
+ store.setState({ isLoading: true, error: null, operation });
46
+ try {
47
+ const result = await exec();
48
+ store.setState({ isLoading: false, error: null, operation: null });
49
+ return result;
50
+ }
51
+ catch (err) {
52
+ const error = err instanceof Error ? err : new Error(String(err));
53
+ store.setState({ isLoading: false, error, operation });
54
+ throw err;
55
+ }
56
+ }
57
+ async function runMutation(operation, exec) {
58
+ const outcome = await run(operation, exec);
59
+ store.setState({ cart: outcome.cart });
60
+ return outcome;
61
+ }
62
+ return {
63
+ store,
64
+ async refetch() {
65
+ const cart = await run('fetch', () => cartClient.get(cartId));
66
+ store.setState({ cart });
67
+ return cart;
68
+ },
69
+ addItems: (lines) => runMutation('addItems', () => cartClient.addItems(cartId, lines)),
70
+ updateItems: (lines) => runMutation('updateItems', () => cartClient.updateItems(cartId, lines)),
71
+ removeItems: (lineIds) => runMutation('removeItems', () => cartClient.removeItems(cartId, lineIds)),
72
+ updateBuyerIdentity: (buyerIdentity) => runMutation('updateBuyerIdentity', () => cartClient.updateBuyerIdentity(cartId, buyerIdentity)),
73
+ setShippingAddress: (address) => runMutation('setShippingAddress', () => cartClient.setShippingAddress({ cartId, address })),
74
+ updateDiscountCodes: (codes) => runMutation('updateDiscountCodes', () => cartClient.updateDiscountCodes(cartId, codes)),
75
+ updateNote: (note) => runMutation('updateNote', () => cartClient.updateNote(cartId, note)),
76
+ updateAttributes: (attributes) => runMutation('updateAttributes', () => cartClient.updateAttributes(cartId, attributes)),
77
+ };
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Hook
81
+ // ---------------------------------------------------------------------------
82
+ export function useCart(cartId, options = {}) {
83
+ const { cartClient } = useStorefrontClientContext();
84
+ const { autoFetch = true, initialCart } = options;
85
+ // Recreate the store when `cartId` or the underlying client changes. The
86
+ // useMemo dependency list captures store identity (component-scoped, not
87
+ // module-level — Inv-3). Mutations capture the store instance for stable
88
+ // references via the api object.
89
+ const api = useMemo(() => createServerCartStore({ cartClient, cartId, initialCart }),
90
+ // initialCart deliberately excluded — it only seeds the FIRST mount; we do
91
+ // not want to reset the store when the parent re-renders with a stale
92
+ // server snapshot.
93
+ // eslint-disable-next-line react-hooks/exhaustive-deps
94
+ [cartClient, cartId]);
95
+ const state = useStore(api.store);
96
+ // Initial fetch — runs once per (cartId, cartClient) pair when autoFetch is
97
+ // enabled. Catches and stores any error via the runner; the caller observes
98
+ // it through `error` rather than an unhandled promise.
99
+ useEffect(() => {
100
+ if (!autoFetch)
101
+ return;
102
+ api.refetch().catch(() => {
103
+ // already captured in store state via the runner
104
+ });
105
+ }, [api, autoFetch]);
106
+ return {
107
+ cart: state.cart,
108
+ isLoading: state.isLoading,
109
+ error: state.error,
110
+ operation: state.operation,
111
+ refetch: api.refetch,
112
+ addItems: api.addItems,
113
+ updateItems: api.updateItems,
114
+ removeItems: api.removeItems,
115
+ updateBuyerIdentity: api.updateBuyerIdentity,
116
+ setShippingAddress: api.setShippingAddress,
117
+ updateDiscountCodes: api.updateDiscountCodes,
118
+ updateNote: api.updateNote,
119
+ updateAttributes: api.updateAttributes,
120
+ };
121
+ }
@@ -20,6 +20,7 @@ export { useLogin, type UseLoginOptions, type UseLoginReturn } from './hooks/use
20
20
  export { useLogout, type UseLogoutOptions, type UseLogoutReturn } from './hooks/use-logout';
21
21
  export { useRefreshToken, type UseRefreshTokenOptions, type UseRefreshTokenReturn } from './hooks/use-refresh-token';
22
22
  export { useCartManager, type CartManagerOperation, type CartManagerStatus, type UseCartManagerResult } from './hooks/use-cart-manager';
23
+ export { useCart, type UseCartOptions, type UseCartResult, type ServerCartOperation } from './hooks/use-cart';
23
24
  export { useStorefrontClient } from './hooks/use-storefront-client';
24
25
  export { useCurrency } from './hooks/use-currency';
25
26
  export { useAuthStore, useAuthStoreApi, useAuthHydrated } from './stores/store-context';
@@ -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":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wCAAwC,CAAC;AACtH,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG7F,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACrH,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACxI,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG/E,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACxH,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGlI,OAAO,EACL,SAAS,EACT,SAAS,EACT,YAAY,EACZ,0BAA0B,EAC1B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,EACL,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGpE,OAAO,EACL,KAAK,EACL,KAAK,UAAU,EACf,KAAK,EACL,KAAK,mBAAmB,EACxB,SAAS,EACT,KAAK,cAAc,EACnB,eAAe,EACf,KAAK,oBAAoB,EACzB,YAAY,EACZ,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC"}
@@ -22,6 +22,7 @@ export { useLogin } from './hooks/use-login';
22
22
  export { useLogout } from './hooks/use-logout';
23
23
  export { useRefreshToken } from './hooks/use-refresh-token';
24
24
  export { useCartManager } from './hooks/use-cart-manager';
25
+ export { useCart } from './hooks/use-cart';
25
26
  export { useStorefrontClient } from './hooks/use-storefront-client';
26
27
  export { useCurrency } from './hooks/use-currency';
27
28
  // Store hooks (Context-based)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-sdk",
3
- "version": "14.0.0",
3
+ "version": "15.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,
@@ -41,16 +41,21 @@
41
41
  "author": "DoSwiftly Team",
42
42
  "license": "MIT",
43
43
  "devDependencies": {
44
+ "@graphql-codegen/cli": "^5.0.0",
45
+ "@graphql-codegen/typescript": "^4.0.0",
46
+ "@graphql-codegen/typescript-operations": "^4.0.0",
44
47
  "@testing-library/react": "^16.1.0",
45
48
  "@types/node": "^22.10.2",
46
49
  "@types/react": "^18.3.0 || ^19.0.0",
47
50
  "@types/react-dom": "^19.0.0",
48
51
  "@vitest/coverage-v8": "^4.1.0",
49
52
  "fast-check": "^3.23.2",
53
+ "graphql": "^16.13.2",
50
54
  "jsdom": "^25.0.1",
51
55
  "next": "^16.2.3",
52
56
  "react": "^19.0.0",
53
57
  "react-dom": "^19.0.0",
58
+ "tsx": "^4.0.0",
54
59
  "typescript": "^5.7.2",
55
60
  "vitest": "^4.1.0",
56
61
  "zustand": "^5.0.2"
@@ -70,6 +75,7 @@
70
75
  "scripts": {
71
76
  "build": "tsc",
72
77
  "build:only": "tsc",
78
+ "codegen": "tsx scripts/codegen.mts",
73
79
  "dev": "tsc --watch",
74
80
  "clean": "rm -rf dist",
75
81
  "test": "vitest run",