TypeScript
@rhi-zone/fractal-type-ir ships one projector per TypeScript-ecosystem target: a plain .d.ts-shaped native emitter, plus one code projector per runtime validator library (Zod, Valibot, TypeBox, io-ts, Yup, Superstruct, runtypes, ArkType, Effect Schema), plus a JSDoc/Closure emitter for plain-JS callers. Every projector follows the same shape — a handlers: Record<string, Converter> table keyed on shape.kind, walked by a toX(ref) entry point — so a TypeRef's object/optional/enum vocabulary reliably degrades to each library's nearest native construct (documented per-handler in each source file, with a spec link).
TypeScript (native)
import { t, types } from "@rhi-zone/fractal-type-ir"
import { toTypeDeclaration } from "@rhi-zone/fractal-type-ir/typescript"
const variant = t(types.enum(["small", "medium", "large"]))
const orderItem = t(
types.object({
productId: t(types.string),
quantity: t(types.integer),
price: t(types.number),
variant: { ...variant, meta: { ...variant.meta, optional: true } },
}),
)
toTypeDeclaration("OrderItem", orderItem)
// type OrderItem = { productId: string; quantity: number; price: number; variant?: "small" | "medium" | "large" };toTypeScript(ref) renders the bare type expression; toTypeDeclaration(name, ref) wraps it in type Name = ...; with a TSDoc comment from meta.description/meta.deprecated; toTypeDeclarations(registry) batches a Record<string, TypeRef> into one block.
Zod
import { toZod } from "@rhi-zone/fractal-type-ir/zod"
const ref = t(types.object({ name: t(types.string), nickname: t(types.string, { optional: true }) }))
toZod(ref)
// z.object({ name: z.string(), nickname: z.string().optional() })meta.discriminator on a union upgrades z.union([...]) to z.discriminatedUnion(key, [...]).
Valibot
import { toValibot } from "@rhi-zone/fractal-type-ir/valibot"
toValibot(ref)
// v.object({ name: v.string(), nickname: v.optional(v.string()) })Constraints compose via v.pipe(schema, ...actions); optional/nullable are wrapper calls (v.optional(...), v.nullable(...)), not chained methods.
TypeBox
import { toTypeBox } from "@rhi-zone/fractal-type-ir/typebox"
toTypeBox(ref)
// Type.Object({ name: Type.String(), nickname: Type.Optional(Type.String()) })Constraints ride as a trailing options-object argument (Type.String({ minLength: 1 })) rather than a chained call.
io-ts
import { toIoTs } from "@rhi-zone/fractal-type-ir/io-ts"
toIoTs(ref)
// t.intersection([t.type({ name: t.string }), t.partial({ nickname: t.string })])io-ts has no per-field optional modifier — a mixed required/optional object splits into t.type({...required}) and t.partial({...optional}), merged via t.intersection. Every primitive codec unsupported natively (uuid, datetime, …) falls back to the nearest built-in with a trailing /* note */ comment.
Yup
import { toYup } from "@rhi-zone/fractal-type-ir/yup"
toYup(ref)
// yup.object({ name: yup.string().required(), nickname: yup.string() })Yup fields are required-by-default — .required() marks the required ones instead of .optional() marking the optional ones. Yup has no native record/union/literal-set type: enums degrade to .oneOf([...]) on mixed(), and schema unions degrade to yup.lazy() with a best-effort runtime type guard per variant.
Superstruct
import { toSuperstruct } from "@rhi-zone/fractal-type-ir/superstruct"
toSuperstruct(ref)
// s.object({ name: s.string(), nickname: s.optional(s.string()) })Constraints wrap the expression outside-in (s.size(s.string(), 1, 50)) rather than chaining.
runtypes
import { toRuntypes } from "@rhi-zone/fractal-type-ir/runtypes"
toRuntypes(ref)
// R.Record({ name: R.String, nickname: R.String.optional() })runtypes has no built-in constraint or format validators at all — every minimum/pattern/… degrades to a .withConstraint(...) predicate, and unsupported formats (uuid, email) fall back to R.String with a comment.
ArkType
import { toArkType } from "@rhi-zone/fractal-type-ir/arktype"
toArkType(ref)
// type({ name: "string", "nickname?": "string" })ArkType's string-based syntax marks optionality on the key ("nickname?"), not the value — the only variant here where "optional" isn't a wrapper or chained call at all.
Effect Schema
import { toEffectSchema } from "@rhi-zone/fractal-type-ir/effect-schema"
toEffectSchema(ref)
// S.Struct({ name: S.String, nickname: S.optional(S.String) })Effect Schema has no schema-level .default() — a field's meta.default is applied at the S.optionalWith(field, { default: () => value }) call site instead of as a generic chained method, unlike the zod/typebox/valibot projectors.
JSDoc
import { toJsDocTypedef } from "@rhi-zone/fractal-type-ir/jsdoc"
const userInput = t(
types.object({
name: t(types.string),
age: t(types.integer, { optional: true }),
roles: t(types.array(t(types.string))),
}),
)
toJsDocTypedef("UserInput", userInput)
// /**
// * @typedef {Object} UserInput
// * @property {string} name
// * @property {number} [age] - optional
// * @property {Array.<string>} roles
// */toJsDocType(ref) renders a bare @type-position expression; toJsDocTypedef supports a mode: "typedef" | "interface" | "class" option to emit @interface/@constructs blocks instead; toJsDocInlineType(ref) wraps a single expression as /** @type {...} */.