@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.
196 lines (195 loc) • 7.9 kB
text/typescript
import { z } from "zod";
//#region src/schema.d.ts
declare const layoutTypeSchema: z.ZodEnum<{
account: "account";
case: "case";
transaction: "transaction";
}>;
declare const moduleCategorySchema: z.ZodEnum<{
build_your_own: "build_your_own";
components: "components";
management: "management";
related_records: "related_records";
}>;
declare const moduleBadgeSchema: z.ZodEnum<{
beta: "beta";
deprecated: "deprecated";
improved: "improved";
new: "new";
}>;
/**
* The module's main view is a page URL that the host iframes.
*/
declare const remoteDeliverySchema: z.ZodObject<{
document: z.ZodString;
}, z.core.$strip>;
declare const remoteModuleRegistrationManifestV1Schema: z.ZodObject<{
allowAddingToLayout: z.ZodBoolean;
allowDynamicHeight: z.ZodOptional<z.ZodBoolean>;
badge: z.ZodOptional<z.ZodEnum<{
beta: "beta";
deprecated: "deprecated";
improved: "improved";
new: "new";
}>>;
category: z.ZodOptional<z.ZodEnum<{
build_your_own: "build_your_own";
components: "components";
management: "management";
related_records: "related_records";
}>>;
categoryCustom: z.ZodOptional<z.ZodString>;
configSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
defaultConfig: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
account: "account";
case: "case";
transaction: "transaction";
}> & z.core.$partial, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
defaultSize: z.ZodRecord<z.ZodEnum<{
account: "account";
case: "case";
transaction: "transaction";
}> & z.core.$partial, z.ZodObject<{
height: z.ZodNumber;
width: z.ZodNumber;
}, z.core.$strip>>;
descriptionShort: z.ZodString;
displayName: z.ZodString;
icon: z.ZodOptional<z.ZodString>;
internalUsersOnly: z.ZodOptional<z.ZodBoolean>;
key: z.ZodString;
layouts: z.ZodDefault<z.ZodArray<z.ZodEnum<{
account: "account";
case: "case";
transaction: "transaction";
}>>>;
manifestVersion: z.ZodLiteral<"v1">;
remote: z.ZodObject<{
render: z.ZodObject<{
document: z.ZodString;
}, z.core.$strip>;
}, z.core.$strip>;
uiConfigSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, z.core.$strip>;
type RemoteModuleRegistrationManifestV1Schema = typeof remoteModuleRegistrationManifestV1Schema;
//#endregion
//#region src/types.d.ts
type Json = boolean | JsonObject | JsonValue[] | null | number | string;
/**
* Loose JSON Schema object. Deliberately not a full JSON Schema type system —
* this package only needs to carry what `z.toJSONSchema()` emits for consumers
* to validate or render.
*/
type JsonSchemaObject = Record<string, unknown>;
/** Mirrors the host's supported layout values. */
type LayoutType = z.infer<typeof layoutTypeSchema>;
/** Mirrors the host's supported module badges. */
type ModuleBadge = z.infer<typeof moduleBadgeSchema>;
/** Mirrors the host's module-library categories. */
type ModuleCategory = z.infer<typeof moduleCategorySchema>;
/** A page URL loaded by the host in an iframe. */
type RemoteModuleDelivery = z.infer<typeof remoteDeliverySchema>;
/**
* Wire format for a remotely-defined layout module. Must stay
* JSON-serializable: no functions, no ReactNode, no zod.
*
* Producers: `defineLayoutModule()` and this package's Vite plugin.
* Consumers: the host application.
*/
type RemoteModuleRegistrationManifestV1 = z.infer<typeof remoteModuleRegistrationManifestV1Schema>;
type JsonObject = {
[key: string]: Json;
};
type JsonValue = Json;
//#endregion
//#region src/define.d.ts
/**
* Authoring input to `defineLayoutModule()`. This is NOT the wire manifest —
* it carries a source/page path (`render`, resolved relative to the Vite root)
* and a live zod schema (`configSchema`), which
* the Vite plugin (./vite.ts) resolves into a `RemoteModuleRegistrationManifestV1`
* at build time: an html entry becomes its (possibly `path`-overridden)
* served URL, and `configSchema` becomes JSON Schema via `z.toJSONSchema()`.
*/
interface LayoutModuleDefinitionInput {
/**
* Whether the layout editor allows adding new instances of this module to
* a layout. Only gates new placements — existing layouts that already
* contain the module are unaffected. The host ANDs this with its own
* gating, never ORs.
*/
allowAddingToLayout: boolean;
/** Whether the module exposes the dynamic-height option in its configuration. */
allowDynamicHeight?: boolean;
badge?: ModuleBadge;
category?: ModuleCategory;
/** Free-form module-library category label; takes precedence over `category` when both are set. */
categoryCustom?: string;
/** Authored as zod; converted to JSON Schema by the plugin. */
configSchema?: z.ZodType;
/**
* Per-layout config values layered over `configSchema`'s top-level defaults
* for new placements. This shallow merge only seeds new instances; it never
* rewrites saved config. Each set key must validate against `configSchema`.
* Hosts predating this field ignore it.
*/
defaultConfig?: Partial<Record<LayoutType, Record<string, Json>>>;
defaultSize: Partial<Record<LayoutType, {
height: number;
width: number;
}>>;
descriptionShort: string;
displayName: string;
/**
* Image path for the module-library tile, origin-relative to the module's
* source (e.g. `/assets/icon.svg`). The host shows a generic icon when it's
* omitted or fails to load; a cross-origin or non-http(s) URL is rejected and
* uses the default icon instead.
*/
icon?: string;
internalUsersOnly?: boolean;
/** Stable key, unique within this app. Never change after release. */
key: string;
/** Layout contexts this module supports. Empty or omitted means every layout. */
layouts?: LayoutType[];
/** How this module's main view is delivered. See `LayoutModuleEntry`. */
render: LayoutModuleEntry;
/**
* RJSF `uiSchema` for the generated config panel — widget choice, field
* order, labels. Already plain data (not zod, unlike `configSchema`), so
* the plugin passes it through to the manifest unchanged.
*/
uiConfigSchema?: Record<string, unknown>;
}
/**
* An entry a module's `render` slot points at.
* Resolved relative to the Vite root — there's no separate per-module
* definition file anymore; modules are configured directly wherever
* `personaLayoutModules()` is called (typically vite.config.ts).
*
* `entry` must point to an `.html` document that the host iframes. The plugin
* ensures Vite builds/serves the file, detecting whether it's already one of
* the app's own build entries (e.g. its main `index.html`) and injecting it
* into the build only when it isn't already there.
*
* `path` is for a client-routed SPA whose `index.html` already renders the
* whole app: rather than iframing
* the SPA's root, this names the LOGICAL route its own router should land on
* (e.g. `/embed/runs/1`) — the final `document` URL IS that path, not the
* html file's physical output path, since a static/SPA-fallback server
* serves the same `index.html` either way.
*/
interface LayoutModuleEntry {
entry: string;
path?: string;
}
/**
* 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.
*/
declare function defineLayoutModule(input: LayoutModuleDefinitionInput): LayoutModuleDefinitionInput;
//#endregion
export { JsonSchemaObject as a, ModuleCategory as c, RemoteModuleRegistrationManifestV1Schema as d, remoteModuleRegistrationManifestV1Schema as f, Json as i, RemoteModuleDelivery as l, LayoutModuleEntry as n, LayoutType as o, defineLayoutModule as r, ModuleBadge as s, LayoutModuleDefinitionInput as t, RemoteModuleRegistrationManifestV1 as u };