The type to be defined recursively
A descriptive name for the recursive type
A function that returns the Type definition. This function is called lazily to allow self-reference
Optionaloptions: Partial<Readonly<{ defaultValue: A }>>Optional configuration
Explicit default value for the type. Recommended for mutually recursive types to avoid infinite loops
A Type that can validate and work with recursive structures
// Simple recursive type: Linked list
import * as t from 'ts-fortress';
type LinkedList = Readonly<{
value: number;
next: LinkedList | null;
}>;
const LinkedListNumber: t.Type<LinkedList> = t.recursion('LinkedList', () =>
t.record({
value: t.number(),
next: t.union([t.nullType, LinkedListNumber]),
}),
);
// Tree structure
import * as t from 'ts-fortress';
type TreeNode<T> = Readonly<{
value: T;
children: readonly TreeNode<T>[];
}>;
const TreeNodeString: t.Type<TreeNode<string>> = t.recursion(
'TreeNode<string>',
() =>
t.record({
value: t.string(),
children: t.array(TreeNodeString),
}),
);
// Mutually recursive types: Even/Odd chain
import * as t from 'ts-fortress';
type EvenNumber = Readonly<{ type: 'even'; next: OddNumber | null }>;
type OddNumber = Readonly<{ type: 'odd'; next: EvenNumber | null }>;
// IMPORTANT: For mutual recursion, place terminal types (like nullType)
// first in unions, or provide explicit defaultValue
const EvenNumber: t.Type<EvenNumber> = t.recursion('EvenNumber', () =>
t.record({
type: t.literal('even'),
next: t.union([t.nullType, OddNumber]), // nullType first!
}),
);
const OddNumber: t.Type<OddNumber> = t.recursion('OddNumber', () =>
t.record({
type: t.literal('odd'),
next: t.union([t.nullType, EvenNumber]), // nullType first!
}),
);
// Alternative: Provide explicit defaultValue for mutual recursion
import * as t from 'ts-fortress';
type EvenNumber = Readonly<{ type: 'even'; next: OddNumber | null }>;
type OddNumber = Readonly<{ type: 'odd'; next: EvenNumber | null }>;
const OddNumber: t.Type<OddNumber> = t.recursion('OddNumber', () =>
t.record({
type: t.literal('odd'),
next: t.union([t.nullType, {} as t.Type<EvenNumber>]),
}),
);
const EvenNumber: t.Type<EvenNumber> = t.recursion(
'EvenNumber',
() =>
t.record({
type: t.literal('even'),
next: t.union([OddNumber, t.nullType]), // Order doesn't matter
}),
{ defaultValue: { type: 'even' as const, next: null } }, // Explicit default
);
// Mutually recursive types with optional fields
import * as t from 'ts-fortress';
type EvenNumber = Readonly<{ type: 'even'; next?: OddNumber }>;
type OddNumber = Readonly<{ type: 'odd'; next?: EvenNumber }>;
// When using optional fields in mutually recursive types,
// use forceUndefinedDefault to avoid infinite loops when accessing defaultValue
const EvenNumber: t.Type<EvenNumber> = t.recursion('EvenNumber', () =>
t.record({
type: t.literal('even'),
next: t.optional(OddNumber, { forceUndefinedDefault: true }),
}),
);
const OddNumber: t.Type<OddNumber> = t.recursion('OddNumber', () =>
t.record({
type: t.literal('odd'),
next: t.optional(EvenNumber, { forceUndefinedDefault: true }),
}),
);
Important Notes:
definition function is called lazily on first use, enabling self-referencedefaultValue may cause infinite loops
unless proper precautions are taken:
nullType) first in union types, ORdefaultValue in options, ORoptional() with forceUndefinedDefault: true for optional fields
Creates a recursive type definition for self-referential or mutually recursive data structures.
This function enables defining types that refer to themselves (like linked lists, trees) or mutually refer to each other (like even/odd number chains).