@replohq/sdk 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,16 @@
1
1
  import type { Cart, CartLine, CartLineAdjustment, CartLinePayload } from "./cart-types";
2
2
  import React from "react";
3
+ export type CartOperation = "add" | "update" | "remove" | "discount";
4
+ /**
5
+ * The most recent cart change that could not be saved. `message` is written
6
+ * for the shopper, so a cart UI can render it as-is next to a retry.
7
+ */
8
+ export interface CartError {
9
+ /** Which cart change failed */
10
+ operation: CartOperation;
11
+ /** Shopper-facing explanation with a retry prompt */
12
+ message: string;
13
+ }
3
14
  /**
4
15
  * Internal cart context type with full functionality.
5
16
  * This is for internal use only - external code should use the limited CartContextType.
@@ -39,6 +50,10 @@ export interface CartContextTypeInternal {
39
50
  openCart: () => void;
40
51
  /** Function to close the cart UI */
41
52
  closeCart: () => void;
53
+ /** The last cart change that failed to save; cleared when the next change starts */
54
+ error: CartError | null;
55
+ /** Function to dismiss the current error */
56
+ clearError: () => void;
42
57
  }
43
58
  /**
44
59
  * Public cart context type for LLM use.
@@ -62,6 +77,15 @@ export interface CartContextType {
62
77
  * checkout route clamped it and bounced back with `?checkout=adjusted`.
63
78
  */
64
79
  refreshCart: () => Promise<void>;
80
+ /**
81
+ * The last cart change that could not be saved, with a shopper-ready
82
+ * message. Set when an add, quantity change, removal, or discount fails;
83
+ * cleared when the next change starts or via `clearError`. Render it in the
84
+ * cart UI so the shopper knows to retry.
85
+ */
86
+ error: CartError | null;
87
+ /** Function to dismiss the current error */
88
+ clearError: () => void;
65
89
  }
66
90
  /**
67
91
  * Provider component that manages global cart state and operations. Handles both
@@ -3,7 +3,6 @@ import { jsx } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { v4 as uuidv4 } from "uuid";
5
5
  import { useAnalyticsOptional } from "../analytics/analytics-provider";
6
- import { CanopyError } from "../lib/canopy-error";
7
6
  import {
8
7
  addToCartAction,
9
8
  createCartAction,
@@ -21,10 +20,9 @@ import {
21
20
  buildCartLineAnalyticsData,
22
21
  buildCartViewAnalyticsData
23
22
  } from "./utils/cart-analytics";
23
+ import { CartContextError, toCartError } from "./utils/cart-error";
24
24
  import { recalculateCartCost } from "./utils/cart-utils";
25
25
  import { createOptimisticSellingPlanAllocation } from "./utils/variant-to-cart-line";
26
- class CartContextError extends CanopyError {
27
- }
28
26
  let cartCreationPromise = null;
29
27
  function getOrCreateSharedCart() {
30
28
  cartCreationPromise ??= getOrCreateCartAction().then((cart) => {
@@ -100,7 +98,19 @@ function CartProvider({
100
98
  []
101
99
  );
102
100
  const [isCartOpen, setIsCartOpen] = React.useState(false);
101
+ const [error, setError] = React.useState(null);
102
+ const clearError = React.useCallback(() => setError(null), []);
103
103
  const isMounted = React.useRef(true);
104
+ const runWithErrorState = async (operation, mutate) => {
105
+ setError(null);
106
+ try {
107
+ return await mutate();
108
+ } catch (caught) {
109
+ console.error(`[Replo] Cart ${operation} failed:`, caught);
110
+ setError(toCartError(operation, caught));
111
+ throw caught;
112
+ }
113
+ };
104
114
  const analytics = useAnalyticsOptional();
105
115
  const serverCart = React.useRef(cart);
106
116
  const needsRefresh = React.useRef(false);
@@ -132,11 +142,11 @@ function CartProvider({
132
142
  message: "Your cart could not be saved. Please try again."
133
143
  });
134
144
  serverCart.current = savedCart;
135
- } catch (error) {
145
+ } catch (error2) {
136
146
  const restoredCart = await getCartAction().catch(() => null);
137
147
  needsRefresh.current = !restoredCart;
138
148
  if (restoredCart) serverCart.current = restoredCart;
139
- throw error;
149
+ throw error2;
140
150
  }
141
151
  });
142
152
  mutationQueue.current = request.catch(() => {
@@ -175,9 +185,9 @@ function CartProvider({
175
185
  }, [updateCartData]);
176
186
  React.useEffect(() => {
177
187
  isMounted.current = true;
178
- void initializeCart().catch((error) => {
188
+ void initializeCart().catch((error2) => {
179
189
  if (isMounted.current) {
180
- console.error("[Replo] Error getting or creating cart:", error);
190
+ console.error("[Replo] Error getting or creating cart:", error2);
181
191
  }
182
192
  });
183
193
  return () => {
@@ -221,158 +231,169 @@ function CartProvider({
221
231
  updateCartData(refreshedCart);
222
232
  }, [updateCartData]);
223
233
  const reportedLineQuantities = React.useRef(/* @__PURE__ */ new Map());
224
- const addToCart = async (lines, openCartAfterAdd = true) => {
225
- const readyCart = cartData ?? serverCart.current ?? await initializeCart();
226
- reportedLineQuantities.current.clear();
227
- const additions = lines.map((line) => {
228
- const lineId = `temp-${uuidv4()}`;
229
- optimisticLines.current.set(lineId, line);
230
- return { line, lineId };
231
- });
232
- const applyLines = (cart2) => {
233
- let updatedCart2 = { ...cart2 };
234
- for (const { line, lineId } of additions) {
235
- updatedCart2 = addLineToCart({ cart: updatedCart2, line, lineId });
236
- }
237
- return updatedCart2;
238
- };
239
- const updatedCart = updateCartData((currentCart) => {
240
- return applyLines(currentCart ?? readyCart);
241
- });
242
- if (analytics) {
243
- for (const line of lines) {
244
- if (!line.merchandise) {
245
- continue;
234
+ const addToCart = (lines, openCartAfterAdd = true) => {
235
+ return runWithErrorState("add", async () => {
236
+ const readyCart = cartData ?? serverCart.current ?? await initializeCart().catch((caught) => {
237
+ if (openCartAfterAdd) setIsCartOpen(true);
238
+ throw caught;
239
+ });
240
+ reportedLineQuantities.current.clear();
241
+ const additions = lines.map((line) => {
242
+ const lineId = `temp-${uuidv4()}`;
243
+ optimisticLines.current.set(lineId, line);
244
+ return { line, lineId };
245
+ });
246
+ const applyLines = (cart2) => {
247
+ let updatedCart2 = { ...cart2 };
248
+ for (const { line, lineId } of additions) {
249
+ updatedCart2 = addLineToCart({ cart: updatedCart2, line, lineId });
250
+ }
251
+ return updatedCart2;
252
+ };
253
+ const updatedCart = updateCartData((currentCart) => {
254
+ return applyLines(currentCart ?? readyCart);
255
+ });
256
+ if (analytics) {
257
+ for (const line of lines) {
258
+ if (!line.merchandise) {
259
+ continue;
260
+ }
261
+ void analytics.addToCart({
262
+ data: buildCartLineAnalyticsData({
263
+ merchandise: line.merchandise,
264
+ quantity: line.quantity,
265
+ currencyCode: readyCart.cost.currencyCode
266
+ })
267
+ });
246
268
  }
247
- void analytics.addToCart({
248
- data: buildCartLineAnalyticsData({
249
- merchandise: line.merchandise,
250
- quantity: line.quantity,
251
- currencyCode: readyCart.cost.currencyCode
252
- })
253
- });
254
269
  }
255
- }
256
- if (openCartAfterAdd) {
257
- setIsCartOpen(true);
258
- emitCartView(updatedCart);
259
- }
260
- const adjustments = [];
261
- await enqueueMutation(async (currentCart) => {
262
- const savedCart = await addToCartAction(lines, currentCart.id);
263
- if (savedCart) {
264
- adjustments.push(
265
- ...deriveAddToCartAdjustments({
266
- cartBeforeAdd: currentCart,
267
- requestedLines: lines,
268
- savedCart
269
- })
270
- );
270
+ if (openCartAfterAdd) {
271
+ setIsCartOpen(true);
272
+ emitCartView(updatedCart);
271
273
  }
272
- return savedCart;
274
+ const adjustments = [];
275
+ await enqueueMutation(async (currentCart) => {
276
+ const savedCart = await addToCartAction(lines, currentCart.id);
277
+ if (savedCart) {
278
+ adjustments.push(
279
+ ...deriveAddToCartAdjustments({
280
+ cartBeforeAdd: currentCart,
281
+ requestedLines: lines,
282
+ savedCart
283
+ })
284
+ );
285
+ }
286
+ return savedCart;
287
+ });
288
+ return { adjustments };
273
289
  });
274
- return { adjustments };
275
290
  };
276
- const updateCartItem = async (line) => {
277
- if (!cartData) {
278
- console.warn(
279
- "[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
280
- );
281
- return { adjustments: [] };
282
- }
283
- const currentLine = cartData.lines.find((item) => item.id === line.id);
284
- const merchandise = currentLine?.merchandise ?? line.merchandise;
285
- const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
286
- reportedLineQuantities.current.set(line.id, line.quantity);
287
- if (analytics && merchandise && quantityDelta !== 0) {
288
- const data = buildCartLineAnalyticsData({
289
- merchandise,
290
- quantity: Math.abs(quantityDelta),
291
- currencyCode: cartData.cost.currencyCode
292
- });
293
- void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
294
- }
295
- updateCartData((currentCart) => {
296
- if (!currentCart) {
297
- return null;
291
+ const updateCartItem = (line) => {
292
+ return runWithErrorState("update", async () => {
293
+ if (!cartData) {
294
+ console.warn(
295
+ "[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
296
+ );
297
+ return { adjustments: [] };
298
298
  }
299
- return updateLineInCart(currentCart, line);
300
- });
301
- const adjustments = [];
302
- await enqueueMutation(async (currentCart) => {
303
- const savedLine = findLine({
304
- currentCart,
305
- lineId: line.id,
306
- originalLine: currentLine
307
- });
308
- if (!savedLine) {
309
- return currentCart;
299
+ const currentLine = cartData.lines.find((item) => item.id === line.id);
300
+ const merchandise = currentLine?.merchandise ?? line.merchandise;
301
+ const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
302
+ reportedLineQuantities.current.set(line.id, line.quantity);
303
+ if (analytics && merchandise && quantityDelta !== 0) {
304
+ const data = buildCartLineAnalyticsData({
305
+ merchandise,
306
+ quantity: Math.abs(quantityDelta),
307
+ currencyCode: cartData.cost.currencyCode
308
+ });
309
+ void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
310
310
  }
311
- const savedCart = await updateCartLineItemAction(
312
- { ...line, id: savedLine.id },
313
- currentCart.id
314
- );
315
- if (savedCart) {
316
- adjustments.push(
317
- ...deriveUpdateCartAdjustments({
318
- cartBeforeUpdate: currentCart,
319
- editedLine: savedLine,
320
- requestedQuantity: line.quantity,
321
- requestedSellingPlanId: line.sellingPlanId,
322
- savedCart
323
- })
311
+ updateCartData((currentCart) => {
312
+ if (!currentCart) {
313
+ return null;
314
+ }
315
+ return updateLineInCart(currentCart, line);
316
+ });
317
+ const adjustments = [];
318
+ await enqueueMutation(async (currentCart) => {
319
+ const savedLine = findLine({
320
+ currentCart,
321
+ lineId: line.id,
322
+ originalLine: currentLine
323
+ });
324
+ if (!savedLine) {
325
+ return currentCart;
326
+ }
327
+ const savedCart = await updateCartLineItemAction(
328
+ { ...line, id: savedLine.id },
329
+ currentCart.id
324
330
  );
325
- }
326
- return savedCart;
331
+ if (savedCart) {
332
+ adjustments.push(
333
+ ...deriveUpdateCartAdjustments({
334
+ cartBeforeUpdate: currentCart,
335
+ editedLine: savedLine,
336
+ requestedQuantity: line.quantity,
337
+ requestedSellingPlanId: line.sellingPlanId,
338
+ savedCart
339
+ })
340
+ );
341
+ }
342
+ return savedCart;
343
+ });
344
+ return { adjustments };
327
345
  });
328
- return { adjustments };
329
346
  };
330
- const removeCartItem = async (lineId) => {
331
- if (!cartData) {
332
- console.warn(
333
- "[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
334
- );
335
- return;
336
- }
337
- const itemToRemove = cartData.lines.find((item) => item.id === lineId);
338
- reportedLineQuantities.current.delete(lineId);
339
- if (analytics && itemToRemove && itemToRemove.merchandise) {
340
- void analytics.removeFromCart({
341
- data: buildCartLineAnalyticsData({
342
- merchandise: itemToRemove.merchandise,
343
- quantity: itemToRemove.quantity,
344
- currencyCode: cartData.cost.currencyCode
345
- })
346
- });
347
- }
348
- updateCartData((currentCart) => {
349
- if (!currentCart) {
350
- return null;
347
+ const removeCartItem = (lineId) => {
348
+ return runWithErrorState("remove", async () => {
349
+ if (!cartData) {
350
+ console.warn(
351
+ "[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
352
+ );
353
+ return;
351
354
  }
352
- return removeLineFromCart(currentCart, lineId);
353
- });
354
- await enqueueMutation(async (currentCart) => {
355
- const savedLine = findLine({
356
- currentCart,
357
- lineId,
358
- originalLine: itemToRemove
355
+ const itemToRemove = cartData.lines.find((item) => item.id === lineId);
356
+ reportedLineQuantities.current.delete(lineId);
357
+ if (analytics && itemToRemove && itemToRemove.merchandise) {
358
+ void analytics.removeFromCart({
359
+ data: buildCartLineAnalyticsData({
360
+ merchandise: itemToRemove.merchandise,
361
+ quantity: itemToRemove.quantity,
362
+ currencyCode: cartData.cost.currencyCode
363
+ })
364
+ });
365
+ }
366
+ updateCartData((currentCart) => {
367
+ if (!currentCart) {
368
+ return null;
369
+ }
370
+ return removeLineFromCart(currentCart, lineId);
371
+ });
372
+ await enqueueMutation(async (currentCart) => {
373
+ const savedLine = findLine({
374
+ currentCart,
375
+ lineId,
376
+ originalLine: itemToRemove
377
+ });
378
+ return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
359
379
  });
360
- return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
361
380
  });
362
381
  };
363
- const updateDiscountCodes = async (discountCodes) => {
364
- const readyCart = cartData ?? serverCart.current ?? await initializeCart();
365
- updateCartData((currentCart) => {
366
- return {
367
- ...currentCart ?? readyCart,
368
- discountCodes: discountCodes.map((code) => ({
369
- code,
370
- applicable: true
371
- }))
372
- };
373
- });
374
- await enqueueMutation((currentCart) => {
375
- return updateCartDiscountCodesAction(discountCodes, currentCart.id);
382
+ const updateDiscountCodes = (discountCodes) => {
383
+ return runWithErrorState("discount", async () => {
384
+ const readyCart = cartData ?? serverCart.current ?? await initializeCart();
385
+ updateCartData((currentCart) => {
386
+ return {
387
+ ...currentCart ?? readyCart,
388
+ discountCodes: discountCodes.map((code) => ({
389
+ code,
390
+ applicable: true
391
+ }))
392
+ };
393
+ });
394
+ await enqueueMutation((currentCart) => {
395
+ return updateCartDiscountCodesAction(discountCodes, currentCart.id);
396
+ });
376
397
  });
377
398
  };
378
399
  const buyNow = async (lines, discountCodes) => {
@@ -409,7 +430,9 @@ function CartProvider({
409
430
  checkoutUrl,
410
431
  isCartOpen,
411
432
  openCart,
412
- closeCart
433
+ closeCart,
434
+ error,
435
+ clearError
413
436
  },
414
437
  children
415
438
  }
@@ -437,7 +460,9 @@ function useCart() {
437
460
  openCart: context.openCart,
438
461
  closeCart: context.closeCart,
439
462
  updateDiscountCodes: context.updateDiscountCodes,
440
- refreshCart: context.refreshCart
463
+ refreshCart: context.refreshCart,
464
+ error: context.error,
465
+ clearError: context.clearError
441
466
  };
442
467
  }
443
468
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../cart/cart-provider.tsx"],
4
- "sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type {\n Cart,\n CartLine,\n CartLineAdjustment,\n CartLinePayload,\n} from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport {\n addToCartAction,\n createCartAction,\n getCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport {\n deriveAddToCartAdjustments,\n deriveUpdateCartAdjustments,\n} from \"./utils/cart-adjustments\";\nimport {\n buildCartLineAnalyticsData,\n buildCartViewAnalyticsData,\n} from \"./utils/cart-analytics\";\nimport { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart> | null = null;\n\nfunction getOrCreateSharedCart() {\n cartCreationPromise ??= getOrCreateCartAction()\n .then((cart) => {\n if (!cart) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n return cart;\n })\n .finally(() => {\n cartCreationPromise = null;\n });\n return cartCreationPromise;\n}\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart({\n cart,\n line,\n lineId,\n}: {\n cart: Cart;\n line: CartLinePayload;\n lineId: string;\n}): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return recalculateCartCost({ ...cart, lines: updatedLines });\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: lineId,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<{ adjustments: CartLineAdjustment[] }>;\n /** Function to update cart item quantity */\n updateCartItem: (\n line: CartLinePayload,\n ) => Promise<{ adjustments: CartLineAdjustment[] }>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Re-read the persisted cart and adopt it if no mutations are in flight */\n refreshCart: () => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /**\n * Re-read the persisted cart and adopt it if no mutations are in flight.\n * Call after something outside the provider rewrote the cart \u2014 e.g. the\n * checkout route clamped it and bounced back with `?checkout=adjusted`.\n */\n refreshCart: () => Promise<void>;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const optimisticCart = React.useRef(cart);\n const updateCartData = React.useCallback(\n (update: React.SetStateAction<Cart | null>) => {\n const nextCart =\n typeof update === \"function\" ? update(optimisticCart.current) : update;\n // Analytics and overlapping mutations need the snapshot before React renders it.\n optimisticCart.current = nextCart;\n setCartData(nextCart);\n return nextCart;\n },\n [],\n );\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const isMounted = React.useRef(true);\n const analytics = useAnalyticsOptional();\n const serverCart = React.useRef(cart);\n const needsRefresh = React.useRef(false);\n const mutationQueue = React.useRef(Promise.resolve());\n const pendingMutations = React.useRef(0);\n const optimisticLines = React.useRef(new Map<string, CartLinePayload>());\n\n const enqueueMutation = async (\n save: (currentCart: Cart) => Promise<Cart | null>,\n ) => {\n pendingMutations.current += 1;\n const request = mutationQueue.current.then(async () => {\n // An existing server cart remains usable if background initialization fails.\n await cartCreationPromise?.catch(() => null);\n if (needsRefresh.current) {\n const restoredCart = await getCartAction();\n if (!restoredCart)\n throw new CartContextError({\n message:\n \"Your cart could not be refreshed. Please reload before changing it.\",\n });\n serverCart.current = restoredCart;\n needsRefresh.current = false;\n }\n if (!serverCart.current) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n try {\n const savedCart = await save(serverCart.current);\n if (!savedCart)\n throw new CartContextError({\n message: \"Your cart could not be saved. Please try again.\",\n });\n serverCart.current = savedCart;\n } catch (error) {\n // A lost response may still have persisted the add that the next removal targets.\n const restoredCart = await getCartAction().catch(() => null);\n needsRefresh.current = !restoredCart;\n if (restoredCart) serverCart.current = restoredCart;\n throw error;\n }\n });\n // Cookie writes must finish in shopper order, including after a failed request.\n mutationQueue.current = request\n .catch(() => {})\n .then(() => {\n pendingMutations.current -= 1;\n if (pendingMutations.current === 0) {\n reportedLineQuantities.current.clear();\n optimisticLines.current.clear();\n updateCartData(serverCart.current);\n }\n });\n await request;\n };\n\n const findLine = ({\n currentCart,\n lineId,\n originalLine,\n }: {\n currentCart: Cart;\n lineId: string;\n originalLine?: CartLine;\n }) => {\n const existingLine = currentCart.lines.find((line) => line.id === lineId);\n if (existingLine) return existingLine;\n const optimisticLine = optimisticLines.current.get(lineId);\n const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;\n const sellingPlanId =\n optimisticLine?.sellingPlanId ??\n originalLine?.sellingPlanAllocation?.sellingPlan.id ??\n null;\n return currentCart.lines.find((line) => {\n return (\n line.merchandise?.id === merchandiseId &&\n (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId\n );\n });\n };\n\n const initializeCart = React.useCallback(async () => {\n const loadedCart = await getOrCreateSharedCart();\n if (!isMounted.current) {\n throw new DOMException(\"Cart provider was unmounted\", \"AbortError\");\n }\n serverCart.current = loadedCart;\n if (pendingMutations.current === 0) updateCartData(loadedCart);\n return loadedCart;\n }, [updateCartData]);\n\n // eslint-disable-next-line replo/no-use-effect -- Initializes the browser cart on mount.\n React.useEffect(() => {\n isMounted.current = true;\n void initializeCart().catch((error) => {\n if (isMounted.current) {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n }\n });\n return () => {\n isMounted.current = false;\n };\n }, [initializeCart]);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const emitCartView = React.useCallback(\n (cart: Cart | null) => {\n if (analytics) {\n void analytics.viewCart({ data: buildCartViewAnalyticsData(cart) });\n }\n },\n [analytics],\n );\n\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n emitCartView(cartData);\n }, [cartData, emitCartView]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n const refreshCart = React.useCallback(async () => {\n const refreshedCart = await getCartAction();\n // In-flight mutations own serverCart; a slow refresh must not clobber\n // their newer result with this older snapshot.\n if (\n !isMounted.current ||\n !refreshedCart ||\n pendingMutations.current !== 0\n ) {\n return;\n }\n serverCart.current = refreshedCart;\n updateCartData(refreshedCart);\n }, [updateCartData]);\n\n // Last quantity reported to analytics per line with an update in flight, so\n // overlapping stepper clicks diff against it instead of the same render-time\n // quantity. Dropped once the server confirms or another mutation replaces\n // the line set.\n const reportedLineQuantities = React.useRef(new Map<string, number>());\n\n const addToCart = async (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n // React callbacks retain their render's cart, even after initialization resolves.\n const readyCart =\n cartData ?? serverCart.current ?? (await initializeCart());\n\n reportedLineQuantities.current.clear();\n const additions = lines.map((line) => {\n const lineId = `temp-${uuidv4()}`;\n optimisticLines.current.set(lineId, line);\n return { line, lineId };\n });\n const applyLines = (cart: Cart) => {\n let updatedCart = { ...cart };\n for (const { line, lineId } of additions) {\n updatedCart = addLineToCart({ cart: updatedCart, line, lineId });\n }\n return updatedCart;\n };\n\n const updatedCart = updateCartData((currentCart) => {\n return applyLines(currentCart ?? readyCart);\n });\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n void analytics.addToCart({\n data: buildCartLineAnalyticsData({\n merchandise: line.merchandise,\n quantity: line.quantity,\n currencyCode: readyCart.cost.currencyCode,\n }),\n });\n }\n }\n\n if (openCartAfterAdd) {\n setIsCartOpen(true);\n emitCartView(updatedCart);\n }\n\n // The write path reconciles the whole cart, so any line can be clamped\n // or dropped \u2014 not just the ones this call added. Intent comes from the\n // request itself, not the optimistic cart, whose lines can lack\n // merchandise or plan metadata.\n const adjustments: CartLineAdjustment[] = [];\n await enqueueMutation(async (currentCart) => {\n const savedCart = await addToCartAction(lines, currentCart.id);\n if (savedCart) {\n adjustments.push(\n ...deriveAddToCartAdjustments({\n cartBeforeAdd: currentCart,\n requestedLines: lines,\n savedCart,\n }),\n );\n }\n return savedCart;\n });\n return { adjustments };\n };\n\n const updateCartItem = async (line: CartLinePayload) => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return { adjustments: [] };\n }\n\n // Ad platforms have no \"quantity changed\" event: an increase is an add of\n // the difference and a decrease is a remove of the difference, matching\n // how Shopify themes report their cart steppers.\n const currentLine = cartData.lines.find((item) => item.id === line.id);\n const merchandise = currentLine?.merchandise ?? line.merchandise;\n const quantityDelta =\n line.quantity -\n (reportedLineQuantities.current.get(line.id) ??\n currentLine?.quantity ??\n 0);\n reportedLineQuantities.current.set(line.id, line.quantity);\n if (analytics && merchandise && quantityDelta !== 0) {\n const data = buildCartLineAnalyticsData({\n merchandise,\n quantity: Math.abs(quantityDelta),\n currencyCode: cartData.cost.currencyCode,\n });\n void (quantityDelta > 0\n ? analytics.addToCart({ data })\n : analytics.removeFromCart({ data }));\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n const adjustments: CartLineAdjustment[] = [];\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId: line.id,\n originalLine: currentLine,\n });\n if (!savedLine) {\n return currentCart;\n }\n const savedCart = await updateCartLineItemAction(\n { ...line, id: savedLine.id },\n currentCart.id,\n );\n if (savedCart) {\n adjustments.push(\n ...deriveUpdateCartAdjustments({\n cartBeforeUpdate: currentCart,\n editedLine: savedLine,\n requestedQuantity: line.quantity,\n requestedSellingPlanId: line.sellingPlanId,\n savedCart,\n }),\n );\n }\n return savedCart;\n });\n return { adjustments };\n };\n\n const removeCartItem = async (lineId: string) => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n reportedLineQuantities.current.delete(lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n void analytics.removeFromCart({\n data: buildCartLineAnalyticsData({\n merchandise: itemToRemove.merchandise,\n quantity: itemToRemove.quantity,\n currencyCode: cartData.cost.currencyCode,\n }),\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId,\n originalLine: itemToRemove,\n });\n return savedLine\n ? await removeCartLineItemsAction([savedLine.id], currentCart.id)\n : currentCart;\n });\n };\n\n const updateDiscountCodes = async (discountCodes: string[]) => {\n const readyCart =\n cartData ?? serverCart.current ?? (await initializeCart());\n\n updateCartData((currentCart) => {\n return {\n ...(currentCart ?? readyCart),\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n await enqueueMutation((currentCart) => {\n return updateCartDiscountCodesAction(discountCodes, currentCart.id);\n });\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl = cartData?.checkoutUrl ?? \"/\";\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n refreshCart,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n refreshCart: context.refreshCart,\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["cart", "updatedCart"]
4
+ "sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type {\n Cart,\n CartLine,\n CartLineAdjustment,\n CartLinePayload,\n} from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport {\n addToCartAction,\n createCartAction,\n getCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport {\n deriveAddToCartAdjustments,\n deriveUpdateCartAdjustments,\n} from \"./utils/cart-adjustments\";\nimport {\n buildCartLineAnalyticsData,\n buildCartViewAnalyticsData,\n} from \"./utils/cart-analytics\";\nimport { CartContextError, toCartError } from \"./utils/cart-error\";\nimport { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nexport type CartOperation = \"add\" | \"update\" | \"remove\" | \"discount\";\n\n/**\n * The most recent cart change that could not be saved. `message` is written\n * for the shopper, so a cart UI can render it as-is next to a retry.\n */\nexport interface CartError {\n /** Which cart change failed */\n operation: CartOperation;\n /** Shopper-facing explanation with a retry prompt */\n message: string;\n}\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart> | null = null;\n\nfunction getOrCreateSharedCart() {\n cartCreationPromise ??= getOrCreateCartAction()\n .then((cart) => {\n if (!cart) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n return cart;\n })\n .finally(() => {\n cartCreationPromise = null;\n });\n return cartCreationPromise;\n}\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart({\n cart,\n line,\n lineId,\n}: {\n cart: Cart;\n line: CartLinePayload;\n lineId: string;\n}): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return recalculateCartCost({ ...cart, lines: updatedLines });\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: lineId,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<{ adjustments: CartLineAdjustment[] }>;\n /** Function to update cart item quantity */\n updateCartItem: (\n line: CartLinePayload,\n ) => Promise<{ adjustments: CartLineAdjustment[] }>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Re-read the persisted cart and adopt it if no mutations are in flight */\n refreshCart: () => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** The last cart change that failed to save; cleared when the next change starts */\n error: CartError | null;\n /** Function to dismiss the current error */\n clearError: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /**\n * Re-read the persisted cart and adopt it if no mutations are in flight.\n * Call after something outside the provider rewrote the cart \u2014 e.g. the\n * checkout route clamped it and bounced back with `?checkout=adjusted`.\n */\n refreshCart: () => Promise<void>;\n /**\n * The last cart change that could not be saved, with a shopper-ready\n * message. Set when an add, quantity change, removal, or discount fails;\n * cleared when the next change starts or via `clearError`. Render it in the\n * cart UI so the shopper knows to retry.\n */\n error: CartError | null;\n /** Function to dismiss the current error */\n clearError: () => void;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const optimisticCart = React.useRef(cart);\n const updateCartData = React.useCallback(\n (update: React.SetStateAction<Cart | null>) => {\n const nextCart =\n typeof update === \"function\" ? update(optimisticCart.current) : update;\n // Analytics and overlapping mutations need the snapshot before React renders it.\n optimisticCart.current = nextCart;\n setCartData(nextCart);\n return nextCart;\n },\n [],\n );\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const [error, setError] = React.useState<CartError | null>(null);\n const clearError = React.useCallback(() => setError(null), []);\n const isMounted = React.useRef(true);\n\n const runWithErrorState = async <ResultType,>(\n operation: CartOperation,\n mutate: () => Promise<ResultType>,\n ) => {\n setError(null);\n try {\n return await mutate();\n } catch (caught) {\n console.error(`[Replo] Cart ${operation} failed:`, caught);\n setError(toCartError(operation, caught));\n throw caught;\n }\n };\n const analytics = useAnalyticsOptional();\n const serverCart = React.useRef(cart);\n const needsRefresh = React.useRef(false);\n const mutationQueue = React.useRef(Promise.resolve());\n const pendingMutations = React.useRef(0);\n const optimisticLines = React.useRef(new Map<string, CartLinePayload>());\n\n const enqueueMutation = async (\n save: (currentCart: Cart) => Promise<Cart | null>,\n ) => {\n pendingMutations.current += 1;\n const request = mutationQueue.current.then(async () => {\n // An existing server cart remains usable if background initialization fails.\n await cartCreationPromise?.catch(() => null);\n if (needsRefresh.current) {\n const restoredCart = await getCartAction();\n if (!restoredCart)\n throw new CartContextError({\n message:\n \"Your cart could not be refreshed. Please reload before changing it.\",\n });\n serverCart.current = restoredCart;\n needsRefresh.current = false;\n }\n if (!serverCart.current) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n try {\n const savedCart = await save(serverCart.current);\n if (!savedCart)\n throw new CartContextError({\n message: \"Your cart could not be saved. Please try again.\",\n });\n serverCart.current = savedCart;\n } catch (error) {\n // A lost response may still have persisted the add that the next removal targets.\n const restoredCart = await getCartAction().catch(() => null);\n needsRefresh.current = !restoredCart;\n if (restoredCart) serverCart.current = restoredCart;\n throw error;\n }\n });\n // Cookie writes must finish in shopper order, including after a failed request.\n mutationQueue.current = request\n .catch(() => {})\n .then(() => {\n pendingMutations.current -= 1;\n if (pendingMutations.current === 0) {\n reportedLineQuantities.current.clear();\n optimisticLines.current.clear();\n updateCartData(serverCart.current);\n }\n });\n await request;\n };\n\n const findLine = ({\n currentCart,\n lineId,\n originalLine,\n }: {\n currentCart: Cart;\n lineId: string;\n originalLine?: CartLine;\n }) => {\n const existingLine = currentCart.lines.find((line) => line.id === lineId);\n if (existingLine) return existingLine;\n const optimisticLine = optimisticLines.current.get(lineId);\n const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;\n const sellingPlanId =\n optimisticLine?.sellingPlanId ??\n originalLine?.sellingPlanAllocation?.sellingPlan.id ??\n null;\n return currentCart.lines.find((line) => {\n return (\n line.merchandise?.id === merchandiseId &&\n (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId\n );\n });\n };\n\n const initializeCart = React.useCallback(async () => {\n const loadedCart = await getOrCreateSharedCart();\n if (!isMounted.current) {\n throw new DOMException(\"Cart provider was unmounted\", \"AbortError\");\n }\n serverCart.current = loadedCart;\n if (pendingMutations.current === 0) updateCartData(loadedCart);\n return loadedCart;\n }, [updateCartData]);\n\n // eslint-disable-next-line replo/no-use-effect -- Initializes the browser cart on mount.\n React.useEffect(() => {\n isMounted.current = true;\n void initializeCart().catch((error) => {\n if (isMounted.current) {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n }\n });\n return () => {\n isMounted.current = false;\n };\n }, [initializeCart]);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const emitCartView = React.useCallback(\n (cart: Cart | null) => {\n if (analytics) {\n void analytics.viewCart({ data: buildCartViewAnalyticsData(cart) });\n }\n },\n [analytics],\n );\n\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n emitCartView(cartData);\n }, [cartData, emitCartView]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n const refreshCart = React.useCallback(async () => {\n const refreshedCart = await getCartAction();\n // In-flight mutations own serverCart; a slow refresh must not clobber\n // their newer result with this older snapshot.\n if (\n !isMounted.current ||\n !refreshedCart ||\n pendingMutations.current !== 0\n ) {\n return;\n }\n serverCart.current = refreshedCart;\n updateCartData(refreshedCart);\n }, [updateCartData]);\n\n // Last quantity reported to analytics per line with an update in flight, so\n // overlapping stepper clicks diff against it instead of the same render-time\n // quantity. Dropped once the server confirms or another mutation replaces\n // the line set.\n const reportedLineQuantities = React.useRef(new Map<string, number>());\n\n const addToCart = (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n return runWithErrorState(\"add\", async () => {\n // React callbacks retain their render's cart, even after initialization resolves.\n const readyCart =\n cartData ??\n serverCart.current ??\n (await initializeCart().catch((caught: unknown) => {\n // The cart UI is where the failure is shown, so a first add that fails\n // before it would have opened the cart still opens it.\n if (openCartAfterAdd) setIsCartOpen(true);\n throw caught;\n }));\n\n reportedLineQuantities.current.clear();\n const additions = lines.map((line) => {\n const lineId = `temp-${uuidv4()}`;\n optimisticLines.current.set(lineId, line);\n return { line, lineId };\n });\n const applyLines = (cart: Cart) => {\n let updatedCart = { ...cart };\n for (const { line, lineId } of additions) {\n updatedCart = addLineToCart({ cart: updatedCart, line, lineId });\n }\n return updatedCart;\n };\n\n const updatedCart = updateCartData((currentCart) => {\n return applyLines(currentCart ?? readyCart);\n });\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n void analytics.addToCart({\n data: buildCartLineAnalyticsData({\n merchandise: line.merchandise,\n quantity: line.quantity,\n currencyCode: readyCart.cost.currencyCode,\n }),\n });\n }\n }\n\n if (openCartAfterAdd) {\n setIsCartOpen(true);\n emitCartView(updatedCart);\n }\n\n // The write path reconciles the whole cart, so any line can be clamped\n // or dropped \u2014 not just the ones this call added. Intent comes from the\n // request itself, not the optimistic cart, whose lines can lack\n // merchandise or plan metadata.\n const adjustments: CartLineAdjustment[] = [];\n await enqueueMutation(async (currentCart) => {\n const savedCart = await addToCartAction(lines, currentCart.id);\n if (savedCart) {\n adjustments.push(\n ...deriveAddToCartAdjustments({\n cartBeforeAdd: currentCart,\n requestedLines: lines,\n savedCart,\n }),\n );\n }\n return savedCart;\n });\n return { adjustments };\n });\n };\n\n const updateCartItem = (line: CartLinePayload) => {\n return runWithErrorState(\"update\", async () => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return { adjustments: [] };\n }\n\n // Ad platforms have no \"quantity changed\" event: an increase is an add of\n // the difference and a decrease is a remove of the difference, matching\n // how Shopify themes report their cart steppers.\n const currentLine = cartData.lines.find((item) => item.id === line.id);\n const merchandise = currentLine?.merchandise ?? line.merchandise;\n const quantityDelta =\n line.quantity -\n (reportedLineQuantities.current.get(line.id) ??\n currentLine?.quantity ??\n 0);\n reportedLineQuantities.current.set(line.id, line.quantity);\n if (analytics && merchandise && quantityDelta !== 0) {\n const data = buildCartLineAnalyticsData({\n merchandise,\n quantity: Math.abs(quantityDelta),\n currencyCode: cartData.cost.currencyCode,\n });\n void (quantityDelta > 0\n ? analytics.addToCart({ data })\n : analytics.removeFromCart({ data }));\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n const adjustments: CartLineAdjustment[] = [];\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId: line.id,\n originalLine: currentLine,\n });\n if (!savedLine) {\n return currentCart;\n }\n const savedCart = await updateCartLineItemAction(\n { ...line, id: savedLine.id },\n currentCart.id,\n );\n if (savedCart) {\n adjustments.push(\n ...deriveUpdateCartAdjustments({\n cartBeforeUpdate: currentCart,\n editedLine: savedLine,\n requestedQuantity: line.quantity,\n requestedSellingPlanId: line.sellingPlanId,\n savedCart,\n }),\n );\n }\n return savedCart;\n });\n return { adjustments };\n });\n };\n\n const removeCartItem = (lineId: string) => {\n return runWithErrorState(\"remove\", async () => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n reportedLineQuantities.current.delete(lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n void analytics.removeFromCart({\n data: buildCartLineAnalyticsData({\n merchandise: itemToRemove.merchandise,\n quantity: itemToRemove.quantity,\n currencyCode: cartData.cost.currencyCode,\n }),\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId,\n originalLine: itemToRemove,\n });\n return savedLine\n ? await removeCartLineItemsAction([savedLine.id], currentCart.id)\n : currentCart;\n });\n });\n };\n\n const updateDiscountCodes = (discountCodes: string[]) => {\n return runWithErrorState(\"discount\", async () => {\n const readyCart =\n cartData ?? serverCart.current ?? (await initializeCart());\n\n updateCartData((currentCart) => {\n return {\n ...(currentCart ?? readyCart),\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n await enqueueMutation((currentCart) => {\n return updateCartDiscountCodesAction(discountCodes, currentCart.id);\n });\n });\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl = cartData?.checkoutUrl ?? \"/\";\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n refreshCart,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n error,\n clearError,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n refreshCart: context.refreshCart,\n error: context.error,\n clearError: context.clearError,\n };\n}\n"],
5
+ "mappings": ";AAyqBI;AA5pBJ,OAAO,WAAW;AAElB,SAAS,MAAM,cAAc;AAE7B,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB,mBAAmB;AAC9C,SAAS,2BAA2B;AACpC,SAAS,6CAA6C;AAgBtD,IAAI,sBAA4C;AAEhD,SAAS,wBAAwB;AAC/B,0BAAwB,sBAAsB,EAC3C,KAAK,CAAC,SAAS;AACd,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,iBAAiB;AAAA,QACzB,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT,CAAC,EACA,QAAQ,MAAM;AACb,0BAAsB;AAAA,EACxB,CAAC;AACH,SAAO;AACT;AAUA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AACF,GAIS;AACP,QAAM,oBAAoB,KAAK,MAAM,UAAU,CAAC,iBAAiB;AAC/D,WACE,aAAa,aAAa,OAAO,KAAK;AAAA,KAErC,KAAK,iBAAiB,WACpB,aAAa,uBAAuB,YAAY,MAAM;AAAA,EAE7D,CAAC;AAED,MAAI,qBAAqB,GAAG;AAE1B,UAAM,eAAe,CAAC,GAAG,KAAK,KAAK;AACnC,iBAAa,iBAAiB,IAAI;AAAA,MAChC,GAAG,aAAa,iBAAiB;AAAA,MACjC,UAAU,aAAa,iBAAiB,EAAG,WAAW,KAAK;AAAA,IAC7D;AACA,WAAO,oBAAoB,EAAE,GAAG,MAAM,OAAO,aAAa,CAAC;AAAA,EAC7D,OAAO;AACL,UAAM,kCACJ,KAAK,iBAAiB,KAAK,cACvB,sCAAsC;AAAA,MACpC,aAAa,KAAK;AAAA,MAClB,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK,MAAM,gBAAgB;AAAA,IAC3C,CAAC,IACD;AAEN,UAAM,UAAoB;AAAA,MACxB,IAAI;AAAA,MACJ,UAAU,KAAK;AAAA,MACf,aAAa,KAAK,eAAe;AAAA,MACjC,YAAY,KAAK,cAAc,CAAC;AAAA,MAChC,uBAAuB;AAAA,IACzB;AACA,WAAO,oBAAoB,EAAE,GAAG,MAAM,OAAO,CAAC,SAAS,GAAG,KAAK,KAAK,EAAE,CAAC;AAAA,EACzE;AACF;AASA,SAAS,iBAAiB,MAAY,MAA6B;AACjE,QAAM,eAAe,KAAK,MAAM,IAAI,CAAC,iBAAiB;AACpD,WAAO,aAAa,OAAO,KAAK,KAC5B,EAAE,GAAG,cAAc,UAAU,KAAK,SAAS,IAC3C;AAAA,EACN,CAAC;AACD,SAAO,oBAAoB,EAAE,GAAG,MAAM,OAAO,aAAa,CAAC;AAC7D;AASA,SAAS,mBAAmB,MAAY,QAAsB;AAC5D,QAAM,eAAe,KAAK,MAAM,OAAO,CAAC,SAAS,KAAK,OAAO,MAAM;AACnE,SAAO,oBAAoB,EAAE,GAAG,MAAM,OAAO,aAAa,CAAC;AAC7D;AAsFA,MAAM,cAAc,MAAM;AAAA,EACxB;AACF;AAQO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AACF,GAGI;AACF,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAsB,IAAI;AAChE,QAAM,iBAAiB,MAAM,OAAO,IAAI;AACxC,QAAM,iBAAiB,MAAM;AAAA,IAC3B,CAAC,WAA8C;AAC7C,YAAM,WACJ,OAAO,WAAW,aAAa,OAAO,eAAe,OAAO,IAAI;AAElE,qBAAe,UAAU;AACzB,kBAAY,QAAQ;AACpB,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AACA,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA2B,IAAI;AAC/D,QAAM,aAAa,MAAM,YAAY,MAAM,SAAS,IAAI,GAAG,CAAC,CAAC;AAC7D,QAAM,YAAY,MAAM,OAAO,IAAI;AAEnC,QAAM,oBAAoB,OACxB,WACA,WACG;AACH,aAAS,IAAI;AACb,QAAI;AACF,aAAO,MAAM,OAAO;AAAA,IACtB,SAAS,QAAQ;AACf,cAAQ,MAAM,gBAAgB,SAAS,YAAY,MAAM;AACzD,eAAS,YAAY,WAAW,MAAM,CAAC;AACvC,YAAM;AAAA,IACR;AAAA,EACF;AACA,QAAM,YAAY,qBAAqB;AACvC,QAAM,aAAa,MAAM,OAAO,IAAI;AACpC,QAAM,eAAe,MAAM,OAAO,KAAK;AACvC,QAAM,gBAAgB,MAAM,OAAO,QAAQ,QAAQ,CAAC;AACpD,QAAM,mBAAmB,MAAM,OAAO,CAAC;AACvC,QAAM,kBAAkB,MAAM,OAAO,oBAAI,IAA6B,CAAC;AAEvE,QAAM,kBAAkB,OACtB,SACG;AACH,qBAAiB,WAAW;AAC5B,UAAM,UAAU,cAAc,QAAQ,KAAK,YAAY;AAErD,YAAM,qBAAqB,MAAM,MAAM,IAAI;AAC3C,UAAI,aAAa,SAAS;AACxB,cAAM,eAAe,MAAM,cAAc;AACzC,YAAI,CAAC;AACH,gBAAM,IAAI,iBAAiB;AAAA,YACzB,SACE;AAAA,UACJ,CAAC;AACH,mBAAW,UAAU;AACrB,qBAAa,UAAU;AAAA,MACzB;AACA,UAAI,CAAC,WAAW,SAAS;AACvB,cAAM,IAAI,iBAAiB;AAAA,UACzB,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AACA,UAAI;AACF,cAAM,YAAY,MAAM,KAAK,WAAW,OAAO;AAC/C,YAAI,CAAC;AACH,gBAAM,IAAI,iBAAiB;AAAA,YACzB,SAAS;AAAA,UACX,CAAC;AACH,mBAAW,UAAU;AAAA,MACvB,SAASA,QAAO;AAEd,cAAM,eAAe,MAAM,cAAc,EAAE,MAAM,MAAM,IAAI;AAC3D,qBAAa,UAAU,CAAC;AACxB,YAAI,aAAc,YAAW,UAAU;AACvC,cAAMA;AAAA,MACR;AAAA,IACF,CAAC;AAED,kBAAc,UAAU,QACrB,MAAM,MAAM;AAAA,IAAC,CAAC,EACd,KAAK,MAAM;AACV,uBAAiB,WAAW;AAC5B,UAAI,iBAAiB,YAAY,GAAG;AAClC,+BAAuB,QAAQ,MAAM;AACrC,wBAAgB,QAAQ,MAAM;AAC9B,uBAAe,WAAW,OAAO;AAAA,MACnC;AAAA,IACF,CAAC;AACH,UAAM;AAAA,EACR;AAEA,QAAM,WAAW,CAAC;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,EACF,MAIM;AACJ,UAAM,eAAe,YAAY,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,MAAM;AACxE,QAAI,aAAc,QAAO;AACzB,UAAM,iBAAiB,gBAAgB,QAAQ,IAAI,MAAM;AACzD,UAAM,gBAAgB,gBAAgB,MAAM,cAAc,aAAa;AACvE,UAAM,gBACJ,gBAAgB,iBAChB,cAAc,uBAAuB,YAAY,MACjD;AACF,WAAO,YAAY,MAAM,KAAK,CAAC,SAAS;AACtC,aACE,KAAK,aAAa,OAAO,kBACxB,KAAK,uBAAuB,YAAY,MAAM,UAAU;AAAA,IAE7D,CAAC;AAAA,EACH;AAEA,QAAM,iBAAiB,MAAM,YAAY,YAAY;AACnD,UAAM,aAAa,MAAM,sBAAsB;AAC/C,QAAI,CAAC,UAAU,SAAS;AACtB,YAAM,IAAI,aAAa,+BAA+B,YAAY;AAAA,IACpE;AACA,eAAW,UAAU;AACrB,QAAI,iBAAiB,YAAY,EAAG,gBAAe,UAAU;AAC7D,WAAO;AAAA,EACT,GAAG,CAAC,cAAc,CAAC;AAGnB,QAAM,UAAU,MAAM;AACpB,cAAU,UAAU;AACpB,SAAK,eAAe,EAAE,MAAM,CAACA,WAAU;AACrC,UAAI,UAAU,SAAS;AACrB,gBAAQ,MAAM,2CAA2CA,MAAK;AAAA,MAChE;AAAA,IACF,CAAC;AACD,WAAO,MAAM;AACX,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,cAAc,CAAC;AAGnB,QAAM,YAAY,UAAU,SAAS,CAAC;AAEtC,QAAM,WACJ,UAAU,MAAM,OAAO,CAAC,KAAK,aAAa;AACxC,QAAI,SAAS,WAAW,KAAK,SAAS,aAAa;AACjD,aAAO,SAAS,YAAY,QAAQ,SAAS;AAAA,IAC/C;AACA,WAAO;AAAA,EACT,GAAG,CAAC,KAAK;AAEX,QAAM,aACJ,UAAU,MAAM,OAAO,CAAC,KAAK,aAAa;AACxC,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO,SAAS;AAAA,IAClB;AACA,WAAO;AAAA,EACT,GAAG,CAAC,KAAK;AAGX,QAAM,eAAe,MAAM;AAAA,IACzB,CAACC,UAAsB;AACrB,UAAI,WAAW;AACb,aAAK,UAAU,SAAS,EAAE,MAAM,2BAA2BA,KAAI,EAAE,CAAC;AAAA,MACpE;AAAA,IACF;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,WAAW,MAAM,YAAY,MAAM;AACvC,kBAAc,IAAI;AAClB,iBAAa,QAAQ;AAAA,EACvB,GAAG,CAAC,UAAU,YAAY,CAAC;AAE3B,QAAM,YAAY,MAAM,YAAY,MAAM;AACxC,kBAAc,KAAK;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,MAAM,YAAY,YAAY;AAChD,UAAM,gBAAgB,MAAM,cAAc;AAG1C,QACE,CAAC,UAAU,WACX,CAAC,iBACD,iBAAiB,YAAY,GAC7B;AACA;AAAA,IACF;AACA,eAAW,UAAU;AACrB,mBAAe,aAAa;AAAA,EAC9B,GAAG,CAAC,cAAc,CAAC;AAMnB,QAAM,yBAAyB,MAAM,OAAO,oBAAI,IAAoB,CAAC;AAErE,QAAM,YAAY,CAChB,OACA,mBAA4B,SACzB;AACH,WAAO,kBAAkB,OAAO,YAAY;AAE1C,YAAM,YACJ,YACA,WAAW,WACV,MAAM,eAAe,EAAE,MAAM,CAAC,WAAoB;AAGjD,YAAI,iBAAkB,eAAc,IAAI;AACxC,cAAM;AAAA,MACR,CAAC;AAEH,6BAAuB,QAAQ,MAAM;AACrC,YAAM,YAAY,MAAM,IAAI,CAAC,SAAS;AACpC,cAAM,SAAS,QAAQ,OAAO,CAAC;AAC/B,wBAAgB,QAAQ,IAAI,QAAQ,IAAI;AACxC,eAAO,EAAE,MAAM,OAAO;AAAA,MACxB,CAAC;AACD,YAAM,aAAa,CAACA,UAAe;AACjC,YAAIC,eAAc,EAAE,GAAGD,MAAK;AAC5B,mBAAW,EAAE,MAAM,OAAO,KAAK,WAAW;AACxC,UAAAC,eAAc,cAAc,EAAE,MAAMA,cAAa,MAAM,OAAO,CAAC;AAAA,QACjE;AACA,eAAOA;AAAA,MACT;AAEA,YAAM,cAAc,eAAe,CAAC,gBAAgB;AAClD,eAAO,WAAW,eAAe,SAAS;AAAA,MAC5C,CAAC;AAED,UAAI,WAAW;AACb,mBAAW,QAAQ,OAAO;AACxB,cAAI,CAAC,KAAK,aAAa;AACrB;AAAA,UACF;AACA,eAAK,UAAU,UAAU;AAAA,YACvB,MAAM,2BAA2B;AAAA,cAC/B,aAAa,KAAK;AAAA,cAClB,UAAU,KAAK;AAAA,cACf,cAAc,UAAU,KAAK;AAAA,YAC/B,CAAC;AAAA,UACH,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,kBAAkB;AACpB,sBAAc,IAAI;AAClB,qBAAa,WAAW;AAAA,MAC1B;AAMA,YAAM,cAAoC,CAAC;AAC3C,YAAM,gBAAgB,OAAO,gBAAgB;AAC3C,cAAM,YAAY,MAAM,gBAAgB,OAAO,YAAY,EAAE;AAC7D,YAAI,WAAW;AACb,sBAAY;AAAA,YACV,GAAG,2BAA2B;AAAA,cAC5B,eAAe;AAAA,cACf,gBAAgB;AAAA,cAChB;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AACD,aAAO,EAAE,YAAY;AAAA,IACvB,CAAC;AAAA,EACH;AAEA,QAAM,iBAAiB,CAAC,SAA0B;AAChD,WAAO,kBAAkB,UAAU,YAAY;AAE7C,UAAI,CAAC,UAAU;AACb,gBAAQ;AAAA,UACN;AAAA,QACF;AACA,eAAO,EAAE,aAAa,CAAC,EAAE;AAAA,MAC3B;AAKA,YAAM,cAAc,SAAS,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,KAAK,EAAE;AACrE,YAAM,cAAc,aAAa,eAAe,KAAK;AACrD,YAAM,gBACJ,KAAK,YACJ,uBAAuB,QAAQ,IAAI,KAAK,EAAE,KACzC,aAAa,YACb;AACJ,6BAAuB,QAAQ,IAAI,KAAK,IAAI,KAAK,QAAQ;AACzD,UAAI,aAAa,eAAe,kBAAkB,GAAG;AACnD,cAAM,OAAO,2BAA2B;AAAA,UACtC;AAAA,UACA,UAAU,KAAK,IAAI,aAAa;AAAA,UAChC,cAAc,SAAS,KAAK;AAAA,QAC9B,CAAC;AACD,cAAM,gBAAgB,IAClB,UAAU,UAAU,EAAE,KAAK,CAAC,IAC5B,UAAU,eAAe,EAAE,KAAK,CAAC;AAAA,MACvC;AAGA,qBAAe,CAAC,gBAAgB;AAC9B,YAAI,CAAC,aAAa;AAChB,iBAAO;AAAA,QACT;AACA,eAAO,iBAAiB,aAAa,IAAI;AAAA,MAC3C,CAAC;AAED,YAAM,cAAoC,CAAC;AAC3C,YAAM,gBAAgB,OAAO,gBAAgB;AAC3C,cAAM,YAAY,SAAS;AAAA,UACzB;AAAA,UACA,QAAQ,KAAK;AAAA,UACb,cAAc;AAAA,QAChB,CAAC;AACD,YAAI,CAAC,WAAW;AACd,iBAAO;AAAA,QACT;AACA,cAAM,YAAY,MAAM;AAAA,UACtB,EAAE,GAAG,MAAM,IAAI,UAAU,GAAG;AAAA,UAC5B,YAAY;AAAA,QACd;AACA,YAAI,WAAW;AACb,sBAAY;AAAA,YACV,GAAG,4BAA4B;AAAA,cAC7B,kBAAkB;AAAA,cAClB,YAAY;AAAA,cACZ,mBAAmB,KAAK;AAAA,cACxB,wBAAwB,KAAK;AAAA,cAC7B;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AACD,aAAO,EAAE,YAAY;AAAA,IACvB,CAAC;AAAA,EACH;AAEA,QAAM,iBAAiB,CAAC,WAAmB;AACzC,WAAO,kBAAkB,UAAU,YAAY;AAC7C,UAAI,CAAC,UAAU;AACb,gBAAQ;AAAA,UACN;AAAA,QACF;AACA;AAAA,MACF;AAEA,YAAM,eAAe,SAAS,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,MAAM;AACrE,6BAAuB,QAAQ,OAAO,MAAM;AAC5C,UAAI,aAAa,gBAAgB,aAAa,aAAa;AACzD,aAAK,UAAU,eAAe;AAAA,UAC5B,MAAM,2BAA2B;AAAA,YAC/B,aAAa,aAAa;AAAA,YAC1B,UAAU,aAAa;AAAA,YACvB,cAAc,SAAS,KAAK;AAAA,UAC9B,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAGA,qBAAe,CAAC,gBAAgB;AAC9B,YAAI,CAAC,aAAa;AAChB,iBAAO;AAAA,QACT;AACA,eAAO,mBAAmB,aAAa,MAAM;AAAA,MAC/C,CAAC;AAED,YAAM,gBAAgB,OAAO,gBAAgB;AAC3C,cAAM,YAAY,SAAS;AAAA,UACzB;AAAA,UACA;AAAA,UACA,cAAc;AAAA,QAChB,CAAC;AACD,eAAO,YACH,MAAM,0BAA0B,CAAC,UAAU,EAAE,GAAG,YAAY,EAAE,IAC9D;AAAA,MACN,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,QAAM,sBAAsB,CAAC,kBAA4B;AACvD,WAAO,kBAAkB,YAAY,YAAY;AAC/C,YAAM,YACJ,YAAY,WAAW,WAAY,MAAM,eAAe;AAE1D,qBAAe,CAAC,gBAAgB;AAC9B,eAAO;AAAA,UACL,GAAI,eAAe;AAAA,UACnB,eAAe,cAAc,IAAI,CAAC,UAAU;AAAA,YAC1C;AAAA,YACA,YAAY;AAAA,UACd,EAAE;AAAA,QACJ;AAAA,MACF,CAAC;AAED,YAAM,gBAAgB,CAAC,gBAAgB;AACrC,eAAO,8BAA8B,eAAe,YAAY,EAAE;AAAA,MACpE,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,QAAM,SAAS,OAAO,OAA0B,kBAA6B;AAE3E,UAAMD,QAAO,MAAM,iBAAiB;AAAA,MAClC;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,IACF,CAAC;AACD,QAAIA,OAAM;AACR,aAAOA;AAAA,IACT,OAAO;AACL,cAAQ;AAAA,QACN;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,cAAc,UAAU,eAAe;AAE7C,SACE;AAAA,IAAC,YAAY;AAAA,IAAZ;AAAA,MACC,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,UAAU,KAAK,gBAAgB;AAAA,QAC7C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAMO,SAAS,kBAA2C;AACzD,QAAM,UAAU,MAAM,WAAW,WAAW;AAC5C,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI,iBAAiB;AAAA,MACzB,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAiBO,SAAS,UAA2B;AACzC,QAAM,UAAU,MAAM,WAAW,WAAW;AAC5C,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI,iBAAiB;AAAA,MACzB,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,SAAO;AAAA,IACL,YAAY,QAAQ;AAAA,IACpB,YAAY,QAAQ;AAAA,IACpB,UAAU,QAAQ;AAAA,IAClB,WAAW,QAAQ;AAAA,IACnB,qBAAqB,QAAQ;AAAA,IAC7B,aAAa,QAAQ;AAAA,IACrB,OAAO,QAAQ;AAAA,IACf,YAAY,QAAQ;AAAA,EACtB;AACF;",
6
+ "names": ["error", "cart", "updatedCart"]
7
7
  }
@@ -1,5 +1,6 @@
1
1
  import type { QueryClient } from "@tanstack/react-query";
2
- import type { CartLineAttribute, CartLineMerchandise } from "../cart-types";
2
+ import type { CartError } from "../cart-provider";
3
+ import type { CartLineAdjustment, CartLineAttribute, CartLineMerchandise } from "../cart-types";
3
4
  /**
4
5
  * Describes a single item to add to the cart. Uses the merchandiseId
5
6
  * (e.g. a variant GID) directly from data loader output. Product
@@ -19,6 +20,18 @@ export interface AddToCartItem {
19
20
  properties?: CartLineAttribute[];
20
21
  merchandise?: CartLineMerchandise;
21
22
  }
23
+ /**
24
+ * What an `addToCart` call did. `adjustments` lists what the server changed
25
+ * about a saved request (a quantity clamped to stock, an unavailable line
26
+ * removed). `error` is set instead when the cart could not be created or
27
+ * saved; the same message is on `useCart().error` for the cart UI.
28
+ */
29
+ export interface AddToCartResult {
30
+ /** What the server changed about a saved add; empty when nothing was */
31
+ adjustments: CartLineAdjustment[];
32
+ /** Why the add was not saved; null on success */
33
+ error: CartError | null;
34
+ }
22
35
  export declare function findMerchandiseInQueryCache({ queryClient, merchandiseId, }: {
23
36
  queryClient: QueryClient;
24
37
  merchandiseId: string;
@@ -37,6 +50,12 @@ export declare function findMerchandiseInQueryCache({ queryClient, merchandiseId
37
50
  * merchandiseId. Pass `merchandise` explicitly only when the variant is
38
51
  * known but its product was not loaded via a supported loader.
39
52
  *
53
+ * `addToCart` never throws. It resolves an `AddToCartResult`: `adjustments`
54
+ * when the add was saved, or `error` when the cart could not be created or
55
+ * saved. On failure the provider also records the error on `useCart().error`
56
+ * and opens the cart UI, which renders it, so a click handler needs no
57
+ * handling of its own.
58
+ *
40
59
  * @example
41
60
  * ```tsx
42
61
  * import { useAddToCart } from "@replohq/sdk/cart/hooks/use-add-to-cart";
@@ -48,7 +67,7 @@ export declare function findMerchandiseInQueryCache({ queryClient, merchandiseId
48
67
  * <button
49
68
  * disabled={!variant.availableForSale || isAdding}
50
69
  * onClick={() => {
51
- * addToCart([{ merchandiseId: variant.id, quantity: 1 }]);
70
+ * void addToCart([{ merchandiseId: variant.id, quantity: 1 }]);
52
71
  * }}
53
72
  * >
54
73
  * {isAdding ? "Adding..." : "Add to Cart"}
@@ -58,8 +77,6 @@ export declare function findMerchandiseInQueryCache({ queryClient, merchandiseId
58
77
  * ```
59
78
  */
60
79
  export declare function useAddToCart(): {
61
- addToCart: (items: AddToCartItem[], openCartAfterAdd?: boolean) => Promise<{
62
- adjustments: import("../cart-types").CartLineAdjustment[];
63
- }>;
80
+ addToCart: (items: AddToCartItem[], openCartAfterAdd?: boolean) => Promise<AddToCartResult>;
64
81
  isAdding: boolean;
65
82
  };
@@ -5,6 +5,7 @@ import { fromMajorUnitsToMinorUnits } from "../../_vendor/schemas/money.mjs";
5
5
  import { DATA_LOADER_KEYS } from "../../loaders/loader-keys";
6
6
  import { useCartInternal } from "../cart-provider";
7
7
  import { REPLO_ATTRIBUTION_PROPERTY } from "../cart-types";
8
+ import { toCartError } from "../utils/cart-error";
8
9
  const PRODUCT_LOADER_KEYS = [
9
10
  DATA_LOADER_KEYS.SHOPIFY_PRODUCT,
10
11
  DATA_LOADER_KEYS.REPLO_PRODUCT
@@ -108,7 +109,13 @@ function useAddToCart() {
108
109
  merchandise
109
110
  };
110
111
  });
111
- return await cart.addToCart(cartLines, openCartAfterAdd);
112
+ const { adjustments } = await cart.addToCart(
113
+ cartLines,
114
+ openCartAfterAdd
115
+ );
116
+ return { adjustments, error: null };
117
+ } catch (caught) {
118
+ return { adjustments: [], error: toCartError("add", caught) };
112
119
  } finally {
113
120
  setIsAdding(false);
114
121
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../cart/hooks/use-add-to-cart.ts"],
4
- "sourcesContent": ["/**\n * Hook for adding items to cart using merchandiseId directly (e.g. variant GIDs from data loaders).\n * Merchandise display data for optimistic UI is auto-resolved from the React Query cache.\n * `addToCart` resolves with `{ adjustments }` describing anything the server\n * changed about the request (a quantity clamped to stock, an unavailable line\n * removed) so the storefront can show a notice.\n * @module\n */\n\"use client\";\n\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { Product } from \"../../loaders/loader-schemas\";\nimport type { CartLineAttribute, CartLineMerchandise } from \"../cart-types\";\n\nimport React from \"react\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { fromMajorUnitsToMinorUnits } from \"schemas/money\";\n\nimport { DATA_LOADER_KEYS } from \"../../loaders/loader-keys\";\nimport { useCartInternal } from \"../cart-provider\";\nimport { REPLO_ATTRIBUTION_PROPERTY } from \"../cart-types\";\n\ntype ProductListLike = { products: Product[] };\n\n/**\n * Describes a single item to add to the cart. Uses the merchandiseId\n * (e.g. a variant GID) directly from data loader output. Product\n * display data for optimistic UI is resolved automatically from the\n * React Query cache for both single-product loaders (e.g. ProductLoader)\n * and product-list loaders (e.g. CollectionProductsLoader,\n * RebuyRecommendationsLoader). Pass `merchandise` explicitly to bypass\n * the cache lookup when the variant is known but its product was not\n * loaded via a supported loader.\n */\nexport interface AddToCartItem {\n /** The cart-addressable merchandise ID (e.g. `product.variants[N].id` from loader output). */\n merchandiseId: string;\n quantity?: number;\n sellingPlanId?: string | null;\n /** Custom line item properties (appear on the order). */\n properties?: CartLineAttribute[];\n merchandise?: CartLineMerchandise;\n}\n\n/**\n * Loaders whose cached value is a single Product. Scanned for variant\n * matches first since they carry the richest product data.\n */\nconst PRODUCT_LOADER_KEYS = [\n DATA_LOADER_KEYS.SHOPIFY_PRODUCT,\n DATA_LOADER_KEYS.REPLO_PRODUCT,\n] as const;\n\nconst PRODUCT_LIST_LOADER_KEYS = [\n DATA_LOADER_KEYS.SHOPIFY_COLLECTION_PRODUCTS,\n DATA_LOADER_KEYS.REBUY_RECOMMENDATIONS,\n] as const;\n\nfunction getMerchandiseFromProduct({\n product,\n merchandiseId,\n}: {\n product: Product;\n merchandiseId: string;\n}): CartLineMerchandise | undefined {\n if (!product.variants) {\n return undefined;\n }\n\n const matchingVariant = product.variants.find(\n (variant) => variant.id === merchandiseId,\n );\n\n if (!matchingVariant) {\n return undefined;\n }\n\n const image = matchingVariant.image ?? product.featuredImage ?? null;\n return {\n id: merchandiseId,\n product: {\n id: product.id,\n title: product.title ?? \"\",\n sellingPlanGroups: product.sellingPlanGroups ?? [],\n },\n title: matchingVariant.title ?? \"\",\n // NOTE (Max, 2026-08-03): loader prices are decimal MoneyV2 strings, but the\n // cart is integer minor units, so convert at this storefront\u2192cart boundary \u2014\n // a raw parseFloat would store \"19.99\" as 19 minor units (~100\u00D7 too cheap).\n price: matchingVariant.price\n ? fromMajorUnitsToMinorUnits(matchingVariant.price)\n : 0,\n compareAtPrice: matchingVariant.compareAtPrice\n ? fromMajorUnitsToMinorUnits(matchingVariant.compareAtPrice)\n : null,\n image: image ? { url: image.url, altText: image.altText } : null,\n selectedOptions: matchingVariant.selectedOptions ?? [],\n };\n}\n\nexport function findMerchandiseInQueryCache({\n queryClient,\n merchandiseId,\n}: {\n queryClient: QueryClient;\n merchandiseId: string;\n}): CartLineMerchandise | undefined {\n for (const loaderKey of PRODUCT_LOADER_KEYS) {\n const productQueries = queryClient.getQueriesData<Product>({\n queryKey: [loaderKey],\n });\n\n for (const [, productData] of productQueries) {\n if (!productData) {\n continue;\n }\n\n const merchandise = getMerchandiseFromProduct({\n product: productData,\n merchandiseId,\n });\n if (merchandise) {\n return merchandise;\n }\n }\n }\n\n for (const loaderKey of PRODUCT_LIST_LOADER_KEYS) {\n const productListQueries = queryClient.getQueriesData<ProductListLike>({\n queryKey: [loaderKey],\n });\n\n for (const [, productListData] of productListQueries) {\n for (const product of productListData?.products ?? []) {\n const merchandise = getMerchandiseFromProduct({\n product,\n merchandiseId,\n });\n if (merchandise) {\n return merchandise;\n }\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Hook for adding items to cart using merchandise IDs from data loader output.\n *\n * This is the primary hook for adding items to cart in the data-loader\n * architecture. It works with the resolver-backed CartGateway so the scaffold\n * never needs direct integration credentials.\n *\n * Merchandise display data (title, price, image) for optimistic cart UI is\n * auto-resolved from the React Query cache for both single-product loaders\n * (e.g. `ProductLoader`) and product-list loaders (e.g.\n * `CollectionProductsLoader`, `RebuyRecommendationsLoader`). Just pass the\n * merchandiseId. Pass `merchandise` explicitly only when the variant is\n * known but its product was not loaded via a supported loader.\n *\n * @example\n * ```tsx\n * import { useAddToCart } from \"@replohq/sdk/cart/hooks/use-add-to-cart\";\n *\n * function AddToCartButton({ variant }) {\n * const { addToCart, isAdding } = useAddToCart();\n *\n * return (\n * <button\n * disabled={!variant.availableForSale || isAdding}\n * onClick={() => {\n * addToCart([{ merchandiseId: variant.id, quantity: 1 }]);\n * }}\n * >\n * {isAdding ? \"Adding...\" : \"Add to Cart\"}\n * </button>\n * );\n * }\n * ```\n */\nexport function useAddToCart() {\n const cart = useCartInternal();\n const queryClient = useQueryClient();\n const [isAdding, setIsAdding] = React.useState(false);\n\n const addToCart = React.useCallback(\n async (items: AddToCartItem[], openCartAfterAdd = true) => {\n setIsAdding(true);\n try {\n const cartLines = items.map((item) => {\n const hasReploAttribution = item.properties?.some(\n (prop) => prop.key === REPLO_ATTRIBUTION_PROPERTY.key,\n );\n const properties = hasReploAttribution\n ? item.properties ?? []\n : [REPLO_ATTRIBUTION_PROPERTY, ...(item.properties ?? [])];\n\n const merchandise =\n item.merchandise ??\n findMerchandiseInQueryCache({\n queryClient,\n merchandiseId: item.merchandiseId,\n });\n\n return {\n id: item.merchandiseId,\n quantity: item.quantity ?? 1,\n sellingPlanId: item.sellingPlanId ?? undefined,\n properties,\n merchandise,\n };\n });\n\n return await cart.addToCart(cartLines, openCartAfterAdd);\n } finally {\n setIsAdding(false);\n }\n },\n [cart, queryClient],\n );\n\n return { addToCart, isAdding };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Hook for adding items to cart using merchandiseId directly (e.g. variant GIDs from data loaders).\n * Merchandise display data for optimistic UI is auto-resolved from the React Query cache.\n * `addToCart` resolves with `{ adjustments }` describing anything the server\n * changed about the request (a quantity clamped to stock, an unavailable line\n * removed) so the storefront can show a notice.\n * @module\n */\n\"use client\";\n\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { Product } from \"../../loaders/loader-schemas\";\nimport type { CartError } from \"../cart-provider\";\nimport type {\n CartLineAdjustment,\n CartLineAttribute,\n CartLineMerchandise,\n} from \"../cart-types\";\n\nimport React from \"react\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { fromMajorUnitsToMinorUnits } from \"schemas/money\";\n\nimport { DATA_LOADER_KEYS } from \"../../loaders/loader-keys\";\nimport { useCartInternal } from \"../cart-provider\";\nimport { REPLO_ATTRIBUTION_PROPERTY } from \"../cart-types\";\nimport { toCartError } from \"../utils/cart-error\";\n\ntype ProductListLike = { products: Product[] };\n\n/**\n * Describes a single item to add to the cart. Uses the merchandiseId\n * (e.g. a variant GID) directly from data loader output. Product\n * display data for optimistic UI is resolved automatically from the\n * React Query cache for both single-product loaders (e.g. ProductLoader)\n * and product-list loaders (e.g. CollectionProductsLoader,\n * RebuyRecommendationsLoader). Pass `merchandise` explicitly to bypass\n * the cache lookup when the variant is known but its product was not\n * loaded via a supported loader.\n */\nexport interface AddToCartItem {\n /** The cart-addressable merchandise ID (e.g. `product.variants[N].id` from loader output). */\n merchandiseId: string;\n quantity?: number;\n sellingPlanId?: string | null;\n /** Custom line item properties (appear on the order). */\n properties?: CartLineAttribute[];\n merchandise?: CartLineMerchandise;\n}\n\n/**\n * What an `addToCart` call did. `adjustments` lists what the server changed\n * about a saved request (a quantity clamped to stock, an unavailable line\n * removed). `error` is set instead when the cart could not be created or\n * saved; the same message is on `useCart().error` for the cart UI.\n */\nexport interface AddToCartResult {\n /** What the server changed about a saved add; empty when nothing was */\n adjustments: CartLineAdjustment[];\n /** Why the add was not saved; null on success */\n error: CartError | null;\n}\n\n/**\n * Loaders whose cached value is a single Product. Scanned for variant\n * matches first since they carry the richest product data.\n */\nconst PRODUCT_LOADER_KEYS = [\n DATA_LOADER_KEYS.SHOPIFY_PRODUCT,\n DATA_LOADER_KEYS.REPLO_PRODUCT,\n] as const;\n\nconst PRODUCT_LIST_LOADER_KEYS = [\n DATA_LOADER_KEYS.SHOPIFY_COLLECTION_PRODUCTS,\n DATA_LOADER_KEYS.REBUY_RECOMMENDATIONS,\n] as const;\n\nfunction getMerchandiseFromProduct({\n product,\n merchandiseId,\n}: {\n product: Product;\n merchandiseId: string;\n}): CartLineMerchandise | undefined {\n if (!product.variants) {\n return undefined;\n }\n\n const matchingVariant = product.variants.find(\n (variant) => variant.id === merchandiseId,\n );\n\n if (!matchingVariant) {\n return undefined;\n }\n\n const image = matchingVariant.image ?? product.featuredImage ?? null;\n return {\n id: merchandiseId,\n product: {\n id: product.id,\n title: product.title ?? \"\",\n sellingPlanGroups: product.sellingPlanGroups ?? [],\n },\n title: matchingVariant.title ?? \"\",\n // NOTE (Max, 2026-08-03): loader prices are decimal MoneyV2 strings, but the\n // cart is integer minor units, so convert at this storefront\u2192cart boundary \u2014\n // a raw parseFloat would store \"19.99\" as 19 minor units (~100\u00D7 too cheap).\n price: matchingVariant.price\n ? fromMajorUnitsToMinorUnits(matchingVariant.price)\n : 0,\n compareAtPrice: matchingVariant.compareAtPrice\n ? fromMajorUnitsToMinorUnits(matchingVariant.compareAtPrice)\n : null,\n image: image ? { url: image.url, altText: image.altText } : null,\n selectedOptions: matchingVariant.selectedOptions ?? [],\n };\n}\n\nexport function findMerchandiseInQueryCache({\n queryClient,\n merchandiseId,\n}: {\n queryClient: QueryClient;\n merchandiseId: string;\n}): CartLineMerchandise | undefined {\n for (const loaderKey of PRODUCT_LOADER_KEYS) {\n const productQueries = queryClient.getQueriesData<Product>({\n queryKey: [loaderKey],\n });\n\n for (const [, productData] of productQueries) {\n if (!productData) {\n continue;\n }\n\n const merchandise = getMerchandiseFromProduct({\n product: productData,\n merchandiseId,\n });\n if (merchandise) {\n return merchandise;\n }\n }\n }\n\n for (const loaderKey of PRODUCT_LIST_LOADER_KEYS) {\n const productListQueries = queryClient.getQueriesData<ProductListLike>({\n queryKey: [loaderKey],\n });\n\n for (const [, productListData] of productListQueries) {\n for (const product of productListData?.products ?? []) {\n const merchandise = getMerchandiseFromProduct({\n product,\n merchandiseId,\n });\n if (merchandise) {\n return merchandise;\n }\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Hook for adding items to cart using merchandise IDs from data loader output.\n *\n * This is the primary hook for adding items to cart in the data-loader\n * architecture. It works with the resolver-backed CartGateway so the scaffold\n * never needs direct integration credentials.\n *\n * Merchandise display data (title, price, image) for optimistic cart UI is\n * auto-resolved from the React Query cache for both single-product loaders\n * (e.g. `ProductLoader`) and product-list loaders (e.g.\n * `CollectionProductsLoader`, `RebuyRecommendationsLoader`). Just pass the\n * merchandiseId. Pass `merchandise` explicitly only when the variant is\n * known but its product was not loaded via a supported loader.\n *\n * `addToCart` never throws. It resolves an `AddToCartResult`: `adjustments`\n * when the add was saved, or `error` when the cart could not be created or\n * saved. On failure the provider also records the error on `useCart().error`\n * and opens the cart UI, which renders it, so a click handler needs no\n * handling of its own.\n *\n * @example\n * ```tsx\n * import { useAddToCart } from \"@replohq/sdk/cart/hooks/use-add-to-cart\";\n *\n * function AddToCartButton({ variant }) {\n * const { addToCart, isAdding } = useAddToCart();\n *\n * return (\n * <button\n * disabled={!variant.availableForSale || isAdding}\n * onClick={() => {\n * void addToCart([{ merchandiseId: variant.id, quantity: 1 }]);\n * }}\n * >\n * {isAdding ? \"Adding...\" : \"Add to Cart\"}\n * </button>\n * );\n * }\n * ```\n */\nexport function useAddToCart() {\n const cart = useCartInternal();\n const queryClient = useQueryClient();\n const [isAdding, setIsAdding] = React.useState(false);\n\n const addToCart = React.useCallback(\n async (\n items: AddToCartItem[],\n openCartAfterAdd = true,\n ): Promise<AddToCartResult> => {\n setIsAdding(true);\n try {\n const cartLines = items.map((item) => {\n const hasReploAttribution = item.properties?.some(\n (prop) => prop.key === REPLO_ATTRIBUTION_PROPERTY.key,\n );\n const properties = hasReploAttribution\n ? item.properties ?? []\n : [REPLO_ATTRIBUTION_PROPERTY, ...(item.properties ?? [])];\n\n const merchandise =\n item.merchandise ??\n findMerchandiseInQueryCache({\n queryClient,\n merchandiseId: item.merchandiseId,\n });\n\n return {\n id: item.merchandiseId,\n quantity: item.quantity ?? 1,\n sellingPlanId: item.sellingPlanId ?? undefined,\n properties,\n merchandise,\n };\n });\n\n const { adjustments } = await cart.addToCart(\n cartLines,\n openCartAfterAdd,\n );\n return { adjustments, error: null };\n } catch (caught) {\n // The provider has already recorded and logged the failure.\n return { adjustments: [], error: toCartError(\"add\", caught) };\n } finally {\n setIsAdding(false);\n }\n },\n [cart, queryClient],\n );\n\n return { addToCart, isAdding };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,5 @@
1
+ import type { CartError, CartOperation } from "../cart-provider";
2
+ import { CanopyError } from "../../lib/canopy-error";
3
+ export declare class CartContextError extends CanopyError {
4
+ }
5
+ export declare function toCartError(operation: CartOperation, caught: unknown): CartError;
@@ -0,0 +1,20 @@
1
+ import { CanopyError } from "../../lib/canopy-error";
2
+ class CartContextError extends CanopyError {
3
+ }
4
+ const FALLBACK_MESSAGES = {
5
+ add: "Could not add to your cart. Please try again.",
6
+ update: "Could not update your cart. Please try again.",
7
+ remove: "Could not remove that item. Please try again.",
8
+ discount: "Could not apply that discount. Please try again."
9
+ };
10
+ function toCartError(operation, caught) {
11
+ return {
12
+ operation,
13
+ message: caught instanceof CartContextError ? caught.message : FALLBACK_MESSAGES[operation]
14
+ };
15
+ }
16
+ export {
17
+ CartContextError,
18
+ toCartError
19
+ };
20
+ //# sourceMappingURL=cart-error.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../cart/utils/cart-error.ts"],
4
+ "sourcesContent": ["import type { CartError, CartOperation } from \"../cart-provider\";\n\nimport { CanopyError } from \"../../lib/canopy-error\";\n\nexport class CartContextError extends CanopyError {}\n\nconst FALLBACK_MESSAGES: Record<CartOperation, string> = {\n add: \"Could not add to your cart. Please try again.\",\n update: \"Could not update your cart. Please try again.\",\n remove: \"Could not remove that item. Please try again.\",\n discount: \"Could not apply that discount. Please try again.\",\n};\n\n// Only the provider's own errors carry shopper-safe text; anything else (a\n// dropped request, a null action result) is technical and gets the fallback.\nexport function toCartError(\n operation: CartOperation,\n caught: unknown,\n): CartError {\n return {\n operation,\n message:\n caught instanceof CartContextError\n ? caught.message\n : FALLBACK_MESSAGES[operation],\n };\n}\n"],
5
+ "mappings": "AAEA,SAAS,mBAAmB;AAErB,MAAM,yBAAyB,YAAY;AAAC;AAEnD,MAAM,oBAAmD;AAAA,EACvD,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AACZ;AAIO,SAAS,YACd,WACA,QACW;AACX,SAAO;AAAA,IACL;AAAA,IACA,SACE,kBAAkB,mBACd,OAAO,UACP,kBAAkB,SAAS;AAAA,EACnC;AACF;",
6
+ "names": []
7
+ }
@@ -23,6 +23,7 @@ declare const reconciledCartLineSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
23
23
  status: z.ZodLiteral<"unavailable">;
24
24
  }, z.core.$strip>], "status">;
25
25
  export type ReconciledCartLine = z.infer<typeof reconciledCartLineSchema>;
26
+ export declare function shouldIncludeInactiveProducts(): boolean;
26
27
  declare class CanopyApiGateway {
27
28
  private apiHost;
28
29
  private baseUrl;
@@ -49,6 +49,9 @@ const reconciledCartLineSchema = z.discriminatedUnion("status", [
49
49
  const reconcileCartLinesResponseSchema = z.object({
50
50
  lines: z.array(reconciledCartLineSchema)
51
51
  });
52
+ function shouldIncludeInactiveProducts() {
53
+ return Boolean(process.env.IS_REPLO_SANDBOX);
54
+ }
52
55
  class CanopyApiGateway {
53
56
  apiHost;
54
57
  baseUrl;
@@ -115,10 +118,7 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
115
118
  projectId: env.PROJECT_ID,
116
119
  loaderKey,
117
120
  args,
118
- // The builder preview must render a product the merchant hasn't launched
119
- // yet; a published storefront must not. Only the sandbox dev server sets
120
- // IS_REPLO_SANDBOX, so it is what separates the two.
121
- includeInactiveProducts: Boolean(process.env.IS_REPLO_SANDBOX)
121
+ includeInactiveProducts: shouldIncludeInactiveProducts()
122
122
  });
123
123
  if (!response.ok) {
124
124
  const errorBody = await response.text().catch(() => "");
@@ -236,6 +236,7 @@ async function getCanopyApiGateway() {
236
236
  }
237
237
  export {
238
238
  CanopyApiGatewayError,
239
- getCanopyApiGateway
239
+ getCanopyApiGateway,
240
+ shouldIncludeInactiveProducts
240
241
  };
241
242
  //# sourceMappingURL=canopy-api.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../gateways/canopy-api.ts"],
4
- "sourcesContent": ["import type { Cart } from \"../cart/cart-schema\";\n\nimport { z } from \"zod\";\n\nimport { cartSchema } from \"../cart/cart-schema\";\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\n\ntype CanopyApiGatewayErrorConfig = {\n message: string;\n statusCode?: number;\n cause?: unknown;\n additionalData?: Record<string, unknown>;\n};\n\nexport class CanopyApiGatewayError extends CanopyError {\n statusCode: number;\n\n constructor(config: CanopyApiGatewayErrorConfig) {\n const resolvedStatusCode = config.statusCode ?? 500;\n super({\n message: config.message,\n cause: config.cause,\n additionalData: {\n ...config.additionalData,\n statusCode: resolvedStatusCode,\n },\n });\n this.statusCode = resolvedStatusCode;\n }\n}\n\nclass UnknownCanopyApiError extends CanopyApiGatewayError {\n constructor(config: Omit<CanopyApiGatewayErrorConfig, \"statusCode\">) {\n super({ ...config, statusCode: 502 });\n }\n}\n\nconst loaderInvokeResponseSchema = z.object({\n data: z.unknown(),\n meta: z\n .object({\n cache: z.object({\n hit: z.boolean(),\n ttlSeconds: z.number(),\n }),\n })\n .optional(),\n});\n\nconst reconciledCartLineSchema = z.discriminatedUnion(\"status\", [\n z.object({\n variantId: z.string(),\n status: z.literal(\"purchasable\"),\n unitPriceMinor: z.number().int(),\n compareAtPriceMinor: z.number().int().nullable(),\n currencyCode: z.string(),\n purchasableQuantity: z.number().int().min(0),\n }),\n z.object({\n variantId: z.string(),\n status: z.literal(\"unavailable\"),\n }),\n]);\n\nconst reconcileCartLinesResponseSchema = z.object({\n lines: z.array(reconciledCartLineSchema),\n});\n\nexport type ReconciledCartLine = z.infer<typeof reconciledCartLineSchema>;\n\nclass CanopyApiGateway {\n private apiHost: string;\n private baseUrl: string;\n constructor(apiHost: string) {\n this.apiHost = apiHost;\n this.baseUrl = buildBaseUrl(apiHost);\n }\n\n private static readonly REQUEST_TIMEOUT_MILLISECONDS = 10_000;\n\n static async setup() {\n const env = await getEnv();\n\n return new CanopyApiGateway(env.CANOPY_API_HOST);\n }\n\n private async _post(url: string, body: unknown) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: abortController.signal,\n });\n } catch (error) {\n let message = `Failed to POST to canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async _get(url: string) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, { signal: abortController.signal });\n } catch (error) {\n let message = `Failed to GET from canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async invokeLoader({\n loaderKey,\n args,\n }: {\n loaderKey: string;\n args: Record<string, unknown>;\n }) {\n const env = await getEnv();\n const response = await this._post(`${this.baseUrl}/api/v1/loaders/invoke`, {\n projectId: env.PROJECT_ID,\n loaderKey,\n args,\n // The builder preview must render a product the merchant hasn't launched\n // yet; a published storefront must not. Only the sandbox dev server sets\n // IS_REPLO_SANDBOX, so it is what separates the two.\n includeInactiveProducts: Boolean(process.env.IS_REPLO_SANDBOX),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to invoke loader ${loaderKey}: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return loaderInvokeResponseSchema.parse(await response.json());\n }\n\n /**\n * The catalog's current answer for a set of cookie-cart lines: per line,\n * the live price and how many units are actually purchasable. Used to\n * reconcile the client-persisted cart, which otherwise never re-reads the\n * catalog facts it snapshotted at add-to-cart time.\n */\n async reconcileCartLines({\n lines,\n }: {\n lines: { variantId: string; quantity: number }[];\n }): Promise<ReconciledCartLine[]> {\n const env = await getEnv();\n const response = await this._post(\n `${this.baseUrl}/api/v1/product-catalog/reconcile-cart-lines`,\n { projectId: env.PROJECT_ID, lines },\n );\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to reconcile cart lines: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return reconcileCartLinesResponseSchema.parse(await response.json()).lines;\n }\n\n // ---------------------------------------------------------------------------\n // Cart operations (canopy-api-backed ExternalCart)\n // ---------------------------------------------------------------------------\n\n private async _cartPost(\n path: string,\n body: Record<string, unknown>,\n ): Promise<Cart> {\n const env = await getEnv();\n const url = `${this.baseUrl}/api/v1/cart${path}`;\n\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ...body, projectId: env.PROJECT_ID }),\n signal: abortController.signal,\n });\n } catch (error) {\n throw new UnknownCanopyApiError({\n message: `Failed to POST cart operation to ${url}: ${error}`,\n cause: error,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Cart operation ${path} failed: ${response.status} ${response.statusText} \u2014 ${text}`,\n statusCode: response.status,\n });\n }\n\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const json = (await response.json()) as { data: unknown };\n return cartSchema.parse(json.data);\n }\n\n async cartGet({ cartId }: { cartId: string }): Promise<Cart | null> {\n try {\n return await this._cartPost(\"/get\", { cartId });\n } catch (error) {\n if (error instanceof CanopyApiGatewayError && error.statusCode === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async cartCreate(options?: {\n lines?: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n discountCodes?: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/create\", {\n lines: options?.lines,\n discountCodes: options?.discountCodes,\n });\n }\n\n async cartAddLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/add-lines\", { cartId, lines });\n }\n\n async cartUpdateLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n lineId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-lines\", { cartId, lines });\n }\n\n async cartRemoveLines({\n cartId,\n lineIds,\n }: {\n cartId: string;\n lineIds: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/remove-lines\", { cartId, lineIds });\n }\n\n async cartUpdateDiscountCodes({\n cartId,\n discountCodes,\n }: {\n cartId: string;\n discountCodes: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-discount-codes\", { cartId, discountCodes });\n }\n}\n\nexport async function getCanopyApiGateway() {\n return CanopyApiGateway.setup();\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Cart } from \"../cart/cart-schema\";\n\nimport { z } from \"zod\";\n\nimport { cartSchema } from \"../cart/cart-schema\";\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\n\ntype CanopyApiGatewayErrorConfig = {\n message: string;\n statusCode?: number;\n cause?: unknown;\n additionalData?: Record<string, unknown>;\n};\n\nexport class CanopyApiGatewayError extends CanopyError {\n statusCode: number;\n\n constructor(config: CanopyApiGatewayErrorConfig) {\n const resolvedStatusCode = config.statusCode ?? 500;\n super({\n message: config.message,\n cause: config.cause,\n additionalData: {\n ...config.additionalData,\n statusCode: resolvedStatusCode,\n },\n });\n this.statusCode = resolvedStatusCode;\n }\n}\n\nclass UnknownCanopyApiError extends CanopyApiGatewayError {\n constructor(config: Omit<CanopyApiGatewayErrorConfig, \"statusCode\">) {\n super({ ...config, statusCode: 502 });\n }\n}\n\nconst loaderInvokeResponseSchema = z.object({\n data: z.unknown(),\n meta: z\n .object({\n cache: z.object({\n hit: z.boolean(),\n ttlSeconds: z.number(),\n }),\n })\n .optional(),\n});\n\nconst reconciledCartLineSchema = z.discriminatedUnion(\"status\", [\n z.object({\n variantId: z.string(),\n status: z.literal(\"purchasable\"),\n unitPriceMinor: z.number().int(),\n compareAtPriceMinor: z.number().int().nullable(),\n currencyCode: z.string(),\n purchasableQuantity: z.number().int().min(0),\n }),\n z.object({\n variantId: z.string(),\n status: z.literal(\"unavailable\"),\n }),\n]);\n\nconst reconcileCartLinesResponseSchema = z.object({\n lines: z.array(reconciledCartLineSchema),\n});\n\nexport type ReconciledCartLine = z.infer<typeof reconciledCartLineSchema>;\n\n// The builder preview must render a product the merchant hasn't launched\n// yet; a published storefront must not. Only the sandbox dev server sets\n// IS_REPLO_SANDBOX, so it is what separates the two.\nexport function shouldIncludeInactiveProducts() {\n return Boolean(process.env.IS_REPLO_SANDBOX);\n}\n\nclass CanopyApiGateway {\n private apiHost: string;\n private baseUrl: string;\n constructor(apiHost: string) {\n this.apiHost = apiHost;\n this.baseUrl = buildBaseUrl(apiHost);\n }\n\n private static readonly REQUEST_TIMEOUT_MILLISECONDS = 10_000;\n\n static async setup() {\n const env = await getEnv();\n\n return new CanopyApiGateway(env.CANOPY_API_HOST);\n }\n\n private async _post(url: string, body: unknown) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: abortController.signal,\n });\n } catch (error) {\n let message = `Failed to POST to canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async _get(url: string) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, { signal: abortController.signal });\n } catch (error) {\n let message = `Failed to GET from canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async invokeLoader({\n loaderKey,\n args,\n }: {\n loaderKey: string;\n args: Record<string, unknown>;\n }) {\n const env = await getEnv();\n const response = await this._post(`${this.baseUrl}/api/v1/loaders/invoke`, {\n projectId: env.PROJECT_ID,\n loaderKey,\n args,\n includeInactiveProducts: shouldIncludeInactiveProducts(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to invoke loader ${loaderKey}: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return loaderInvokeResponseSchema.parse(await response.json());\n }\n\n /**\n * The catalog's current answer for a set of cookie-cart lines: per line,\n * the live price and how many units are actually purchasable. Used to\n * reconcile the client-persisted cart, which otherwise never re-reads the\n * catalog facts it snapshotted at add-to-cart time.\n */\n async reconcileCartLines({\n lines,\n }: {\n lines: { variantId: string; quantity: number }[];\n }): Promise<ReconciledCartLine[]> {\n const env = await getEnv();\n const response = await this._post(\n `${this.baseUrl}/api/v1/product-catalog/reconcile-cart-lines`,\n { projectId: env.PROJECT_ID, lines },\n );\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to reconcile cart lines: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return reconcileCartLinesResponseSchema.parse(await response.json()).lines;\n }\n\n // ---------------------------------------------------------------------------\n // Cart operations (canopy-api-backed ExternalCart)\n // ---------------------------------------------------------------------------\n\n private async _cartPost(\n path: string,\n body: Record<string, unknown>,\n ): Promise<Cart> {\n const env = await getEnv();\n const url = `${this.baseUrl}/api/v1/cart${path}`;\n\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ...body, projectId: env.PROJECT_ID }),\n signal: abortController.signal,\n });\n } catch (error) {\n throw new UnknownCanopyApiError({\n message: `Failed to POST cart operation to ${url}: ${error}`,\n cause: error,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Cart operation ${path} failed: ${response.status} ${response.statusText} \u2014 ${text}`,\n statusCode: response.status,\n });\n }\n\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const json = (await response.json()) as { data: unknown };\n return cartSchema.parse(json.data);\n }\n\n async cartGet({ cartId }: { cartId: string }): Promise<Cart | null> {\n try {\n return await this._cartPost(\"/get\", { cartId });\n } catch (error) {\n if (error instanceof CanopyApiGatewayError && error.statusCode === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async cartCreate(options?: {\n lines?: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n discountCodes?: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/create\", {\n lines: options?.lines,\n discountCodes: options?.discountCodes,\n });\n }\n\n async cartAddLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/add-lines\", { cartId, lines });\n }\n\n async cartUpdateLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n lineId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-lines\", { cartId, lines });\n }\n\n async cartRemoveLines({\n cartId,\n lineIds,\n }: {\n cartId: string;\n lineIds: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/remove-lines\", { cartId, lineIds });\n }\n\n async cartUpdateDiscountCodes({\n cartId,\n discountCodes,\n }: {\n cartId: string;\n discountCodes: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-discount-codes\", { cartId, discountCodes });\n }\n}\n\nexport async function getCanopyApiGateway() {\n return CanopyApiGateway.setup();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "branch": "HEAD",
5
- "commit": "4864322a9ff1f3dd385fee9bc0010f4535520cbb",
6
- "builtAt": "2026-09-10T21:47:40.227Z"
5
+ "commit": "02341f30e58f02e0d8482f829ff4346d95f3f3ec",
6
+ "builtAt": "2026-09-11T19:49:03.840Z"
7
7
  };
@@ -7,6 +7,10 @@
7
7
  * to canopy-api. The canopy-api's own in-memory cache still acts as a second
8
8
  * layer for cold starts and cross-instance sharing.
9
9
  *
10
+ * A canopy 404 is cached as a not-found marker and rethrown as a 404
11
+ * `CanopyApiGatewayError`, so a successful revalidation can displace a stale
12
+ * entry once the underlying data disappears (e.g. a product goes draft).
13
+ *
10
14
  * @param revalidateSeconds - How long (in seconds) the cached result is
11
15
  * considered fresh. Defaults to 60. Set to `false` to cache indefinitely
12
16
  * (use tag-based invalidation via `revalidateTag` to purge).
@@ -1,15 +1,34 @@
1
1
  import { unstable_cache } from "next/cache";
2
- import { getCanopyApiGateway } from "../gateways/canopy-api";
2
+ import {
3
+ CanopyApiGatewayError,
4
+ getCanopyApiGateway,
5
+ shouldIncludeInactiveProducts
6
+ } from "../gateways/canopy-api";
3
7
  import { resolvePreviewPlaceholderData } from "./preview-placeholder";
4
8
  const DEFAULT_REVALIDATE_SECONDS = 60;
9
+ class LoaderDataNotFoundError extends CanopyApiGatewayError {
10
+ constructor({ loaderKey }) {
11
+ super({
12
+ message: "Loader data not found",
13
+ statusCode: 404,
14
+ additionalData: { loaderKey }
15
+ });
16
+ }
17
+ }
5
18
  function buildStableCacheKeyParts({
6
19
  loaderKey,
7
- args
20
+ args,
21
+ includeInactiveProducts
8
22
  }) {
9
23
  const sortedArgsJson = JSON.stringify(
10
24
  Object.entries(args).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => [key, value])
11
25
  );
12
- return ["loader", loaderKey, sortedArgsJson];
26
+ return [
27
+ "loader",
28
+ loaderKey,
29
+ sortedArgsJson,
30
+ `includeInactiveProducts:${includeInactiveProducts}`
31
+ ];
13
32
  }
14
33
  async function invokeLoaderServer({
15
34
  loaderKey,
@@ -20,12 +39,23 @@ async function invokeLoaderServer({
20
39
  if (placeholderData !== null) {
21
40
  return placeholderData;
22
41
  }
23
- const cacheKeyParts = buildStableCacheKeyParts({ loaderKey, args });
24
- return unstable_cache(
42
+ const cacheKeyParts = buildStableCacheKeyParts({
43
+ loaderKey,
44
+ args,
45
+ includeInactiveProducts: shouldIncludeInactiveProducts()
46
+ });
47
+ const cachedInvocation = await unstable_cache(
25
48
  async () => {
26
49
  const gateway = await getCanopyApiGateway();
27
- const response = await gateway.invokeLoader({ loaderKey, args });
28
- return response.data;
50
+ try {
51
+ const response = await gateway.invokeLoader({ loaderKey, args });
52
+ return { kind: "data", data: response.data };
53
+ } catch (error) {
54
+ if (error instanceof CanopyApiGatewayError && error.statusCode === 404) {
55
+ return { kind: "notFound" };
56
+ }
57
+ throw error;
58
+ }
29
59
  },
30
60
  cacheKeyParts,
31
61
  {
@@ -33,6 +63,10 @@ async function invokeLoaderServer({
33
63
  tags: [`loader:${loaderKey}`, ...cacheKeyParts]
34
64
  }
35
65
  )();
66
+ if (cachedInvocation.kind === "notFound") {
67
+ throw new LoaderDataNotFoundError({ loaderKey });
68
+ }
69
+ return cachedInvocation.data;
36
70
  }
37
71
  export {
38
72
  invokeLoaderServer
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../loaders/invoke-loader-server.ts"],
4
- "sourcesContent": ["import { unstable_cache } from \"next/cache\";\n\nimport { getCanopyApiGateway } from \"../gateways/canopy-api\";\nimport { resolvePreviewPlaceholderData } from \"./preview-placeholder\";\n\nconst DEFAULT_REVALIDATE_SECONDS = 60;\n\nfunction buildStableCacheKeyParts({\n loaderKey,\n args,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\n}): string[] {\n const sortedArgsJson = JSON.stringify(\n Object.entries(args)\n .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))\n .map(([key, value]) => [key, value]),\n );\n return [\"loader\", loaderKey, sortedArgsJson];\n}\n\n/**\n * Server-only loader invocation that calls the canopy-api gateway directly.\n * Used by PrefetchedLoaders to seed the React Query cache during SSR.\n *\n * Results are cached via Next.js `unstable_cache` so repeated SSR renders\n * for the same loader+args return instantly without a network round-trip\n * to canopy-api. The canopy-api's own in-memory cache still acts as a second\n * layer for cold starts and cross-instance sharing.\n *\n * @param revalidateSeconds - How long (in seconds) the cached result is\n * considered fresh. Defaults to 60. Set to `false` to cache indefinitely\n * (use tag-based invalidation via `revalidateTag` to purge).\n */\nexport async function invokeLoaderServer({\n loaderKey,\n args,\n revalidateSeconds = DEFAULT_REVALIDATE_SECONDS,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\n revalidateSeconds?: number | false;\n}) {\n // Dev-server previews of an unfilled dynamic route prefetch sample data\n // instead of fetching \u2014 a real fetch with the literal bracket param can only\n // 404, and the thrown error marks the whole preview frame as broken. Kept\n // outside unstable_cache so sample data never enters the loader cache.\n const placeholderData = resolvePreviewPlaceholderData({ loaderKey, args });\n if (placeholderData !== null) {\n return placeholderData;\n }\n const cacheKeyParts = buildStableCacheKeyParts({ loaderKey, args });\n\n return unstable_cache(\n async () => {\n const gateway = await getCanopyApiGateway();\n const response = await gateway.invokeLoader({ loaderKey, args });\n return response.data;\n },\n cacheKeyParts,\n {\n revalidate: revalidateSeconds,\n tags: [`loader:${loaderKey}`, ...cacheKeyParts],\n },\n )();\n}\n"],
5
- "mappings": "AAAA,SAAS,sBAAsB;AAE/B,SAAS,2BAA2B;AACpC,SAAS,qCAAqC;AAE9C,MAAM,6BAA6B;AAEnC,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AACF,GAGa;AACX,QAAM,iBAAiB,KAAK;AAAA,IAC1B,OAAO,QAAQ,IAAI,EAChB,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,MAAM,KAAK,cAAc,IAAI,CAAC,EACjD,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,KAAK,CAAC;AAAA,EACvC;AACA,SAAO,CAAC,UAAU,WAAW,cAAc;AAC7C;AAeA,eAAsB,mBAAmB;AAAA,EACvC;AAAA,EACA;AAAA,EACA,oBAAoB;AACtB,GAIG;AAKD,QAAM,kBAAkB,8BAA8B,EAAE,WAAW,KAAK,CAAC;AACzE,MAAI,oBAAoB,MAAM;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,gBAAgB,yBAAyB,EAAE,WAAW,KAAK,CAAC;AAElE,SAAO;AAAA,IACL,YAAY;AACV,YAAM,UAAU,MAAM,oBAAoB;AAC1C,YAAM,WAAW,MAAM,QAAQ,aAAa,EAAE,WAAW,KAAK,CAAC;AAC/D,aAAO,SAAS;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,MAAM,CAAC,UAAU,SAAS,IAAI,GAAG,aAAa;AAAA,IAChD;AAAA,EACF,EAAE;AACJ;",
4
+ "sourcesContent": ["import { unstable_cache } from \"next/cache\";\n\nimport {\n CanopyApiGatewayError,\n getCanopyApiGateway,\n shouldIncludeInactiveProducts,\n} from \"../gateways/canopy-api\";\nimport { resolvePreviewPlaceholderData } from \"./preview-placeholder\";\n\nconst DEFAULT_REVALIDATE_SECONDS = 60;\n\n// NOTE (Max, 2026-09-11): cached values are wrapped so the not-found marker\n// stays disjoint from every possible loader payload.\ntype CachedLoaderInvocation =\n | { kind: \"data\"; data: unknown }\n | { kind: \"notFound\" };\n\nclass LoaderDataNotFoundError extends CanopyApiGatewayError {\n constructor({ loaderKey }: { loaderKey: string }) {\n super({\n message: \"Loader data not found\",\n statusCode: 404,\n additionalData: { loaderKey },\n });\n }\n}\n\nfunction buildStableCacheKeyParts({\n loaderKey,\n args,\n includeInactiveProducts,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\n includeInactiveProducts: boolean;\n}): string[] {\n const sortedArgsJson = JSON.stringify(\n Object.entries(args)\n .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))\n .map(([key, value]) => [key, value]),\n );\n return [\n \"loader\",\n loaderKey,\n sortedArgsJson,\n `includeInactiveProducts:${includeInactiveProducts}`,\n ];\n}\n\n/**\n * Server-only loader invocation that calls the canopy-api gateway directly.\n * Used by PrefetchedLoaders to seed the React Query cache during SSR.\n *\n * Results are cached via Next.js `unstable_cache` so repeated SSR renders\n * for the same loader+args return instantly without a network round-trip\n * to canopy-api. The canopy-api's own in-memory cache still acts as a second\n * layer for cold starts and cross-instance sharing.\n *\n * A canopy 404 is cached as a not-found marker and rethrown as a 404\n * `CanopyApiGatewayError`, so a successful revalidation can displace a stale\n * entry once the underlying data disappears (e.g. a product goes draft).\n *\n * @param revalidateSeconds - How long (in seconds) the cached result is\n * considered fresh. Defaults to 60. Set to `false` to cache indefinitely\n * (use tag-based invalidation via `revalidateTag` to purge).\n */\nexport async function invokeLoaderServer({\n loaderKey,\n args,\n revalidateSeconds = DEFAULT_REVALIDATE_SECONDS,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\n revalidateSeconds?: number | false;\n}) {\n // Dev-server previews of an unfilled dynamic route prefetch sample data\n // instead of fetching \u2014 a real fetch with the literal bracket param can only\n // 404, and the thrown error marks the whole preview frame as broken. Kept\n // outside unstable_cache so sample data never enters the loader cache.\n const placeholderData = resolvePreviewPlaceholderData({ loaderKey, args });\n if (placeholderData !== null) {\n return placeholderData;\n }\n const cacheKeyParts = buildStableCacheKeyParts({\n loaderKey,\n args,\n includeInactiveProducts: shouldIncludeInactiveProducts(),\n });\n\n const cachedInvocation = await unstable_cache(\n async (): Promise<CachedLoaderInvocation> => {\n const gateway = await getCanopyApiGateway();\n try {\n const response = await gateway.invokeLoader({ loaderKey, args });\n return { kind: \"data\", data: response.data };\n } catch (error) {\n // NOTE (Max, 2026-09-10): unstable_cache serves a stale entry forever\n // when every background revalidation rejects, so a 404 must resolve\n // with a cacheable marker that displaces the stale payload (REPL-31653).\n if (\n error instanceof CanopyApiGatewayError &&\n error.statusCode === 404\n ) {\n return { kind: \"notFound\" };\n }\n throw error;\n }\n },\n cacheKeyParts,\n {\n revalidate: revalidateSeconds,\n tags: [`loader:${loaderKey}`, ...cacheKeyParts],\n },\n )();\n\n if (cachedInvocation.kind === \"notFound\") {\n throw new LoaderDataNotFoundError({ loaderKey });\n }\n return cachedInvocation.data;\n}\n"],
5
+ "mappings": "AAAA,SAAS,sBAAsB;AAE/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qCAAqC;AAE9C,MAAM,6BAA6B;AAQnC,MAAM,gCAAgC,sBAAsB;AAAA,EAC1D,YAAY,EAAE,UAAU,GAA0B;AAChD,UAAM;AAAA,MACJ,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,gBAAgB,EAAE,UAAU;AAAA,IAC9B,CAAC;AAAA,EACH;AACF;AAEA,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,GAIa;AACX,QAAM,iBAAiB,KAAK;AAAA,IAC1B,OAAO,QAAQ,IAAI,EAChB,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,MAAM,KAAK,cAAc,IAAI,CAAC,EACjD,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,KAAK,CAAC;AAAA,EACvC;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,2BAA2B,uBAAuB;AAAA,EACpD;AACF;AAmBA,eAAsB,mBAAmB;AAAA,EACvC;AAAA,EACA;AAAA,EACA,oBAAoB;AACtB,GAIG;AAKD,QAAM,kBAAkB,8BAA8B,EAAE,WAAW,KAAK,CAAC;AACzE,MAAI,oBAAoB,MAAM;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,gBAAgB,yBAAyB;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,yBAAyB,8BAA8B;AAAA,EACzD,CAAC;AAED,QAAM,mBAAmB,MAAM;AAAA,IAC7B,YAA6C;AAC3C,YAAM,UAAU,MAAM,oBAAoB;AAC1C,UAAI;AACF,cAAM,WAAW,MAAM,QAAQ,aAAa,EAAE,WAAW,KAAK,CAAC;AAC/D,eAAO,EAAE,MAAM,QAAQ,MAAM,SAAS,KAAK;AAAA,MAC7C,SAAS,OAAO;AAId,YACE,iBAAiB,yBACjB,MAAM,eAAe,KACrB;AACA,iBAAO,EAAE,MAAM,WAAW;AAAA,QAC5B;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,MAAM,CAAC,UAAU,SAAS,IAAI,GAAG,aAAa;AAAA,IAChD;AAAA,EACF,EAAE;AAEF,MAAI,iBAAiB,SAAS,YAAY;AACxC,UAAM,IAAI,wBAAwB,EAAE,UAAU,CAAC;AAAA,EACjD;AACA,SAAO,iBAAiB;AAC1B;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.6.0",
6
+ "version": "1.7.1",
7
7
  "branch": "HEAD",
8
- "commit": "4864322a9ff1f3dd385fee9bc0010f4535520cbb",
9
- "builtAt": "2026-09-10T21:47:40.227Z"
8
+ "commit": "02341f30e58f02e0d8482f829ff4346d95f3f3ec",
9
+ "builtAt": "2026-09-11T19:49:03.840Z"
10
10
  },
11
11
  "description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
12
12
  "license": "SEE LICENSE IN LICENSE",