UNPKG

2.26 kBTypeScriptView Raw
1import type {KeysOfUnion} from './keys-of-union';
2
3/**
4Omits keys from a type, distributing the operation over a union.
5
6TypeScript's `Omit` doesn't distribute over unions, leading to the erasure of unique properties from union members when omitting keys. This creates a type that only retains properties common to all union members, making it impossible to access member-specific properties after the Omit. Essentially, using `Omit` on a union type merges the types into a less specific one, hindering type narrowing and property access based on discriminants. This type solves that.
7
8Example:
9
10```
11type A = {
12 discriminant: 'A';
13 foo: string;
14 a: number;
15};
16
17type B = {
18 discriminant: 'B';
19 foo: string;
20 b: string;
21};
22
23type Union = A | B;
24
25type OmittedUnion = Omit<Union, 'foo'>;
26//=> {discriminant: 'A' | 'B'}
27
28const omittedUnion: OmittedUnion = createOmittedUnion();
29
30if (omittedUnion.discriminant === 'A') {
31 // We would like to narrow `omittedUnion`'s type
32 // to `A` here, but we can't because `Omit`
33 // doesn't distribute over unions.
34
35 omittedUnion.a;
36 //=> Error: `a` is not a property of `{discriminant: 'A' | 'B'}`
37}
38```
39
40While `Except` solves this problem, it restricts the keys you can omit to the ones that are present in **ALL** union members, where `DistributedOmit` allows you to omit keys that are present in **ANY** union member.
41
42@example
43```
44type A = {
45 discriminant: 'A';
46 foo: string;
47 a: number;
48};
49
50type B = {
51 discriminant: 'B';
52 foo: string;
53 bar: string;
54 b: string;
55};
56
57type C = {
58 discriminant: 'C';
59 bar: string;
60 c: boolean;
61};
62
63// Notice that `foo` exists in `A` and `B`, but not in `C`, and
64// `bar` exists in `B` and `C`, but not in `A`.
65
66type Union = A | B | C;
67
68type OmittedUnion = DistributedOmit<Union, 'foo' | 'bar'>;
69
70const omittedUnion: OmittedUnion = createOmittedUnion();
71
72if (omittedUnion.discriminant === 'A') {
73 omittedUnion.a;
74 //=> OK
75
76 omittedUnion.foo;
77 //=> Error: `foo` is not a property of `{discriminant: 'A'; a: string}`
78
79 omittedUnion.bar;
80 //=> Error: `bar` is not a property of `{discriminant: 'A'; a: string}`
81}
82```
83
84@category Object
85*/
86export type DistributedOmit<ObjectType, KeyType extends KeysOfUnion<ObjectType>> =
87 ObjectType extends unknown
88 ? Omit<ObjectType, KeyType>
89 : never;