Typescript: type check that record can be constructed out of a list of pieces of record

I want something like this function:

<RecordType>(list: Partial<RecordType>[]) => RecordType

How can I type check that all elements in the list will correspond to one and only one piece of the record?

Example:

type Person = {
  id: string;
  name: string;
  lastname: string;
};

fromPieces<Person>([
 { name: "oscar" },
 { id: "arst" },
 { lastname: "mendez" }
]) => ({
  name: "oscar",
  id: "arst",
  lastname: "mendez"
});

Solution 1:

As already explained in my comment I think that this is not possible as you can not statically typecheck that you put every "Partial" of the "Type" into the function to guarantee that you can build a whole "Type" of it.

But you can write a generic function aggregating the parts to a more complete Partial.

I don't know it this is the direction you want to go but this is a simple example:

const builder = <T> (...parts: Partial<T>[]):Partial<T> =>
  parts.reduce((acc, part) => ({...acc, ...part}), {})

builder<{x: number, y: number}>({x: 5}, {y: 3}) // this works

builder<{x: number, y: number}>({x: 5}, {z: 3}) // this gives an error

Solution 2:

It's possible with recursive types but its order dependen. You need to cast Person to Unions, so with EntriesOf I convert Person to: { id: string } | { name: string } | { lastname: string }

And then you can cast the union to an array with a requirement for each type. So UnionToTuple converts it to [{ id: string }, { name: string }, { lastname: string }]

With that, you can ensure that every property is present on the input, but it also order dependent(this can be fixed tho).

type EntriesOf<Type> = { [Key in keyof Type]: Pick<Type, Key> }[keyof Type];

type UnionToTuple<T> = (
    (
        (
            T extends any
                ? (t: T) => T
                : never
        ) extends infer U
            ? (U extends any
                ? (u: U) => any
                : never
            ) extends (v: infer V) => any
                ? V
                : never
            : never
    ) extends (_: any) => infer W
        ? [...UnionToTuple<Exclude<T, W>>, W]
        : []
);


type Person = {
  id: string;
  name: string;
  lastname: string;
};


function fromPieces<T>(arg: UnionToTuple<EntriesOf<T>>): T {
    return {} as T;
}

// this works 
const test = fromPieces<Person>([
  { id: "" },
  { name: "" },
  { lastname: "test" } 
])

// this wont work
const test1 = fromPieces<Person>([
  { name: "" },
  { id: "" },
  { lastname: "test" } 
])

See it in action

Credits to https://github.com/microsoft/TypeScript/issues/13298#issuecomment-707364842 for the UnionToTuple type.