Files
CapaKraken/packages/shared/src/schemas/blueprint.schema.ts
T

125 lines
4.1 KiB
TypeScript

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<typeof CreateBlueprintSchema>;
export type UpdateBlueprintInput = z.infer<typeof UpdateBlueprintSchema>;
/** Generate a Zod schema from blueprint field definitions at runtime */
export function generateDynamicZodSchema(
fieldDefs: z.infer<typeof BlueprintFieldDefinitionSchema>[],
): z.ZodObject<Record<string, z.ZodTypeAny>> {
const shape: Record<string, z.ZodTypeAny> = {};
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);
}