| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- import { z } from 'zod';
- import type {
- ComponentSchema,
- ValidationMode,
- UIElement,
- UITree,
- VisibilityCondition,
- } from './types';
- import { VisibilityConditionSchema } from './visibility';
- import { ActionSchema, type ActionDefinition } from './actions';
- import { ValidationConfigSchema, type ValidationFunction } from './validation';
- /**
- * Component definition with visibility and validation support
- */
- export interface ComponentDefinition<TProps extends ComponentSchema = ComponentSchema> {
- /** Zod schema for component props */
- props: TProps;
- /** Whether this component can have children */
- hasChildren?: boolean;
- /** Description for AI generation */
- description?: string;
- }
- /**
- * Catalog configuration
- */
- export interface CatalogConfig<
- TComponents extends Record<string, ComponentDefinition> = Record<string, ComponentDefinition>,
- TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>,
- TFunctions extends Record<string, ValidationFunction> = Record<string, ValidationFunction>
- > {
- /** Catalog name */
- name?: string;
- /** Component definitions */
- components: TComponents;
- /** Action definitions with param schemas */
- actions?: TActions;
- /** Custom validation functions */
- functions?: TFunctions;
- /** Validation mode */
- validation?: ValidationMode;
- }
- /**
- * Catalog instance
- */
- export interface Catalog<
- TComponents extends Record<string, ComponentDefinition> = Record<string, ComponentDefinition>,
- TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>,
- TFunctions extends Record<string, ValidationFunction> = Record<string, ValidationFunction>
- > {
- /** Catalog name */
- readonly name: string;
- /** Component names */
- readonly componentNames: (keyof TComponents)[];
- /** Action names */
- readonly actionNames: (keyof TActions)[];
- /** Function names */
- readonly functionNames: (keyof TFunctions)[];
- /** Validation mode */
- readonly validation: ValidationMode;
- /** Component definitions */
- readonly components: TComponents;
- /** Action definitions */
- readonly actions: TActions;
- /** Custom validation functions */
- readonly functions: TFunctions;
- /** Full element schema for AI generation */
- readonly elementSchema: z.ZodType<UIElement>;
- /** Full UI tree schema */
- readonly treeSchema: z.ZodType<UITree>;
- /** Check if component exists */
- hasComponent(type: string): boolean;
- /** Check if action exists */
- hasAction(name: string): boolean;
- /** Check if function exists */
- hasFunction(name: string): boolean;
- /** Validate an element */
- validateElement(element: unknown): { success: boolean; data?: UIElement; error?: z.ZodError };
- /** Validate a UI tree */
- validateTree(tree: unknown): { success: boolean; data?: UITree; error?: z.ZodError };
- }
- /**
- * Create a v2 catalog with visibility, actions, and validation support
- */
- export function createCatalog<
- TComponents extends Record<string, ComponentDefinition>,
- TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>,
- TFunctions extends Record<string, ValidationFunction> = Record<string, ValidationFunction>
- >(
- config: CatalogConfig<TComponents, TActions, TFunctions>
- ): Catalog<TComponents, TActions, TFunctions> {
- const {
- name = 'unnamed',
- components,
- actions = {} as TActions,
- functions = {} as TFunctions,
- validation = 'strict',
- } = config;
- const componentNames = Object.keys(components) as (keyof TComponents)[];
- const actionNames = Object.keys(actions) as (keyof TActions)[];
- const functionNames = Object.keys(functions) as (keyof TFunctions)[];
- // Create element schema for each component type
- const componentSchemas = componentNames.map((componentName) => {
- const def = components[componentName]!;
-
- return z.object({
- key: z.string(),
- type: z.literal(componentName as string),
- props: def.props,
- children: z.array(z.string()).optional(),
- parentKey: z.string().nullable().optional(),
- visible: VisibilityConditionSchema.optional(),
- });
- });
- // Create union schema for all components
- let elementSchema: z.ZodType<UIElement>;
-
- if (componentSchemas.length === 0) {
- elementSchema = z.object({
- key: z.string(),
- type: z.string(),
- props: z.record(z.unknown()),
- children: z.array(z.string()).optional(),
- parentKey: z.string().nullable().optional(),
- visible: VisibilityConditionSchema.optional(),
- }) as unknown as z.ZodType<UIElement>;
- } else if (componentSchemas.length === 1) {
- elementSchema = componentSchemas[0] as unknown as z.ZodType<UIElement>;
- } else {
- elementSchema = z.discriminatedUnion('type', [
- componentSchemas[0] as z.ZodObject<any>,
- componentSchemas[1] as z.ZodObject<any>,
- ...componentSchemas.slice(2) as z.ZodObject<any>[],
- ]) as unknown as z.ZodType<UIElement>;
- }
- // Create tree schema
- const treeSchema = z.object({
- root: z.string(),
- elements: z.record(elementSchema),
- }) as unknown as z.ZodType<UITree>;
- return {
- name,
- componentNames,
- actionNames,
- functionNames,
- validation,
- components,
- actions,
- functions,
- elementSchema,
- treeSchema,
- hasComponent(type: string) {
- return type in components;
- },
- hasAction(name: string) {
- return name in actions;
- },
- hasFunction(name: string) {
- return name in functions;
- },
- validateElement(element: unknown) {
- const result = elementSchema.safeParse(element);
- if (result.success) {
- return { success: true, data: result.data };
- }
- return { success: false, error: result.error };
- },
- validateTree(tree: unknown) {
- const result = treeSchema.safeParse(tree);
- if (result.success) {
- return { success: true, data: result.data };
- }
- return { success: false, error: result.error };
- },
- };
- }
- /**
- * Generate a prompt for AI that describes the catalog
- */
- export function generateCatalogPrompt<
- TComponents extends Record<string, ComponentDefinition>,
- TActions extends Record<string, ActionDefinition>,
- TFunctions extends Record<string, ValidationFunction>
- >(catalog: Catalog<TComponents, TActions, TFunctions>): string {
- const lines: string[] = [
- `# ${catalog.name} Component Catalog`,
- '',
- '## Available Components',
- '',
- ];
- // Components
- for (const name of catalog.componentNames) {
- const def = catalog.components[name]!;
- lines.push(`### ${String(name)}`);
- if (def.description) {
- lines.push(def.description);
- }
- lines.push('');
- }
- // Actions
- if (catalog.actionNames.length > 0) {
- lines.push('## Available Actions');
- lines.push('');
- for (const name of catalog.actionNames) {
- const def = catalog.actions[name]!;
- lines.push(`- \`${String(name)}\`${def.description ? `: ${def.description}` : ''}`);
- }
- lines.push('');
- }
- // Visibility
- lines.push('## Visibility Conditions');
- lines.push('');
- lines.push('Components can have a `visible` property:');
- lines.push('- `true` / `false` - Always visible/hidden');
- lines.push('- `{ "path": "/data/path" }` - Visible when path is truthy');
- lines.push('- `{ "auth": "signedIn" }` - Visible when user is signed in');
- lines.push('- `{ "and": [...] }` - All conditions must be true');
- lines.push('- `{ "or": [...] }` - Any condition must be true');
- lines.push('- `{ "not": {...} }` - Negates a condition');
- lines.push('- `{ "eq": [a, b] }` - Equality check');
- lines.push('');
- // Validation
- lines.push('## Validation Functions');
- lines.push('');
- lines.push('Built-in: `required`, `email`, `minLength`, `maxLength`, `pattern`, `min`, `max`, `url`');
- if (catalog.functionNames.length > 0) {
- lines.push(`Custom: ${catalog.functionNames.map(String).join(', ')}`);
- }
- lines.push('');
- return lines.join('\n');
- }
- /**
- * Type helper to infer component props from catalog
- */
- export type InferCatalogComponentProps<
- C extends Catalog<Record<string, ComponentDefinition>>
- > = {
- [K in keyof C['components']]: z.infer<C['components'][K]['props']>;
- };
|