@replohq/sdk 1.13.0 → 1.13.2-beta.repl-30952-actions-validation.3.1.g5b43eb6

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.
@@ -80,4 +80,5 @@ export declare function isNetworkErrorMessage(message: string): boolean;
80
80
  * fallback string or `String(error)`.
81
81
  */
82
82
  export declare function getErrorMessage(error: unknown, fallback?: string): string;
83
+ export declare function appendErrorId(detail: string, errorId: string | null | undefined): string;
83
84
  export {};
@@ -436,6 +436,12 @@ export declare class IframeRpcChannelTimeoutError extends IframeRpcChannelError
436
436
  timeoutMs: number;
437
437
  });
438
438
  }
439
+ export declare class IframeRpcChannelTargetUnavailableError extends IframeRpcChannelError {
440
+ constructor({ callType, method, }: {
441
+ callType: "notify" | "request" | "response";
442
+ method?: string;
443
+ });
444
+ }
439
445
  export declare class IframeRpcChannelRemoteError extends IframeRpcChannelError {
440
446
  constructor({ method, message }: {
441
447
  method: string;
@@ -1,13 +1,6 @@
1
1
  import type { StorefrontCountryCode } from "../loaders/markets";
2
2
  import type { AddToCartPayload, Cart, CartLineAttribute, CartLinePayload, RemoveFromCartPayload, UpdateCartLineItemPayload } from "./cart-types";
3
- /**
4
- * Gets the existing cart or creates a new one if none exists. This is necessary
5
- * because we may not have cookies in the server context during initial load.
6
- *
7
- * See: https://nextjs.org/docs/app/api-reference/functions/cookies#understanding-cookie-behavior-in-server-components
8
- *
9
- * @returns The existing cart or a newly created cart
10
- */
3
+ /** Gets the existing cart or creates a new one if none exists. */
11
4
  export declare function getOrCreateCartAction(options?: {
12
5
  country?: StorefrontCountryCode;
13
6
  }): Promise<Cart | null>;
@@ -28,8 +28,25 @@ import {
28
28
  import { CartContextError, toCartError } from "./utils/cart-error";
29
29
  import { recalculateCartCost } from "./utils/cart-utils";
30
30
  import { createOptimisticSellingPlanAllocation } from "./utils/variant-to-cart-line";
31
+ let cartLoadPromise = null;
32
+ let cartLoadCountry;
31
33
  let cartCreationPromise = null;
32
34
  let cartCreationCountry;
35
+ function getSharedReconciledCart(country) {
36
+ if (cartLoadPromise && cartLoadCountry === country) {
37
+ return cartLoadPromise;
38
+ }
39
+ const previousLoad = cartLoadPromise;
40
+ const load = (async () => {
41
+ if (previousLoad) await previousLoad.catch(() => null);
42
+ return await reconcileCartAction({ country });
43
+ })().finally(() => {
44
+ if (cartLoadPromise === load) cartLoadPromise = null;
45
+ });
46
+ cartLoadCountry = country;
47
+ cartLoadPromise = load;
48
+ return load;
49
+ }
33
50
  function getOrCreateSharedCart(country) {
34
51
  if (cartCreationPromise && cartCreationCountry === country) {
35
52
  return cartCreationPromise;
@@ -161,6 +178,7 @@ function CartProvider({
161
178
  const mutationQueue = React.useRef(Promise.resolve());
162
179
  const pendingMutations = React.useRef(0);
163
180
  const optimisticLines = React.useRef(/* @__PURE__ */ new Map());
181
+ const cartInitialization = React.useRef(null);
164
182
  const lastReconciliationCountry = React.useRef(false);
165
183
  const startSuppliedCartReconciliation = React.useCallback(() => {
166
184
  if (!shouldReconcileSuppliedCart || lastReconciliationCountry.current === country) {
@@ -187,6 +205,7 @@ function CartProvider({
187
205
  startSuppliedCartReconciliation();
188
206
  pendingMutations.current += 1;
189
207
  const request = mutationQueue.current.then(async () => {
208
+ await cartLoadPromise?.catch(() => null);
190
209
  await cartCreationPromise?.catch(() => null);
191
210
  if (needsRefresh.current) {
192
211
  const restoredCart = await getCartAction();
@@ -246,34 +265,59 @@ function CartProvider({
246
265
  },
247
266
  []
248
267
  );
249
- const initializeCart = React.useCallback(async () => {
268
+ const reconcilePersistedCart = React.useCallback(() => {
269
+ const currentInitialization = cartInitialization.current;
270
+ if (currentInitialization && currentInitialization.country === country) {
271
+ return currentInitialization.promise;
272
+ }
273
+ const selection = marketSelection.current;
274
+ const initialization = (async () => {
275
+ const loadedCart = await getSharedReconciledCart(country);
276
+ if (!isMounted.current || marketSelection.current !== selection) {
277
+ throw new DOMException(
278
+ "Cart initialization was superseded",
279
+ "AbortError"
280
+ );
281
+ }
282
+ serverCart.current = loadedCart;
283
+ if (pendingMutations.current === 0) updateCartData(loadedCart);
284
+ return loadedCart;
285
+ })();
286
+ cartInitialization.current = { country, promise: initialization };
287
+ void initialization.catch(() => {
288
+ if (cartInitialization.current?.promise === initialization) {
289
+ cartInitialization.current = null;
290
+ }
291
+ });
292
+ return initialization;
293
+ }, [country, updateCartData]);
294
+ const getOrCreateCartForMutation = React.useCallback(async () => {
295
+ const loadedCart = await reconcilePersistedCart();
296
+ if (loadedCart) return loadedCart;
250
297
  const selection = marketSelection.current;
251
- const loadedCart = await getOrCreateSharedCart(country);
298
+ const createdCart = await getOrCreateSharedCart(country);
252
299
  if (!isMounted.current || marketSelection.current !== selection) {
253
- throw new DOMException(
254
- "Cart initialization was superseded",
255
- "AbortError"
256
- );
300
+ throw new DOMException("Cart creation was superseded", "AbortError");
257
301
  }
258
- serverCart.current = loadedCart;
259
- if (pendingMutations.current === 0) updateCartData(loadedCart);
260
- return loadedCart;
261
- }, [country, updateCartData]);
302
+ serverCart.current = createdCart;
303
+ if (pendingMutations.current === 0) updateCartData(createdCart);
304
+ return createdCart;
305
+ }, [country, reconcilePersistedCart, updateCartData]);
262
306
  React.useEffect(() => {
263
307
  isMounted.current = true;
264
308
  if (isSuppliedCart) {
265
309
  startSuppliedCartReconciliation();
266
310
  } else {
267
- void initializeCart().catch((error2) => {
311
+ void reconcilePersistedCart().catch((error2) => {
268
312
  if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
269
- console.error("[Replo] Error getting or creating cart:", error2);
313
+ console.error("[Replo] Error loading cart:", error2);
270
314
  }
271
315
  });
272
316
  }
273
317
  return () => {
274
318
  isMounted.current = false;
275
319
  };
276
- }, [initializeCart, isSuppliedCart, startSuppliedCartReconciliation]);
320
+ }, [isSuppliedCart, reconcilePersistedCart, startSuppliedCartReconciliation]);
277
321
  const subtotal = cartData?.lines.reduce((sum, lineItem) => {
278
322
  if (lineItem.quantity > 0 && lineItem.merchandise) {
279
323
  sum += lineItem.merchandise.price * lineItem.quantity;
@@ -313,7 +357,7 @@ function CartProvider({
313
357
  const addToCart = React.useCallback(
314
358
  (lines, openCartAfterAdd = true) => {
315
359
  return runWithErrorState("add", async () => {
316
- const readyCart = cartData ?? serverCart.current ?? await initializeCart().catch((caught) => {
360
+ const readyCart = cartData ?? serverCart.current ?? await getOrCreateCartForMutation().catch((caught) => {
317
361
  if (openCartAfterAdd) setIsCartOpen(true);
318
362
  throw caught;
319
363
  });
@@ -373,7 +417,7 @@ function CartProvider({
373
417
  cartData,
374
418
  emitCartView,
375
419
  enqueueMutation,
376
- initializeCart,
420
+ getOrCreateCartForMutation,
377
421
  runWithErrorState,
378
422
  updateCartData
379
423
  ]
@@ -381,13 +425,14 @@ function CartProvider({
381
425
  const updateCartItem = React.useCallback(
382
426
  (line) => {
383
427
  return runWithErrorState("update", async () => {
384
- if (!cartData) {
428
+ const readyCart = cartData ?? serverCart.current ?? await reconcilePersistedCart();
429
+ if (!readyCart) {
385
430
  console.warn(
386
431
  "[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
387
432
  );
388
433
  return { adjustments: [] };
389
434
  }
390
- const currentLine = cartData.lines.find((item) => item.id === line.id);
435
+ const currentLine = readyCart.lines.find((item) => item.id === line.id);
391
436
  const merchandise = currentLine?.merchandise ?? line.merchandise;
392
437
  const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
393
438
  reportedLineQuantities.current.set(line.id, line.quantity);
@@ -395,15 +440,12 @@ function CartProvider({
395
440
  const data = buildCartLineAnalyticsData({
396
441
  merchandise,
397
442
  quantity: Math.abs(quantityDelta),
398
- currencyCode: cartData.cost.currencyCode
443
+ currencyCode: readyCart.cost.currencyCode
399
444
  });
400
445
  void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
401
446
  }
402
447
  updateCartData((currentCart) => {
403
- if (!currentCart) {
404
- return null;
405
- }
406
- return updateLineInCart(currentCart, line);
448
+ return updateLineInCart(currentCart ?? readyCart, line);
407
449
  });
408
450
  const adjustments = [];
409
451
  await enqueueMutation(async (currentCart) => {
@@ -440,6 +482,7 @@ function CartProvider({
440
482
  cartData,
441
483
  enqueueMutation,
442
484
  findLine,
485
+ reconcilePersistedCart,
443
486
  runWithErrorState,
444
487
  updateCartData
445
488
  ]
@@ -447,28 +490,26 @@ function CartProvider({
447
490
  const removeCartItem = React.useCallback(
448
491
  (lineId) => {
449
492
  return runWithErrorState("remove", async () => {
450
- if (!cartData) {
493
+ const readyCart = cartData ?? serverCart.current ?? await reconcilePersistedCart();
494
+ if (!readyCart) {
451
495
  console.warn(
452
496
  "[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
453
497
  );
454
498
  return;
455
499
  }
456
- const itemToRemove = cartData.lines.find((item) => item.id === lineId);
500
+ const itemToRemove = readyCart.lines.find((item) => item.id === lineId);
457
501
  reportedLineQuantities.current.delete(lineId);
458
502
  if (analytics && itemToRemove && itemToRemove.merchandise) {
459
503
  void analytics.removeFromCart({
460
504
  data: buildCartLineAnalyticsData({
461
505
  merchandise: itemToRemove.merchandise,
462
506
  quantity: itemToRemove.quantity,
463
- currencyCode: cartData.cost.currencyCode
507
+ currencyCode: readyCart.cost.currencyCode
464
508
  })
465
509
  });
466
510
  }
467
511
  updateCartData((currentCart) => {
468
- if (!currentCart) {
469
- return null;
470
- }
471
- return removeLineFromCart(currentCart, lineId);
512
+ return removeLineFromCart(currentCart ?? readyCart, lineId);
472
513
  });
473
514
  await enqueueMutation(async (currentCart) => {
474
515
  const savedLine = findLine({
@@ -485,6 +526,7 @@ function CartProvider({
485
526
  cartData,
486
527
  enqueueMutation,
487
528
  findLine,
529
+ reconcilePersistedCart,
488
530
  runWithErrorState,
489
531
  updateCartData
490
532
  ]
@@ -492,7 +534,7 @@ function CartProvider({
492
534
  const updateCartField = React.useCallback(
493
535
  (operation, patch, save) => {
494
536
  return runWithErrorState(operation, async () => {
495
- const readyCart = cartData ?? serverCart.current ?? await initializeCart();
537
+ const readyCart = cartData ?? serverCart.current ?? await getOrCreateCartForMutation();
496
538
  updateCartData((currentCart) => ({
497
539
  ...currentCart ?? readyCart,
498
540
  ...patch
@@ -503,7 +545,7 @@ function CartProvider({
503
545
  [
504
546
  cartData,
505
547
  enqueueMutation,
506
- initializeCart,
548
+ getOrCreateCartForMutation,
507
549
  runWithErrorState,
508
550
  updateCartData
509
551
  ]
@@ -46,7 +46,7 @@ class CookieBasedCartGateway {
46
46
  const reconciledCart = await this.reconcile(cart, {
47
47
  clampQuantities: false
48
48
  });
49
- if (JSON.stringify(reconciledCart.lines) !== JSON.stringify(cart.lines)) {
49
+ if (JSON.stringify(reconciledCart.lines) !== JSON.stringify(cart.lines) || reconciledCart.cost.currencyCode !== cart.cost.currencyCode) {
50
50
  await saveCartToCookie(reconciledCart, this.config.cookieName);
51
51
  }
52
52
  return reconciledCart;
@@ -21,9 +21,10 @@ export declare function calculateLineItemsSubtotal(lineItems: CartLine[]): numbe
21
21
  */
22
22
  export declare function recalculateCartCost(cart: Cart): Cart;
23
23
  /**
24
- * Rewrite a cookie cart with the catalog's current facts: live prices always,
25
- * unavailable lines dropped, and — on write paths only — quantities clamped to
26
- * the purchasable remainder. Reads never clamp (`clampQuantities: false`):
24
+ * Rewrite a cookie cart with the catalog's current facts: live prices and
25
+ * currency always, unavailable lines dropped, and — on write paths only —
26
+ * quantities clamped to the purchasable remainder. Reads never clamp
27
+ * (`clampQuantities: false`):
27
28
  * a quantity change without messaging is worse than one the mutation or
28
29
  * checkout path reports, mirroring Shopify's cart behavior.
29
30
  *
@@ -58,7 +58,14 @@ function applyCartLineReconciliation(cart, reconciledLines, { clampQuantities })
58
58
  }
59
59
  ];
60
60
  });
61
- return recalculateCartCost({ ...cart, lines });
61
+ const currencyCode = reconciledLines.find((catalogLine) => {
62
+ return catalogLine.status === "purchasable";
63
+ })?.currencyCode ?? cart.cost.currencyCode;
64
+ return recalculateCartCost({
65
+ ...cart,
66
+ lines,
67
+ cost: { ...cart.cost, currencyCode }
68
+ });
62
69
  }
63
70
  function calculateCartTotals({
64
71
  lineItems,
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.13.0",
3
+ "version": "1.13.2",
4
4
  "branch": "HEAD",
5
- "commit": "55186f7cb6b1441e4d471b2d954118f3d11767ae",
6
- "builtAt": "2026-09-22T17:30:33.714Z"
5
+ "commit": "5b43eb62ec6c5a6e06f23132aa71c8498c5ef416",
6
+ "builtAt": "2026-09-29T00:55:45.107Z"
7
7
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.13.0",
3
+ "version": "1.13.2-beta.repl-30952-actions-validation.3.1.g5b43eb6",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.13.0",
6
+ "version": "1.13.2",
7
7
  "branch": "HEAD",
8
- "commit": "55186f7cb6b1441e4d471b2d954118f3d11767ae",
9
- "builtAt": "2026-09-22T17:30:33.714Z"
8
+ "commit": "5b43eb62ec6c5a6e06f23132aa71c8498c5ef416",
9
+ "builtAt": "2026-09-29T00:55:45.107Z"
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",