@replohq/sdk 1.15.0 → 1.16.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,6 +1,7 @@
1
1
  import '../../chunk-UJCSKKID.mjs';
2
2
 
3
3
  var DATA_LOADER_KEYS = {
4
+ SHOPIFY_INTEGRATION_DETAILS: "shopify_storefront.integration_details",
4
5
  SHOPIFY_PRODUCT: "shopify_storefront.product",
5
6
  SHOPIFY_COLLECTION: "shopify_storefront.collection",
6
7
  SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products",
@@ -4,6 +4,8 @@ import type { AnalyticsSink } from "./analytics-sink";
4
4
  export declare function AnalyticsProvider({ children, checkoutProvider, }: React.PropsWithChildren<{
5
5
  checkoutProvider?: CheckoutProvider;
6
6
  }>): import("react/jsx-runtime").JSX.Element;
7
+ /** Register one stable, consent-aware sink beneath ReploProvider. */
8
+ export declare function useAnalyticsSink(sink: AnalyticsSink): void;
7
9
  /**
8
10
  * Returns the AnalyticsSink from the nearest AnalyticsProvider.
9
11
  * Throws if used outside of an AnalyticsProvider.
@@ -1,6 +1,14 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useEffect, useMemo, useRef } from "react";
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState
11
+ } from "react";
4
12
  import { useConsent } from "../consent/consent-store";
5
13
  import { useRegisteredScripts } from "../consent/script-registration-store";
6
14
  import { combineSinks } from "./combine-sinks";
@@ -13,9 +21,18 @@ function AnalyticsProvider({
13
21
  }) {
14
22
  const consent = useConsent();
15
23
  const registeredScripts = useRegisteredScripts();
24
+ const [additionalSinks, setAdditionalSinks] = useState([]);
25
+ const registerSink = useCallback((sink) => {
26
+ setAdditionalSinks((current) => [...current, sink]);
27
+ return () => {
28
+ setAdditionalSinks((current) => {
29
+ return current.filter((registered) => registered !== sink);
30
+ });
31
+ };
32
+ }, []);
16
33
  const analytics = useMemo(() => {
17
- return combineSinks(
18
- getAnalyticsSinks({
34
+ return combineSinks([
35
+ ...getAnalyticsSinks({
19
36
  consent: {
20
37
  cmp: consent.cmp,
21
38
  mode: consent.mode,
@@ -24,15 +41,17 @@ function AnalyticsProvider({
24
41
  },
25
42
  registeredScripts,
26
43
  checkoutProvider
27
- })
28
- );
44
+ }),
45
+ ...additionalSinks
46
+ ]);
29
47
  }, [
30
48
  consent.cmp,
31
49
  consent.mode,
32
50
  consent.categories,
33
51
  consent.decidedAt,
34
52
  registeredScripts,
35
- checkoutProvider
53
+ checkoutProvider,
54
+ additionalSinks
36
55
  ]);
37
56
  const analyticsRef = useRef(analytics);
38
57
  analyticsRef.current = analytics;
@@ -51,7 +70,17 @@ function AnalyticsProvider({
51
70
  navigationTracker.current?.cleanup();
52
71
  };
53
72
  }, []);
54
- return /* @__PURE__ */ jsx(AnalyticsContext.Provider, { value: { analytics }, children });
73
+ return /* @__PURE__ */ jsx(AnalyticsContext.Provider, { value: { analytics, registerSink }, children });
74
+ }
75
+ function useAnalyticsSink(sink) {
76
+ const context = useContext(AnalyticsContext);
77
+ if (!context) {
78
+ throw new AnalyticsContextError({
79
+ message: "useAnalyticsSink must be used within <AnalyticsProvider>"
80
+ });
81
+ }
82
+ const registerSink = context.registerSink;
83
+ useEffect(() => registerSink(sink), [registerSink, sink]);
55
84
  }
56
85
  class AnalyticsContextError extends Error {
57
86
  constructor({ message }) {
@@ -74,5 +103,6 @@ function useAnalyticsOptional() {
74
103
  export {
75
104
  AnalyticsProvider,
76
105
  useAnalytics,
77
- useAnalyticsOptional
106
+ useAnalyticsOptional,
107
+ useAnalyticsSink
78
108
  };
@@ -13,7 +13,16 @@ export type PurchasePayload = {
13
13
  currency: string;
14
14
  data: Record<string, any>;
15
15
  };
16
+ export type AnalyticsProperty = string | number | boolean | null | AnalyticsProperty[] | {
17
+ [key: string]: AnalyticsProperty;
18
+ };
19
+ export interface CustomEventPayload {
20
+ event: string;
21
+ properties?: Record<string, AnalyticsProperty>;
22
+ }
16
23
  export interface AnalyticsSink {
24
+ /** Explicit business goals; sinks without custom-event support may omit this. */
25
+ customEvent?(payload: CustomEventPayload): Promise<void>;
17
26
  pageView(metadata?: PageMetadata): Promise<void>;
18
27
  purchase(payload: PurchasePayload): Promise<void>;
19
28
  click(payload: DataPayload): Promise<void>;
@@ -12,7 +12,7 @@ function combineSinks(sinks) {
12
12
  sinks.map(async (sink) => {
13
13
  try {
14
14
  const sinkMethod = sink[method];
15
- await sinkMethod.apply(sink, args);
15
+ await sinkMethod?.apply(sink, args);
16
16
  } catch {
17
17
  if (typeof window !== "undefined") {
18
18
  console.warn("[analytics] sink error", { method });
@@ -22,6 +22,7 @@ function combineSinks(sinks) {
22
22
  );
23
23
  };
24
24
  return {
25
+ customEvent: (...args) => broadcast("customEvent", ...args),
25
26
  pageView: (...args) => broadcast("pageView", ...args),
26
27
  purchase: (...args) => broadcast("purchase", ...args),
27
28
  click: (...args) => broadcast("click", ...args),
@@ -0,0 +1,29 @@
1
+ import type { AnalyticsSink, CustomEventPayload, DataPayload, PurchasePayload } from "../analytics-sink";
2
+ import type { PageMetadata } from "../utils/navigation-tracker";
3
+ export type IntelligemsEventMappings = {
4
+ [Method in Exclude<keyof AnalyticsSink, "customEvent">]?: (...args: Parameters<AnalyticsSink[Method]>) => CustomEventPayload | null;
5
+ };
6
+ /**
7
+ * Sends explicitly selected custom goals through the installed Intelligems SDK.
8
+ * Native page views, experiment assignment, and order attribution remain owned
9
+ * by useIgTrack and Shopify; duplicating them as custom events changes reports.
10
+ */
11
+ export declare class IntelligemsSink implements AnalyticsSink {
12
+ private readonly options;
13
+ constructor(options: {
14
+ /** Read live SDK readiness; do not accept events before assignment resolves. */
15
+ isReady: () => boolean;
16
+ events?: IntelligemsEventMappings;
17
+ });
18
+ customEvent(payload: CustomEventPayload): Promise<void>;
19
+ private send;
20
+ pageView(metadata?: PageMetadata): Promise<void>;
21
+ purchase(payload: PurchasePayload): Promise<void>;
22
+ click(payload: DataPayload): Promise<void>;
23
+ productViewed(payload: DataPayload): Promise<void>;
24
+ addToCart(payload: DataPayload): Promise<void>;
25
+ removeFromCart(payload: DataPayload): Promise<void>;
26
+ viewCart(payload: DataPayload): Promise<void>;
27
+ startCheckout(payload: DataPayload): Promise<void>;
28
+ viewCollection(payload: DataPayload): Promise<void>;
29
+ }
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { consentStore, isConsentAllowed } from "../../consent/consent-store";
3
+ class IntelligemsSink {
4
+ constructor(options) {
5
+ this.options = options;
6
+ }
7
+ async customEvent(payload) {
8
+ if (typeof window === "undefined" || !this.options.isReady()) {
9
+ return;
10
+ }
11
+ if (!isConsentAllowed({
12
+ state: consentStore.getSnapshot(),
13
+ requiredConsent: ["analytics"]
14
+ })) {
15
+ return;
16
+ }
17
+ if (!payload.event.trim() || /[,']/.test(payload.event) || payload.event !== payload.event.trim()) {
18
+ throw new IntelligemsEventNameError(
19
+ "Intelligems event names must be nonempty, trimmed, and contain no commas or single quotes"
20
+ );
21
+ }
22
+ if (!("igEvents" in window)) {
23
+ return;
24
+ }
25
+ const transport = window.igEvents;
26
+ if (!transport || typeof transport !== "object" || Array.isArray(transport) || !("push" in transport) || typeof transport.push !== "function") {
27
+ return;
28
+ }
29
+ await transport.push(payload);
30
+ }
31
+ async send(payload) {
32
+ if (payload) {
33
+ await this.customEvent(payload);
34
+ }
35
+ }
36
+ async pageView(metadata) {
37
+ await this.send(this.options.events?.pageView?.(metadata));
38
+ }
39
+ async purchase(payload) {
40
+ await this.send(this.options.events?.purchase?.(payload));
41
+ }
42
+ async click(payload) {
43
+ await this.send(this.options.events?.click?.(payload));
44
+ }
45
+ async productViewed(payload) {
46
+ await this.send(this.options.events?.productViewed?.(payload));
47
+ }
48
+ async addToCart(payload) {
49
+ await this.send(this.options.events?.addToCart?.(payload));
50
+ }
51
+ async removeFromCart(payload) {
52
+ await this.send(this.options.events?.removeFromCart?.(payload));
53
+ }
54
+ async viewCart(payload) {
55
+ await this.send(this.options.events?.viewCart?.(payload));
56
+ }
57
+ async startCheckout(payload) {
58
+ await this.send(this.options.events?.startCheckout?.(payload));
59
+ }
60
+ async viewCollection(payload) {
61
+ await this.send(this.options.events?.viewCollection?.(payload));
62
+ }
63
+ }
64
+ class IntelligemsEventNameError extends Error {
65
+ }
66
+ export {
67
+ IntelligemsSink
68
+ };
@@ -28,6 +28,10 @@ export declare function buyNowWithRecoveryAction(input: {
28
28
  properties: CartLineAttribute[];
29
29
  }[];
30
30
  discountCodes: string[];
31
+ attributes?: {
32
+ key: string;
33
+ value: string;
34
+ }[];
31
35
  pageUrl: string;
32
36
  }): Promise<{
33
37
  result: "created";
@@ -122,7 +122,8 @@ async function buyNowWithRecoveryAction(input) {
122
122
  lines: cartLines,
123
123
  skipStoringCart: true,
124
124
  country: input.country,
125
- discountCodes: discountCodes.length > 0 ? discountCodes : void 0
125
+ discountCodes: discountCodes.length > 0 ? discountCodes : void 0,
126
+ attributes: input.attributes
126
127
  });
127
128
  if (!cart?.checkoutUrl) {
128
129
  throw new BuyNowCartCreationError({
@@ -32,6 +32,10 @@ export declare function createCartAction(options?: {
32
32
  lines?: CartLinePayload[];
33
33
  skipStoringCart?: boolean;
34
34
  discountCodes?: string[];
35
+ attributes?: {
36
+ key: string;
37
+ value: string;
38
+ }[];
35
39
  country?: StorefrontCountryCode;
36
40
  }): Promise<Cart | null>;
37
41
  /**
@@ -65,6 +65,7 @@ async function createCartAction(options) {
65
65
  lines,
66
66
  skipStoringCart = false,
67
67
  discountCodes,
68
+ attributes,
68
69
  country
69
70
  } = options ?? {};
70
71
  const checkoutProvider = await getCheckoutProvider();
@@ -72,6 +73,7 @@ async function createCartAction(options) {
72
73
  const cart = await cartGateway.createCart({
73
74
  lines,
74
75
  discountCodes,
76
+ attributes,
75
77
  buyerIdentity: country ? { country } : void 0
76
78
  });
77
79
  if (cart && !skipStoringCart) {
@@ -66,6 +66,8 @@ export interface CartContextTypeInternal {
66
66
  * For buy now functionality, use useBuyNow.
67
67
  */
68
68
  export interface CartContextType {
69
+ /** Current cart ID for integrations that track checkout attribution. */
70
+ cartId: string | null;
69
71
  /** Total number of items in cart */
70
72
  itemsCount: number;
71
73
  /** Whether the cart UI is open */
@@ -675,6 +675,7 @@ function useCart() {
675
675
  });
676
676
  }
677
677
  return {
678
+ cartId: context.cartData?.id ?? null,
678
679
  itemsCount: context.itemsCount,
679
680
  isCartOpen: context.isCartOpen,
680
681
  openCart: context.openCart,
@@ -11,6 +11,10 @@ export interface CartGateway {
11
11
  createCart(options?: {
12
12
  lines?: CartLinePayload[];
13
13
  discountCodes?: string[];
14
+ attributes?: {
15
+ key: string;
16
+ value: string;
17
+ }[];
14
18
  buyerIdentity?: CartBuyerIdentity;
15
19
  }): Promise<Cart | null>;
16
20
  addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;
@@ -11,6 +11,7 @@ class ResolverCartGateway {
11
11
  async createCart({
12
12
  lines,
13
13
  discountCodes,
14
+ attributes,
14
15
  buyerIdentity
15
16
  } = {}) {
16
17
  try {
@@ -26,6 +27,7 @@ class ResolverCartGateway {
26
27
  sellingPlanId: line.sellingPlanId
27
28
  })),
28
29
  discountCodes,
30
+ attributes,
29
31
  country: buyerIdentity?.country
30
32
  });
31
33
  } catch (error) {
@@ -81,5 +81,10 @@ export interface VariantBuyNowInfo {
81
81
  export declare function useBuyNow(): {
82
82
  buyNow: (variants: VariantBuyNowInfo[], options?: {
83
83
  discountCodes?: string[];
84
+ /** Order-level Shopify cart attributes, including experiment attribution. */
85
+ attributes?: {
86
+ key: string;
87
+ value: string;
88
+ }[];
84
89
  }) => Promise<BuyNowOutcome>;
85
90
  };
@@ -35,6 +35,7 @@ function useBuyNow() {
35
35
  properties: variant.properties ?? []
36
36
  })),
37
37
  discountCodes: options?.discountCodes ?? [],
38
+ attributes: options?.attributes ?? [],
38
39
  pageUrl: window.location.href
39
40
  };
40
41
  const attemptStorageKey = `replo-checkout-attempt:${JSON.stringify(checkoutInput)}`;
@@ -72,6 +72,10 @@ declare class CanopyApiGateway {
72
72
  sellingPlanId?: string | null;
73
73
  }[];
74
74
  discountCodes?: string[];
75
+ attributes?: {
76
+ key: string;
77
+ value: string;
78
+ }[];
75
79
  country?: StorefrontCountryCode;
76
80
  }): Promise<Cart>;
77
81
  cartAddLines({ cartId, lines, }: {
@@ -195,6 +195,7 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
195
195
  return this._cartPost("/create", {
196
196
  lines: options?.lines,
197
197
  discountCodes: options?.discountCodes,
198
+ attributes: options?.attributes,
198
199
  country: options?.country
199
200
  });
200
201
  }
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "branch": "HEAD",
5
- "commit": "3759f7d83c0c4991b7ccbafed0d1b55da1ed48b4",
6
- "builtAt": "2026-10-09T21:52:34.432Z"
5
+ "commit": "702e4d053e7d73e648c62dce85f5d14634d87bd8",
6
+ "builtAt": "2026-10-09T23:28:16.932Z"
7
7
  };
@@ -10,6 +10,7 @@
10
10
  * `<integration>.<objectType>_<qualifier>`.
11
11
  */
12
12
  export declare const DATA_LOADER_KEYS: {
13
+ readonly SHOPIFY_INTEGRATION_DETAILS: "shopify_storefront.integration_details";
13
14
  readonly SHOPIFY_PRODUCT: "shopify_storefront.product";
14
15
  readonly SHOPIFY_COLLECTION: "shopify_storefront.collection";
15
16
  readonly SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products";
@@ -1,4 +1,5 @@
1
1
  const DATA_LOADER_KEYS = {
2
+ SHOPIFY_INTEGRATION_DETAILS: "shopify_storefront.integration_details",
2
3
  SHOPIFY_PRODUCT: "shopify_storefront.product",
3
4
  SHOPIFY_COLLECTION: "shopify_storefront.collection",
4
5
  SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products",
@@ -1098,3 +1098,8 @@ export type ShopifyKachingBundlesLoaderOutput = z.infer<typeof shopifyKachingBun
1098
1098
  export type StorefrontMetaobject = z.infer<typeof storefrontMetaobjectSchema>;
1099
1099
  export type ShopifyMetaobjectLoaderOutput = z.infer<typeof shopifyMetaobjectLoaderOutputSchema>;
1100
1100
  export type ShopifyMetaobjectsLoaderOutput = z.infer<typeof shopifyMetaobjectsLoaderOutputSchema>;
1101
+ export declare const shopifyIntegrationDetailsLoaderOutputSchema: z.ZodObject<{
1102
+ shopifyDomain: z.ZodString;
1103
+ storefrontAccessToken: z.ZodString;
1104
+ }, z.core.$strip>;
1105
+ export type ShopifyIntegrationDetails = z.infer<typeof shopifyIntegrationDetailsLoaderOutputSchema>;
@@ -362,6 +362,14 @@ const shopifyMetaobjectsLoaderOutputSchema = z.object({
362
362
  const shopifyKachingBundlesLoaderOutputSchema = z.object({
363
363
  bundles: z.record(z.string(), z.unknown()).nullable()
364
364
  }).describe("ShopifyKachingBundles");
365
+ const shopifyIntegrationDetailsLoaderOutputSchema = z.object({
366
+ shopifyDomain: z.string().min(1).describe(
367
+ "The connected store's myshopify.com domain, without a protocol."
368
+ ),
369
+ storefrontAccessToken: z.string().min(1).describe(
370
+ "Public Shopify Storefront API token intended for browser use. Never an Admin API or private Storefront token."
371
+ )
372
+ });
365
373
  export {
366
374
  contentfulAssetObjectSchema,
367
375
  contentfulEntriesLoaderOutputSchema,
@@ -400,6 +408,7 @@ export {
400
408
  sellingPlanOptionValueSchema,
401
409
  sellingPlanPriceAdjustmentSchema,
402
410
  sellingPlanSchema,
411
+ shopifyIntegrationDetailsLoaderOutputSchema,
403
412
  shopifyKachingBundlesLoaderOutputSchema,
404
413
  shopifyMetaobjectLoaderOutputSchema,
405
414
  shopifyMetaobjectsLoaderOutputSchema,
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ShopifyIntegrationDetails } from "./loader-schemas";
3
+ type ShopifyIntegrationDetailsLoaderProps = {
4
+ loaderKey: string;
5
+ children: (data: ShopifyIntegrationDetails) => ReactNode;
6
+ fallback?: ReactNode;
7
+ };
8
+ export declare function ShopifyIntegrationDetailsLoader({ loaderKey, children, fallback, }: ShopifyIntegrationDetailsLoaderProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { Fragment, jsx } from "react/jsx-runtime";
3
+ import { Suspense } from "react";
4
+ import { useSuspenseQuery } from "@tanstack/react-query";
5
+ import { invokeLoader } from "./invoke-loader";
6
+ import { LoaderErrorBoundary } from "./loader-error-boundary";
7
+ import { shopifyIntegrationDetailsLoaderOutputSchema } from "./loader-schemas";
8
+ function ShopifyIntegrationDetailsLoaderInner({
9
+ loaderKey,
10
+ children
11
+ }) {
12
+ const { data } = useSuspenseQuery({
13
+ queryKey: [loaderKey, {}],
14
+ queryFn: async () => {
15
+ const loadedData = await invokeLoader({
16
+ loaderKey,
17
+ args: {}
18
+ });
19
+ return shopifyIntegrationDetailsLoaderOutputSchema.parse(loadedData);
20
+ }
21
+ });
22
+ return /* @__PURE__ */ jsx(Fragment, { children: children(data) });
23
+ }
24
+ function ShopifyIntegrationDetailsLoader({
25
+ loaderKey,
26
+ children,
27
+ fallback
28
+ }) {
29
+ return /* @__PURE__ */ jsx(LoaderErrorBoundary, { fallback: fallback ?? null, resetKey: loaderKey, children: /* @__PURE__ */ jsx(Suspense, { fallback: fallback ?? null, children: /* @__PURE__ */ jsx(ShopifyIntegrationDetailsLoaderInner, { loaderKey, children }) }) });
30
+ }
31
+ export {
32
+ ShopifyIntegrationDetailsLoader
33
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.15.0",
6
+ "version": "1.16.0",
7
7
  "branch": "HEAD",
8
- "commit": "3759f7d83c0c4991b7ccbafed0d1b55da1ed48b4",
9
- "builtAt": "2026-10-09T21:52:34.432Z"
8
+ "commit": "702e4d053e7d73e648c62dce85f5d14634d87bd8",
9
+ "builtAt": "2026-10-09T23:28:16.932Z"
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",
@@ -85,6 +85,10 @@
85
85
  "types": "./analytics/analytics-provider.d.ts",
86
86
  "default": "./analytics/analytics-provider.js"
87
87
  },
88
+ "./analytics/sinks/intelligems-sink": {
89
+ "types": "./analytics/sinks/intelligems-sink.d.ts",
90
+ "default": "./analytics/sinks/intelligems-sink.js"
91
+ },
88
92
  "./analytics/hooks/use-product-viewed-analytics": {
89
93
  "types": "./analytics/hooks/use-product-viewed-analytics.d.ts",
90
94
  "default": "./analytics/hooks/use-product-viewed-analytics.js"
@@ -165,6 +169,10 @@
165
169
  "types": "./loaders/reviews-io-reviews-loader.d.ts",
166
170
  "default": "./loaders/reviews-io-reviews-loader.js"
167
171
  },
172
+ "./loaders/shopify-integration-details-loader": {
173
+ "types": "./loaders/shopify-integration-details-loader.d.ts",
174
+ "default": "./loaders/shopify-integration-details-loader.js"
175
+ },
168
176
  "./loaders/smile-earning-rules-loader": {
169
177
  "types": "./loaders/smile-earning-rules-loader.d.ts",
170
178
  "default": "./loaders/smile-earning-rules-loader.js"