@replohq/sdk 1.10.0 → 1.10.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.
@@ -120,6 +120,13 @@ export declare class Uncommon_GloballyMappedError extends ReploError {
120
120
  userFacingDetails: ErrorUserFacingDetails;
121
121
  });
122
122
  }
123
+ /**
124
+ * The `userFacingDetails.key` publisher puts on its 404 for a missing project.
125
+ * It lives here rather than next to the error class because a caller across a
126
+ * service boundary (airlock) matches on it to tell "this project is gone" apart
127
+ * from any other 404 that URL could produce.
128
+ */
129
+ export declare const PROJECT_NOT_FOUND_ERROR_KEY = "errors.project.notFound";
123
130
  export declare class ReploValidationError extends ReploError {
124
131
  constructor(message: string);
125
132
  }
@@ -1,4 +1,4 @@
1
- import { ReploError } from '../../chunk-YJQVNUW7.mjs';
1
+ import { ReploError } from '../../chunk-Y3AYABMK.mjs';
2
2
  import '../../chunk-ZKAX2FW3.mjs';
3
3
  import '../../chunk-EUIVDITZ.mjs';
4
4
  import '../../chunk-SSBIMQES.mjs';
@@ -1,4 +1,4 @@
1
- import { ReploError } from '../../../chunk-YJQVNUW7.mjs';
1
+ import { ReploError } from '../../../chunk-Y3AYABMK.mjs';
2
2
  import '../../../chunk-ZKAX2FW3.mjs';
3
3
  import '../../../chunk-EUIVDITZ.mjs';
4
4
  import '../../../chunk-SSBIMQES.mjs';
@@ -1,5 +1,5 @@
1
1
  import { getParamSegment, getRouteParams, hasRouteParam, matchSource } from '../../../chunk-VPEVERWE.mjs';
2
- import { ReploError } from '../../../chunk-YJQVNUW7.mjs';
2
+ import { ReploError } from '../../../chunk-Y3AYABMK.mjs';
3
3
  import '../../../chunk-ZKAX2FW3.mjs';
4
4
  import '../../../chunk-EUIVDITZ.mjs';
5
5
  import '../../../chunk-SSBIMQES.mjs';
@@ -3,45 +3,9 @@ import { jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext, useEffect, useMemo, useRef } from "react";
4
4
  import { useConsent } from "../consent/consent-store";
5
5
  import { useRegisteredScripts } from "../consent/script-registration-store";
6
+ import { combineSinks } from "./combine-sinks";
6
7
  import { getAnalyticsSinks } from "./get-analytics-sinks";
7
8
  import { NavigationTracker } from "./utils/navigation-tracker";
8
- function combineSinks(sinks) {
9
- const broadcast = async (method, ...args) => {
10
- if (typeof window !== "undefined") {
11
- console.debug(
12
- "[analytics] broadcast",
13
- method,
14
- "to",
15
- sinks.length,
16
- "sinks",
17
- { args }
18
- );
19
- }
20
- await Promise.all(
21
- sinks.map(async (sink) => {
22
- try {
23
- const sinkMethod = sink[method];
24
- await sinkMethod.apply(sink, args);
25
- } catch {
26
- if (typeof window !== "undefined") {
27
- console.warn("[analytics] sink error", { method, sink });
28
- }
29
- }
30
- })
31
- );
32
- };
33
- return {
34
- pageView: (...args) => broadcast("pageView", ...args),
35
- purchase: (...args) => broadcast("purchase", ...args),
36
- click: (...args) => broadcast("click", ...args),
37
- productViewed: (...args) => broadcast("productViewed", ...args),
38
- addToCart: (...args) => broadcast("addToCart", ...args),
39
- removeFromCart: (...args) => broadcast("removeFromCart", ...args),
40
- viewCart: (...args) => broadcast("viewCart", ...args),
41
- startCheckout: (...args) => broadcast("startCheckout", ...args),
42
- viewCollection: (...args) => broadcast("viewCollection", ...args)
43
- };
44
- }
45
9
  const AnalyticsContext = createContext(null);
46
10
  function AnalyticsProvider({
47
11
  children,
@@ -0,0 +1,2 @@
1
+ import type { AnalyticsSink } from "./analytics-sink";
2
+ export declare function combineSinks(sinks: AnalyticsSink[]): AnalyticsSink;
@@ -0,0 +1,38 @@
1
+ import { logAnalyticsActivity } from "./utils/analytics-log";
2
+ function combineSinks(sinks) {
3
+ const broadcast = async (method, ...args) => {
4
+ if (typeof window !== "undefined") {
5
+ logAnalyticsActivity({
6
+ kind: "broadcast",
7
+ method,
8
+ sinkCount: sinks.length
9
+ });
10
+ }
11
+ await Promise.all(
12
+ sinks.map(async (sink) => {
13
+ try {
14
+ const sinkMethod = sink[method];
15
+ await sinkMethod.apply(sink, args);
16
+ } catch {
17
+ if (typeof window !== "undefined") {
18
+ console.warn("[analytics] sink error", { method });
19
+ }
20
+ }
21
+ })
22
+ );
23
+ };
24
+ return {
25
+ pageView: (...args) => broadcast("pageView", ...args),
26
+ purchase: (...args) => broadcast("purchase", ...args),
27
+ click: (...args) => broadcast("click", ...args),
28
+ productViewed: (...args) => broadcast("productViewed", ...args),
29
+ addToCart: (...args) => broadcast("addToCart", ...args),
30
+ removeFromCart: (...args) => broadcast("removeFromCart", ...args),
31
+ viewCart: (...args) => broadcast("viewCart", ...args),
32
+ startCheckout: (...args) => broadcast("startCheckout", ...args),
33
+ viewCollection: (...args) => broadcast("viewCollection", ...args)
34
+ };
35
+ }
36
+ export {
37
+ combineSinks
38
+ };
@@ -0,0 +1,20 @@
1
+ type AnalyticsActivity = {
2
+ kind: "broadcast";
3
+ method: string;
4
+ sinkCount: number;
5
+ } | {
6
+ kind: "flush";
7
+ eventCount: number;
8
+ queueName: string;
9
+ } | {
10
+ kind: "queueTimeout";
11
+ queueName: string;
12
+ } | {
13
+ kind: "drop";
14
+ queueName: string;
15
+ } | {
16
+ kind: "methodQueueTimeout";
17
+ queueName: string;
18
+ };
19
+ export declare function logAnalyticsActivity(activity: AnalyticsActivity): void;
20
+ export {};
@@ -0,0 +1,6 @@
1
+ import "../../_vendor/replo-utils/lib/misc.mjs";
2
+ function logAnalyticsActivity(activity) {
3
+ }
4
+ export {
5
+ logAnalyticsActivity
6
+ };
@@ -31,8 +31,7 @@ function splitCollectionProducts(data) {
31
31
  const lines = parseAnalyticsProductLines(products);
32
32
  if (Array.isArray(products) && products.length > 0 && lines.length === 0) {
33
33
  console.warn(
34
- "[analytics] collection view products did not match the per-line shape and were dropped",
35
- { products }
34
+ "[analytics] collection view products did not match the per-line shape and were dropped"
36
35
  );
37
36
  }
38
37
  return { products: lines, rest };
@@ -1,3 +1,4 @@
1
+ import { logAnalyticsActivity } from "./analytics-log";
1
2
  const QUEUE_TIMEOUT_MS = 3e4;
2
3
  function createAnalyticsQueue({
3
4
  isReady,
@@ -22,9 +23,11 @@ function createAnalyticsQueue({
22
23
  if (!isReady()) {
23
24
  return;
24
25
  }
25
- console.debug(
26
- `[analytics] flushing ${queue.length} queued events for ${queueName}`
27
- );
26
+ logAnalyticsActivity({
27
+ kind: "flush",
28
+ eventCount: queue.length,
29
+ queueName
30
+ });
28
31
  try {
29
32
  queue.forEach(send);
30
33
  } finally {
@@ -40,16 +43,13 @@ function createAnalyticsQueue({
40
43
  timeoutTimer = window.setTimeout(() => {
41
44
  queue.length = 0;
42
45
  permanentlyDisabled = true;
43
- console.debug(`Analytics queue timed out for ${queueName}`);
46
+ logAnalyticsActivity({ kind: "queueTimeout", queueName });
44
47
  cleanup();
45
48
  }, QUEUE_TIMEOUT_MS);
46
49
  };
47
50
  return (payload) => {
48
51
  if (permanentlyDisabled) {
49
- console.debug(
50
- `[analytics] ${queueName} queue is disabled, dropping event`,
51
- payload
52
- );
52
+ logAnalyticsActivity({ kind: "drop", queueName });
53
53
  return;
54
54
  }
55
55
  if (isReady()) {
@@ -113,9 +113,7 @@ function createMethodQueue(checkInstanceReady, options) {
113
113
  queue.length = 0;
114
114
  permanentlyDisabled = true;
115
115
  if (typeof window !== "undefined") {
116
- console.debug(
117
- `[analytics] ${queueName} timed out and has been disabled`
118
- );
116
+ logAnalyticsActivity({ kind: "methodQueueTimeout", queueName });
119
117
  }
120
118
  options?.onTimeout?.();
121
119
  cleanup();
@@ -11,6 +11,14 @@ import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, Up
11
11
  export declare function getOrCreateCartAction(options?: {
12
12
  country?: StorefrontCountryCode;
13
13
  }): Promise<Cart | null>;
14
+ /**
15
+ * Reconciles an existing persisted cart without creating one when none exists.
16
+ *
17
+ * @returns The reconciled cart, or null when no persisted cart exists
18
+ */
19
+ export declare function reconcileCartAction(options?: {
20
+ country?: StorefrontCountryCode;
21
+ }): Promise<Cart | null>;
14
22
  /**
15
23
  * Retrieves the current cart from cookies. Returns null if no cart exists or
16
24
  * if the cart has expired/been deleted.
@@ -11,21 +11,31 @@ import {
11
11
  saveCartToCookie
12
12
  } from "./utils/cookie-cart-persistence";
13
13
  const SHOPIFY_CART_COOKIE_NAME = "replo_cart_id";
14
+ async function reconcileExistingCart(cart, country) {
15
+ const cartGateway = await getCartGateway();
16
+ const reconciledCart = await cartGateway.reconcileStoredCart(cart);
17
+ if (country && reconciledCart.buyerIdentity?.country !== country) {
18
+ return await cartGateway.updateCartBuyerIdentity({
19
+ cartId: reconciledCart.id,
20
+ buyerIdentity: { country }
21
+ });
22
+ }
23
+ return reconciledCart;
24
+ }
14
25
  async function getOrCreateCartAction(options) {
15
26
  const existingCart = await getCartAction();
16
27
  if (existingCart) {
17
- const cartGateway = await getCartGateway();
18
- const reconciledCart = await cartGateway.reconcileStoredCart(existingCart);
19
- if (options?.country && reconciledCart.buyerIdentity?.country !== options.country) {
20
- return await cartGateway.updateCartBuyerIdentity({
21
- cartId: reconciledCart.id,
22
- buyerIdentity: { country: options.country }
23
- });
24
- }
25
- return reconciledCart;
28
+ return await reconcileExistingCart(existingCart, options?.country);
26
29
  }
27
30
  return await createCartAction({ country: options?.country });
28
31
  }
32
+ async function reconcileCartAction(options) {
33
+ const existingCart = await getCartAction();
34
+ if (!existingCart) {
35
+ return null;
36
+ }
37
+ return await reconcileExistingCart(existingCart, options?.country);
38
+ }
29
39
  async function getCartAction() {
30
40
  const checkoutProvider = await getCheckoutProvider();
31
41
  if (checkoutProvider === "stripe") {
@@ -103,6 +113,7 @@ export {
103
113
  createCartAction,
104
114
  getCartAction,
105
115
  getOrCreateCartAction,
116
+ reconcileCartAction,
106
117
  removeCartLineItemsAction,
107
118
  updateCartDiscountCodesAction,
108
119
  updateCartLineItemAction
@@ -7,6 +7,7 @@ import type { StorefrontCountryCode } from "../loaders/markets";
7
7
  * @param children - Child components that will have access to the cart
8
8
  * @returns A CartProvider component wrapping the children with cart data
9
9
  */
10
- export declare function CartLoader({ children, country, }: React.PropsWithChildren<{
10
+ export declare function CartLoader({ children, country, shouldReconcileSuppliedCart, }: React.PropsWithChildren<{
11
11
  country?: StorefrontCountryCode;
12
+ shouldReconcileSuppliedCart: boolean;
12
13
  }>): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -2,16 +2,18 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { unstable_rethrow } from "next/navigation";
3
3
  import { getCartAction } from "./cart-actions";
4
4
  import { CartProvider } from "./cart-provider";
5
+ import { SuppliedCartReconciliationBoundary } from "./supplied-cart-reconciliation";
5
6
  async function CartLoader({
6
7
  children,
7
- country
8
+ country,
9
+ shouldReconcileSuppliedCart
8
10
  }) {
9
- const cart = process.env.NODE_ENV === "development" ? null : await getCartAction().catch((error) => {
11
+ const cart = false ? null : await getCartAction().catch((error) => {
10
12
  unstable_rethrow(error);
11
13
  console.warn("[Replo] Failed to load cart:", error);
12
14
  return null;
13
15
  });
14
- return /* @__PURE__ */ jsx(CartProvider, { cart, country, children });
16
+ return /* @__PURE__ */ jsx(SuppliedCartReconciliationBoundary, { enabled: shouldReconcileSuppliedCart, children: /* @__PURE__ */ jsx(CartProvider, { cart, country, children }) });
15
17
  }
16
18
  export {
17
19
  CartLoader
@@ -8,11 +8,13 @@ import {
8
8
  createCartAction,
9
9
  getCartAction,
10
10
  getOrCreateCartAction,
11
+ reconcileCartAction,
11
12
  removeCartLineItemsAction,
12
13
  updateCartDiscountCodesAction,
13
14
  updateCartLineItemAction
14
15
  } from "./cart-actions";
15
16
  import { CartCountryContext } from "./cart-country-context";
17
+ import { useShouldReconcileSuppliedCart } from "./supplied-cart-reconciliation";
16
18
  import {
17
19
  deriveAddToCartAdjustments,
18
20
  deriveUpdateCartAdjustments
@@ -89,6 +91,25 @@ function removeLineFromCart(cart, lineId) {
89
91
  const updatedLines = cart.lines.filter((line) => line.id !== lineId);
90
92
  return recalculateCartCost({ ...cart, lines: updatedLines });
91
93
  }
94
+ async function createBuyNowCart({
95
+ lines,
96
+ discountCodes,
97
+ country
98
+ }) {
99
+ const cart = await createCartAction({
100
+ lines,
101
+ skipStoringCart: true,
102
+ discountCodes,
103
+ country
104
+ });
105
+ if (!cart) {
106
+ console.warn(
107
+ "[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists."
108
+ );
109
+ return null;
110
+ }
111
+ return cart;
112
+ }
92
113
  const CartContext = React.createContext(
93
114
  void 0
94
115
  );
@@ -97,6 +118,9 @@ function CartProvider({
97
118
  cart,
98
119
  country
99
120
  }) {
121
+ const isSuppliedCart = Boolean(cart);
122
+ const isInternalSuppliedCartReconciliationEnabled = useShouldReconcileSuppliedCart();
123
+ const shouldReconcileSuppliedCart = isSuppliedCart && (isInternalSuppliedCartReconciliationEnabled || Boolean(country));
100
124
  const [cartData, setCartData] = React.useState(cart);
101
125
  const optimisticCart = React.useRef(cart);
102
126
  const updateCartData = React.useCallback(
@@ -116,79 +140,110 @@ function CartProvider({
116
140
  if (marketSelection.current.country !== country) {
117
141
  marketSelection.current = { country };
118
142
  }
119
- const runWithErrorState = async (operation, mutate) => {
120
- setError(null);
121
- try {
122
- return await mutate();
123
- } catch (caught) {
124
- console.error(`[Replo] Cart ${operation} failed:`, caught);
125
- setError(toCartError(operation, caught));
126
- throw caught;
127
- }
128
- };
143
+ const runWithErrorState = React.useCallback(
144
+ async (operation, mutate) => {
145
+ setError(null);
146
+ try {
147
+ return await mutate();
148
+ } catch (caught) {
149
+ console.error(`[Replo] Cart ${operation} failed:`, caught);
150
+ setError(toCartError(operation, caught));
151
+ throw caught;
152
+ }
153
+ },
154
+ []
155
+ );
129
156
  const analytics = useAnalyticsOptional();
130
157
  const serverCart = React.useRef(cart);
131
158
  const needsRefresh = React.useRef(false);
132
159
  const mutationQueue = React.useRef(Promise.resolve());
133
160
  const pendingMutations = React.useRef(0);
134
161
  const optimisticLines = React.useRef(/* @__PURE__ */ new Map());
135
- const enqueueMutation = async (save) => {
136
- pendingMutations.current += 1;
137
- const request = mutationQueue.current.then(async () => {
138
- await cartCreationPromise?.catch(() => null);
139
- if (needsRefresh.current) {
140
- const restoredCart = await getCartAction();
141
- if (!restoredCart)
142
- throw new CartContextError({
143
- message: "Your cart could not be refreshed. Please reload before changing it."
144
- });
145
- serverCart.current = restoredCart;
146
- needsRefresh.current = false;
147
- }
148
- if (!serverCart.current) {
149
- throw new CartContextError({
150
- message: "Your cart could not be loaded. Please try again."
151
- });
152
- }
162
+ const lastReconciliationCountry = React.useRef(false);
163
+ const startSuppliedCartReconciliation = React.useCallback(() => {
164
+ if (!shouldReconcileSuppliedCart || lastReconciliationCountry.current === country) {
165
+ return;
166
+ }
167
+ lastReconciliationCountry.current = country;
168
+ mutationQueue.current = mutationQueue.current.then(async () => {
153
169
  try {
154
- const savedCart = await save(serverCart.current);
155
- if (!savedCart)
156
- throw new CartContextError({
157
- message: "Your cart could not be saved. Please try again."
158
- });
159
- serverCart.current = savedCart;
170
+ const reconciledCart = await reconcileCartAction({ country });
171
+ if (!reconciledCart) {
172
+ return;
173
+ }
174
+ serverCart.current = reconciledCart;
175
+ if (isMounted.current && pendingMutations.current === 0) {
176
+ updateCartData(reconciledCart);
177
+ }
160
178
  } catch (error2) {
161
- const restoredCart = await getCartAction().catch(() => null);
162
- needsRefresh.current = !restoredCart;
163
- if (restoredCart) serverCart.current = restoredCart;
164
- throw error2;
179
+ console.warn("[Replo] Failed to reconcile supplied cart:", error2);
165
180
  }
166
181
  });
167
- mutationQueue.current = request.catch(() => {
168
- }).then(() => {
169
- pendingMutations.current -= 1;
170
- if (pendingMutations.current === 0) {
171
- reportedLineQuantities.current.clear();
172
- optimisticLines.current.clear();
173
- updateCartData(serverCart.current);
174
- }
175
- });
176
- await request;
177
- };
178
- const findLine = ({
179
- currentCart,
180
- lineId,
181
- originalLine
182
- }) => {
183
- const existingLine = currentCart.lines.find((line) => line.id === lineId);
184
- if (existingLine) return existingLine;
185
- const optimisticLine = optimisticLines.current.get(lineId);
186
- const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;
187
- const sellingPlanId = optimisticLine?.sellingPlanId ?? originalLine?.sellingPlanAllocation?.sellingPlan.id ?? null;
188
- return currentCart.lines.find((line) => {
189
- return line.merchandise?.id === merchandiseId && (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId;
190
- });
191
- };
182
+ }, [country, shouldReconcileSuppliedCart, updateCartData]);
183
+ const enqueueMutation = React.useCallback(
184
+ async (save) => {
185
+ startSuppliedCartReconciliation();
186
+ pendingMutations.current += 1;
187
+ const request = mutationQueue.current.then(async () => {
188
+ await cartCreationPromise?.catch(() => null);
189
+ if (needsRefresh.current) {
190
+ const restoredCart = await getCartAction();
191
+ if (!restoredCart)
192
+ throw new CartContextError({
193
+ message: "Your cart could not be refreshed. Please reload before changing it."
194
+ });
195
+ serverCart.current = restoredCart;
196
+ needsRefresh.current = false;
197
+ }
198
+ if (!serverCart.current) {
199
+ throw new CartContextError({
200
+ message: "Your cart could not be loaded. Please try again."
201
+ });
202
+ }
203
+ try {
204
+ const savedCart = await save(serverCart.current);
205
+ if (!savedCart)
206
+ throw new CartContextError({
207
+ message: "Your cart could not be saved. Please try again."
208
+ });
209
+ serverCart.current = savedCart;
210
+ } catch (error2) {
211
+ const restoredCart = await getCartAction().catch(() => null);
212
+ needsRefresh.current = !restoredCart;
213
+ if (restoredCart) serverCart.current = restoredCart;
214
+ throw error2;
215
+ }
216
+ });
217
+ mutationQueue.current = request.catch(() => {
218
+ }).then(() => {
219
+ pendingMutations.current -= 1;
220
+ if (pendingMutations.current === 0) {
221
+ reportedLineQuantities.current.clear();
222
+ optimisticLines.current.clear();
223
+ updateCartData(serverCart.current);
224
+ }
225
+ });
226
+ await request;
227
+ },
228
+ [startSuppliedCartReconciliation, updateCartData]
229
+ );
230
+ const findLine = React.useCallback(
231
+ ({
232
+ currentCart,
233
+ lineId,
234
+ originalLine
235
+ }) => {
236
+ const existingLine = currentCart.lines.find((line) => line.id === lineId);
237
+ if (existingLine) return existingLine;
238
+ const optimisticLine = optimisticLines.current.get(lineId);
239
+ const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;
240
+ const sellingPlanId = optimisticLine?.sellingPlanId ?? originalLine?.sellingPlanAllocation?.sellingPlan.id ?? null;
241
+ return currentCart.lines.find((line) => {
242
+ return line.merchandise?.id === merchandiseId && (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId;
243
+ });
244
+ },
245
+ []
246
+ );
192
247
  const initializeCart = React.useCallback(async () => {
193
248
  const selection = marketSelection.current;
194
249
  const loadedCart = await getOrCreateSharedCart(country);
@@ -204,16 +259,19 @@ function CartProvider({
204
259
  }, [country, updateCartData]);
205
260
  React.useEffect(() => {
206
261
  isMounted.current = true;
207
- void initializeCart().catch((error2) => {
208
- if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
209
- console.error("[Replo] Error getting or creating cart:", error2);
210
- }
211
- });
262
+ if (isSuppliedCart) {
263
+ startSuppliedCartReconciliation();
264
+ } else {
265
+ void initializeCart().catch((error2) => {
266
+ if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
267
+ console.error("[Replo] Error getting or creating cart:", error2);
268
+ }
269
+ });
270
+ }
212
271
  return () => {
213
272
  isMounted.current = false;
214
273
  };
215
- }, [initializeCart]);
216
- const lineItems = cartData?.lines ?? [];
274
+ }, [initializeCart, isSuppliedCart, startSuppliedCartReconciliation]);
217
275
  const subtotal = cartData?.lines.reduce((sum, lineItem) => {
218
276
  if (lineItem.quantity > 0 && lineItem.merchandise) {
219
277
  sum += lineItem.merchandise.price * lineItem.quantity;
@@ -250,213 +308,255 @@ function CartProvider({
250
308
  updateCartData(refreshedCart);
251
309
  }, [updateCartData]);
252
310
  const reportedLineQuantities = React.useRef(/* @__PURE__ */ new Map());
253
- const addToCart = (lines, openCartAfterAdd = true) => {
254
- return runWithErrorState("add", async () => {
255
- const readyCart = cartData ?? serverCart.current ?? await initializeCart().catch((caught) => {
256
- if (openCartAfterAdd) setIsCartOpen(true);
257
- throw caught;
258
- });
259
- reportedLineQuantities.current.clear();
260
- const additions = lines.map((line) => {
261
- const lineId = `temp-${uuidv4()}`;
262
- optimisticLines.current.set(lineId, line);
263
- return { line, lineId };
264
- });
265
- const applyLines = (cart2) => {
266
- let updatedCart2 = { ...cart2 };
267
- for (const { line, lineId } of additions) {
268
- updatedCart2 = addLineToCart({ cart: updatedCart2, line, lineId });
311
+ const addToCart = React.useCallback(
312
+ (lines, openCartAfterAdd = true) => {
313
+ return runWithErrorState("add", async () => {
314
+ const readyCart = cartData ?? serverCart.current ?? await initializeCart().catch((caught) => {
315
+ if (openCartAfterAdd) setIsCartOpen(true);
316
+ throw caught;
317
+ });
318
+ reportedLineQuantities.current.clear();
319
+ const additions = lines.map((line) => {
320
+ const lineId = `temp-${uuidv4()}`;
321
+ optimisticLines.current.set(lineId, line);
322
+ return { line, lineId };
323
+ });
324
+ const applyLines = (cart2) => {
325
+ let updatedCart2 = { ...cart2 };
326
+ for (const { line, lineId } of additions) {
327
+ updatedCart2 = addLineToCart({ cart: updatedCart2, line, lineId });
328
+ }
329
+ return updatedCart2;
330
+ };
331
+ const updatedCart = updateCartData((currentCart) => {
332
+ return applyLines(currentCart ?? readyCart);
333
+ });
334
+ if (analytics) {
335
+ for (const line of lines) {
336
+ if (!line.merchandise) {
337
+ continue;
338
+ }
339
+ void analytics.addToCart({
340
+ data: buildCartLineAnalyticsData({
341
+ merchandise: line.merchandise,
342
+ quantity: line.quantity,
343
+ currencyCode: readyCart.cost.currencyCode
344
+ })
345
+ });
346
+ }
269
347
  }
270
- return updatedCart2;
271
- };
272
- const updatedCart = updateCartData((currentCart) => {
273
- return applyLines(currentCart ?? readyCart);
274
- });
275
- if (analytics) {
276
- for (const line of lines) {
277
- if (!line.merchandise) {
278
- continue;
348
+ if (openCartAfterAdd) {
349
+ setIsCartOpen(true);
350
+ emitCartView(updatedCart);
351
+ }
352
+ const adjustments = [];
353
+ await enqueueMutation(async (currentCart) => {
354
+ const savedCart = await addToCartAction(lines, currentCart.id);
355
+ if (savedCart) {
356
+ adjustments.push(
357
+ ...deriveAddToCartAdjustments({
358
+ cartBeforeAdd: currentCart,
359
+ requestedLines: lines,
360
+ savedCart
361
+ })
362
+ );
279
363
  }
280
- void analytics.addToCart({
281
- data: buildCartLineAnalyticsData({
282
- merchandise: line.merchandise,
283
- quantity: line.quantity,
284
- currencyCode: readyCart.cost.currencyCode
285
- })
364
+ return savedCart;
365
+ });
366
+ return { adjustments };
367
+ });
368
+ },
369
+ [
370
+ analytics,
371
+ cartData,
372
+ emitCartView,
373
+ enqueueMutation,
374
+ initializeCart,
375
+ runWithErrorState,
376
+ updateCartData
377
+ ]
378
+ );
379
+ const updateCartItem = React.useCallback(
380
+ (line) => {
381
+ return runWithErrorState("update", async () => {
382
+ if (!cartData) {
383
+ console.warn(
384
+ "[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
385
+ );
386
+ return { adjustments: [] };
387
+ }
388
+ const currentLine = cartData.lines.find((item) => item.id === line.id);
389
+ const merchandise = currentLine?.merchandise ?? line.merchandise;
390
+ const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
391
+ reportedLineQuantities.current.set(line.id, line.quantity);
392
+ if (analytics && merchandise && quantityDelta !== 0) {
393
+ const data = buildCartLineAnalyticsData({
394
+ merchandise,
395
+ quantity: Math.abs(quantityDelta),
396
+ currencyCode: cartData.cost.currencyCode
286
397
  });
398
+ void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
287
399
  }
288
- }
289
- if (openCartAfterAdd) {
290
- setIsCartOpen(true);
291
- emitCartView(updatedCart);
292
- }
293
- const adjustments = [];
294
- await enqueueMutation(async (currentCart) => {
295
- const savedCart = await addToCartAction(lines, currentCart.id);
296
- if (savedCart) {
297
- adjustments.push(
298
- ...deriveAddToCartAdjustments({
299
- cartBeforeAdd: currentCart,
300
- requestedLines: lines,
301
- savedCart
302
- })
400
+ updateCartData((currentCart) => {
401
+ if (!currentCart) {
402
+ return null;
403
+ }
404
+ return updateLineInCart(currentCart, line);
405
+ });
406
+ const adjustments = [];
407
+ await enqueueMutation(async (currentCart) => {
408
+ const savedLine = findLine({
409
+ currentCart,
410
+ lineId: line.id,
411
+ originalLine: currentLine
412
+ });
413
+ if (!savedLine) {
414
+ return currentCart;
415
+ }
416
+ const savedCart = await updateCartLineItemAction(
417
+ { ...line, id: savedLine.id },
418
+ currentCart.id
303
419
  );
304
- }
305
- return savedCart;
306
- });
307
- return { adjustments };
308
- });
309
- };
310
- const updateCartItem = (line) => {
311
- return runWithErrorState("update", async () => {
312
- if (!cartData) {
313
- console.warn(
314
- "[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
315
- );
316
- return { adjustments: [] };
317
- }
318
- const currentLine = cartData.lines.find((item) => item.id === line.id);
319
- const merchandise = currentLine?.merchandise ?? line.merchandise;
320
- const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
321
- reportedLineQuantities.current.set(line.id, line.quantity);
322
- if (analytics && merchandise && quantityDelta !== 0) {
323
- const data = buildCartLineAnalyticsData({
324
- merchandise,
325
- quantity: Math.abs(quantityDelta),
326
- currencyCode: cartData.cost.currencyCode
420
+ if (savedCart) {
421
+ adjustments.push(
422
+ ...deriveUpdateCartAdjustments({
423
+ cartBeforeUpdate: currentCart,
424
+ editedLine: savedLine,
425
+ requestedQuantity: line.quantity,
426
+ requestedSellingPlanId: line.sellingPlanId,
427
+ savedCart
428
+ })
429
+ );
430
+ }
431
+ return savedCart;
327
432
  });
328
- void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
329
- }
330
- updateCartData((currentCart) => {
331
- if (!currentCart) {
332
- return null;
333
- }
334
- return updateLineInCart(currentCart, line);
433
+ return { adjustments };
335
434
  });
336
- const adjustments = [];
337
- await enqueueMutation(async (currentCart) => {
338
- const savedLine = findLine({
339
- currentCart,
340
- lineId: line.id,
341
- originalLine: currentLine
342
- });
343
- if (!savedLine) {
344
- return currentCart;
435
+ },
436
+ [
437
+ analytics,
438
+ cartData,
439
+ enqueueMutation,
440
+ findLine,
441
+ runWithErrorState,
442
+ updateCartData
443
+ ]
444
+ );
445
+ const removeCartItem = React.useCallback(
446
+ (lineId) => {
447
+ return runWithErrorState("remove", async () => {
448
+ if (!cartData) {
449
+ console.warn(
450
+ "[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
451
+ );
452
+ return;
345
453
  }
346
- const savedCart = await updateCartLineItemAction(
347
- { ...line, id: savedLine.id },
348
- currentCart.id
349
- );
350
- if (savedCart) {
351
- adjustments.push(
352
- ...deriveUpdateCartAdjustments({
353
- cartBeforeUpdate: currentCart,
354
- editedLine: savedLine,
355
- requestedQuantity: line.quantity,
356
- requestedSellingPlanId: line.sellingPlanId,
357
- savedCart
454
+ const itemToRemove = cartData.lines.find((item) => item.id === lineId);
455
+ reportedLineQuantities.current.delete(lineId);
456
+ if (analytics && itemToRemove && itemToRemove.merchandise) {
457
+ void analytics.removeFromCart({
458
+ data: buildCartLineAnalyticsData({
459
+ merchandise: itemToRemove.merchandise,
460
+ quantity: itemToRemove.quantity,
461
+ currencyCode: cartData.cost.currencyCode
358
462
  })
359
- );
463
+ });
360
464
  }
361
- return savedCart;
362
- });
363
- return { adjustments };
364
- });
365
- };
366
- const removeCartItem = (lineId) => {
367
- return runWithErrorState("remove", async () => {
368
- if (!cartData) {
369
- console.warn(
370
- "[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
371
- );
372
- return;
373
- }
374
- const itemToRemove = cartData.lines.find((item) => item.id === lineId);
375
- reportedLineQuantities.current.delete(lineId);
376
- if (analytics && itemToRemove && itemToRemove.merchandise) {
377
- void analytics.removeFromCart({
378
- data: buildCartLineAnalyticsData({
379
- merchandise: itemToRemove.merchandise,
380
- quantity: itemToRemove.quantity,
381
- currencyCode: cartData.cost.currencyCode
382
- })
465
+ updateCartData((currentCart) => {
466
+ if (!currentCart) {
467
+ return null;
468
+ }
469
+ return removeLineFromCart(currentCart, lineId);
383
470
  });
384
- }
385
- updateCartData((currentCart) => {
386
- if (!currentCart) {
387
- return null;
388
- }
389
- return removeLineFromCart(currentCart, lineId);
390
- });
391
- await enqueueMutation(async (currentCart) => {
392
- const savedLine = findLine({
393
- currentCart,
394
- lineId,
395
- originalLine: itemToRemove
471
+ await enqueueMutation(async (currentCart) => {
472
+ const savedLine = findLine({
473
+ currentCart,
474
+ lineId,
475
+ originalLine: itemToRemove
476
+ });
477
+ return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
396
478
  });
397
- return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
398
- });
399
- });
400
- };
401
- const updateDiscountCodes = (discountCodes) => {
402
- return runWithErrorState("discount", async () => {
403
- const readyCart = cartData ?? serverCart.current ?? await initializeCart();
404
- updateCartData((currentCart) => {
405
- return {
406
- ...currentCart ?? readyCart,
407
- discountCodes: discountCodes.map((code) => ({
408
- code,
409
- applicable: true
410
- }))
411
- };
412
479
  });
413
- await enqueueMutation((currentCart) => {
414
- return updateCartDiscountCodesAction(discountCodes, currentCart.id);
480
+ },
481
+ [
482
+ analytics,
483
+ cartData,
484
+ enqueueMutation,
485
+ findLine,
486
+ runWithErrorState,
487
+ updateCartData
488
+ ]
489
+ );
490
+ const updateDiscountCodes = React.useCallback(
491
+ (discountCodes) => {
492
+ return runWithErrorState("discount", async () => {
493
+ const readyCart = cartData ?? serverCart.current ?? await initializeCart();
494
+ updateCartData((currentCart) => {
495
+ return {
496
+ ...currentCart ?? readyCart,
497
+ discountCodes: discountCodes.map((code) => ({
498
+ code,
499
+ applicable: true
500
+ }))
501
+ };
502
+ });
503
+ await enqueueMutation((currentCart) => {
504
+ return updateCartDiscountCodesAction(discountCodes, currentCart.id);
505
+ });
415
506
  });
416
- });
417
- };
418
- const buyNow = async (lines, discountCodes) => {
419
- const cart2 = await createCartAction({
420
- lines,
421
- skipStoringCart: true,
422
- discountCodes,
423
- country
424
- });
425
- if (cart2) {
426
- return cart2;
427
- } else {
428
- console.warn(
429
- "[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists."
430
- );
431
- return null;
432
- }
433
- };
434
- const checkoutUrl = cartData?.checkoutUrl ?? "/";
435
- return /* @__PURE__ */ jsx(CartCountryContext.Provider, { value: country, children: /* @__PURE__ */ jsx(
436
- CartContext.Provider,
437
- {
438
- value: {
439
- cartData,
440
- lineItems,
441
- subtotal,
442
- itemsCount,
443
- currencyCode: cartData?.cost.currencyCode ?? "USD",
444
- addToCart,
445
- updateCartItem,
446
- removeCartItem,
447
- updateDiscountCodes,
448
- refreshCart,
449
- buyNow,
450
- checkoutUrl,
451
- isCartOpen,
452
- openCart,
453
- closeCart,
454
- error,
455
- clearError
456
- },
457
- children
458
- }
459
- ) });
507
+ },
508
+ [
509
+ cartData,
510
+ enqueueMutation,
511
+ initializeCart,
512
+ runWithErrorState,
513
+ updateCartData
514
+ ]
515
+ );
516
+ const buyNow = React.useCallback(
517
+ (lines, discountCodes) => {
518
+ return createBuyNowCart({ lines, discountCodes, country });
519
+ },
520
+ [country]
521
+ );
522
+ const contextValue = React.useMemo(
523
+ () => ({
524
+ cartData,
525
+ lineItems: cartData?.lines ?? [],
526
+ subtotal,
527
+ itemsCount,
528
+ currencyCode: cartData?.cost.currencyCode ?? "USD",
529
+ addToCart,
530
+ updateCartItem,
531
+ removeCartItem,
532
+ updateDiscountCodes,
533
+ refreshCart,
534
+ buyNow,
535
+ checkoutUrl: cartData?.checkoutUrl ?? "/",
536
+ isCartOpen,
537
+ openCart,
538
+ closeCart,
539
+ error,
540
+ clearError
541
+ }),
542
+ [
543
+ addToCart,
544
+ buyNow,
545
+ cartData,
546
+ clearError,
547
+ closeCart,
548
+ error,
549
+ isCartOpen,
550
+ itemsCount,
551
+ openCart,
552
+ refreshCart,
553
+ removeCartItem,
554
+ subtotal,
555
+ updateCartItem,
556
+ updateDiscountCodes
557
+ ]
558
+ );
559
+ return /* @__PURE__ */ jsx(CartCountryContext.Provider, { value: country, children: /* @__PURE__ */ jsx(CartContext.Provider, { value: contextValue, children }) });
460
560
  }
461
561
  function useCartInternal() {
462
562
  const context = React.useContext(CartContext);
@@ -15,7 +15,7 @@ function getCartCookieOptions() {
15
15
  }
16
16
  return {
17
17
  ...sharedOptions,
18
- secure: process.env.NODE_ENV === "production",
18
+ secure: true,
19
19
  sameSite: "lax"
20
20
  };
21
21
  }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export declare function SuppliedCartReconciliationBoundary({ children, enabled, }: React.PropsWithChildren<{
3
+ enabled: boolean;
4
+ }>): import("react/jsx-runtime").JSX.Element;
5
+ export declare function useShouldReconcileSuppliedCart(): boolean;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import React from "react";
4
+ const ShouldReconcileSuppliedCartContext = React.createContext(true);
5
+ function SuppliedCartReconciliationBoundary({
6
+ children,
7
+ enabled
8
+ }) {
9
+ return /* @__PURE__ */ jsx(ShouldReconcileSuppliedCartContext.Provider, { value: enabled, children });
10
+ }
11
+ function useShouldReconcileSuppliedCart() {
12
+ return React.useContext(ShouldReconcileSuppliedCartContext);
13
+ }
14
+ export {
15
+ SuppliedCartReconciliationBoundary,
16
+ useShouldReconcileSuppliedCart
17
+ };
package/content.js CHANGED
@@ -370,7 +370,7 @@ async function getEntryListingStatus({
370
370
  slug,
371
371
  contentSource
372
372
  }) {
373
- if (process.env.NODE_ENV !== "development") {
373
+ if (true) {
374
374
  return "published";
375
375
  }
376
376
  const [draft, publishedCopy] = await Promise.all([
@@ -445,7 +445,7 @@ async function readVisibleEntry({
445
445
  if (getContentEntryFieldValidationIssues({
446
446
  fields: collectionSchema.fields,
447
447
  values,
448
- requireRequiredFields: process.env.NODE_ENV !== "development"
448
+ requireRequiredFields: true
449
449
  }).length > 0) {
450
450
  return null;
451
451
  }
package/env.js CHANGED
@@ -13,7 +13,7 @@ const wranglerConfigSchema = z.object({ vars: envSchema });
13
13
  class MissingWranglerConfigError extends CanopyError {
14
14
  }
15
15
  const isDeployedWorker = () => {
16
- return process.env.NODE_ENV === "production" && process.env.BUILD_ENV !== "build" && process.env.NEXT_PHASE !== "phase-production-build";
16
+ return process.env.BUILD_ENV !== "build" && process.env.NEXT_PHASE !== "phase-production-build";
17
17
  };
18
18
  const getEnv = async () => {
19
19
  if (isDeployedWorker()) {
@@ -78,7 +78,7 @@ class CanopyApiGateway {
78
78
  });
79
79
  } catch (error) {
80
80
  let message = `Failed to POST to canopy-api at ${url}: ${error}`;
81
- if (process.env.NODE_ENV !== "production") {
81
+ if (false) {
82
82
  message += `
83
83
 
84
84
  apiHost: ${this.apiHost}
@@ -98,7 +98,7 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
98
98
  return await fetch(url, { signal: abortController.signal });
99
99
  } catch (error) {
100
100
  let message = `Failed to GET from canopy-api at ${url}: ${error}`;
101
- if (process.env.NODE_ENV !== "production") {
101
+ if (false) {
102
102
  message += `
103
103
 
104
104
  apiHost: ${this.apiHost}
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "branch": "HEAD",
5
- "commit": "24c482167732242f8a358ac9f85c39183293880a",
6
- "builtAt": "2026-09-18T23:32:34.853Z"
5
+ "commit": "5ee9af27c9ec5545655f225b5029ef7572f61344",
6
+ "builtAt": "2026-09-20T21:55:46.106Z"
7
7
  };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  const LoaderErrorDetailsContext = React.createContext(false);
5
5
  function LoaderErrorDetailsProvider({
@@ -52,7 +52,7 @@ class LoaderErrorBoundary extends React.Component {
52
52
  if (!this.state.hasError) {
53
53
  return this.props.children;
54
54
  }
55
- if (process.env.NODE_ENV !== "production" && this.context) {
55
+ if (false) {
56
56
  return /* @__PURE__ */ jsxs(Fragment, { children: [
57
57
  this.props.fallback,
58
58
  /* @__PURE__ */ jsx(LoaderErrorDetails, { error: this.state.error })
@@ -1,6 +1,6 @@
1
1
  import "server-only";
2
2
  function shouldShowLoaderErrorDetails() {
3
- return process.env.NODE_ENV === "development" && !process.env.IS_REPLO_SANDBOX;
3
+ return false;
4
4
  }
5
5
  export {
6
6
  shouldShowLoaderErrorDetails
@@ -180,7 +180,7 @@ function resolvePreviewPlaceholderData({
180
180
  return null;
181
181
  }
182
182
  if (typeof window === "undefined") {
183
- return process.env.NODE_ENV === "development" ? placeholderData : null;
183
+ return false ? placeholderData : null;
184
184
  }
185
185
  return window.parent === window ? null : placeholderData;
186
186
  }
@@ -20,7 +20,7 @@ function cloudflareLoader({
20
20
  width,
21
21
  quality
22
22
  }) {
23
- const isDevelopment = process.env.NODE_ENV === "development";
23
+ const isDevelopment = false;
24
24
  const params = [`width=${width}`, `quality=${quality ?? 75}`, "format=auto"];
25
25
  if (/^https:\/\//i.test(src)) {
26
26
  let hostname;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.10.0",
6
+ "version": "1.10.2",
7
7
  "branch": "HEAD",
8
- "commit": "24c482167732242f8a358ac9f85c39183293880a",
9
- "builtAt": "2026-09-18T23:32:34.853Z"
8
+ "commit": "5ee9af27c9ec5545655f225b5029ef7572f61344",
9
+ "builtAt": "2026-09-20T21:55:46.106Z"
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",
@@ -18,7 +18,14 @@ async function ReploProvider({
18
18
  }
19
19
  return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
20
20
  /* @__PURE__ */ jsx(ReploFirstPartyPixel, {}),
21
- /* @__PURE__ */ jsx(CartLoader, { country: markets?.shopify?.country, children })
21
+ /* @__PURE__ */ jsx(
22
+ CartLoader,
23
+ {
24
+ country: markets?.shopify?.country,
25
+ shouldReconcileSuppliedCart: checkoutProvider === "stripe",
26
+ children
27
+ }
28
+ )
22
29
  ] }) }) });
23
30
  }
24
31
  export {
File without changes