{"version":3,"file":"recursion.mjs","sources":["../../src/other-types/recursion.mts"],"sourcesContent":["import { hasKey, isRecord, memoizeFunction } from 'ts-data-forge';\nimport { hasRecordInternals, type Type } from '../type.mjs';\nimport { createAssertFn, createCastFn, createIsFn } from '../utils/index.mjs';\n\n/**\n * Creates a recursive type definition for self-referential or mutually recursive data structures.\n *\n * This function enables defining types that refer to themselves (like linked lists, trees)\n * or mutually refer to each other (like even/odd number chains).\n *\n * @template A - The type to be defined recursively\n * @param typeName - A descriptive name for the recursive type\n * @param definition - A function that returns the Type definition. This function is called\n *                     lazily to allow self-reference\n * @param options - Optional configuration\n * @param options.defaultValue - Explicit default value for the type. Recommended for\n *                               mutually recursive types to avoid infinite loops\n *\n * @returns A Type that can validate and work with recursive structures\n *\n * @example\n * // Simple recursive type: Linked list\n * ```ts\n * import * as t from 'ts-fortress';\n *\n * type LinkedList = Readonly<{\n *   value: number;\n *   next: LinkedList | null;\n * }>;\n *\n * const LinkedListNumber: t.Type<LinkedList> = t.recursion('LinkedList', () =>\n *   t.record({\n *     value: t.number(),\n *     next: t.union([t.nullType, LinkedListNumber]),\n *   }),\n * );\n * ```\n *\n * @example\n * // Tree structure\n * ```ts\n * import * as t from 'ts-fortress';\n *\n * type TreeNode<T> = Readonly<{\n *   value: T;\n *   children: readonly TreeNode<T>[];\n * }>;\n *\n * const TreeNodeString: t.Type<TreeNode<string>> = t.recursion(\n *   'TreeNode<string>',\n *   () =>\n *     t.record({\n *       value: t.string(),\n *       children: t.array(TreeNodeString),\n *     }),\n * );\n * ```\n *\n * @example\n * // Mutually recursive types: Even/Odd chain\n * ```ts\n * import * as t from 'ts-fortress';\n *\n * type EvenNumber = Readonly<{ type: 'even'; next: OddNumber | null }>;\n *\n * type OddNumber = Readonly<{ type: 'odd'; next: EvenNumber | null }>;\n *\n * // IMPORTANT: For mutual recursion, place terminal types (like nullType)\n * // first in unions, or provide explicit defaultValue\n * const EvenNumber: t.Type<EvenNumber> = t.recursion('EvenNumber', () =>\n *   t.record({\n *     type: t.literal('even'),\n *     next: t.union([t.nullType, OddNumber]), // nullType first!\n *   }),\n * );\n *\n * const OddNumber: t.Type<OddNumber> = t.recursion('OddNumber', () =>\n *   t.record({\n *     type: t.literal('odd'),\n *     next: t.union([t.nullType, EvenNumber]), // nullType first!\n *   }),\n * );\n * ```\n *\n * @example\n * // Alternative: Provide explicit defaultValue for mutual recursion\n * ```ts\n * import * as t from 'ts-fortress';\n *\n * type EvenNumber = Readonly<{ type: 'even'; next: OddNumber | null }>;\n *\n * type OddNumber = Readonly<{ type: 'odd'; next: EvenNumber | null }>;\n *\n * const OddNumber: t.Type<OddNumber> = t.recursion('OddNumber', () =>\n *   t.record({\n *     type: t.literal('odd'),\n *     next: t.union([t.nullType, {} as t.Type<EvenNumber>]),\n *   }),\n * );\n *\n * const EvenNumber: t.Type<EvenNumber> = t.recursion(\n *   'EvenNumber',\n *   () =>\n *     t.record({\n *       type: t.literal('even'),\n *       next: t.union([OddNumber, t.nullType]), // Order doesn't matter\n *     }),\n *   { defaultValue: { type: 'even' as const, next: null } }, // Explicit default\n * );\n * ```\n *\n * @example\n * // Mutually recursive types with optional fields\n * ```ts\n * import * as t from 'ts-fortress';\n *\n * type EvenNumber = Readonly<{ type: 'even'; next?: OddNumber }>;\n *\n * type OddNumber = Readonly<{ type: 'odd'; next?: EvenNumber }>;\n *\n * // When using optional fields in mutually recursive types,\n * // use forceUndefinedDefault to avoid infinite loops when accessing defaultValue\n * const EvenNumber: t.Type<EvenNumber> = t.recursion('EvenNumber', () =>\n *   t.record({\n *     type: t.literal('even'),\n *     next: t.optional(OddNumber, { forceUndefinedDefault: true }),\n *   }),\n * );\n *\n * const OddNumber: t.Type<OddNumber> = t.recursion('OddNumber', () =>\n *   t.record({\n *     type: t.literal('odd'),\n *     next: t.optional(EvenNumber, { forceUndefinedDefault: true }),\n *   }),\n * );\n * ```\n *\n * @remarks\n * **Important Notes:**\n * - The `definition` function is called lazily on first use, enabling self-reference\n * - For mutually recursive types, accessing `defaultValue` may cause infinite loops\n *   unless proper precautions are taken:\n *   1. Place terminal types (like `nullType`) first in union types, OR\n *   2. Provide an explicit `defaultValue` in options, OR\n *   3. Use `optional()` with `forceUndefinedDefault: true` for optional fields\n * - The type definition itself (validation, type checking) works fine regardless of\n *   union ordering or defaultValue specification\n */\nexport const recursion = <A,>(\n  typeName: string,\n  definition: () => Type<A>,\n  options?: Partial<\n    Readonly<{\n      defaultValue: A;\n    }>\n  >,\n): Type<A> => {\n  const getDefaultValue = memoizeFunction(\n    (): A => options?.defaultValue ?? getInnerType().defaultValue,\n  );\n\n  const getInnerType: () => Type<A> = memoizeFunction(definition);\n\n  const validate: Type<A>['validate'] = (a) => getInnerType().validate(a);\n\n  const is = createIsFn<A>(validate);\n\n  const fill: Type<A>['fill'] = (a) => getInnerType().fill(a);\n\n  const prune = (a: A): A => getInnerType().prune(a);\n\n  const baseType: Type<A> = {\n    typeName,\n    get defaultValue(): A {\n      return getDefaultValue();\n    },\n    fill,\n    prune,\n    validate,\n    is,\n    assertIs: createAssertFn(validate),\n    cast: createCastFn(validate),\n  } as const;\n\n  // Proxy to propagate RecordTypeInternals if the inner type has them\n  return new Proxy(baseType, {\n    get: (target, prop) => {\n      if (prop === 'shapeStructure' || prop === 'excessProperty') {\n        const inner = getInnerType();\n\n        if (hasRecordInternals(inner)) {\n          return inner[prop];\n        }\n      }\n\n      if (\n        typeof prop === 'string' &&\n        isRecord(target) &&\n        hasKey(target, prop)\n      ) {\n        return target[prop as keyof typeof target];\n      }\n\n      return undefined;\n    },\n    has: (target, prop) => {\n      if (prop === 'shapeStructure' || prop === 'excessProperty') {\n        const inner = getInnerType();\n\n        return hasRecordInternals(inner);\n      }\n\n      return isRecord(target) && hasKey(target, prop);\n    },\n    ownKeys: (target) => {\n      const inner = getInnerType();\n\n      if (hasRecordInternals(inner)) {\n        return [...Object.keys(target), 'shapeStructure', 'excessProperty'];\n      }\n\n      return Object.keys(target);\n    },\n    getOwnPropertyDescriptor: (target, prop) => {\n      if (prop === 'shapeStructure' || prop === 'excessProperty') {\n        const inner = getInnerType();\n\n        if (hasRecordInternals(inner)) {\n          return {\n            enumerable: true,\n            configurable: true,\n            value: inner[prop],\n          };\n        }\n      }\n\n      return Object.getOwnPropertyDescriptor(target, prop);\n    },\n  });\n};\n"],"names":[],"mappings":"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"}