type-fest 4.33.0 → 4.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -130,6 +130,7 @@ export type {ArraySplice} from './source/array-splice';
130
130
  export type {ArrayTail} from './source/array-tail';
131
131
  export type {SetFieldType} from './source/set-field-type';
132
132
  export type {Paths} from './source/paths';
133
+ export type {AllUnionFields} from './source/all-union-fields';
133
134
  export type {SharedUnionFields} from './source/shared-union-fields';
134
135
  export type {SharedUnionFieldsDeep} from './source/shared-union-fields-deep';
135
136
  export type {IsNull} from './source/is-null';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "type-fest",
3
- "version": "4.33.0",
3
+ "version": "4.34.0",
4
4
  "description": "A collection of essential TypeScript types",
5
5
  "license": "(MIT OR CC0-1.0)",
6
6
  "repository": "sindresorhus/type-fest",
package/readme.md CHANGED
@@ -203,6 +203,7 @@ Click the type names for complete docs.
203
203
  - [`Paths`](source/paths.d.ts) - Generate a union of all possible paths to properties in the given object.
204
204
  - [`SharedUnionFields`](source/shared-union-fields.d.ts) - Create a type with shared fields from a union of object types.
205
205
  - [`SharedUnionFieldsDeep`](source/shared-union-fields-deep.d.ts) - Create a type with shared fields from a union of object types, deeply traversing nested structures.
206
+ - [`AllUnionFields`](source/all-union-fields.d.ts) - Create a type with all fields from a union of object types.
206
207
  - [`DistributedOmit`](source/distributed-omit.d.ts) - Omits keys from a type, distributing the operation over a union.
207
208
  - [`DistributedPick`](source/distributed-pick.d.ts) - Picks keys from a type, distributing the operation over a union.
208
209
  - [`And`](source/and.d.ts) - Returns a boolean for whether two given types are both true.
@@ -0,0 +1,88 @@
1
+ import type {NonRecursiveType, ReadonlyKeysOfUnion, ValueOfUnion} from './internal';
2
+ import type {KeysOfUnion} from './keys-of-union';
3
+ import type {SharedUnionFields} from './shared-union-fields';
4
+ import type {Simplify} from './simplify';
5
+ import type {UnknownArray} from './unknown-array';
6
+
7
+ /**
8
+ Create a type with all fields from a union of object types.
9
+
10
+ Use-cases:
11
+ - You want a safe object type where each key exists in the union object.
12
+
13
+ @example
14
+ ```
15
+ import type {AllUnionFields} from 'type-fest';
16
+
17
+ type Cat = {
18
+ name: string;
19
+ type: 'cat';
20
+ catType: string;
21
+ };
22
+
23
+ type Dog = {
24
+ name: string;
25
+ type: 'dog';
26
+ dogType: string;
27
+ };
28
+
29
+ function displayPetInfo(petInfo: Cat | Dog) {
30
+ // typeof petInfo =>
31
+ // {
32
+ // name: string;
33
+ // type: 'cat';
34
+ // catType: string;
35
+ // } | {
36
+ // name: string;
37
+ // type: 'dog';
38
+ // dogType: string;
39
+ // }
40
+
41
+ console.log('name: ', petInfo.name);
42
+ console.log('type: ', petInfo.type);
43
+
44
+ // TypeScript complains about `catType` and `dogType` not existing on type `Cat | Dog`.
45
+ console.log('animal type: ', petInfo.catType ?? petInfo.dogType);
46
+ }
47
+
48
+ function displayPetInfo(petInfo: AllUnionFields<Cat | Dog>) {
49
+ // typeof petInfo =>
50
+ // {
51
+ // name: string;
52
+ // type: 'cat' | 'dog';
53
+ // catType?: string;
54
+ // dogType?: string;
55
+ // }
56
+
57
+ console.log('name: ', petInfo.name);
58
+ console.log('type: ', petInfo.type);
59
+
60
+ // No TypeScript error.
61
+ console.log('animal type: ', petInfo.catType ?? petInfo.dogType);
62
+ }
63
+ ```
64
+
65
+ @see SharedUnionFields
66
+
67
+ @category Object
68
+ @category Union
69
+ */
70
+ export type AllUnionFields<Union> =
71
+ Extract<Union, NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | UnknownArray> extends infer SkippedMembers
72
+ ? Exclude<Union, SkippedMembers> extends infer RelevantMembers
73
+ ?
74
+ | SkippedMembers
75
+ | Simplify<
76
+ // Include fields that are common in all union members
77
+ SharedUnionFields<RelevantMembers> &
78
+ // Include readonly fields present in any union member
79
+ {
80
+ readonly [P in ReadonlyKeysOfUnion<RelevantMembers>]?: ValueOfUnion<RelevantMembers, P & KeysOfUnion<RelevantMembers>>
81
+ } &
82
+ // Include remaining fields that are neither common nor readonly
83
+ {
84
+ [P in Exclude<KeysOfUnion<RelevantMembers>, ReadonlyKeysOfUnion<RelevantMembers> | keyof RelevantMembers>]?: ValueOfUnion<RelevantMembers, P>
85
+ }
86
+ >
87
+ : never
88
+ : never;
@@ -1,5 +1,6 @@
1
1
  import type {Simplify} from '../simplify';
2
2
  import type {UnknownArray} from '../unknown-array';
3
+ import type {IsEqual} from '../is-equal';
3
4
  import type {KeysOfUnion} from '../keys-of-union';
4
5
  import type {FilterDefinedKeys, FilterOptionalKeys} from './keys';
5
6
  import type {NonRecursiveType} from './type';
@@ -122,3 +123,39 @@ type IndexSignature = HomomorphicPick<{[k: string]: unknown}, number>;
122
123
  export type HomomorphicPick<T, Keys extends KeysOfUnion<T>> = {
123
124
  [P in keyof T as Extract<P, Keys>]: T[P]
124
125
  };
126
+
127
+ /**
128
+ Extract all possible values for a given key from a union of object types.
129
+
130
+ @example
131
+ ```
132
+ type Statuses = ValueOfUnion<{ id: 1, status: "open" } | { id: 2, status: "closed" }, "status">;
133
+ //=> "open" | "closed"
134
+ ```
135
+ */
136
+ export type ValueOfUnion<Union, Key extends KeysOfUnion<Union>> =
137
+ Union extends unknown ? Key extends keyof Union ? Union[Key] : never : never;
138
+
139
+ /**
140
+ Extract all readonly keys from a union of object types.
141
+
142
+ @example
143
+ ```
144
+ type User = {
145
+ readonly id: string;
146
+ name: string;
147
+ };
148
+
149
+ type Post = {
150
+ readonly id: string;
151
+ readonly author: string;
152
+ body: string;
153
+ };
154
+
155
+ type ReadonlyKeys = ReadonlyKeysOfUnion<User | Post>;
156
+ //=> "id" | "author"
157
+ ```
158
+ */
159
+ export type ReadonlyKeysOfUnion<Union> = Union extends unknown ? keyof {
160
+ [Key in keyof Union as IsEqual<{[K in Key]: Union[Key]}, {readonly [K in Key]: Union[Key]}> extends true ? Key : never]: never
161
+ } : never;
@@ -1,10 +1,10 @@
1
+ import type {UnionToTuple} from 'expect-type';
1
2
  import type {ArraySplice} from './array-splice';
2
3
  import type {ExactKey, IsArrayReadonly, NonRecursiveType, SetArrayAccess, ToString} from './internal';
3
4
  import type {IsEqual} from './is-equal';
4
5
  import type {IsNever} from './is-never';
5
6
  import type {LiteralUnion} from './literal-union';
6
7
  import type {Paths} from './paths';
7
- import type {SharedUnionFieldsDeep} from './shared-union-fields-deep';
8
8
  import type {SimplifyDeep} from './simplify-deep';
9
9
  import type {UnknownArray} from './unknown-array';
10
10
 
@@ -92,11 +92,19 @@ type AddressInfo = OmitDeep<Info1, 'address.1.foo'>;
92
92
  */
93
93
  export type OmitDeep<T, PathUnion extends LiteralUnion<Paths<T>, string>> =
94
94
  SimplifyDeep<
95
- SharedUnionFieldsDeep<
96
- {[P in PathUnion]: OmitDeepWithOnePath<T, P>}[PathUnion]
97
- >,
95
+ OmitDeepHelper<T, UnionToTuple<PathUnion>>,
98
96
  UnknownArray>;
99
97
 
98
+ /**
99
+ Internal helper for {@link OmitDeep}.
100
+
101
+ Recursively transforms `T` by applying {@link OmitDeepWithOnePath} for each path in `PathTuple`.
102
+ */
103
+ type OmitDeepHelper<T, PathTuple extends UnknownArray> =
104
+ PathTuple extends [infer Path, ...infer RestPaths]
105
+ ? OmitDeepHelper<OmitDeepWithOnePath<T, Path & (string | number)>, RestPaths>
106
+ : T;
107
+
100
108
  /**
101
109
  Omit one path from the given object/array.
102
110
  */
package/source/paths.d.ts CHANGED
@@ -50,11 +50,53 @@ export type PathsOptions = {
50
50
  ```
51
51
  */
52
52
  bracketNotation?: boolean;
53
+
54
+ /**
55
+ Only include leaf paths in the output.
56
+
57
+ @default false
58
+
59
+ @example
60
+ ```
61
+ type Post = {
62
+ id: number;
63
+ author: {
64
+ id: number;
65
+ name: {
66
+ first: string;
67
+ last: string;
68
+ };
69
+ };
70
+ };
71
+
72
+ type AllPaths = Paths<Post, {leavesOnly: false}>;
73
+ //=> 'id' | 'author' | 'author.id' | 'author.name' | 'author.name.first' | 'author.name.last'
74
+
75
+ type LeafPaths = Paths<Post, {leavesOnly: true}>;
76
+ //=> 'id' | 'author.id' | 'author.name.first' | 'author.name.last'
77
+ ```
78
+
79
+ @example
80
+ ```
81
+ type ArrayExample = {
82
+ array: Array<{foo: string}>;
83
+ tuple: [string, {bar: string}];
84
+ };
85
+
86
+ type AllPaths = Paths<ArrayExample, {leavesOnly: false}>;
87
+ //=> 'array' | `array.${number}` | `array.${number}.foo` | 'tuple' | 'tuple.0' | 'tuple.1' | 'tuple.1.bar'
88
+
89
+ type LeafPaths = Paths<ArrayExample, {leavesOnly: true}>;
90
+ //=> `array.${number}.foo` | 'tuple.0' | 'tuple.1.bar'
91
+ ```
92
+ */
93
+ leavesOnly?: boolean;
53
94
  };
54
95
 
55
96
  type DefaultPathsOptions = {
56
97
  maxRecursionDepth: 10;
57
98
  bracketNotation: false;
99
+ leavesOnly: false;
58
100
  };
59
101
 
60
102
  /**
@@ -103,6 +145,8 @@ export type Paths<T, Options extends PathsOptions = {}> = _Paths<T, {
103
145
  maxRecursionDepth: Options['maxRecursionDepth'] extends number ? Options['maxRecursionDepth'] : DefaultPathsOptions['maxRecursionDepth'];
104
146
  // Set default bracketNotation to false
105
147
  bracketNotation: Options['bracketNotation'] extends boolean ? Options['bracketNotation'] : DefaultPathsOptions['bracketNotation'];
148
+ // Set default leavesOnly to false
149
+ leavesOnly: Options['leavesOnly'] extends boolean ? Options['leavesOnly'] : DefaultPathsOptions['leavesOnly'];
106
150
  }>;
107
151
 
108
152
  type _Paths<T, Options extends Required<PathsOptions>> =
@@ -142,11 +186,17 @@ type InternalPaths<T, Options extends Required<PathsOptions>> =
142
186
  ) extends infer TranformedKey extends string | number ?
143
187
  // 1. If style is 'a[0].b' and 'Key' is a numberlike value like 3 or '3', transform 'Key' to `[${Key}]`, else to `${Key}` | Key
144
188
  // 2. If style is 'a.0.b', transform 'Key' to `${Key}` | Key
145
- | TranformedKey
189
+ | (Options['leavesOnly'] extends true
190
+ ? MaxDepth extends 0
191
+ ? TranformedKey
192
+ : T[Key] extends EmptyObject | readonly [] | NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>
193
+ ? TranformedKey
194
+ : never
195
+ : TranformedKey)
146
196
  | (
147
197
  // Recursively generate paths for the current key
148
198
  GreaterThan<MaxDepth, 0> extends true // Limit the depth to prevent infinite recursion
149
- ? _Paths<T[Key], {bracketNotation: Options['bracketNotation']; maxRecursionDepth: Subtract<MaxDepth, 1>}> extends infer SubPath
199
+ ? _Paths<T[Key], {bracketNotation: Options['bracketNotation']; maxRecursionDepth: Subtract<MaxDepth, 1>; leavesOnly: Options['leavesOnly']}> extends infer SubPath
150
200
  ? SubPath extends string | number
151
201
  ? (
152
202
  Options['bracketNotation'] extends true
@@ -59,6 +59,7 @@ function displayPetInfo(petInfo: SharedUnionFields<Cat | Dog>) {
59
59
  ```
60
60
 
61
61
  @see SharedUnionFieldsDeep
62
+ @see AllUnionFields
62
63
 
63
64
  @category Object
64
65
  @category Union