Skip to content

Roadmap

Mixin-based multi-codec base classes

Goal: allow a single class to support multiple codecs with named helpers (e.g. both fromXML() and fromJSON()) without any codec-specific re-declaration of extend().

Each codec package exports a mixin and a pre-built base:

ts
// xml-model
export function xmlMixin<T extends ModelConstructor<any>>(Base: T) {
  return class extends Base {
    static fromXML(input) { … }
    static toXML(instance) { … }
    static toXMLString(instance, opts?) { … }
  };
}
export const XMLBase = xmlMixin(model(z.object({})));

Users compose mixins for multi-codec classes:

ts
import { xmlMixin, XMLBase } from "xml-model";
import { jsonMixin } from "json-model"; // hypothetical

// Single-codec (most common)
class Vehicle extends XMLBase.extend(
  { vin: xml.attr(z.string(), { name: "vin" }), make: z.string() },
  xml.root({ tagname: "vehicle" }),
) {}

// Multi-codec
const MultiBase = jsonMixin(XMLBase);
class Book extends MultiBase.extend(
  { title: z.string(), pages: z.number() },
  { ...xml.root({ tagname: "book" }), ...json.root({ key: "book" }) },
) {}

Status: design only — not yet implemented

Per-constructor default conversions

Goal: let users register a default XML conversion strategy for a Zod type class (e.g. "all z.ZodString schemas serialize as CDATA", "z.ZodNumber coerces via parseInt not parseFloat").

ts
xmlCodec.registerDefault(z.ZodString, myStringConverter);

The main complication: refine() wraps schemas in a ZodPipe in v4, losing the constructor identity. The codec would need to walk the def chain to find the innermost primitive. Non-trivial but self-contained inside codec.ts; per-field .meta() overrides take precedence regardless.

Leave for later unless there is a concrete use case driving it.