import { z } from "zod"; import { BlueprintTarget, FieldType } from "../types/enums.js"; export const FieldOptionSchema = z.object({ value: z.string().min(1), label: z.string().min(1), color: z.string().optional(), }); export const FieldValidationSchema = z.object({ min: z.number().optional(), max: z.number().optional(), minLength: z.number().int().optional(), maxLength: z.number().int().optional(), pattern: z.string().optional(), message: z.string().optional(), }); export const BlueprintFieldDefinitionSchema = z.object({ id: z.string().min(1), label: z.string().min(1).max(200), key: z.string().min(1).max(100).regex(/^[a-z_][a-z0-9_]*$/, "Must be snake_case"), type: z.nativeEnum(FieldType), required: z.boolean().default(false), description: z.string().optional(), placeholder: z.string().optional(), defaultValue: z.unknown().optional(), options: z.array(FieldOptionSchema).optional(), validation: FieldValidationSchema.optional(), order: z.number().int().min(0), group: z.string().optional(), }); export const CreateBlueprintSchema = z.object({ name: z.string().min(1).max(200), target: z.nativeEnum(BlueprintTarget), description: z.string().optional(), fieldDefs: z.array(BlueprintFieldDefinitionSchema).default([]), defaults: z.record(z.string(), z.unknown()).default({}), validationRules: z.array(z.object({ field: z.string(), rule: z.enum(["required_if", "unique", "min", "max"]), params: z.unknown().optional(), message: z.string().optional(), })).default([]), }); export const UpdateBlueprintSchema = CreateBlueprintSchema.partial(); export type CreateBlueprintInput = z.infer; export type UpdateBlueprintInput = z.infer; /** Generate a Zod schema from blueprint field definitions at runtime */ export function generateDynamicZodSchema( fieldDefs: z.infer[], ): z.ZodObject> { const shape: Record = {}; for (const field of fieldDefs) { let fieldSchema: z.ZodTypeAny; switch (field.type) { case FieldType.TEXT: case FieldType.TEXTAREA: case FieldType.URL: case FieldType.EMAIL: fieldSchema = z.string(); if (field.validation?.minLength !== undefined) { fieldSchema = (fieldSchema as z.ZodString).min(field.validation.minLength); } if (field.validation?.maxLength !== undefined) { fieldSchema = (fieldSchema as z.ZodString).max(field.validation.maxLength); } if (field.type === FieldType.EMAIL) { fieldSchema = (fieldSchema as z.ZodString).email(); } if (field.type === FieldType.URL) { fieldSchema = (fieldSchema as z.ZodString).url(); } break; case FieldType.NUMBER: fieldSchema = z.number(); if (field.validation?.min !== undefined) { fieldSchema = (fieldSchema as z.ZodNumber).min(field.validation.min); } if (field.validation?.max !== undefined) { fieldSchema = (fieldSchema as z.ZodNumber).max(field.validation.max); } break; case FieldType.BOOLEAN: fieldSchema = z.boolean(); break; case FieldType.DATE: fieldSchema = z.coerce.date(); break; case FieldType.SELECT: if (field.options && field.options.length > 0) { const values = field.options.map((o) => o.value) as [string, ...string[]]; fieldSchema = z.enum(values); } else { fieldSchema = z.string(); } break; case FieldType.MULTI_SELECT: if (field.options && field.options.length > 0) { const values = field.options.map((o) => o.value) as [string, ...string[]]; fieldSchema = z.array(z.enum(values)); } else { fieldSchema = z.array(z.string()); } break; default: fieldSchema = z.unknown(); } if (!field.required) { fieldSchema = fieldSchema.optional(); } shape[field.key] = fieldSchema; } return z.object(shape); }