@replohq/sdk 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import type { StorefrontCountryCode } from "../loaders/markets";
2
- import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, UpdateCartLineItemPayload } from "./cart-types";
2
+ import type { AddToCartPayload, Cart, CartLineAttribute, CartLinePayload, RemoveFromCartPayload, UpdateCartLineItemPayload } from "./cart-types";
3
3
  /**
4
4
  * Gets the existing cart or creates a new one if none exists. This is necessary
5
5
  * because we may not have cookies in the server context during initial load.
@@ -73,3 +73,19 @@ export declare function removeCartLineItemsAction(lineIds: RemoveFromCartPayload
73
73
  * @returns The updated cart
74
74
  */
75
75
  export declare function updateCartDiscountCodesAction(discountCodes: string[], cartId: string): Promise<Cart | null>;
76
+ /**
77
+ * Sets the order-level note on a cart. Pass an empty string to clear it.
78
+ *
79
+ * @param note - The note text
80
+ * @param cartId - The ID of the cart to update
81
+ * @returns The updated cart
82
+ */
83
+ export declare function updateCartNoteAction(note: string, cartId: string): Promise<Cart | null>;
84
+ /**
85
+ * Replaces the order-level attributes on a cart.
86
+ *
87
+ * @param attributes - The full attribute list to store on the cart
88
+ * @param cartId - The ID of the cart to update
89
+ * @returns The updated cart
90
+ */
91
+ export declare function replaceCartAttributesAction(attributes: CartLineAttribute[], cartId: string): Promise<Cart | null>;
@@ -108,6 +108,14 @@ async function updateCartDiscountCodesAction(discountCodes, cartId) {
108
108
  const cartGateway = await getCartGateway();
109
109
  return await cartGateway.updateCartDiscountCodes({ discountCodes, cartId });
110
110
  }
111
+ async function updateCartNoteAction(note, cartId) {
112
+ const cartGateway = await getCartGateway();
113
+ return await cartGateway.updateCartNote({ note, cartId });
114
+ }
115
+ async function replaceCartAttributesAction(attributes, cartId) {
116
+ const cartGateway = await getCartGateway();
117
+ return await cartGateway.replaceCartAttributes({ attributes, cartId });
118
+ }
111
119
  export {
112
120
  addToCartAction,
113
121
  createCartAction,
@@ -115,6 +123,8 @@ export {
115
123
  getOrCreateCartAction,
116
124
  reconcileCartAction,
117
125
  removeCartLineItemsAction,
126
+ replaceCartAttributesAction,
118
127
  updateCartDiscountCodesAction,
119
- updateCartLineItemAction
128
+ updateCartLineItemAction,
129
+ updateCartNoteAction
120
130
  };
@@ -1,7 +1,7 @@
1
1
  import type { StorefrontCountryCode } from "../loaders/markets";
2
- import type { Cart, CartLine, CartLineAdjustment, CartLinePayload } from "./cart-types";
2
+ import type { Cart, CartLine, CartLineAdjustment, CartLineAttribute, CartLinePayload } from "./cart-types";
3
3
  import React from "react";
4
- export type CartOperation = "add" | "update" | "remove" | "discount";
4
+ export type CartOperation = "add" | "update" | "remove" | "discount" | "note" | "attributes";
5
5
  /**
6
6
  * The most recent cart change that could not be saved. `message` is written
7
7
  * for the shopper, so a cart UI can render it as-is next to a retry.
@@ -39,6 +39,10 @@ export interface CartContextTypeInternal {
39
39
  removeCartItem: (lineId: string) => Promise<void>;
40
40
  /** Function to update discount codes on cart */
41
41
  updateDiscountCodes: (discountCodes: string[]) => Promise<void>;
42
+ /** Function to set the order-level cart note (empty string clears it) */
43
+ updateNote: (note: string) => Promise<void>;
44
+ /** Function to replace the order-level cart attributes */
45
+ replaceAttributes: (attributes: CartLineAttribute[]) => Promise<void>;
42
46
  /** Re-read the persisted cart and adopt it if no mutations are in flight */
43
47
  refreshCart: () => Promise<void>;
44
48
  /** Function to create cart and checkout immediately */
@@ -72,6 +76,24 @@ export interface CartContextType {
72
76
  closeCart: () => void;
73
77
  /** Function to update discount codes on cart */
74
78
  updateDiscountCodes: (discountCodes: string[]) => Promise<void>;
79
+ /**
80
+ * Set the order-level cart note (Shopify `cart.note`, the "special
81
+ * instructions" field on the order). Pass an empty string to clear it.
82
+ * Each call is one Shopify mutation: save on blur or after the shopper
83
+ * stops typing, never on every keystroke. Shopify checkout only; on Replo
84
+ * Products (Stripe) sites the note is kept on the cart but is not
85
+ * forwarded to the order.
86
+ */
87
+ updateNote: (note: string) => Promise<void>;
88
+ /**
89
+ * Replace the order-level cart attributes (Shopify `cart.attributes`), the
90
+ * key/value pairs gift-message and fulfillment apps read off the order.
91
+ * These are distinct from per-line `properties`. The full list is replaced
92
+ * on every call, so a key left out is removed; values are stored as
93
+ * strings. Shopify checkout only; on Replo Products (Stripe) sites the
94
+ * attributes are kept on the cart but are not forwarded to the order.
95
+ */
96
+ replaceAttributes: (attributes: CartLineAttribute[]) => Promise<void>;
75
97
  /**
76
98
  * Re-read the persisted cart and adopt it if no mutations are in flight.
77
99
  * Call after something outside the provider rewrote the cart — e.g. the
@@ -10,8 +10,10 @@ import {
10
10
  getOrCreateCartAction,
11
11
  reconcileCartAction,
12
12
  removeCartLineItemsAction,
13
+ replaceCartAttributesAction,
13
14
  updateCartDiscountCodesAction,
14
- updateCartLineItemAction
15
+ updateCartLineItemAction,
16
+ updateCartNoteAction
15
17
  } from "./cart-actions";
16
18
  import { CartCountryContext } from "./cart-country-context";
17
19
  import { useShouldReconcileSuppliedCart } from "./supplied-cart-reconciliation";
@@ -487,22 +489,15 @@ function CartProvider({
487
489
  updateCartData
488
490
  ]
489
491
  );
490
- const updateDiscountCodes = React.useCallback(
491
- (discountCodes) => {
492
- return runWithErrorState("discount", async () => {
492
+ const updateCartField = React.useCallback(
493
+ (operation, patch, save) => {
494
+ return runWithErrorState(operation, async () => {
493
495
  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
- });
496
+ updateCartData((currentCart) => ({
497
+ ...currentCart ?? readyCart,
498
+ ...patch
499
+ }));
500
+ await enqueueMutation((currentCart) => save(currentCart.id));
506
501
  });
507
502
  },
508
503
  [
@@ -513,6 +508,43 @@ function CartProvider({
513
508
  updateCartData
514
509
  ]
515
510
  );
511
+ const updateDiscountCodes = React.useCallback(
512
+ (discountCodes) => {
513
+ return updateCartField(
514
+ "discount",
515
+ {
516
+ discountCodes: discountCodes.map((code) => ({
517
+ code,
518
+ applicable: true
519
+ }))
520
+ },
521
+ (cartId) => updateCartDiscountCodesAction(discountCodes, cartId)
522
+ );
523
+ },
524
+ [updateCartField]
525
+ );
526
+ const updateNote = React.useCallback(
527
+ (note) => {
528
+ return updateCartField("note", { note }, (cartId) => {
529
+ return updateCartNoteAction(note, cartId);
530
+ });
531
+ },
532
+ [updateCartField]
533
+ );
534
+ const replaceAttributes = React.useCallback(
535
+ (attributes) => {
536
+ const stringAttributes = attributes.map((attribute) => ({
537
+ key: attribute.key,
538
+ value: String(attribute.value)
539
+ }));
540
+ return updateCartField(
541
+ "attributes",
542
+ { attributes: stringAttributes },
543
+ (cartId) => replaceCartAttributesAction(stringAttributes, cartId)
544
+ );
545
+ },
546
+ [updateCartField]
547
+ );
516
548
  const buyNow = React.useCallback(
517
549
  (lines, discountCodes) => {
518
550
  return createBuyNowCart({ lines, discountCodes, country });
@@ -530,6 +562,8 @@ function CartProvider({
530
562
  updateCartItem,
531
563
  removeCartItem,
532
564
  updateDiscountCodes,
565
+ updateNote,
566
+ replaceAttributes,
533
567
  refreshCart,
534
568
  buyNow,
535
569
  checkoutUrl: cartData?.checkoutUrl ?? "/",
@@ -552,8 +586,10 @@ function CartProvider({
552
586
  refreshCart,
553
587
  removeCartItem,
554
588
  subtotal,
589
+ replaceAttributes,
555
590
  updateCartItem,
556
- updateDiscountCodes
591
+ updateDiscountCodes,
592
+ updateNote
557
593
  ]
558
594
  );
559
595
  return /* @__PURE__ */ jsx(CartCountryContext.Provider, { value: country, children: /* @__PURE__ */ jsx(CartContext.Provider, { value: contextValue, children }) });
@@ -580,6 +616,8 @@ function useCart() {
580
616
  openCart: context.openCart,
581
617
  closeCart: context.closeCart,
582
618
  updateDiscountCodes: context.updateDiscountCodes,
619
+ updateNote: context.updateNote,
620
+ replaceAttributes: context.replaceAttributes,
583
621
  refreshCart: context.refreshCart,
584
622
  error: context.error,
585
623
  clearError: context.clearError
@@ -77,6 +77,11 @@ export declare const cartSchema: z.ZodObject<{
77
77
  buyerIdentity: z.ZodOptional<z.ZodObject<{
78
78
  country: z.ZodNullable<z.ZodString>;
79
79
  }, z.core.$strip>>;
80
+ note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
81
+ attributes: z.ZodOptional<z.ZodArray<z.ZodObject<{
82
+ key: z.ZodString;
83
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
84
+ }, z.core.$strip>>>;
80
85
  }, z.core.$strip>;
81
86
  export declare const clientCartSchema: z.ZodObject<{
82
87
  id: z.ZodString;
@@ -97,6 +102,11 @@ export declare const clientCartSchema: z.ZodObject<{
97
102
  buyerIdentity: z.ZodOptional<z.ZodObject<{
98
103
  country: z.ZodNullable<z.ZodString>;
99
104
  }, z.core.$strip>>;
105
+ note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
106
+ attributes: z.ZodOptional<z.ZodArray<z.ZodObject<{
107
+ key: z.ZodString;
108
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
109
+ }, z.core.$strip>>>;
100
110
  lines: z.ZodArray<z.ZodObject<{
101
111
  id: z.ZodString;
102
112
  quantity: z.ZodNumber;
@@ -67,7 +67,9 @@ const cartSchema = z.object({
67
67
  checkoutUrl: z.string().optional(),
68
68
  discountCodes: z.array(cartDiscountCodeSchema).optional(),
69
69
  discountAllocations: z.array(cartDiscountAllocationSchema).optional(),
70
- buyerIdentity: z.object({ country: z.string().nullable() }).optional()
70
+ buyerIdentity: z.object({ country: z.string().nullable() }).optional(),
71
+ note: z.string().nullish(),
72
+ attributes: z.array(cartLineAttributeSchema).optional()
71
73
  });
72
74
  const clientCartSchema = cartSchema.extend({
73
75
  lines: z.array(
@@ -127,6 +127,10 @@ export interface Cart {
127
127
  buyerIdentity?: {
128
128
  country: string | null;
129
129
  };
130
+ /** Order-level note the shopper attached to the cart (Shopify `cart.note`) */
131
+ note?: string | null;
132
+ /** Order-level attributes (Shopify `cart.attributes`), distinct from line properties */
133
+ attributes?: CartLineAttribute[];
130
134
  }
131
135
  /**
132
136
  * How a cart write differed from what the shopper asked for, derived by
@@ -166,6 +170,14 @@ export interface UpdateCartDiscountCodesPayload {
166
170
  cartId: string;
167
171
  discountCodes: string[];
168
172
  }
173
+ export interface UpdateCartNotePayload {
174
+ cartId: string;
175
+ note: string;
176
+ }
177
+ export interface ReplaceCartAttributesPayload {
178
+ cartId: string;
179
+ attributes: CartLineAttribute[];
180
+ }
169
181
  export interface CartBuyerIdentity {
170
182
  country: StorefrontCountryCode;
171
183
  }
@@ -1,4 +1,4 @@
1
- import type { AddToCartPayload, Cart, CartBuyerIdentity, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
1
+ import type { AddToCartPayload, Cart, CartBuyerIdentity, CartLinePayload, RemoveFromCartPayload, ReplaceCartAttributesPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload, UpdateCartNotePayload } from "../cart-types";
2
2
  export interface CartGateway {
3
3
  getCart(cartId: string): Promise<Cart | null>;
4
4
  /**
@@ -21,5 +21,7 @@ export interface CartGateway {
21
21
  updateCartLineItem(cartPayload: UpdateCartLineItemPayload): Promise<Cart | null>;
22
22
  removeCartLineItems(cartPayload: RemoveFromCartPayload): Promise<Cart | null>;
23
23
  updateCartDiscountCodes(cartPayload: UpdateCartDiscountCodesPayload): Promise<Cart | null>;
24
+ updateCartNote(cartPayload: UpdateCartNotePayload): Promise<Cart | null>;
25
+ replaceCartAttributes(cartPayload: ReplaceCartAttributesPayload): Promise<Cart | null>;
24
26
  }
25
27
  export declare function getCartGateway(): Promise<CartGateway>;
@@ -1,5 +1,5 @@
1
1
  import type { ReconciledCartLine } from "../../gateways/canopy-api";
2
- import type { AddToCartPayload, Cart, CartBuyerIdentity, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
2
+ import type { AddToCartPayload, Cart, CartBuyerIdentity, CartLinePayload, RemoveFromCartPayload, ReplaceCartAttributesPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload, UpdateCartNotePayload } from "../cart-types";
3
3
  import type { LineMergeFn } from "../utils/cookie-cart-persistence";
4
4
  import type { CartGateway } from "./cart-gateway";
5
5
  /**
@@ -41,5 +41,8 @@ export declare abstract class CookieBasedCartGateway implements CartGateway {
41
41
  cartId: string;
42
42
  buyerIdentity: CartBuyerIdentity;
43
43
  }): Promise<Cart | null>;
44
+ updateCartNote({ cartId, note, }: UpdateCartNotePayload): Promise<Cart | null>;
45
+ replaceCartAttributes({ cartId, attributes, }: ReplaceCartAttributesPayload): Promise<Cart | null>;
46
+ private patchCart;
44
47
  }
45
48
  export {};
@@ -5,6 +5,7 @@ import {
5
5
  loadCartFromCookie,
6
6
  removeCartLineItemsWithPersistence,
7
7
  saveCartToCookie,
8
+ updateCartInCookie,
8
9
  updateCartLineItemWithPersistence
9
10
  } from "../utils/cookie-cart-persistence";
10
11
  class CookieBasedCartGateway {
@@ -156,6 +157,45 @@ class CookieBasedCartGateway {
156
157
  }) {
157
158
  return this.getCart(cartId);
158
159
  }
160
+ async updateCartNote({
161
+ cartId,
162
+ note
163
+ }) {
164
+ return this.patchCart(cartId, { note });
165
+ }
166
+ async replaceCartAttributes({
167
+ cartId,
168
+ attributes
169
+ }) {
170
+ return this.patchCart(cartId, {
171
+ attributes: attributes.map((attribute) => ({
172
+ key: attribute.key,
173
+ value: String(attribute.value)
174
+ }))
175
+ });
176
+ }
177
+ async patchCart(cartId, patch) {
178
+ console.warn(
179
+ `${this.config.gatewayName} cart gateway keeps cart note/attributes on the cart but does not forward them to checkout`
180
+ );
181
+ try {
182
+ return await updateCartInCookie(
183
+ cartId,
184
+ this.config.cookieName,
185
+ (cart) => ({
186
+ ...cart,
187
+ ...patch
188
+ })
189
+ );
190
+ } catch (error) {
191
+ console.warn(`Failed to update ${this.config.gatewayName} cart:`, {
192
+ error: error instanceof Error ? error.message : String(error),
193
+ cartId,
194
+ patch
195
+ });
196
+ return null;
197
+ }
198
+ }
159
199
  }
160
200
  export {
161
201
  CookieBasedCartGateway
@@ -117,6 +117,33 @@ class ResolverCartGateway {
117
117
  return null;
118
118
  }
119
119
  }
120
+ async updateCartNote(cartPayload) {
121
+ try {
122
+ const gateway = await getCanopyApiGateway();
123
+ return await gateway.cartUpdateNote({
124
+ cartId: cartPayload.cartId,
125
+ note: cartPayload.note
126
+ });
127
+ } catch (error) {
128
+ console.warn("Failed to update cart note via canopy-api:", error);
129
+ return null;
130
+ }
131
+ }
132
+ async replaceCartAttributes(cartPayload) {
133
+ try {
134
+ const gateway = await getCanopyApiGateway();
135
+ return await gateway.cartReplaceAttributes({
136
+ cartId: cartPayload.cartId,
137
+ attributes: cartPayload.attributes.map((attribute) => ({
138
+ key: attribute.key,
139
+ value: String(attribute.value)
140
+ }))
141
+ });
142
+ } catch (error) {
143
+ console.warn("Failed to update cart attributes via canopy-api:", error);
144
+ return null;
145
+ }
146
+ }
120
147
  }
121
148
  async function createResolverCartGateway() {
122
149
  return new ResolverCartGateway();
@@ -5,7 +5,9 @@ const FALLBACK_MESSAGES = {
5
5
  add: "Could not add to your cart. Please try again.",
6
6
  update: "Could not update your cart. Please try again.",
7
7
  remove: "Could not remove that item. Please try again.",
8
- discount: "Could not apply that discount. Please try again."
8
+ discount: "Could not apply that discount. Please try again.",
9
+ note: "Could not save your note. Please try again.",
10
+ attributes: "Could not save your cart details. Please try again."
9
11
  };
10
12
  function toCartError(operation, caught) {
11
13
  return {
@@ -110,6 +110,17 @@ declare class CanopyApiGateway {
110
110
  cartId: string;
111
111
  country: StorefrontCountryCode;
112
112
  }): Promise<Cart>;
113
+ cartUpdateNote({ cartId, note, }: {
114
+ cartId: string;
115
+ note: string;
116
+ }): Promise<Cart>;
117
+ cartReplaceAttributes({ cartId, attributes, }: {
118
+ cartId: string;
119
+ attributes: {
120
+ key: string;
121
+ value: string;
122
+ }[];
123
+ }): Promise<Cart>;
113
124
  }
114
125
  export declare function getCanopyApiGateway(): Promise<CanopyApiGateway>;
115
126
  export {};
@@ -228,6 +228,18 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
228
228
  }) {
229
229
  return this._cartPost("/update-buyer-identity", { cartId, country });
230
230
  }
231
+ async cartUpdateNote({
232
+ cartId,
233
+ note
234
+ }) {
235
+ return this._cartPost("/update-note", { cartId, note });
236
+ }
237
+ async cartReplaceAttributes({
238
+ cartId,
239
+ attributes
240
+ }) {
241
+ return this._cartPost("/replace-attributes", { cartId, attributes });
242
+ }
231
243
  }
232
244
  async function getCanopyApiGateway() {
233
245
  return CanopyApiGateway.setup();
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "branch": "HEAD",
5
- "commit": "3d57d078085b1361b40fd9feb54651a1fca5a985",
6
- "builtAt": "2026-09-22T02:12:30.362Z"
5
+ "commit": "55186f7cb6b1441e4d471b2d954118f3d11767ae",
6
+ "builtAt": "2026-09-22T17:30:33.714Z"
7
7
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.12.0",
6
+ "version": "1.13.0",
7
7
  "branch": "HEAD",
8
- "commit": "3d57d078085b1361b40fd9feb54651a1fca5a985",
9
- "builtAt": "2026-09-22T02:12:30.362Z"
8
+ "commit": "55186f7cb6b1441e4d471b2d954118f3d11767ae",
9
+ "builtAt": "2026-09-22T17:30:33.714Z"
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",