{"version":3,"file":"intersection.mjs","sources":["../../src/compose/intersection.mts"],"sourcesContent":["import { Arr, expectType, hasKey, isRecord, Result, tp } from 'ts-data-forge';\nimport { type Intersection, type NonEmptyTuple } from 'ts-type-forge';\nimport {\n  hasRecordInternals,\n  type AnyType,\n  type ExcessPropertyOption,\n  type Type,\n  type TypeOf,\n} from '../type.mjs';\nimport {\n  createAssertFn,\n  createCastFn,\n  createIsFn,\n  toIntersectionString,\n  type ValidationError,\n} from '../utils/index.mjs';\n\nexport const intersection = <const Types extends NonEmptyTuple<AnyType>>(\n  types: Types,\n  defaultType: IntersectionType<Types>,\n  options?: Partial<\n    Readonly<{\n      typeName: string;\n    }>\n  >,\n): IntersectionType<Types> => {\n  type T = IntersectionTypeValue<Types>;\n\n  const typeNameFilled: string =\n    options?.typeName ??\n    `(${toIntersectionString(types.map((a) => a.typeName))})`;\n\n  const validate: Type<T>['validate'] = (a) => {\n    const errors: readonly ValidationError[] = Arr.generate(function* () {\n      for (const type of types) {\n        const res = type.validate(a);\n\n        if (Result.isErr(res)) {\n          yield {\n            path: [],\n            actualValue: a,\n            expectedType: typeNameFilled,\n            typeName: typeNameFilled,\n            details: {\n              kind: 'intersection',\n              typeNames: types.map((t) => t.typeName),\n            },\n          } satisfies ValidationError;\n\n          yield* res.value;\n        }\n      }\n    });\n\n    if (Arr.isNonEmpty(errors)) {\n      return Result.err(errors);\n    }\n\n    // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n    return Result.ok(a as T);\n  };\n\n  const is = createIsFn<T>(validate);\n\n  const fill: Type<T>['fill'] = (a) => (is(a) ? a : defaultType.fill(a));\n\n  const baseType: Type<T> = {\n    typeName: typeNameFilled,\n    defaultValue: defaultType.defaultValue,\n    fill,\n    // Non-record intersections (e.g. branded primitives) have no excess paths\n    // to remove. Record intersections override this below.\n    prune: (a) => a,\n    validate,\n    is,\n    assertIs: createAssertFn(validate),\n    cast: createCastFn(validate),\n  } as const;\n\n  // If all types are records, add RecordTypeInternals\n  if (types.every(hasRecordInternals)) {\n    const shapeStructures = Arr.map(types, (t) => t.shapeStructure);\n\n    const excessProperty: ExcessPropertyOption = Arr.some(\n      types,\n      (t) => t.excessProperty === 'reject',\n    )\n      ? 'reject'\n      : 'allow';\n\n    // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n    return {\n      ...baseType,\n      // A value path is represented by a record intersection if it is\n      // represented by any member, so the pruned members are merged (deeply;\n      // see `mergePruned`). All members are record types here\n      // (`hasRecordInternals`), so each pruned result is a record.\n      prune: (a: T): T =>\n        // eslint-disable-next-line total-functions/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-return\n        types.map((t) => t.prune(a)).reduce(mergePruned, {}) as T,\n      shapeStructure: Arr.isFixedLengthTuple(shapeStructures, 1)\n        ? shapeStructures[0]\n        : ({ kind: 'intersection', parts: shapeStructures } as const),\n      excessProperty,\n    } as IntersectionType<Types>;\n  }\n\n  return baseType;\n};\n\n/**\n * Deep-merges the pruned results of intersection members. Each pruned result\n * contains only the value paths represented by its own member type, so the\n * results have to be merged recursively to keep the union of all represented\n * paths — a shallow spread would drop nested paths kept by an earlier member\n * whenever a later member shares the same key (e.g. two members both having a\n * `pos` sub-record with different fields).\n */\nconst mergePruned = (x: unknown, y: unknown): unknown => {\n  if (isRecord(x) && isRecord(y)) {\n    const xMergedWithY = Object.fromEntries(\n      Object.entries(x).map(([k, xv]) =>\n        tp(k, hasKey(y, k) ? mergePruned(xv, y[k]) : xv),\n      ),\n    );\n\n    const yOnly = Object.fromEntries(\n      Object.entries(y).filter(([k]) => !hasKey(x, k)),\n    );\n\n    return { ...xMergedWithY, ...yOnly };\n  }\n\n  // Both members pruned the same input array, so the lengths always match;\n  // merge element-wise to keep every represented element path.\n  if (Arr.isArray(x) && Arr.isArray(y) && x.length === y.length) {\n    return x.map((xi, i) => mergePruned(xi, y[i]));\n  }\n\n  // Not structurally mergeable — the later member wins.\n  return y;\n};\n\ntype IntersectionType<Types extends NonEmptyTuple<AnyType>> = Type<\n  IntersectionTypeValue<Types>\n>;\n\ntype IntersectionTypeValue<Types extends NonEmptyTuple<AnyType>> =\n  TsFortressInternal.IntersectionTypeValueImpl<Types>;\n\nnamespace TsFortressInternal {\n  export type IntersectionTypeValueImpl<Types extends NonEmptyTuple<AnyType>> =\n    Intersection<Cast0<UnwrapTypeList<Types>>>;\n\n  type Cast0<T> = readonly [T] extends readonly [NonEmptyTuple<unknown>]\n    ? T\n    : never;\n}\n\nexpectType<\n  IntersectionType<\n    readonly [\n      Type<\n        Readonly<{\n          a: 0;\n          b: 0;\n        }>\n      >,\n      Type<\n        Readonly<{\n          b: 0;\n          c: 0;\n        }>\n      >,\n    ]\n  >,\n  Type<\n    Readonly<{\n      a: 0;\n      b: 0;\n      c: 0;\n    }>\n  >\n>('=');\n\ntype UnwrapTypeList<Types extends readonly AnyType[]> =\n  TsFortressInternal.UnwrapTypeImpl<Types>;\n\nnamespace TsFortressInternal {\n  export type UnwrapTypeImpl<Types extends readonly unknown[]> =\n    Types extends readonly []\n      ? readonly []\n      : Types extends readonly [infer Head, ...infer Tail]\n        ? readonly [TypeOf<Cast1<Head>>, ...UnwrapTypeImpl<Tail>]\n        : never;\n\n  type Cast1<T> = [T] extends [AnyType] ? T : never;\n}\n\nexpectType<\n  TypeOf<\n    Type<\n      Readonly<{\n        a: 0;\n        b: 0;\n      }>\n    >\n  >,\n  Readonly<{\n    a: 0;\n    b: 0;\n  }>\n>('=');\n\nexpectType<\n  UnwrapTypeList<\n    readonly [\n      Type<\n        Readonly<{\n          a: 0;\n          b: 0;\n        }>\n      >,\n      Type<\n        Readonly<{\n          b: 0;\n          c: 0;\n        }>\n      >,\n    ]\n  >,\n  readonly [\n    Readonly<{\n      a: 0;\n      b: 0;\n    }>,\n    Readonly<{\n      b: 0;\n      c: 0;\n    }>,\n  ]\n>('=');\n\nexpectType<\n  Intersection<\n    readonly [\n      Readonly<{\n        a: 0;\n        b: 0;\n      }>,\n      Readonly<{\n        b: 0;\n        c: 0;\n      }>,\n    ]\n  >,\n  Readonly<{\n    a: 0;\n    b: 0;\n    c: 0;\n  }>\n>('=');\n\nexpectType<\n  Intersection<\n    readonly [\n      Readonly<{\n        a: 0;\n        b: 0;\n      }>,\n      Readonly<{\n        b: 0;\n        c: 0;\n      }>,\n      Readonly<{\n        c: 0;\n        d: 0;\n      }>,\n    ]\n  >,\n  Readonly<{\n    a: 0;\n    b: 0;\n    c: 0;\n    d: 0;\n  }>\n>('=');\n\nexpectType<\n  Intersection<\n    readonly [\n      Readonly<{\n        a: 0;\n        b: 0;\n      }>,\n      Readonly<{\n        b: 1;\n        c: 0;\n      }>,\n    ]\n  >,\n  never\n>('=');\n"],"names":[],"mappings":"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"}