{"version":3,"file":"merge-records.mjs","sources":["../../src/record/merge-records.mts"],"sourcesContent":["import { Arr, expectType, Obj } from 'ts-data-forge';\nimport {\n  type Intersection,\n  type NonEmptyTuple,\n  type UnknownRecord,\n} from 'ts-type-forge';\nimport { union } from '../compose/index.mjs';\nimport { literal } from '../other-types/index.mjs';\nimport {\n  type ExcessPropertyOption,\n  type RecordTypeInternals,\n  type Type,\n  type TypeOf,\n  expandShapeStructure,\n  hasRecordInternals,\n} from '../type.mjs';\nimport { toIntersectionString } from '../utils/index.mjs';\nimport { record } from './record.mjs';\n\nconst MERGE_RECORDS_MAX_VARIANTS = 10_000;\n\nexport const mergeRecords = <\n  const Types extends NonEmptyTuple<Type<UnknownRecord>>,\n>(\n  recordTypes: Types,\n  options?: Partial<\n    Readonly<{\n      typeName: string;\n      excessProperty: ExcessPropertyOption;\n    }>\n  >,\n): MergeRecordsType<Types> => {\n  if (!recordTypes.every(hasRecordInternals)) {\n    throw new Error(\n      'Expected a record type but received a non-record type in mergeRecords',\n    );\n  }\n\n  const typeNameFilled: string =\n    options?.typeName ??\n    `(${toIntersectionString(recordTypes.map((a) => a.typeName))})`;\n\n  // Expand all shape structures to get all possible shape combinations\n  const expandedShapesPerType = Arr.map(recordTypes, (t) =>\n    expandShapeStructure(t.shapeStructure),\n  );\n\n  // Estimate the total number of merged variants that would be produced.\n  // This is the product of the number of variants for each record type.\n  const estimatedVariantCount = expandedShapesPerType.reduce(\n    (acc, shapes) => acc * shapes.length,\n    1,\n  );\n\n  if (estimatedVariantCount > MERGE_RECORDS_MAX_VARIANTS) {\n    throw new Error(\n      `mergeRecords would create ${estimatedVariantCount} record variants, exceeding the limit of ${MERGE_RECORDS_MAX_VARIANTS}. ` +\n        'This could lead to excessive memory or CPU usage. Consider simplifying the record types or reducing union/intersection nesting.',\n    );\n  }\n\n  // For merging records (which is an intersection operation),\n  // we need to create the cartesian product of all variants\n  // For example: {a} & ({b} | {c}) = ({a} & {b}) | ({a} & {c})\n  const allCombinations = Arr.cartesianProduct(expandedShapesPerType);\n\n  // Merge each combination\n  const mergedShapes = Arr.map(allCombinations, (shapes) =>\n    Obj.merge(...shapes),\n  );\n\n  const excessProperty =\n    options?.excessProperty ?? deriveStrictestExcessProperty(recordTypes);\n\n  // If there's only one merged shape, return it directly\n  if (Arr.isFixedLengthTuple(mergedShapes, 1)) {\n    // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n    return record(mergedShapes[0], {\n      typeName: typeNameFilled,\n      excessProperty,\n    }) as MergeRecordsType<Types>;\n  }\n\n  // If there are multiple variants, we need to return a union\n  const variants = Arr.map(mergedShapes, (shape) =>\n    record(shape, { excessProperty }),\n  );\n\n  // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n  return union(variants as NonEmptyTuple<Type<UnknownRecord>>, {\n    typeName: typeNameFilled,\n  }) as MergeRecordsType<Types>;\n};\n\ntype MergeRecordsType<Types extends readonly Type<UnknownRecord>[]> = Type<\n  MergedExactValue<Types>\n>;\n\n/** Compute the merged value type directly from input types' `defaultValue`. */\ntype MergedExactValue<Types extends readonly Type<UnknownRecord>[]> =\n  FlattenIntersection<Intersection<ExactValueTuple<Types>>>;\n\n/** Flatten an intersection result into a single mapped type for better TypeScript compatibility. */\ntype FlattenIntersection<T> = T extends UnknownRecord\n  ? Readonly<{ [K in keyof T]: T[K] }>\n  : T;\n\ntype ExactValueTuple<Types extends readonly unknown[]> =\n  Types extends readonly [infer Head, ...infer Tail]\n    ? readonly [ExactValueOf<Head>, ...ExactValueTuple<Tail>]\n    : readonly [];\n\ntype ExactValueOf<T> =\n  T extends Readonly<{ defaultValue: infer V }> ? V : never;\n\nconst deriveStrictestExcessProperty = (\n  types: readonly RecordTypeInternals[],\n): ExcessPropertyOption =>\n  types.some((t) => t.excessProperty === 'reject') ? 'reject' : 'allow';\n\n// Verify MergedExactValue flattens correctly\n{\n  type R1 = ReturnType<\n    typeof record<Readonly<{ x: Type<number>; y: Type<number> }>>\n  >;\n\n  type R2 = ReturnType<\n    typeof record<Readonly<{ z: Type<number>; w: Type<number> }>>\n  >;\n\n  expectType<\n    MergedExactValue<readonly [R1, R2]>,\n    Readonly<{ x: number; y: number; z: number; w: number }>\n  >('=');\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  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\nif (import.meta.vitest !== undefined) {\n  test('Obj.merge for shapes', () => {\n    const _m1 = Obj.merge(\n      { a: literal(0), b: literal(0) },\n      { c: literal(0), d: literal(0) },\n    );\n\n    expectType<\n      typeof _m1,\n      Readonly<{\n        a: Type<0>;\n        b: Type<0>;\n        c: Type<0>;\n        d: Type<0>;\n      }>\n    >('=');\n\n    const _m2 = Obj.merge(\n      { a: literal(0), b: literal(0) },\n      { b: literal(0), c: literal(0) },\n    );\n\n    expectType<\n      typeof _m2,\n      Readonly<{\n        a: Type<0>;\n        b: Type<0>;\n        c: Type<0>;\n      }>\n    >('=');\n\n    assert.isTrue(true); // dummy assertion to avoid \"Test has no assertions\" error\n  });\n}\n"],"names":[],"mappings":";;;;;;;;AAmBA,MAAM,0BAAA,GAA6B,GAAA;MAEtB,YAAA,GAAe,CAG1B,WAAA,EACA,OAAA,KAM4B;AAC5B,EAAA,IAAI,CAAC,WAAA,CAAY,KAAA,CAAM,kBAAkB,CAAA,EAAG;AAC1C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EAAA;AAGF,EAAA,MAAM,cAAA,GACJ,OAAA,EAAS,QAAA,IACT,CAAA,CAAA,EAAI,oBAAA,CAAqB,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,QAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAG9D,EAAA,MAAM,wBAAwB,GAAA,CAAI,GAAA;AAAA,IAAI,WAAA;AAAA,IAAa,CAAC,CAAA,KAClD,oBAAA,CAAqB,CAAA,CAAE,cAAc;AAAA,GACvC;AAIA,EAAA,MAAM,wBAAwB,qBAAA,CAAsB,MAAA;AAAA,IAClD,CAAC,GAAA,EAAK,MAAA,KAAW,GAAA,GAAM,MAAA,CAAO,MAAA;AAAA,IAC9B;AAAA,GACF;AAEA,EAAA,IAAI,wBAAwB,0BAAA,EAA4B;AACtD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,0BAAA,EAA6B,qBAAqB,CAAA,yCAAA,EAA4C,0BAA0B,CAAA,iIAAA;AAAA,KAE1H;AAAA,EAAA;AAMF,EAAA,MAAM,eAAA,GAAkB,GAAA,CAAI,gBAAA,CAAiB,qBAAqB,CAAA;AAGlE,EAAA,MAAM,eAAe,GAAA,CAAI,GAAA;AAAA,IAAI,eAAA;AAAA,IAAiB,CAAC,MAAA,KAC7C,GAAA,CAAI,KAAA,CAAM,GAAG,MAAM;AAAA,GACrB;AAEA,EAAA,MAAM,cAAA,GACJ,OAAA,EAAS,cAAA,IAAkB,6BAAA,CAA8B,WAAW,CAAA;AAGtE,EAAA,IAAI,GAAA,CAAI,kBAAA,CAAmB,YAAA,EAAc,CAAC,CAAA,EAAG;AAE3C,IAAA,OAAO,MAAA,CAAO,YAAA,CAAa,CAAC,CAAA,EAAG;AAAA,MAC7B,QAAA,EAAU,cAAA;AAAA,MACV;AAAA,KACD,CAAA;AAAA,EAAA;AAIH,EAAA,MAAM,WAAW,GAAA,CAAI,GAAA;AAAA,IAAI,YAAA;AAAA,IAAc,CAAC,KAAA,KACtC,MAAA,CAAO,KAAA,EAAO,EAAE,gBAAgB;AAAA,GAClC;AAGA,EAAA,OAAO,MAAM,QAAA,EAAgD;AAAA,IAC3D,QAAA,EAAU;AAAA,GACX,CAAA;AACH;AAuBA,MAAM,6BAAA,GAAgC,CACpC,KAAA,KAEA,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,cAAA,KAAmB,QAAQ,CAAA,GAAI,QAAA,GAAW,OAAA;;;;"}