1 | import type {KeysOfUnion} from './keys-of-union';
|
2 |
|
3 | /**
|
4 | Pick keys from a type, distributing the operation over a union.
|
5 |
|
6 | TypeScript's `Pick` doesn't distribute over unions, leading to the erasure of unique properties from union members when picking keys. This creates a type that only retains properties common to all union members, making it impossible to access member-specific properties after the Pick. Essentially, using `Pick` 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: {
|
14 | bar: string;
|
15 | };
|
16 | };
|
17 |
|
18 | type B = {
|
19 | discriminant: 'B';
|
20 | foo: {
|
21 | baz: string;
|
22 | };
|
23 | };
|
24 |
|
25 | type Union = A | B;
|
26 |
|
27 | type PickedUnion = Pick<Union, 'discriminant' | 'foo'>;
|
28 | //=> {discriminant: 'A' | 'B', foo: {bar: string} | {baz: string}}
|
29 |
|
30 | const pickedUnion: PickedUnion = createPickedUnion();
|
31 |
|
32 | if (pickedUnion.discriminant === 'A') {
|
33 | // We would like to narrow `pickedUnion`'s type
|
34 | // to `A` here, but we can't because `Pick`
|
35 | // doesn't distribute over unions.
|
36 |
|
37 | pickedUnion.foo.bar;
|
38 | //=> Error: Property 'bar' does not exist on type '{bar: string} | {baz: string}'.
|
39 | }
|
40 | ```
|
41 |
|
42 | @example
|
43 | ```
|
44 | type A = {
|
45 | discriminant: 'A';
|
46 | foo: {
|
47 | bar: string;
|
48 | };
|
49 | extraneous: boolean;
|
50 | };
|
51 |
|
52 | type B = {
|
53 | discriminant: 'B';
|
54 | foo: {
|
55 | baz: string;
|
56 | };
|
57 | extraneous: boolean;
|
58 | };
|
59 |
|
60 | // Notice that `foo.bar` exists in `A` but not in `B`.
|
61 |
|
62 | type Union = A | B;
|
63 |
|
64 | type PickedUnion = DistributedPick<Union, 'discriminant' | 'foo'>;
|
65 |
|
66 | const pickedUnion: PickedUnion = createPickedUnion();
|
67 |
|
68 | if (pickedUnion.discriminant === 'A') {
|
69 | pickedUnion.foo.bar;
|
70 | //=> OK
|
71 |
|
72 | pickedUnion.extraneous;
|
73 | //=> Error: Property `extraneous` does not exist on type `Pick<A, 'discriminant' | 'foo'>`.
|
74 |
|
75 | pickedUnion.foo.baz;
|
76 | //=> Error: `bar` is not a property of `{discriminant: 'A'; a: string}`.
|
77 | }
|
78 | ```
|
79 |
|
80 | @category Object
|
81 | */
|
82 | export type DistributedPick<ObjectType, KeyType extends KeysOfUnion<ObjectType>> =
|
83 | ObjectType extends unknown
|
84 | ? Pick<ObjectType, Extract<KeyType, keyof ObjectType>>
|
85 | : never;
|