{"version":3,"file":"type.mjs","sources":["../src/type.mts"],"sourcesContent":["import { Arr, Obj, hasKey, isRecord, type Result } from 'ts-data-forge';\nimport { type ReadonlyRecord } from 'ts-type-forge';\nimport { type ValidationError } from './utils/index.mjs';\n\n/**\n * - `typeName` : Name for this type\n * - `is` : Type guard function\n * - `assertIs` : Type assertion function\n * - `cast` : Cast function (returns the original value, no transformation)\n * - `fill` : Default value filling function\n * - `prune` : Excess property pruning function (recursively removes value\n *   paths that are not represented by the type; unlike `fill`, it never fills\n *   in default values)\n * - `validate` : A base function to be used in `is` and `assertIs`. `validate`\n *   returns Result.Ok if the value is of Type A, otherwise returns Result.Err\n *   with structured validation error information.\n */\nexport type Type<A> = Readonly<{\n  typeName: string;\n  defaultValue: A;\n  is: (a: unknown) => a is A;\n  assertIs: (a: unknown) => asserts a is A;\n  cast: (a: unknown) => A;\n  fill: (a: unknown) => A;\n\n  /**\n   * Recursively removes value paths that are not represented by the type\n   * (e.g. excess record properties). Unlike `fill`, it never fills in default\n   * values, so the input must already be of type `A`.\n   *\n   * NOTE: The parameter is intentionally a naked type parameter\n   * (`<B extends A>(a: B) => A`) rather than `(a: A) => A`:\n   *\n   * 1. `(a: A) => A` would put `A` in a contravariant position, so `Type<X>`\n   *    would no longer be assignable to `Type<unknown>` under\n   *    `strictFunctionTypes`, breaking `UnknownShape` (the shape constraint\n   *    of `record`), `TypeOf`, and every `readonly Type<unknown>[]` argument\n   *    (`tuple`, `union`, `intersection`, ...).\n   * 2. `(a: A) => A` would reject fresh object literals with excess\n   *    properties (e.g. `Point2D.prune({ x: 1, y: 2, z: 3 })`) via excess\n   *    property checking (TS2353), which is the main use case of `prune`.\n   *    Inference onto the naked type parameter `B` skips that check, while\n   *    `B extends A` still rejects inputs with missing keys.\n   *\n   * (A bivariance hack such as `{ m(a: A): A }['m']` would solve 1 but not\n   * 2.)\n   */\n  prune: <B extends A>(a: B) => A;\n  validate: (a: unknown) => Result<A, readonly ValidationError[]>;\n\n  /** @internal Used to mark properties as optional in record type validation */\n  optional?: true;\n}>;\n\n/**\n * A maximally-permissive `Type` for use in **constraint positions only** —\n * e.g. `<T extends AnyType>`, `UnknownShape`, and the element bounds of\n * `union` / `tuple` / `intersection`.\n *\n * `Type<A>` is **not** covariant in `A`: `prune` accepts `<B extends A>`, which\n * puts `A` in a constraint position. TypeScript 5.x tolerated this, but the\n * native (Go) compiler / TypeScript 7 enforces the variance, so a concrete\n * `Type<SomeUnion>` is no longer assignable to `Type<unknown>` across a\n * declaration-file boundary. That broke every `Type<unknown>` bound\n * (`record`/`strictRecord`, `TypeOf`, `union`, `tuple`, `intersection`, ...)\n * for codecs imported from a built dependency.\n *\n * Making `Type<A>` covariant is impossible without dropping `prune`'s\n * missing-key rejection (that check inherently needs `A` in a non-covariant\n * position). Instead we loosen only the **bound** to `Type<any>`: `any` makes\n * every concrete `Type<X>` assignable to it, so a codec from a built dependency\n * once again satisfies the shape / element / `TypeOf` constraints. The `any` is\n * confined to this constraint-only alias — it never appears in inferred or\n * public output types (those go through the concrete `Type<X>` / `TypeOf<X>`),\n * so consumer-facing types stay precise. `AnyType` still requires a real codec:\n * every `Type` member must be present.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- confined to this constraint-only bound; see above\nexport type AnyType = Type<any>;\n\nexport type TypeOf<A extends AnyType> = A['defaultValue'];\n\n/**\n * Controls how excess properties (keys not in shape) are handled.\n *\n * - `'allow'` (default) — accept excess properties at runtime\n * - `'reject'` — reject objects with excess properties at runtime\n *\n * This option only affects runtime validation behavior.\n * The value type is always exact regardless of the setting.\n */\nexport type ExcessPropertyOption = 'allow' | 'reject';\n\n/** @internal */\nexport type UnknownShape = ReadonlyRecord<string, AnyType>;\n\n/** @internal Shape structure that can represent union and intersection */\nexport type ShapeStructure = Readonly<\n  | { kind: 'simple'; shape: UnknownShape }\n  | { kind: 'union'; variants: readonly ShapeStructure[] }\n  | { kind: 'intersection'; parts: readonly ShapeStructure[] }\n>;\n\n/** @internal Runtime type for accessing internal record properties via cast. */\nexport type RecordTypeInternals = Readonly<{\n  shapeStructure: ShapeStructure;\n  excessProperty: ExcessPropertyOption;\n}>;\n\n/**\n * @internal Internal properties attached to tuple types so that the element\n * type at a given index can be recovered (e.g. by {@link at}).\n */\nexport type TupleTypeInternals = Readonly<{\n  elementTypes: readonly Type<unknown>[];\n}>;\n\n/**\n * @internal Internal properties attached to union types so that a union\n * nested directly inside another union can contribute its own members to the\n * parent (see {@link hasUnionInternals}).\n */\nexport type UnionTypeInternals = Readonly<{\n  /** The member types, with directly nested unions already flattened in. */\n  memberTypes: readonly AnyType[];\n}>;\n\n/** @internal Helper to flatten ShapeStructure to a simple shape if possible */\nexport const flattenShapeStructure = (\n  structure: ShapeStructure,\n): UnknownShape | undefined => {\n  switch (structure.kind) {\n    case 'simple': {\n      return structure.shape;\n    }\n    case 'intersection': {\n      // Intersection can be flattened by merging all parts\n      const parts = structure.parts.map(flattenShapeStructure);\n\n      if (parts.includes(undefined)) {\n        // Contains union - cannot flatten\n        return undefined;\n      }\n\n      // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n      return Obj.merge(...(parts as readonly UnknownShape[]));\n    }\n    case 'union': {\n      // Union cannot be flattened to a single shape\n      return undefined;\n    }\n  }\n};\n\n/**\n * @internal Upper bound on the number of concrete shapes that\n * {@link expandShapeStructure} may produce. Expanding an intersection of unions\n * multiplies the variant counts, so a deeply nested structure can grow\n * exponentially; this bound prevents excessive CPU/memory usage (mirrors the\n * guard in `mergeRecords`).\n */\nconst EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS = 10_000;\n\n/**\n * @internal Expands a ShapeStructure into all possible simple shapes.\n * For union structures, returns an array of all variant shapes.\n * For intersection structures, computes the cartesian product of all parts.\n *\n * Unlike {@link flattenShapeStructure}, this never returns `undefined`: a\n * union of records is represented as the list of its concrete member shapes.\n *\n * @throws If the intersection expansion would exceed\n *   {@link EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS} concrete shapes.\n */\nexport const expandShapeStructure = (\n  structure: ShapeStructure,\n): readonly UnknownShape[] => {\n  switch (structure.kind) {\n    case 'simple': {\n      return [structure.shape];\n    }\n    case 'union': {\n      // Union: flatten all variants\n      return structure.variants.flatMap(expandShapeStructure);\n    }\n    case 'intersection': {\n      // Intersection: compute cartesian product of all parts\n      const expandedParts = structure.parts.map(expandShapeStructure);\n\n      // The cartesian product size is the product of the per-part variant\n      // counts, which grows multiplicatively. Bound it before materializing to\n      // avoid excessive CPU/memory usage.\n      const estimatedVariantCount = expandedParts.reduce(\n        (acc, shapes) => acc * shapes.length,\n        1,\n      );\n\n      if (estimatedVariantCount > EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS) {\n        throw new Error(\n          `Expanding this record type would create ${estimatedVariantCount} variants, exceeding the limit of ${EXPAND_SHAPE_STRUCTURE_MAX_VARIANTS}. This could lead to excessive memory or CPU usage. Consider simplifying the record types or reducing union/intersection nesting.`,\n        );\n      }\n\n      const combinations = Arr.cartesianProduct(expandedParts);\n\n      return Arr.map(combinations, (shapes) => Obj.merge(...shapes));\n    }\n  }\n};\n\n/** @internal Backward compatibility: simple shape accessor */\nexport const getShape = (internals: RecordTypeInternals): UnknownShape => {\n  const flattened = flattenShapeStructure(internals.shapeStructure);\n\n  if (flattened === undefined) {\n    throw new Error(\n      `getShape() can only be called on simple or intersection record types, but received a union type. Use shapeStructure instead.`,\n    );\n  }\n\n  return flattened;\n};\n\n/** @internal Runtime check for record type internals. */\nexport const hasRecordInternals = <T extends AnyType>(\n  t: T,\n): t is T & RecordTypeInternals => hasRecordInternalsImpl(t);\n\nconst hasRecordInternalsImpl = (t: unknown): t is RecordTypeInternals =>\n  isRecord(t) &&\n  hasKey(t, 'shapeStructure') &&\n  isValidShapeStructure(t.shapeStructure) &&\n  hasKey(t, 'excessProperty') &&\n  (t.excessProperty === 'allow' || t.excessProperty === 'reject');\n\n/** @internal Runtime check for tuple type internals. */\nexport const hasTupleInternals = <T extends AnyType>(\n  t: T,\n): t is T & TupleTypeInternals => hasTupleInternalsImpl(t);\n\nconst hasTupleInternalsImpl = (t: unknown): t is TupleTypeInternals =>\n  isRecord(t) && hasKey(t, 'elementTypes') && Arr.isArray(t.elementTypes);\n\n/**\n * @internal Runtime check for union type internals.\n *\n * A `recursion` type answers this without resolving its definition (its proxy\n * only forwards the keys the base type already has), so a recursive member is\n * treated as an opaque type rather than being flattened.\n */\nexport const hasUnionInternals = <T extends AnyType>(\n  t: T,\n): t is T & UnionTypeInternals => hasUnionInternalsImpl(t);\n\nconst hasUnionInternalsImpl = (t: unknown): t is UnionTypeInternals =>\n  isRecord(t) && hasKey(t, 'memberTypes') && Arr.isArray(t.memberTypes);\n\nconst isValidShapeStructure = (s: unknown): s is ShapeStructure => {\n  if (!isRecord(s) || !hasKey(s, 'kind')) return false;\n\n  if (s.kind === 'simple') {\n    return hasKey(s, 'shape') && isRecord(s.shape);\n  }\n\n  if (s.kind === 'union') {\n    return (\n      hasKey(s, 'variants') &&\n      Arr.isArray(s.variants) &&\n      s.variants.every(isValidShapeStructure)\n    );\n  }\n\n  if (s.kind === 'intersection') {\n    return (\n      hasKey(s, 'parts') &&\n      Arr.isArray(s.parts) &&\n      s.parts.every(isValidShapeStructure)\n    );\n  }\n\n  return false;\n};\n"],"names":[],"mappings":"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"}