The terms "union" and "intersection" as used in Typescript are mathematically correct, but still a bit confusing when applied to objects/interfaces. A union of two interfaces A and B is a type for all objects which are A or B. To work with such type reliably, the compiler has to be sure that only keys that are present in both A and B are used. In other words, a union of objects contains only an intersection of their keys. Conversely, an intersection of A and B, A & B, contains all keys from both A and B, that is, an intersection of interfaces is a union of their keys:
type A = { a: 1, b: 2, x: 3 }
type B = { a: 1, b: 2, y: 3 }
type U = keyof (A | B) // a|b
type I = keyof (A & B) // a|b|x|y
Since your objects don't have any common keys, their union is essentially an empty object ({never: any}). It cannot have any properties.
What you can do, is to introduce a tagged union, in which all member types have a common property ("tag") which enables the compiler to differentiate between them. Example:
type SomeTask<T, P extends {}> = { type: T } & P
type TaskOne = SomeTask<'first', {
one: number
}>
type TaskTwo = SomeTask<'second', {
two: number
}>
type Task = TaskOne | TaskTwo;
function validateTask(t: Task) {
if (t.type === 'first')
console.log(t.one);
if (t.type === 'second')
console.log(t.two);
}
Here, the compiler knows, that if the type is first, then the object is expected to have the one property.
const task:Taskmight beTaskAandTaskAhas nofee.feeat the moment I guess I am handling it wrong than. Any idea how can I make it work in this scenario?