import { z } from "zod/mini" // Accepts string | string[], normalizes string to [string] const MasksArrayStringSchema = z.pipe( z.union([z.array(z.string()), z.string()]), z.transform((val): string[] => (typeof val === "string" ? [val] : val)), ) // --- CustomTypes hierarchy (field selection in content relationship fields) --- // Level 2 const noDuplicateStrings = z.refine( (arr) => new Set(arr as string[]).size === (arr as string[]).length, "Fields have duplicates.", ) const CustomTypeLevel2FieldGroupFieldsSchema = z.array(z.string()).check(noDuplicateStrings) const CustomTypeLevel2FieldSchema = z.union([ z.object({ id: z.string(), fields: CustomTypeLevel2FieldGroupFieldsSchema }), z.string(), ]) const noDuplicateIds = (items: unknown) => { const arr = items as { id: string }[] | string[] const ids = new Set(arr.map((item) => (typeof item === "string" ? item : item.id))) return ids.size === arr.length } const CustomTypeLevel2FieldsSchema = z .array(CustomTypeLevel2FieldSchema) .check(z.refine(noDuplicateIds, "Fields have duplicates.")) const CustomTypeLevel2Schema = z.union([ z.string(), z.object({ id: z.string(), fields: CustomTypeLevel2FieldsSchema }), ]) const CustomTypesLevel2Schema = z .array(CustomTypeLevel2Schema) .check(z.refine(noDuplicateIds, "Custom types have duplicates.")) // Level 1 const CustomTypeLevel1FieldCustomTypesSchema = z.object({ id: z.string(), customtypes: CustomTypesLevel2Schema, }) const CustomTypeLevel1FieldGroupFieldSchema = z.union([ CustomTypeLevel1FieldCustomTypesSchema, z.string(), ]) const CustomTypeLevel1FieldGroupFieldsSchema = z .array(CustomTypeLevel1FieldGroupFieldSchema) .check(z.refine(noDuplicateIds, "Fields have duplicates.")) const CustomTypeLevel1FieldSchema = z.union([ z.object({ id: z.string(), fields: CustomTypeLevel1FieldGroupFieldsSchema, }), CustomTypeLevel1FieldCustomTypesSchema, z.string(), ]) const CustomTypeLevel1FieldsSchema = z .array(CustomTypeLevel1FieldSchema) .check(z.refine(noDuplicateIds, "Fields have duplicates.")) const CustomTypeLevel1Schema = z.union([ z.string(), z.object({ id: z.string(), fields: CustomTypeLevel1FieldsSchema }), ]) const CustomTypesSchema = z.array(CustomTypeLevel1Schema).check(({ value, issues }) => { const cts = value as ({ id: string } | string)[] const push = (message: string) => issues.push({ code: "custom", input: value, message }) const strings = new Set() const objects = new Set() for (const ct of cts) { let failed = false if (typeof ct === "string") { failed = objects.has(ct) strings.add(ct) } else { const { id } = ct failed = strings.has(id) || objects.has(id) objects.add(id) } if (failed) { push("Custom types have duplicates.") return } } if (objects.size > 1) { push("Cannot have multiple custom types with fields selection.") return } if (strings.size > 0 && objects.size > 0) { push("Cannot mix custom types as strings and objects with fields.") } }) // --- Select with fallback to null --- const SelectWithFallbackSchema = z.pipe( z.unknown(), z.transform((val): "media" | "document" | "web" | null => { if (val === "media" || val === "document" || val === "web" || val === null) { return val } return null }), ) // --- LinkConfig and Link --- const LinkConfigSchema = z.object({ label: z.nullish(z.string()), useAsTitle: z.optional(z.boolean()), placeholder: z.optional(z.string()), select: z.optional(SelectWithFallbackSchema), customtypes: z.optional(CustomTypesSchema), masks: z.optional(MasksArrayStringSchema), tags: z.optional(MasksArrayStringSchema), allowTargetBlank: z.optional(z.boolean()), allowText: z.optional(z.boolean()), repeat: z.optional(z.boolean()), variants: z.optional(z.array(z.string())), }) export const LinkModelSchema = z.object({ type: z.literal("Link"), fieldset: z.nullish(z.string()), config: z.optional(LinkConfigSchema), }) export type LinkModel = z.infer