import { z } from "zod"; export class ChiSchemaError extends Error { constructor(message: string) { super(message); this.name = "ChiSchemaError"; } } export type EditableField = | { type: "enum"; key: string; values: readonly string[]; defaultValue: string } | { type: "string"; key: string; defaultValue: string }; export interface SchemaInfo { empty: boolean; fields: readonly EditableField[]; } function zodTypeName(value: unknown): string | undefined { const def = (value as { _def?: { typeName?: string; type?: string } } | undefined)?._def; return def?.typeName ?? def?.type; } export function inspectSchema(schema: z.ZodObject): SchemaInfo { const fields = Object.entries(schema.shape); if (fields.length === 0) return { empty: true, fields: [] }; return { empty: false, fields: fields.flatMap(([key, field]) => { // Do not use instanceof here: Chi modules are separate Pi packages and // may load their own copy of zod. Structural checks keep the contract // valid across package boundaries while retaining strict field shapes. if (zodTypeName(field) !== "ZodDefault") { throw new ChiSchemaError("invalid schema field " + key + ": expected a field with a default"); } const inner = (field as { _def: { innerType: unknown } })._def.innerType; const innerType = zodTypeName(inner); if (innerType !== "ZodString" && innerType !== "ZodEnum") return []; const defaultValue = (field as { parse(value: undefined): unknown }).parse(undefined); if (typeof defaultValue !== "string") { throw new ChiSchemaError("invalid schema field " + key + ": editable default must be a string"); } if (innerType === "ZodString") return [{ type: "string", key, defaultValue }]; const options = (inner as { options?: unknown }).options; if (!Array.isArray(options) || !options.every((value) => typeof value === "string")) { throw new ChiSchemaError("invalid schema field " + key + ": enum options must be strings"); } return [{ type: "enum", key, values: options, defaultValue, }]; }), }; }