@persona/layout-editor-remote-module 0.0.0-bootstrap.0 → 0.5.1

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,207 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/bridge/origin.ts
4
+ const dashboardHostOriginPattern = /^https:\/\/([a-z0-9-]+\.)*withpersona(-staging)?\.com$/;
5
+ const localHostOriginPattern = /^http:\/\/(?:localhost|127\.0\.0\.1):\d+$/;
6
+ function personaDashboardHostOrigins(origin) {
7
+ return dashboardHostOriginPattern.test(origin) || localHostOriginPattern.test(origin);
8
+ }
9
+ function resolveAllowedHostOrigins(allowedHostOrigins) {
10
+ if (allowedHostOrigins === void 0) return personaDashboardHostOrigins;
11
+ if (typeof allowedHostOrigins === "function") return allowedHostOrigins;
12
+ return (origin) => allowedHostOrigins.some((allowedOrigin) => typeof allowedOrigin === "string" ? allowedOrigin === origin : allowedOrigin.test(origin));
13
+ }
14
+ function candidateParentOrigin() {
15
+ if (typeof window === "undefined") return;
16
+ try {
17
+ const ancestorOrigin = window.location.ancestorOrigins?.[0];
18
+ if (ancestorOrigin) return ancestorOrigin;
19
+ } catch {}
20
+ try {
21
+ return document.referrer ? new URL(document.referrer).origin : void 0;
22
+ } catch {
23
+ return;
24
+ }
25
+ }
26
+ function assertNotSelfOrigin(moduleOrigin) {
27
+ if (typeof window !== "undefined" && moduleOrigin === window.location.origin) throw new Error("Remote module origin must not match the host origin");
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/schema.ts
32
+ /**
33
+ * Mirrors the host's layout grid column count; the enum schemas below must
34
+ * match the host's values.
35
+ */
36
+ const MAX_GRID_COLUMNS = 12;
37
+ const layoutTypeSchema = z.enum([
38
+ "account",
39
+ "case",
40
+ "transaction"
41
+ ]);
42
+ const moduleCategorySchema = z.enum([
43
+ "build_your_own",
44
+ "components",
45
+ "management",
46
+ "related_records"
47
+ ]);
48
+ const moduleBadgeSchema = z.enum([
49
+ "beta",
50
+ "deprecated",
51
+ "improved",
52
+ "new"
53
+ ]);
54
+ /**
55
+ * A manifest `key` is embedded verbatim into a persisted module identity
56
+ * string (`${sourceId}/${key}`) and used in DOM ids and analytics — restrict it
57
+ * to a safe, stable charset up front rather than discovering the hard way that
58
+ * some producer emitted spaces or slashes.
59
+ */
60
+ const moduleKeySchema = z.string().min(1).regex(/^[a-z0-9_]+$/, "key must be lowercase alphanumeric with underscores only");
61
+ const gridSizeSchema = z.object({
62
+ height: z.number().int().min(1),
63
+ width: z.number().int().min(1).max(MAX_GRID_COLUMNS)
64
+ });
65
+ /**
66
+ * `code` (a chunk URL, direct-`import()`ed) and `document` (a page URL, host
67
+ * iframes it instead) are mutually exclusive — a delivery is one or the
68
+ * other, never both, never neither.
69
+ */
70
+ const remoteDeliverySchema = z.union([z.object({ code: z.string().min(1) }), z.object({ document: z.string().min(1) })]);
71
+ const remoteEntrySchema = z.object({
72
+ css: z.array(z.string()).optional(),
73
+ render: remoteDeliverySchema
74
+ });
75
+ /**
76
+ * Loose check shared by `configSchema` and `uiConfigSchema` — this validates
77
+ * the manifest envelope, not the arbitrary object either one carries. Both
78
+ * are trusted structurally (an object) and used for their stated purpose
79
+ * (JSON Schema validation, RJSF ui hints) by the consumer, not re-validated
80
+ * here.
81
+ */
82
+ const looseObjectSchema = z.record(z.string(), z.unknown());
83
+ const remoteModuleRegistrationManifestV1Schema = z.object({
84
+ allowAddingToLayout: z.boolean(),
85
+ allowDynamicHeight: z.boolean().optional(),
86
+ badge: moduleBadgeSchema.optional(),
87
+ category: moduleCategorySchema.optional(),
88
+ configSchema: looseObjectSchema.optional(),
89
+ defaultSize: z.partialRecord(layoutTypeSchema, gridSizeSchema),
90
+ descriptionShort: z.string().min(1),
91
+ displayName: z.string().min(1),
92
+ icon: z.string().min(1).optional(),
93
+ internalUsersOnly: z.boolean().optional(),
94
+ key: moduleKeySchema,
95
+ layouts: z.array(layoutTypeSchema).default([]),
96
+ manifestVersion: z.literal("v1"),
97
+ remote: remoteEntrySchema,
98
+ uiConfigSchema: looseObjectSchema.optional()
99
+ }).refine((manifest) => manifest.layouts.every((layout) => manifest.defaultSize[layout]), {
100
+ message: "defaultSize must have an entry for every layout in `layouts`",
101
+ path: ["defaultSize"]
102
+ });
103
+
104
+ //#endregion
105
+ //#region src/bridge/protocol.ts
106
+ const SOURCE = "persona-layout-module";
107
+ const PROTOCOL_VERSION = 1;
108
+ const jsonSchema = z.lazy(() => z.union([
109
+ z.boolean(),
110
+ z.null(),
111
+ z.number(),
112
+ z.string(),
113
+ z.array(jsonSchema),
114
+ z.record(z.string(), jsonSchema)
115
+ ]));
116
+ const envelopeSchema = {
117
+ source: z.literal(SOURCE),
118
+ protocolVersion: z.literal(PROTOCOL_VERSION)
119
+ };
120
+ const requestIdSchema = z.string().min(1);
121
+ const variableMapSchema = z.record(z.string(), z.union([z.string(), z.number()]));
122
+ const readySchema = z.object({
123
+ ...envelopeSchema,
124
+ type: z.literal("ready")
125
+ }).strict();
126
+ const requestDataSchema = z.object({
127
+ ...envelopeSchema,
128
+ type: z.literal("request-data"),
129
+ requestId: requestIdSchema
130
+ }).strict();
131
+ const setHeightSchema = z.object({
132
+ ...envelopeSchema,
133
+ type: z.literal("set-height"),
134
+ height: z.number()
135
+ }).strict();
136
+ const renderErrorSchema = z.object({
137
+ ...envelopeSchema,
138
+ type: z.literal("render-error"),
139
+ message: z.string()
140
+ }).strict();
141
+ const resolveDataPathSchema = z.object({
142
+ ...envelopeSchema,
143
+ type: z.literal("resolve-data-path"),
144
+ requestId: requestIdSchema,
145
+ dataPath: z.string(),
146
+ variableMap: variableMapSchema.optional()
147
+ }).strict();
148
+ /**
149
+ * Runtime facts about where the module is rendered, owned by the host and not
150
+ * derivable by the module itself. Distinct from `data`, which is the module's
151
+ * persisted config.
152
+ */
153
+ const remoteModuleContextSchema = z.object({
154
+ layoutType: layoutTypeSchema,
155
+ layoutObjectId: z.string().nullable(),
156
+ isReadOnly: z.boolean(),
157
+ isInEditor: z.boolean(),
158
+ isUsingStubData: z.boolean()
159
+ }).strict();
160
+ const moduleToHostMessageSchema = z.discriminatedUnion("type", [
161
+ readySchema,
162
+ requestDataSchema,
163
+ setHeightSchema,
164
+ renderErrorSchema,
165
+ resolveDataPathSchema
166
+ ]);
167
+ const initSchema = z.object({
168
+ ...envelopeSchema,
169
+ type: z.literal("init"),
170
+ data: jsonSchema,
171
+ context: remoteModuleContextSchema
172
+ }).strict();
173
+ const dataSchema = z.object({
174
+ ...envelopeSchema,
175
+ type: z.literal("data"),
176
+ requestId: requestIdSchema.optional(),
177
+ data: jsonSchema
178
+ }).strict();
179
+ const contextSchema = z.object({
180
+ ...envelopeSchema,
181
+ type: z.literal("context"),
182
+ context: remoteModuleContextSchema
183
+ }).strict();
184
+ const dataPathResultSchema = z.object({
185
+ ...envelopeSchema,
186
+ type: z.literal("data-path-result"),
187
+ requestId: requestIdSchema,
188
+ result: jsonSchema.optional(),
189
+ error: z.string().optional()
190
+ }).strict().refine(({ result, error }) => result === void 0 !== (error === void 0));
191
+ const hostToModuleMessageSchema = z.discriminatedUnion("type", [
192
+ initSchema,
193
+ dataSchema,
194
+ contextSchema,
195
+ dataPathResultSchema
196
+ ]);
197
+ function parseModuleToHostMessage(data) {
198
+ const parsed = moduleToHostMessageSchema.safeParse(data);
199
+ return parsed.success ? parsed.data : null;
200
+ }
201
+ function parseHostToModuleMessage(data) {
202
+ const parsed = hostToModuleMessageSchema.safeParse(data);
203
+ return parsed.success ? parsed.data : null;
204
+ }
205
+
206
+ //#endregion
207
+ export { assertNotSelfOrigin as a, resolveAllowedHostOrigins as c, parseModuleToHostMessage as i, SOURCE as n, candidateParentOrigin as o, parseHostToModuleMessage as r, personaDashboardHostOrigins as s, PROTOCOL_VERSION as t };
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/types.d.ts
4
+
5
+ type Json = boolean | JsonObject | JsonValue[] | null | number | string;
6
+ type JsonObject = {
7
+ [key: string]: Json;
8
+ };
9
+ type JsonValue = Json;
10
+ //#endregion
11
+ //#region src/bridge/protocol.d.ts
12
+ /**
13
+ * Runtime facts about where the module is rendered, owned by the host and not
14
+ * derivable by the module itself. Distinct from `data`, which is the module's
15
+ * persisted config.
16
+ */
17
+ declare const remoteModuleContextSchema: z.ZodObject<{
18
+ layoutType: z.ZodEnum<{
19
+ account: "account";
20
+ case: "case";
21
+ transaction: "transaction";
22
+ }>;
23
+ layoutObjectId: z.ZodNullable<z.ZodString>;
24
+ isReadOnly: z.ZodBoolean;
25
+ isInEditor: z.ZodBoolean;
26
+ isUsingStubData: z.ZodBoolean;
27
+ }, z.core.$strict>;
28
+ type RemoteModuleContext = z.infer<typeof remoteModuleContextSchema>;
29
+ //#endregion
30
+ export { Json as n, RemoteModuleContext as t };
@@ -0,0 +1,103 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/define.ts
4
+ /**
5
+ * Identity function at runtime — it exists to give authors a typed,
6
+ * discoverable way to build a `personaLayoutModules({ modules: [...] })`
7
+ * entry, nothing more. All real work (path resolution, build-graph
8
+ * injection, schema conversion, manifest emission) happens in the Vite
9
+ * plugin, not here.
10
+ */
11
+ function defineLayoutModule(input) {
12
+ return input;
13
+ }
14
+
15
+ //#endregion
16
+ //#region src/manifest-path.ts
17
+ /**
18
+ * Default path (site-root-relative) the manifest document is served from and
19
+ * written to, when `PersonaLayoutModulesOptions.manifestPath` isn't
20
+ * overridden. A standalone module (rather than living in vite-plugin.ts, the
21
+ * one place that actually uses it) so it can also be re-exported from the
22
+ * runtime-safe `.` entry — a consumer wiring up something like CORS for the
23
+ * manifest route in its own server, outside of vite.config.ts, needs this
24
+ * default without pulling in vite/esbuild.
25
+ */
26
+ const DEFAULT_MANIFEST_PATH = "/_layout_modules_manifest.json";
27
+
28
+ //#endregion
29
+ //#region src/schema.ts
30
+ /**
31
+ * Mirrors the host's layout grid column count; the enum schemas below must
32
+ * match the host's values.
33
+ */
34
+ const MAX_GRID_COLUMNS = 12;
35
+ const layoutTypeSchema = z.enum([
36
+ "account",
37
+ "case",
38
+ "transaction"
39
+ ]);
40
+ const moduleCategorySchema = z.enum([
41
+ "build_your_own",
42
+ "components",
43
+ "management",
44
+ "related_records"
45
+ ]);
46
+ const moduleBadgeSchema = z.enum([
47
+ "beta",
48
+ "deprecated",
49
+ "improved",
50
+ "new"
51
+ ]);
52
+ /**
53
+ * A manifest `key` is embedded verbatim into a persisted module identity
54
+ * string (`${sourceId}/${key}`) and used in DOM ids and analytics — restrict it
55
+ * to a safe, stable charset up front rather than discovering the hard way that
56
+ * some producer emitted spaces or slashes.
57
+ */
58
+ const moduleKeySchema = z.string().min(1).regex(/^[a-z0-9_]+$/, "key must be lowercase alphanumeric with underscores only");
59
+ const gridSizeSchema = z.object({
60
+ height: z.number().int().min(1),
61
+ width: z.number().int().min(1).max(MAX_GRID_COLUMNS)
62
+ });
63
+ /**
64
+ * `code` (a chunk URL, direct-`import()`ed) and `document` (a page URL, host
65
+ * iframes it instead) are mutually exclusive — a delivery is one or the
66
+ * other, never both, never neither.
67
+ */
68
+ const remoteDeliverySchema = z.union([z.object({ code: z.string().min(1) }), z.object({ document: z.string().min(1) })]);
69
+ const remoteEntrySchema = z.object({
70
+ css: z.array(z.string()).optional(),
71
+ render: remoteDeliverySchema
72
+ });
73
+ /**
74
+ * Loose check shared by `configSchema` and `uiConfigSchema` — this validates
75
+ * the manifest envelope, not the arbitrary object either one carries. Both
76
+ * are trusted structurally (an object) and used for their stated purpose
77
+ * (JSON Schema validation, RJSF ui hints) by the consumer, not re-validated
78
+ * here.
79
+ */
80
+ const looseObjectSchema = z.record(z.string(), z.unknown());
81
+ const remoteModuleRegistrationManifestV1Schema = z.object({
82
+ allowAddingToLayout: z.boolean(),
83
+ allowDynamicHeight: z.boolean().optional(),
84
+ badge: moduleBadgeSchema.optional(),
85
+ category: moduleCategorySchema.optional(),
86
+ configSchema: looseObjectSchema.optional(),
87
+ defaultSize: z.partialRecord(layoutTypeSchema, gridSizeSchema),
88
+ descriptionShort: z.string().min(1),
89
+ displayName: z.string().min(1),
90
+ icon: z.string().min(1).optional(),
91
+ internalUsersOnly: z.boolean().optional(),
92
+ key: moduleKeySchema,
93
+ layouts: z.array(layoutTypeSchema).default([]),
94
+ manifestVersion: z.literal("v1"),
95
+ remote: remoteEntrySchema,
96
+ uiConfigSchema: looseObjectSchema.optional()
97
+ }).refine((manifest) => manifest.layouts.every((layout) => manifest.defaultSize[layout]), {
98
+ message: "defaultSize must have an entry for every layout in `layouts`",
99
+ path: ["defaultSize"]
100
+ });
101
+
102
+ //#endregion
103
+ export { DEFAULT_MANIFEST_PATH as n, defineLayoutModule as r, remoteModuleRegistrationManifestV1Schema as t };
@@ -0,0 +1,43 @@
1
+ import { n as LayoutModuleEntry, r as defineLayoutModule, t as LayoutModuleDefinitionInput } from "./define-C-3P4CFz.mjs";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/vite-plugin.d.ts
5
+
6
+ interface PersonaLayoutModulesOptions {
7
+ /**
8
+ * Where the manifest document is emitted, relative to the site root — must
9
+ * include the full JSON file name (e.g. `/manifests/my-app.json`), not just
10
+ * a directory. Defaults to `DEFAULT_MANIFEST_PATH` (exported from the
11
+ * package's `.` entry). A leading `/` is optional and stripped if present;
12
+ * either form resolves to the same path.
13
+ */
14
+ manifestPath?: string;
15
+ /**
16
+ * Plugins the production build of `code`-delivered layout-module entries
17
+ * needs (e.g. `react()`, `tailwindcss()`) — called once. These entries are
18
+ * built via a SEPARATE, isolated `vite.build()` call (see the
19
+ * `closeBundle` hook), deliberately not part of the app's own build graph
20
+ * — see its doc comment for why — so the app's own plugin list isn't
21
+ * inherited automatically. Omit if your modules only need plain TS/JSX
22
+ * (Vite's built-in esbuild transform already handles that with no plugin).
23
+ * `document` (`.html`) entries never need this — they run through the
24
+ * app's own build graph and its own configured plugins directly.
25
+ */
26
+ modulePlugins?: () => Plugin[];
27
+ /** Every module this app registers — passed directly, not discovered. */
28
+ modules: LayoutModuleDefinitionInput[];
29
+ }
30
+ /**
31
+ * Vite plugin that takes a statically-declared list of layout modules
32
+ * (`options.modules`, authored via `defineLayoutModule` — see ./define.ts),
33
+ * wires every `code`-delivered entry into the build as an addressable ESM
34
+ * chunk, ensures every `document`-delivered (`.html`) entry is actually
35
+ * built/served, and emits a `RemoteModuleRegistrationManifestV1[]` document
36
+ * the host can consume.
37
+ *
38
+ * Modules are configured directly here, not discovered by globbing the
39
+ * source tree — a module IS whatever's in `options.modules`, full stop.
40
+ */
41
+ declare function personaLayoutModules(options: PersonaLayoutModulesOptions): Plugin;
42
+ //#endregion
43
+ export { type LayoutModuleDefinitionInput, type LayoutModuleEntry, PersonaLayoutModulesOptions, defineLayoutModule, personaLayoutModules };