UNPKG

@persona/layout-editor-remote-module

Version:

Shared manifest format, Vite plugin, and host/iframe bridge for remotely-defined dashboard layout modules in Persona dashboard apps.

103 lines (99 loc) • 3.95 kB
import { z } from "zod"; //#region src/define.ts /** * Identity function at runtime — it exists to give authors a typed, * discoverable way to build a `personaLayoutModules({ modules: [...] })` * entry, nothing more. All real work (path resolution, build-graph * injection, schema conversion, manifest emission) happens in the Vite * plugin, not here. */ function defineLayoutModule(input) { return input; } //#endregion //#region src/manifest-path.ts /** * Default path (site-root-relative) the manifest document is served from and * written to, when `PersonaLayoutModulesOptions.manifestPath` isn't * overridden. A standalone module (rather than living in vite-plugin.ts, the * one place that actually uses it) so it can also be re-exported from the * runtime-safe `.` entry — a consumer wiring up something like CORS for the * manifest route in its own server, outside of vite.config.ts, needs this * default without pulling in Vite. */ const DEFAULT_MANIFEST_PATH = "/_layout_modules_manifest.json"; //#endregion //#region src/schema.ts /** * Mirrors the host's layout grid column count; the enum schemas below must * match the host's values. */ const MAX_GRID_COLUMNS = 12; const layoutTypeSchema = z.enum([ "account", "case", "transaction" ]); const moduleCategorySchema = z.enum([ "build_your_own", "components", "management", "related_records" ]); const moduleBadgeSchema = z.enum([ "beta", "deprecated", "improved", "new" ]); /** * A manifest `key` is embedded verbatim into a persisted module identity * string (`${sourceId}/${key}`) and used in DOM ids and analytics — restrict it * to a safe, stable charset up front rather than discovering the hard way that * some producer emitted spaces or slashes. */ const moduleKeySchema = z.string().min(1).regex(/^[a-z0-9_]+$/, "key must be lowercase alphanumeric with underscores only"); const gridSizeSchema = z.object({ height: z.number().int().min(1), width: z.number().int().min(1).max(MAX_GRID_COLUMNS) }); /** * The module's main view is a page URL that the host iframes. */ const remoteDeliverySchema = z.object({ document: z.string().min(1) }); const remoteEntrySchema = z.object({ render: remoteDeliverySchema }); /** * Loose check shared by `configSchema` and `uiConfigSchema` — this validates * the manifest envelope, not the arbitrary object either one carries. Both * are trusted structurally (an object) and used for their stated purpose * (JSON Schema validation, RJSF ui hints) by the consumer, not re-validated * here. */ const looseObjectSchema = z.record(z.string(), z.unknown()); const remoteModuleRegistrationManifestV1Schema = z.object({ allowAddingToLayout: z.boolean(), allowDynamicHeight: z.boolean().optional(), badge: moduleBadgeSchema.optional(), category: moduleCategorySchema.optional(), categoryCustom: z.string().trim().min(1).max(80).optional(), configSchema: looseObjectSchema.optional(), defaultConfig: z.partialRecord(layoutTypeSchema, looseObjectSchema).optional(), defaultSize: z.partialRecord(layoutTypeSchema, gridSizeSchema), descriptionShort: z.string().min(1), displayName: z.string().min(1), icon: z.string().min(1).optional(), internalUsersOnly: z.boolean().optional(), key: moduleKeySchema, layouts: z.array(layoutTypeSchema).default([]), manifestVersion: z.literal("v1"), remote: remoteEntrySchema, uiConfigSchema: looseObjectSchema.optional() }).refine((manifest) => manifest.layouts.every((layout) => manifest.defaultSize[layout]), { message: "defaultSize must have an entry for every layout in `layouts`", path: ["defaultSize"] }).refine((manifest) => manifest.layouts.length === 0 || Object.keys(manifest.defaultConfig ?? {}).every((layout) => manifest.layouts.includes(layout)), { message: "defaultConfig has an entry for a layout not in `layouts`", path: ["defaultConfig"] }); //#endregion export { DEFAULT_MANIFEST_PATH as n, defineLayoutModule as r, remoteModuleRegistrationManifestV1Schema as t };