Technical Documentation

Internals: Zod Schema Compiler

The zod-generator.ts is the compiler of the DFBE. It transforms a declarative JSON object into a fully functional, type-safe Zod validation schema.

1. The Compilation Pipeline

When generateZodSchema is called, it follows these steps:

  1. Visibility Filtering: Evaluates rules to identify which fields are currently visible. Hidden fields are completely omitted from the schema, ensuring they don't block submission.
  2. Primitive Layer: Creates the base schema based on field.type (e.g., z.string() for text, z.number() for number, z.boolean() for checkbox).
  3. Constraint Layer: Iterates through validation properties (minLength, max, regex, minDate, etc.) and appends them to the Zod chain.
  4. Optionality Wrapper: Wraps the schema in .optional() or .or(z.null()) to handle empty states gracefully.
  5. Requirement Refinement: Applies a .superRefine() or .refine() block if the field is currently required.
  6. Dataset Transform: If a datasetLookup Map exists for the field, appends a .transform() that replaces the raw submitted ID with the full rich object stored in the Map.
  7. Nested Object Build: Splits dot-notation field paths (e.g. profile.email) into a recursive tree, then converts it into nested z.object() calls. This produces the final deeply-typed output schema.
  8. allowOther superRefine: If any field has allowOther: true, a single root-level .superRefine() is attached as the final step to enforce that the _other companion field is non-empty when the main value is "other".

2. Handling Nested Paths (Dot-Notation)

One of the most complex parts of the compiler is converting a flat list of field names into a nested Zod object.

// Example Input ["user.name", "user.email", "settings.theme"]; // Compiler Output (Recursive Construction) z.object({ user: z.object({ name: z.string(), email: z.string().email(), }), settings: z.object({ theme: z.string(), }), });

The compiler recursively traverses the path segments and constructs the nested z.object calls.

3. The allowOther Side-Effect

When allowOther: true is set, the compiler does two things:

  1. Registers Companion Field: It adds an optional string field named ${fieldName}_other to the schema.
  2. Cross-Field Refinement: It appends a root-level .superRefine() that checks if the main field's value is "other". If it is, the companion field must be a non-empty string.

4. Dataset Transformation Logic

The compiler is responsible for the ID-to-Object transformation.

if (fieldDatasetMap) { s = s.transform((val) => { // Keys are always stored as strings in the Map const lookup = (v: any) => { if (v === undefined || v === null) return v; return fieldDatasetMap.get(String(v)) ?? v; }; // Supports both single values and arrays (checkbox-group) if (Array.isArray(val)) return val.map(lookup); return lookup(val); }); }

This transformation happens inside the Zod chain (step 6), which is why handleSubmit returns rich objects while getValues() returns raw primitive IDs.

5. Zod Number Coercion Guard

Standard Zod coercion can convert empty strings to 0. DFBE uses a custom preprocessing layer for number fields:

z.preprocess((val) => { if (val === "" || val === undefined || val === null) return undefined; return Number(val); }, inner.optional());

This ensures that an empty numeric field stays undefined, allowing the required validation to trigger correctly.