@persona/layout-editor-remote-module 0.0.0-bootstrap.0 → 0.5.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,211 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/bridge/origin.ts
4
+ const dashboardHostOriginPattern = /^https:\/\/([a-z0-9-]+\.)*withpersona(-staging)?\.com$/;
5
+ const pagesHostOriginPattern = /^https:\/\/[a-z0-9-]+\.persona-web\.pages\.dev$/;
6
+ const coderHostOriginPattern = /^https:\/\/[a-z0-9-]+\.coder\.development\.personerds\.com$/;
7
+ const localHostOriginPattern = /^http:\/\/(?:localhost|127\.0\.0\.1):\d+$/;
8
+ function personaDashboardHostOrigins(origin) {
9
+ return dashboardHostOriginPattern.test(origin) || pagesHostOriginPattern.test(origin) || coderHostOriginPattern.test(origin) || localHostOriginPattern.test(origin);
10
+ }
11
+ function resolveAllowedHostOrigins(allowedHostOrigins) {
12
+ if (allowedHostOrigins === void 0) return personaDashboardHostOrigins;
13
+ if (typeof allowedHostOrigins === "function") return allowedHostOrigins;
14
+ return (origin) => allowedHostOrigins.some((allowedOrigin) => typeof allowedOrigin === "string" ? allowedOrigin === origin : allowedOrigin.test(origin));
15
+ }
16
+ function candidateParentOrigin() {
17
+ if (typeof window === "undefined") return;
18
+ try {
19
+ const ancestorOrigin = window.location.ancestorOrigins?.[0];
20
+ if (ancestorOrigin) return ancestorOrigin;
21
+ } catch {}
22
+ try {
23
+ return document.referrer ? new URL(document.referrer).origin : void 0;
24
+ } catch {
25
+ return;
26
+ }
27
+ }
28
+ function assertNotSelfOrigin(moduleOrigin) {
29
+ if (typeof window !== "undefined" && moduleOrigin === window.location.origin) throw new Error("Remote module origin must not match the host origin");
30
+ }
31
+
32
+ //#endregion
33
+ //#region src/schema.ts
34
+ /**
35
+ * Mirrors persona-web's `LAYOUT_COLUMN_TOTAL`
36
+ * (front-end/dashboard/modules/lib/constants.ts). The enum schemas below
37
+ * mirror persona-web's corresponding enum values and are kept in sync by hand;
38
+ * this package has no dependency on persona-web.
39
+ */
40
+ const MAX_GRID_COLUMNS = 12;
41
+ const layoutTypeSchema = z.enum([
42
+ "account",
43
+ "case",
44
+ "transaction"
45
+ ]);
46
+ const moduleCategorySchema = z.enum([
47
+ "build_your_own",
48
+ "components",
49
+ "management",
50
+ "related_records"
51
+ ]);
52
+ const moduleBadgeSchema = z.enum([
53
+ "beta",
54
+ "deprecated",
55
+ "improved",
56
+ "new"
57
+ ]);
58
+ /**
59
+ * A manifest `key` is embedded verbatim into a persisted module identity
60
+ * string (`${sourceId}/${key}`) and used in DOM ids and analytics — restrict it
61
+ * to a safe, stable charset up front rather than discovering the hard way that
62
+ * some producer emitted spaces or slashes.
63
+ */
64
+ const moduleKeySchema = z.string().min(1).regex(/^[a-z0-9_]+$/, "key must be lowercase alphanumeric with underscores only");
65
+ const gridSizeSchema = z.object({
66
+ height: z.number().int().min(1),
67
+ width: z.number().int().min(1).max(MAX_GRID_COLUMNS)
68
+ });
69
+ /**
70
+ * `code` (a chunk URL, direct-`import()`ed) and `document` (a page URL, host
71
+ * iframes it instead) are mutually exclusive — a delivery is one or the
72
+ * other, never both, never neither.
73
+ */
74
+ const remoteDeliverySchema = z.union([z.object({ code: z.string().min(1) }), z.object({ document: z.string().min(1) })]);
75
+ const remoteEntrySchema = z.object({
76
+ css: z.array(z.string()).optional(),
77
+ render: remoteDeliverySchema
78
+ });
79
+ /**
80
+ * Loose check shared by `configSchema` and `uiConfigSchema` — this validates
81
+ * the manifest envelope, not the arbitrary object either one carries. Both
82
+ * are trusted structurally (an object) and used for their stated purpose
83
+ * (JSON Schema validation, RJSF ui hints) by the consumer, not re-validated
84
+ * here.
85
+ */
86
+ const looseObjectSchema = z.record(z.string(), z.unknown());
87
+ const remoteModuleRegistrationManifestV1Schema = z.object({
88
+ allowAddingToLayout: z.boolean(),
89
+ allowDynamicHeight: z.boolean().optional(),
90
+ badge: moduleBadgeSchema.optional(),
91
+ category: moduleCategorySchema.optional(),
92
+ configSchema: looseObjectSchema.optional(),
93
+ defaultSize: z.partialRecord(layoutTypeSchema, gridSizeSchema),
94
+ descriptionShort: z.string().min(1),
95
+ displayName: z.string().min(1),
96
+ icon: z.string().min(1).optional(),
97
+ internalUsersOnly: z.boolean().optional(),
98
+ key: moduleKeySchema,
99
+ layouts: z.array(layoutTypeSchema).default([]),
100
+ manifestVersion: z.literal("v1"),
101
+ remote: remoteEntrySchema,
102
+ uiConfigSchema: looseObjectSchema.optional()
103
+ }).refine((manifest) => manifest.layouts.every((layout) => manifest.defaultSize[layout]), {
104
+ message: "defaultSize must have an entry for every layout in `layouts`",
105
+ path: ["defaultSize"]
106
+ });
107
+
108
+ //#endregion
109
+ //#region src/bridge/protocol.ts
110
+ const SOURCE = "persona-layout-module";
111
+ const PROTOCOL_VERSION = 1;
112
+ const jsonSchema = z.lazy(() => z.union([
113
+ z.boolean(),
114
+ z.null(),
115
+ z.number(),
116
+ z.string(),
117
+ z.array(jsonSchema),
118
+ z.record(z.string(), jsonSchema)
119
+ ]));
120
+ const envelopeSchema = {
121
+ source: z.literal(SOURCE),
122
+ protocolVersion: z.literal(PROTOCOL_VERSION)
123
+ };
124
+ const requestIdSchema = z.string().min(1);
125
+ const variableMapSchema = z.record(z.string(), z.union([z.string(), z.number()]));
126
+ const readySchema = z.object({
127
+ ...envelopeSchema,
128
+ type: z.literal("ready")
129
+ }).strict();
130
+ const requestDataSchema = z.object({
131
+ ...envelopeSchema,
132
+ type: z.literal("request-data"),
133
+ requestId: requestIdSchema
134
+ }).strict();
135
+ const setHeightSchema = z.object({
136
+ ...envelopeSchema,
137
+ type: z.literal("set-height"),
138
+ height: z.number()
139
+ }).strict();
140
+ const renderErrorSchema = z.object({
141
+ ...envelopeSchema,
142
+ type: z.literal("render-error"),
143
+ message: z.string()
144
+ }).strict();
145
+ const resolveDataPathSchema = z.object({
146
+ ...envelopeSchema,
147
+ type: z.literal("resolve-data-path"),
148
+ requestId: requestIdSchema,
149
+ dataPath: z.string(),
150
+ variableMap: variableMapSchema.optional()
151
+ }).strict();
152
+ /**
153
+ * Runtime facts about where the module is rendered, owned by the host and not
154
+ * derivable by the module itself. Distinct from `data`, which is the module's
155
+ * persisted config.
156
+ */
157
+ const remoteModuleContextSchema = z.object({
158
+ layoutType: layoutTypeSchema,
159
+ layoutObjectId: z.string().nullable(),
160
+ isReadOnly: z.boolean(),
161
+ isInEditor: z.boolean(),
162
+ isUsingStubData: z.boolean()
163
+ }).strict();
164
+ const moduleToHostMessageSchema = z.discriminatedUnion("type", [
165
+ readySchema,
166
+ requestDataSchema,
167
+ setHeightSchema,
168
+ renderErrorSchema,
169
+ resolveDataPathSchema
170
+ ]);
171
+ const initSchema = z.object({
172
+ ...envelopeSchema,
173
+ type: z.literal("init"),
174
+ data: jsonSchema,
175
+ context: remoteModuleContextSchema
176
+ }).strict();
177
+ const dataSchema = z.object({
178
+ ...envelopeSchema,
179
+ type: z.literal("data"),
180
+ requestId: requestIdSchema.optional(),
181
+ data: jsonSchema
182
+ }).strict();
183
+ const contextSchema = z.object({
184
+ ...envelopeSchema,
185
+ type: z.literal("context"),
186
+ context: remoteModuleContextSchema
187
+ }).strict();
188
+ const dataPathResultSchema = z.object({
189
+ ...envelopeSchema,
190
+ type: z.literal("data-path-result"),
191
+ requestId: requestIdSchema,
192
+ result: jsonSchema.optional(),
193
+ error: z.string().optional()
194
+ }).strict().refine(({ result, error }) => result === void 0 !== (error === void 0));
195
+ const hostToModuleMessageSchema = z.discriminatedUnion("type", [
196
+ initSchema,
197
+ dataSchema,
198
+ contextSchema,
199
+ dataPathResultSchema
200
+ ]);
201
+ function parseModuleToHostMessage(data) {
202
+ const parsed = moduleToHostMessageSchema.safeParse(data);
203
+ return parsed.success ? parsed.data : null;
204
+ }
205
+ function parseHostToModuleMessage(data) {
206
+ const parsed = hostToModuleMessageSchema.safeParse(data);
207
+ return parsed.success ? parsed.data : null;
208
+ }
209
+
210
+ //#endregion
211
+ 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,105 @@
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 persona-web's `LAYOUT_COLUMN_TOTAL`
32
+ * (front-end/dashboard/modules/lib/constants.ts). The enum schemas below
33
+ * mirror persona-web's corresponding enum values and are kept in sync by hand;
34
+ * this package has no dependency on persona-web.
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
+ 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-Dt8sbrVO.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
+ * persona-web can intake.
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 };