@replohq/sdk 1.13.0 → 1.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_vendor/replo-utils/lib/errors.d.ts +1 -0
- package/_vendor/schemas/errors.d.ts +6 -0
- package/cart/cart-actions.d.ts +1 -8
- package/cart/cart-provider.js +73 -31
- package/cart/gateways/cookie-cart-gateway.js +1 -1
- package/cart/utils/cart-utils.d.ts +4 -3
- package/cart/utils/cart-utils.js +8 -1
- package/lib/buildMetadata.js +3 -3
- package/package.json +4 -4
|
@@ -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;
|
package/cart/cart-actions.d.ts
CHANGED
|
@@ -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>;
|
package/cart/cart-provider.js
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
259
|
-
if (pendingMutations.current === 0) updateCartData(
|
|
260
|
-
return
|
|
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
|
|
311
|
+
void reconcilePersistedCart().catch((error2) => {
|
|
268
312
|
if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
|
|
269
|
-
console.error("[Replo] Error
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
507
|
+
currencyCode: readyCart.cost.currencyCode
|
|
464
508
|
})
|
|
465
509
|
});
|
|
466
510
|
}
|
|
467
511
|
updateCartData((currentCart) => {
|
|
468
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
25
|
-
* unavailable lines dropped, and — on write paths only —
|
|
26
|
-
* the purchasable remainder. Reads never clamp
|
|
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
|
*
|
package/cart/utils/cart-utils.js
CHANGED
|
@@ -58,7 +58,14 @@ function applyCartLineReconciliation(cart, reconciledLines, { clampQuantities })
|
|
|
58
58
|
}
|
|
59
59
|
];
|
|
60
60
|
});
|
|
61
|
-
|
|
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,
|
package/lib/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sdk",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.2",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-09-
|
|
5
|
+
"commit": "9717e963b637fc156b2ca32880d1612b38dce67a",
|
|
6
|
+
"builtAt": "2026-09-22T23:53:49.827Z"
|
|
7
7
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.2",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "1.13.
|
|
6
|
+
"version": "1.13.2",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-09-
|
|
8
|
+
"commit": "9717e963b637fc156b2ca32880d1612b38dce67a",
|
|
9
|
+
"builtAt": "2026-09-22T23:53:49.827Z"
|
|
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",
|