@replohq/sandbox-runtime 0.2.0 → 0.3.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.
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ export declare const MAX_COMPONENT_PREVIEW_PROPS_BYTES: number;
3
+ export declare const componentPreviewPropNameSchema: z.ZodString;
4
+ export declare const componentPreviewPropsSchema: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
5
+ export declare const COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID: "replo:component-preview-iframe-rpc";
6
+ export declare const COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION: 1;
7
+ export declare const websiteBuilderToComponentPreviewMethodConfig: {
8
+ setProps: {
9
+ inputSchema: z.ZodObject<{
10
+ props: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
11
+ sourceRevision: z.ZodString;
12
+ }, z.core.$strip>;
13
+ outputSchema: z.ZodVoid;
14
+ };
15
+ };
16
+ export declare const componentPreviewToWebsiteBuilderMethodConfig: {
17
+ iframeReady: {
18
+ inputSchema: z.ZodUndefined;
19
+ outputSchema: z.ZodVoid;
20
+ };
21
+ };
22
+ export declare const componentPreviewProductReferenceSchema: z.ZodObject<{
23
+ type: z.ZodLiteral<"product">;
24
+ loaderKey: z.ZodEnum<{
25
+ "shopify_storefront.product": "shopify_storefront.product";
26
+ "replo_products.product": "replo_products.product";
27
+ }>;
28
+ productId: z.ZodString;
29
+ }, z.core.$strict>;
30
+ export declare const componentPreviewPathSchema: z.ZodString;
31
+ export declare const componentPreviewSelectionSchema: z.ZodObject<{
32
+ siteId: z.ZodUUID;
33
+ componentPath: z.ZodString;
34
+ }, z.core.$strip>;
35
+ export declare const componentPreviewComponentSchema: z.ZodObject<{
36
+ path: z.ZodString;
37
+ name: z.ZodString;
38
+ }, z.core.$strip>;
39
+ export declare const componentPreviewIndexSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
40
+ status: z.ZodLiteral<"unavailable">;
41
+ }, z.core.$strip>, z.ZodObject<{
42
+ status: z.ZodLiteral<"ready">;
43
+ components: z.ZodArray<z.ZodObject<{
44
+ path: z.ZodString;
45
+ name: z.ZodString;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$strip>], "status">;
48
+ export declare const componentPreviewSourceFileSchema: z.ZodObject<{
49
+ path: z.ZodString;
50
+ content: z.ZodString;
51
+ }, z.core.$strip>;
52
+ export declare const componentPreviewSourceSchema: z.ZodObject<{
53
+ component: z.ZodObject<{
54
+ path: z.ZodString;
55
+ name: z.ZodString;
56
+ }, z.core.$strip>;
57
+ files: z.ZodArray<z.ZodObject<{
58
+ path: z.ZodString;
59
+ content: z.ZodString;
60
+ }, z.core.$strip>>;
61
+ revision: z.ZodString;
62
+ }, z.core.$strip>;
63
+ export declare const componentPreviewPreparationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
64
+ status: z.ZodLiteral<"ready">;
65
+ pathname: z.ZodString;
66
+ }, z.core.$strip>, z.ZodObject<{
67
+ status: z.ZodLiteral<"siteUpdateRequired">;
68
+ }, z.core.$strip>], "status">;
@@ -0,0 +1,97 @@
1
+ import { z } from 'zod';
2
+
3
+ // ../schemas/componentPreview.ts
4
+ var DATA_LOADER_KEYS = {
5
+ SHOPIFY_PRODUCT: "shopify_storefront.product",
6
+ SHOPIFY_COLLECTION: "shopify_storefront.collection",
7
+ SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products",
8
+ SHOPIFY_METAOBJECT: "shopify_storefront.metaobject",
9
+ SHOPIFY_METAOBJECTS: "shopify_storefront.metaobjects",
10
+ REPLO_PRODUCT: "replo_products.product",
11
+ OKENDO_PRODUCT_REVIEWS: "okendo.product_reviews",
12
+ OKENDO_PRODUCT_REVIEW_AGGREGATE: "okendo.product_review_aggregate",
13
+ REBUY_RECOMMENDATIONS: "rebuy.recommendations",
14
+ LEVANTA_PRODUCT: "levanta_products.product",
15
+ SMILE_IO_VIP_TIERS: "smile_io.vip_tiers",
16
+ SMILE_IO_EARNING_RULES: "smile_io.earning_rules",
17
+ SMILE_IO_REWARDS: "smile_io.rewards",
18
+ YOTPO_PRODUCT_REVIEWS: "yotpo.product_reviews",
19
+ YOTPO_PRODUCT_REVIEW_AGGREGATE: "yotpo.product_review_aggregate",
20
+ REVIEWS_IO_PRODUCT_REVIEWS: "reviews_io.product_reviews",
21
+ REVIEWS_IO_PRODUCT_REVIEW_AGGREGATE: "reviews_io.product_review_aggregate",
22
+ STATSIG_EXPERIMENT: "statsig.experiment",
23
+ CONTENTFUL_ENTRY: "contentful.entry",
24
+ CONTENTFUL_ENTRIES: "contentful.entries"
25
+ };
26
+ z.enum(Object.values(DATA_LOADER_KEYS));
27
+
28
+ // ../schemas/componentPreview.ts
29
+ var MAX_COMPONENT_PREVIEW_PROPS_BYTES = 16 * 1024;
30
+ var componentPreviewPropNameSchema = z.string().min(1).max(80).regex(/^[A-Za-z_$][\w$]*$/u).refine((name) => {
31
+ return !["__proto__", "constructor", "dangerouslySetInnerHTML"].includes(
32
+ name
33
+ );
34
+ }, "Unsafe prop name");
35
+ var componentPreviewPropsSchema = z.record(z.string(), z.json());
36
+ var COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID = "replo:component-preview-iframe-rpc";
37
+ var COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION = 1;
38
+ var websiteBuilderToComponentPreviewMethodConfig = {
39
+ setProps: {
40
+ inputSchema: z.object({
41
+ props: componentPreviewPropsSchema,
42
+ sourceRevision: z.string().min(1)
43
+ }),
44
+ outputSchema: z.void()
45
+ }
46
+ };
47
+ var componentPreviewToWebsiteBuilderMethodConfig = {
48
+ iframeReady: {
49
+ inputSchema: z.undefined(),
50
+ outputSchema: z.void()
51
+ }
52
+ };
53
+ var componentPreviewProductReferenceSchema = z.object({
54
+ type: z.literal("product"),
55
+ loaderKey: z.enum([
56
+ DATA_LOADER_KEYS.SHOPIFY_PRODUCT,
57
+ DATA_LOADER_KEYS.REPLO_PRODUCT
58
+ ]),
59
+ productId: z.string().min(1)
60
+ }).strict().describe("ComponentPreviewProductReference");
61
+ var componentPreviewPathSchema = z.string().regex(
62
+ /^components\/[\w$.-]+\.tsx$/u,
63
+ "Component paths must be top-level TSX files in components/"
64
+ );
65
+ var componentPreviewSelectionSchema = z.object({
66
+ siteId: z.uuid(),
67
+ componentPath: componentPreviewPathSchema
68
+ });
69
+ var componentPreviewComponentSchema = z.object({
70
+ path: componentPreviewPathSchema,
71
+ name: z.string().min(1).max(120)
72
+ }).describe("ComponentPreviewComponent");
73
+ var componentPreviewIndexSchema = z.discriminatedUnion("status", [
74
+ z.object({ status: z.literal("unavailable") }),
75
+ z.object({
76
+ status: z.literal("ready"),
77
+ components: z.array(componentPreviewComponentSchema)
78
+ })
79
+ ]).describe("ComponentPreviewIndex");
80
+ var componentPreviewSourceFileSchema = z.object({
81
+ path: z.string().min(1),
82
+ content: z.string()
83
+ }).describe("ComponentPreviewSourceFile");
84
+ var componentPreviewSourceSchema = z.object({
85
+ component: componentPreviewComponentSchema,
86
+ files: z.array(componentPreviewSourceFileSchema),
87
+ revision: z.string().min(1)
88
+ }).describe("ComponentPreviewSource");
89
+ var componentPreviewPreparationSchema = z.discriminatedUnion("status", [
90
+ z.object({
91
+ status: z.literal("ready"),
92
+ pathname: z.string().regex(/^\/[A-Za-z0-9/_-]+$/u)
93
+ }),
94
+ z.object({ status: z.literal("siteUpdateRequired") })
95
+ ]).describe("ComponentPreviewPreparation");
96
+
97
+ export { COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID, COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION, MAX_COMPONENT_PREVIEW_PROPS_BYTES, componentPreviewComponentSchema, componentPreviewIndexSchema, componentPreviewPathSchema, componentPreviewPreparationSchema, componentPreviewProductReferenceSchema, componentPreviewPropNameSchema, componentPreviewPropsSchema, componentPreviewSelectionSchema, componentPreviewSourceFileSchema, componentPreviewSourceSchema, componentPreviewToWebsiteBuilderMethodConfig, websiteBuilderToComponentPreviewMethodConfig };
@@ -0,0 +1,30 @@
1
+ export type ComponentPreviewComponent = {
2
+ path: string;
3
+ name: string;
4
+ };
5
+ export type ComponentPreviewProductReference = {
6
+ type: "product";
7
+ loaderKey: "shopify_storefront.product" | "replo_products.product";
8
+ productId: string;
9
+ };
10
+ export type ComponentPreviewIndex = {
11
+ status: "unavailable";
12
+ } | {
13
+ status: "ready";
14
+ components: ComponentPreviewComponent[];
15
+ };
16
+ export type ComponentPreviewSourceFile = {
17
+ path: string;
18
+ content: string;
19
+ };
20
+ export type ComponentPreviewSource = {
21
+ component: ComponentPreviewComponent;
22
+ files: ComponentPreviewSourceFile[];
23
+ revision: string;
24
+ };
25
+ export type ComponentPreviewPreparation = {
26
+ status: "ready";
27
+ pathname: string;
28
+ } | {
29
+ status: "siteUpdateRequired";
30
+ };
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Shared data-loader key constants. Both the integration registry and the
4
+ * dependency resolver import from here so the strings are never duplicated.
5
+ *
6
+ * These keys serve double duty: they identify which backend loader to invoke
7
+ * AND are used as React Query cache key prefixes. Loader components
8
+ * internally construct queryKey as `[loaderKey, args]`.
9
+ *
10
+ * Naming convention: `<integration>.<objectType>` or
11
+ * `<integration>.<objectType>_<qualifier>`.
12
+ */
13
+ export declare const DATA_LOADER_KEYS: {
14
+ readonly SHOPIFY_PRODUCT: "shopify_storefront.product";
15
+ readonly SHOPIFY_COLLECTION: "shopify_storefront.collection";
16
+ readonly SHOPIFY_COLLECTION_PRODUCTS: "shopify_storefront.collection_products";
17
+ readonly SHOPIFY_METAOBJECT: "shopify_storefront.metaobject";
18
+ readonly SHOPIFY_METAOBJECTS: "shopify_storefront.metaobjects";
19
+ readonly REPLO_PRODUCT: "replo_products.product";
20
+ readonly OKENDO_PRODUCT_REVIEWS: "okendo.product_reviews";
21
+ readonly OKENDO_PRODUCT_REVIEW_AGGREGATE: "okendo.product_review_aggregate";
22
+ readonly REBUY_RECOMMENDATIONS: "rebuy.recommendations";
23
+ readonly LEVANTA_PRODUCT: "levanta_products.product";
24
+ readonly SMILE_IO_VIP_TIERS: "smile_io.vip_tiers";
25
+ readonly SMILE_IO_EARNING_RULES: "smile_io.earning_rules";
26
+ readonly SMILE_IO_REWARDS: "smile_io.rewards";
27
+ readonly YOTPO_PRODUCT_REVIEWS: "yotpo.product_reviews";
28
+ readonly YOTPO_PRODUCT_REVIEW_AGGREGATE: "yotpo.product_review_aggregate";
29
+ readonly REVIEWS_IO_PRODUCT_REVIEWS: "reviews_io.product_reviews";
30
+ readonly REVIEWS_IO_PRODUCT_REVIEW_AGGREGATE: "reviews_io.product_review_aggregate";
31
+ readonly STATSIG_EXPERIMENT: "statsig.experiment";
32
+ readonly CONTENTFUL_ENTRY: "contentful.entry";
33
+ readonly CONTENTFUL_ENTRIES: "contentful.entries";
34
+ };
35
+ export type DataLoaderKey = (typeof DATA_LOADER_KEYS)[keyof typeof DATA_LOADER_KEYS];
36
+ export declare const dataLoaderKeySchema: z.ZodEnum<{
37
+ "shopify_storefront.product": "shopify_storefront.product";
38
+ "shopify_storefront.collection": "shopify_storefront.collection";
39
+ "shopify_storefront.collection_products": "shopify_storefront.collection_products";
40
+ "shopify_storefront.metaobject": "shopify_storefront.metaobject";
41
+ "shopify_storefront.metaobjects": "shopify_storefront.metaobjects";
42
+ "replo_products.product": "replo_products.product";
43
+ "okendo.product_reviews": "okendo.product_reviews";
44
+ "okendo.product_review_aggregate": "okendo.product_review_aggregate";
45
+ "rebuy.recommendations": "rebuy.recommendations";
46
+ "levanta_products.product": "levanta_products.product";
47
+ "smile_io.vip_tiers": "smile_io.vip_tiers";
48
+ "smile_io.earning_rules": "smile_io.earning_rules";
49
+ "smile_io.rewards": "smile_io.rewards";
50
+ "yotpo.product_reviews": "yotpo.product_reviews";
51
+ "yotpo.product_review_aggregate": "yotpo.product_review_aggregate";
52
+ "reviews_io.product_reviews": "reviews_io.product_reviews";
53
+ "reviews_io.product_review_aggregate": "reviews_io.product_review_aggregate";
54
+ "statsig.experiment": "statsig.experiment";
55
+ "contentful.entry": "contentful.entry";
56
+ "contentful.entries": "contentful.entries";
57
+ }>;
@@ -0,0 +1,5 @@
1
+ export declare function getComponentPreviewHref({ currentUrl, props, sourceRevision, }: {
2
+ currentUrl: string;
3
+ props: Record<string, unknown>;
4
+ sourceRevision: string;
5
+ }): string;
@@ -0,0 +1,14 @@
1
+ function getComponentPreviewHref({
2
+ currentUrl,
3
+ props,
4
+ sourceRevision
5
+ }) {
6
+ const url = new URL(currentUrl);
7
+ url.searchParams.set("props", JSON.stringify(props));
8
+ url.searchParams.set("sourceRevision", sourceRevision);
9
+ return `${url.pathname}${url.search}${url.hash}`;
10
+ }
11
+ export {
12
+ getComponentPreviewHref
13
+ };
14
+ //# sourceMappingURL=component-preview-href.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../next/component-preview-href.ts"],
4
+ "sourcesContent": ["export function getComponentPreviewHref({\n currentUrl,\n props,\n sourceRevision,\n}: {\n currentUrl: string;\n props: Record<string, unknown>;\n sourceRevision: string;\n}) {\n const url = new URL(currentUrl);\n url.searchParams.set(\"props\", JSON.stringify(props));\n url.searchParams.set(\"sourceRevision\", sourceRevision);\n return `${url.pathname}${url.search}${url.hash}`;\n}\n"],
5
+ "mappings": "AAAO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,MAAM,IAAI,IAAI,UAAU;AAC9B,MAAI,aAAa,IAAI,SAAS,KAAK,UAAU,KAAK,CAAC;AACnD,MAAI,aAAa,IAAI,kBAAkB,cAAc;AACrD,SAAO,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI;AAChD;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ export declare function ComponentPreviewNavigation(): null;
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { useMemo } from "react";
3
+ import { useRouter } from "next/navigation";
4
+ import { useIframeRpcChannel } from "../_vendor/replo-utils/react/use-iframe-rpc-channel.mjs";
5
+ import {
6
+ COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,
7
+ COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,
8
+ componentPreviewToWebsiteBuilderMethodConfig,
9
+ websiteBuilderToComponentPreviewMethodConfig
10
+ } from "../_vendor/schemas/componentPreview.mjs";
11
+ import {
12
+ createIframeRpcChannel,
13
+ createIframeRpcRemote
14
+ } from "../_vendor/schemas/iframeRpcChannel.mjs";
15
+ import { getComponentPreviewHref } from "./component-preview-href";
16
+ function ComponentPreviewNavigation() {
17
+ const router = useRouter();
18
+ useIframeRpcChannel(
19
+ useMemo(() => {
20
+ if (typeof window === "undefined" || !document.referrer) {
21
+ return null;
22
+ }
23
+ const parentOrigin = new URL(document.referrer).origin;
24
+ const rpcChannel = createIframeRpcChannel({
25
+ subscriberWindow: window,
26
+ channelIdentifier: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,
27
+ version: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,
28
+ getTargetWindow: () => {
29
+ return window.parent === window ? null : window.parent;
30
+ },
31
+ targetOrigin: parentOrigin,
32
+ isExpectedMessage: (event) => {
33
+ return event.source === window.parent && event.origin === parentOrigin;
34
+ },
35
+ outgoingMethods: componentPreviewToWebsiteBuilderMethodConfig,
36
+ incomingMethods: websiteBuilderToComponentPreviewMethodConfig,
37
+ handlers: {
38
+ setProps: ({ props, sourceRevision }) => {
39
+ const nextHref = getComponentPreviewHref({
40
+ currentUrl: window.location.href,
41
+ props,
42
+ sourceRevision
43
+ });
44
+ const currentHref = `${window.location.pathname}${window.location.search}${window.location.hash}`;
45
+ if (nextHref !== currentHref) {
46
+ router.replace(nextHref, { scroll: false });
47
+ }
48
+ }
49
+ }
50
+ });
51
+ return {
52
+ ...rpcChannel,
53
+ subscribe() {
54
+ const unsubscribe = rpcChannel.subscribe();
55
+ createIframeRpcRemote(rpcChannel).iframeReady();
56
+ return unsubscribe;
57
+ }
58
+ };
59
+ }, [router])
60
+ );
61
+ return null;
62
+ }
63
+ export {
64
+ ComponentPreviewNavigation
65
+ };
66
+ //# sourceMappingURL=component-preview-navigation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../next/component-preview-navigation.tsx"],
4
+ "sourcesContent": ["\"use client\";\n\nimport { useMemo } from \"react\";\n\nimport { useRouter } from \"next/navigation\";\n\nimport { useIframeRpcChannel } from \"replo-utils/react/use-iframe-rpc-channel\";\nimport {\n COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,\n COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,\n componentPreviewToWebsiteBuilderMethodConfig,\n websiteBuilderToComponentPreviewMethodConfig,\n} from \"schemas/componentPreview\";\nimport {\n createIframeRpcChannel,\n createIframeRpcRemote,\n} from \"schemas/iframeRpcChannel\";\n\nimport { getComponentPreviewHref } from \"./component-preview-href\";\n\nexport function ComponentPreviewNavigation() {\n const router = useRouter();\n useIframeRpcChannel(\n useMemo(() => {\n if (typeof window === \"undefined\" || !document.referrer) {\n return null;\n }\n const parentOrigin = new URL(document.referrer).origin;\n const rpcChannel = createIframeRpcChannel({\n subscriberWindow: window,\n channelIdentifier: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_ID,\n version: COMPONENT_PREVIEW_IFRAME_RPC_CHANNEL_VERSION,\n getTargetWindow: () => {\n return window.parent === window ? null : window.parent;\n },\n targetOrigin: parentOrigin,\n isExpectedMessage: (event) => {\n return (\n event.source === window.parent && event.origin === parentOrigin\n );\n },\n outgoingMethods: componentPreviewToWebsiteBuilderMethodConfig,\n incomingMethods: websiteBuilderToComponentPreviewMethodConfig,\n handlers: {\n setProps: ({ props, sourceRevision }) => {\n const nextHref = getComponentPreviewHref({\n currentUrl: window.location.href,\n props,\n sourceRevision,\n });\n const currentHref = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n if (nextHref !== currentHref) {\n router.replace(nextHref, { scroll: false });\n }\n },\n },\n });\n return {\n ...rpcChannel,\n subscribe() {\n const unsubscribe = rpcChannel.subscribe();\n createIframeRpcRemote(rpcChannel).iframeReady();\n return unsubscribe;\n },\n };\n }, [router]),\n );\n\n return null;\n}\n"],
5
+ "mappings": ";AAEA,SAAS,eAAe;AAExB,SAAS,iBAAiB;AAE1B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,+BAA+B;AAEjC,SAAS,6BAA6B;AAC3C,QAAM,SAAS,UAAU;AACzB;AAAA,IACE,QAAQ,MAAM;AACZ,UAAI,OAAO,WAAW,eAAe,CAAC,SAAS,UAAU;AACvD,eAAO;AAAA,MACT;AACA,YAAM,eAAe,IAAI,IAAI,SAAS,QAAQ,EAAE;AAChD,YAAM,aAAa,uBAAuB;AAAA,QACxC,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB,SAAS;AAAA,QACT,iBAAiB,MAAM;AACrB,iBAAO,OAAO,WAAW,SAAS,OAAO,OAAO;AAAA,QAClD;AAAA,QACA,cAAc;AAAA,QACd,mBAAmB,CAAC,UAAU;AAC5B,iBACE,MAAM,WAAW,OAAO,UAAU,MAAM,WAAW;AAAA,QAEvD;AAAA,QACA,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,QACjB,UAAU;AAAA,UACR,UAAU,CAAC,EAAE,OAAO,eAAe,MAAM;AACvC,kBAAM,WAAW,wBAAwB;AAAA,cACvC,YAAY,OAAO,SAAS;AAAA,cAC5B;AAAA,cACA;AAAA,YACF,CAAC;AACD,kBAAM,cAAc,GAAG,OAAO,SAAS,QAAQ,GAAG,OAAO,SAAS,MAAM,GAAG,OAAO,SAAS,IAAI;AAC/F,gBAAI,aAAa,aAAa;AAC5B,qBAAO,QAAQ,UAAU,EAAE,QAAQ,MAAM,CAAC;AAAA,YAC5C;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AACD,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AACV,gBAAM,cAAc,WAAW,UAAU;AACzC,gCAAsB,UAAU,EAAE,YAAY;AAC9C,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF,GAAG,CAAC,MAAM,CAAC;AAAA,EACb;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -1,5 +1,8 @@
1
1
  import type { ComponentType } from "react";
2
- export declare function createComponentPreviewPage(Component: ComponentType<Record<string, unknown>>): ({ searchParams, }: {
2
+ import type { ComponentPreviewProductReference } from "../_vendor/schemas/generated/componentPreview";
3
+ export declare function createComponentPreviewPage(Component: ComponentType<Record<string, unknown>>, options?: {
4
+ getProduct: (reference: ComponentPreviewProductReference) => Promise<unknown>;
5
+ }): ({ searchParams, }: {
3
6
  searchParams: Promise<{
4
7
  props?: string | string[];
5
8
  }>;
@@ -1,35 +1,70 @@
1
- import { jsx } from "react/jsx-runtime";
2
- const MAX_PREVIEW_PROPS_BYTES = 16 * 1024;
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import {
3
+ componentPreviewProductReferenceSchema,
4
+ componentPreviewPropsSchema,
5
+ MAX_COMPONENT_PREVIEW_PROPS_BYTES
6
+ } from "../_vendor/schemas/componentPreview.mjs";
7
+ import { DocumentReadyBeacon } from "../DocumentReadyBeacon";
8
+ import { NextErrorReporter } from "../NextErrorReporter";
9
+ import { ComponentPreviewNavigation } from "./component-preview-navigation";
3
10
  const BLOCKED_PROP_NAMES = /* @__PURE__ */ new Set([
4
11
  "__proto__",
5
12
  "constructor",
6
13
  "dangerouslySetInnerHTML"
7
14
  ]);
8
- function getPreviewProps(value) {
9
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
15
+ async function getPreviewProps({
16
+ value,
17
+ getProduct
18
+ }) {
19
+ const previewPropsResult = componentPreviewPropsSchema.safeParse(value);
20
+ if (!previewPropsResult.success) {
10
21
  return {};
11
22
  }
12
- return Object.fromEntries(
13
- Object.entries(value).filter(([name]) => !BLOCKED_PROP_NAMES.has(name))
14
- );
23
+ const previewProps = {};
24
+ for (const [name, propValue] of Object.entries(previewPropsResult.data)) {
25
+ if (BLOCKED_PROP_NAMES.has(name)) {
26
+ continue;
27
+ }
28
+ const productReferenceResult = componentPreviewProductReferenceSchema.safeParse(propValue);
29
+ if (!productReferenceResult.success) {
30
+ previewProps[name] = propValue;
31
+ continue;
32
+ }
33
+ if (getProduct) {
34
+ previewProps[name] = await getProduct(productReferenceResult.data);
35
+ }
36
+ }
37
+ return previewProps;
15
38
  }
16
- function parseProps(value) {
17
- if (typeof value !== "string" || new TextEncoder().encode(value).byteLength > MAX_PREVIEW_PROPS_BYTES) {
39
+ async function parseProps({
40
+ value,
41
+ getProduct
42
+ }) {
43
+ if (typeof value !== "string" || new TextEncoder().encode(value).byteLength > MAX_COMPONENT_PREVIEW_PROPS_BYTES) {
18
44
  return {};
19
45
  }
46
+ let parsed;
20
47
  try {
21
- const parsed = JSON.parse(value);
22
- return getPreviewProps(parsed);
48
+ parsed = JSON.parse(value);
23
49
  } catch {
24
50
  return {};
25
51
  }
52
+ return await getPreviewProps({ value: parsed, getProduct });
26
53
  }
27
- function createComponentPreviewPage(Component) {
54
+ function createComponentPreviewPage(Component, options) {
28
55
  return async function ComponentPreviewPage({
29
56
  searchParams
30
57
  }) {
31
- const props = parseProps((await searchParams).props);
32
- return /* @__PURE__ */ jsx(Component, { ...props });
58
+ const props = await parseProps({
59
+ value: (await searchParams).props,
60
+ getProduct: options?.getProduct
61
+ });
62
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
63
+ /* @__PURE__ */ jsx(ComponentPreviewNavigation, {}),
64
+ /* @__PURE__ */ jsx(DocumentReadyBeacon, {}),
65
+ /* @__PURE__ */ jsx(NextErrorReporter, {}),
66
+ /* @__PURE__ */ jsx(Component, { ...props })
67
+ ] });
33
68
  };
34
69
  }
35
70
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../next/component-preview.tsx"],
4
- "sourcesContent": ["import type { ComponentType } from \"react\";\n\nconst MAX_PREVIEW_PROPS_BYTES = 16 * 1024;\nconst BLOCKED_PROP_NAMES = new Set([\n \"__proto__\",\n \"constructor\",\n \"dangerouslySetInnerHTML\",\n]);\n\nfunction getPreviewProps(value: unknown): Record<string, unknown> {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return {};\n }\n\n return Object.fromEntries(\n Object.entries(value).filter(([name]) => !BLOCKED_PROP_NAMES.has(name)),\n );\n}\n\nfunction parseProps(value: string | string[] | undefined) {\n if (\n typeof value !== \"string\" ||\n new TextEncoder().encode(value).byteLength > MAX_PREVIEW_PROPS_BYTES\n ) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(value);\n return getPreviewProps(parsed);\n } catch {\n return {};\n }\n}\n\nexport function createComponentPreviewPage(\n Component: ComponentType<Record<string, unknown>>,\n) {\n return async function ComponentPreviewPage({\n searchParams,\n }: {\n searchParams: Promise<{ props?: string | string[] }>;\n }) {\n const props = parseProps((await searchParams).props);\n return <Component {...props} />;\n };\n}\n"],
5
- "mappings": "AA2CW;AAzCX,MAAM,0BAA0B,KAAK;AACrC,MAAM,qBAAqB,oBAAI,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,gBAAgB,OAAyC;AAChE,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AACvE,WAAO,CAAC;AAAA,EACV;AAEA,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,mBAAmB,IAAI,IAAI,CAAC;AAAA,EACxE;AACF;AAEA,SAAS,WAAW,OAAsC;AACxD,MACE,OAAO,UAAU,YACjB,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE,aAAa,yBAC7C;AACA,WAAO,CAAC;AAAA,EACV;AACA,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,KAAK;AACxC,WAAO,gBAAgB,MAAM;AAAA,EAC/B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,2BACd,WACA;AACA,SAAO,eAAe,qBAAqB;AAAA,IACzC;AAAA,EACF,GAEG;AACD,UAAM,QAAQ,YAAY,MAAM,cAAc,KAAK;AACnD,WAAO,oBAAC,aAAW,GAAG,OAAO;AAAA,EAC/B;AACF;",
4
+ "sourcesContent": ["import type { ComponentType } from \"react\";\nimport type { ComponentPreviewProductReference } from \"schemas/generated/componentPreview\";\n\nimport {\n componentPreviewProductReferenceSchema,\n componentPreviewPropsSchema,\n MAX_COMPONENT_PREVIEW_PROPS_BYTES,\n} from \"schemas/componentPreview\";\n\nimport { DocumentReadyBeacon } from \"../DocumentReadyBeacon\";\nimport { NextErrorReporter } from \"../NextErrorReporter\";\nimport { ComponentPreviewNavigation } from \"./component-preview-navigation\";\n\nconst BLOCKED_PROP_NAMES = new Set([\n \"__proto__\",\n \"constructor\",\n \"dangerouslySetInnerHTML\",\n]);\n\nasync function getPreviewProps({\n value,\n getProduct,\n}: {\n value: unknown;\n getProduct?: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n}): Promise<Record<string, unknown>> {\n const previewPropsResult = componentPreviewPropsSchema.safeParse(value);\n if (!previewPropsResult.success) {\n return {};\n }\n\n const previewProps: Record<string, unknown> = {};\n for (const [name, propValue] of Object.entries(previewPropsResult.data)) {\n if (BLOCKED_PROP_NAMES.has(name)) {\n continue;\n }\n const productReferenceResult =\n componentPreviewProductReferenceSchema.safeParse(propValue);\n if (!productReferenceResult.success) {\n previewProps[name] = propValue;\n continue;\n }\n if (getProduct) {\n previewProps[name] = await getProduct(productReferenceResult.data);\n }\n }\n return previewProps;\n}\n\nasync function parseProps({\n value,\n getProduct,\n}: {\n value: string | string[] | undefined;\n getProduct?: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n}) {\n if (\n typeof value !== \"string\" ||\n new TextEncoder().encode(value).byteLength >\n MAX_COMPONENT_PREVIEW_PROPS_BYTES\n ) {\n return {};\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return {};\n }\n return await getPreviewProps({ value: parsed, getProduct });\n}\n\nexport function createComponentPreviewPage(\n Component: ComponentType<Record<string, unknown>>,\n options?: {\n getProduct: (\n reference: ComponentPreviewProductReference,\n ) => Promise<unknown>;\n },\n) {\n return async function ComponentPreviewPage({\n searchParams,\n }: {\n searchParams: Promise<{ props?: string | string[] }>;\n }) {\n const props = await parseProps({\n value: (await searchParams).props,\n getProduct: options?.getProduct,\n });\n return (\n <>\n <ComponentPreviewNavigation />\n <DocumentReadyBeacon />\n <NextErrorReporter />\n <Component {...props} />\n </>\n );\n };\n}\n"],
5
+ "mappings": "AA8FM,mBACE,KADF;AA3FN;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAE3C,MAAM,qBAAqB,oBAAI,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,eAAe,gBAAgB;AAAA,EAC7B;AAAA,EACA;AACF,GAKqC;AACnC,QAAM,qBAAqB,4BAA4B,UAAU,KAAK;AACtE,MAAI,CAAC,mBAAmB,SAAS;AAC/B,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,eAAwC,CAAC;AAC/C,aAAW,CAAC,MAAM,SAAS,KAAK,OAAO,QAAQ,mBAAmB,IAAI,GAAG;AACvE,QAAI,mBAAmB,IAAI,IAAI,GAAG;AAChC;AAAA,IACF;AACA,UAAM,yBACJ,uCAAuC,UAAU,SAAS;AAC5D,QAAI,CAAC,uBAAuB,SAAS;AACnC,mBAAa,IAAI,IAAI;AACrB;AAAA,IACF;AACA,QAAI,YAAY;AACd,mBAAa,IAAI,IAAI,MAAM,WAAW,uBAAuB,IAAI;AAAA,IACnE;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,WAAW;AAAA,EACxB;AAAA,EACA;AACF,GAKG;AACD,MACE,OAAO,UAAU,YACjB,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE,aAC9B,mCACF;AACA,WAAO,CAAC;AAAA,EACV;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,KAAK;AAAA,EAC3B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,SAAO,MAAM,gBAAgB,EAAE,OAAO,QAAQ,WAAW,CAAC;AAC5D;AAEO,SAAS,2BACd,WACA,SAKA;AACA,SAAO,eAAe,qBAAqB;AAAA,IACzC;AAAA,EACF,GAEG;AACD,UAAM,QAAQ,MAAM,WAAW;AAAA,MAC7B,QAAQ,MAAM,cAAc;AAAA,MAC5B,YAAY,SAAS;AAAA,IACvB,CAAC;AACD,WACE,iCACE;AAAA,0BAAC,8BAA2B;AAAA,MAC5B,oBAAC,uBAAoB;AAAA,MACrB,oBAAC,qBAAkB;AAAA,MACnB,oBAAC,aAAW,GAAG,OAAO;AAAA,OACxB;AAAA,EAEJ;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replohq/sandbox-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",