ts-fortress
    Preparing search index...
    • 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).

      Type Parameters

      • A

        The type to be defined recursively

      Parameters

      • typeName: string

        A descriptive name for the recursive type

      • definition: () => Type<A>

        A function that returns the Type definition. This function is called lazily to allow self-reference

      • Optionaloptions: Partial<Readonly<{ defaultValue: A }>>

        Optional configuration

        • defaultValue

          Explicit default value for the type. Recommended for mutually recursive types to avoid infinite loops

      Returns Type<A>

      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:

      • The definition function is called lazily on first use, enabling self-reference
      • For mutually recursive types, accessing defaultValue may cause infinite loops unless proper precautions are taken:
        1. Place terminal types (like nullType) first in union types, OR
        2. Provide an explicit defaultValue in options, OR
        3. Use optional() with forceUndefinedDefault: true for optional fields
      • The type definition itself (validation, type checking) works fine regardless of union ordering or defaultValue specification