1 | import type {KeysOfUnion} from './keys-of-union';
|
2 |
|
3 | /**
|
4 | Omits keys from a type, distributing the operation over a union.
|
5 |
|
6 | TypeScript'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 |
|
8 | Example:
|
9 |
|
10 | ```
|
11 | type A = {
|
12 | discriminant: 'A';
|
13 | foo: string;
|
14 | a: number;
|
15 | };
|
16 |
|
17 | type B = {
|
18 | discriminant: 'B';
|
19 | foo: string;
|
20 | b: string;
|
21 | };
|
22 |
|
23 | type Union = A | B;
|
24 |
|
25 | type OmittedUnion = Omit<Union, 'foo'>;
|
26 | //=> {discriminant: 'A' | 'B'}
|
27 |
|
28 | const omittedUnion: OmittedUnion = createOmittedUnion();
|
29 |
|
30 | if (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 |
|
40 | While `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 | ```
|
44 | type A = {
|
45 | discriminant: 'A';
|
46 | foo: string;
|
47 | a: number;
|
48 | };
|
49 |
|
50 | type B = {
|
51 | discriminant: 'B';
|
52 | foo: string;
|
53 | bar: string;
|
54 | b: string;
|
55 | };
|
56 |
|
57 | type 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 |
|
66 | type Union = A | B | C;
|
67 |
|
68 | type OmittedUnion = DistributedOmit<Union, 'foo' | 'bar'>;
|
69 |
|
70 | const omittedUnion: OmittedUnion = createOmittedUnion();
|
71 |
|
72 | if (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 | */
|
86 | export type DistributedOmit<ObjectType, KeyType extends KeysOfUnion<ObjectType>> =
|
87 | ObjectType extends unknown
|
88 | ? Omit<ObjectType, KeyType>
|
89 | : never;
|