type-fest 4.32.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.32.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.
@@ -370,6 +371,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
370
371
  - `SetEntry` - See [`IterableElement`](source/iterable-element.d.ts)
371
372
  - `SetValues` - See [`IterableElement`](source/iterable-element.d.ts)
372
373
  - `PickByTypes` - See [`ConditionalPick`](source/conditional-pick.d.ts)
374
+ - `HomomorphicOmit` - See [`Except`](source/except.d.ts)
373
375
 
374
376
  ## Tips
375
377
 
@@ -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;
@@ -69,6 +69,26 @@ type FooWithoutB = Except<Foo, 'b', {requireExactProps: true}>;
69
69
 
70
70
  const fooWithoutB: FooWithoutB = {a: 1, b: '2'};
71
71
  //=> errors at 'b': Type 'string' is not assignable to type 'undefined'.
72
+
73
+ // The `Omit` utility type doesn't work when omitting specific keys from objects containing index signatures.
74
+
75
+ // Consider the following example:
76
+
77
+ type UserData = {
78
+ [metadata: string]: string;
79
+ email: string;
80
+ name: string;
81
+ role: 'admin' | 'user';
82
+ };
83
+
84
+ // `Omit` clearly doesn't behave as expected in this case:
85
+ type PostPayload = Omit<UserData, 'email'>;
86
+ //=> type PostPayload = { [x: string]: string; [x: number]: string; }
87
+
88
+ // In situations like this, `Except` works better.
89
+ // It simply removes the `email` key while preserving all the other keys.
90
+ type PostPayload = Except<UserData, 'email'>;
91
+ //=> type PostPayload = { [x: string]: string; name: string; role: 'admin' | 'user'; }
72
92
  ```
73
93
 
74
94
  @category Object
package/source/get.d.ts CHANGED
@@ -205,6 +205,6 @@ export type Get<
205
205
  BaseType,
206
206
  Path extends
207
207
  | readonly string[]
208
- | LiteralStringUnion<ToString<Paths<BaseType, {bracketNotation: false}> | Paths<BaseType, {bracketNotation: true}>>>,
208
+ | LiteralStringUnion<ToString<Paths<BaseType, {bracketNotation: false; maxRecursionDepth: 2}> | Paths<BaseType, {bracketNotation: true; maxRecursionDepth: 2}>>>,
209
209
  Options extends GetOptions = {}> =
210
210
  GetWithPath<BaseType, Path extends string ? ToPath<Path> : Path, Options>;
@@ -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;
@@ -37,9 +37,11 @@ If `<Fill>` is not provided, it will default to `unknown`.
37
37
 
38
38
  @link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f
39
39
  */
40
- export type BuildTuple<L extends number, Fill = unknown, T extends readonly unknown[] = []> = T['length'] extends L
41
- ? T
42
- : BuildTuple<L, Fill, [...T, Fill]>;
40
+ export type BuildTuple<L extends number, Fill = unknown, T extends readonly unknown[] = []> = number extends L
41
+ ? Fill[]
42
+ : L extends T['length']
43
+ ? T
44
+ : BuildTuple<L, Fill, [...T, Fill]>;
43
45
 
44
46
  /**
45
47
  Returns the maximum value from a tuple of integers.
@@ -2,6 +2,7 @@ import type {Primitive} from './primitive';
2
2
  import type {Numeric} from './numeric';
3
3
  import type {IsNotFalse, IsPrimitive} from './internal';
4
4
  import type {IsNever} from './is-never';
5
+ import type {IfNever} from './if-never';
5
6
 
6
7
  /**
7
8
  Returns a boolean for whether the given type `T` is the specified `LiteralType`.
@@ -65,6 +66,8 @@ Useful for:
65
66
  - constraining strings to be a string literal
66
67
  - type utilities, such as when constructing parsers and ASTs
67
68
 
69
+ The implementation of this type is inspired by the trick mentioned in this [StackOverflow answer](https://stackoverflow.com/a/68261113/420747).
70
+
68
71
  @example
69
72
  ```
70
73
  import type {IsStringLiteral} from 'type-fest';
@@ -80,10 +83,45 @@ const output = capitalize('hello, world!');
80
83
  //=> 'Hello, world!'
81
84
  ```
82
85
 
86
+ @example
87
+ ```
88
+ // String types with infinite set of possible values return `false`.
89
+
90
+ import type {IsStringLiteral} from 'type-fest';
91
+
92
+ type AllUppercaseStrings = IsStringLiteral<Uppercase<string>>;
93
+ //=> false
94
+
95
+ type StringsStartingWithOn = IsStringLiteral<`on${string}`>;
96
+ //=> false
97
+
98
+ // This behaviour is particularly useful in string manipulation utilities, as infinite string types often require separate handling.
99
+
100
+ type Length<S extends string, Counter extends never[] = []> =
101
+ IsStringLiteral<S> extends false
102
+ ? number // return `number` for infinite string types
103
+ : S extends `${string}${infer Tail}`
104
+ ? Length<Tail, [...Counter, never]>
105
+ : Counter['length'];
106
+
107
+ type L1 = Length<Lowercase<string>>;
108
+ //=> number
109
+
110
+ type L2 = Length<`${number}`>;
111
+ //=> number
112
+ ```
113
+
83
114
  @category Type Guard
84
115
  @category Utilities
85
116
  */
86
- export type IsStringLiteral<T> = LiteralCheck<T, string>;
117
+ export type IsStringLiteral<T> = IfNever<T, false,
118
+ // If `T` is an infinite string type (e.g., `on${string}`), `Record<T, never>` produces an index signature,
119
+ // and since `{}` extends index signatures, the result becomes `false`.
120
+ T extends string
121
+ ? {} extends Record<T, never>
122
+ ? false
123
+ : true
124
+ : false>;
87
125
 
88
126
  /**
89
127
  Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
@@ -1,3 +1,5 @@
1
+ import type {UnionToIntersection} from './union-to-intersection';
2
+
1
3
  /**
2
4
  Create a union of all keys from a given type, even those exclusive to specific union members.
3
5
 
@@ -35,6 +37,6 @@ type AllKeys = KeysOfUnion<Union>;
35
37
 
36
38
  @category Object
37
39
  */
38
- export type KeysOfUnion<ObjectType> = ObjectType extends unknown
39
- ? keyof ObjectType
40
- : never;
40
+ export type KeysOfUnion<ObjectType> =
41
+ // Hack to fix https://github.com/sindresorhus/type-fest/issues/1008
42
+ keyof UnionToIntersection<ObjectType extends unknown ? Record<keyof ObjectType, never> : 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
  */
@@ -61,18 +61,6 @@ type OmitIndexSignature<ObjectType> = {
61
61
 
62
62
  If `{}` is assignable, it means that `KeyType` is an index signature and we want to remove it. If it is not assignable, `KeyType` is a "real" key and we want to keep it.
63
63
 
64
- ```
65
- import type {OmitIndexSignature} from 'type-fest';
66
-
67
- type OmitIndexSignature<ObjectType> = {
68
- [KeyType in keyof ObjectType
69
- as {} extends Record<KeyType, unknown>
70
- ? never // => Remove this `KeyType`.
71
- : KeyType // => Keep this `KeyType` as it is.
72
- ]: ObjectType[KeyType];
73
- };
74
- ```
75
-
76
64
  @example
77
65
  ```
78
66
  import type {OmitIndexSignature} from 'type-fest';
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
@@ -3,8 +3,6 @@ Pick only index signatures from the given object type, leaving out all explicitl
3
3
 
4
4
  This is the counterpart of `OmitIndexSignature`.
5
5
 
6
- When you use a type that will iterate through an object that has indexed keys and explicitly defined keys you end up with a type where only the indexed keys are kept. This is because `keyof` of an indexed type always returns `string | number | symbol`, because every key is possible in that object. With this type, you can save the indexed keys and reinject them later, like in the second example below.
7
-
8
6
  @example
9
7
  ```
10
8
  import type {PickIndexSignature} from 'type-fest';
@@ -23,7 +21,7 @@ type Example = {
23
21
  [x: `embedded-${number}`]: string;
24
22
 
25
23
  // These explicitly defined keys will be removed.
26
- ['snake-case-key']: string;
24
+ ['kebab-case-key']: string;
27
25
  [symbolKey]: string;
28
26
  foo: 'bar';
29
27
  qux?: 'baz';
@@ -42,56 +40,6 @@ type ExampleIndexSignature = PickIndexSignature<Example>;
42
40
  // }
43
41
  ```
44
42
 
45
- @example
46
- ```
47
- import type {OmitIndexSignature, PickIndexSignature, Simplify} from 'type-fest';
48
-
49
- type Foo = {
50
- [x: string]: string;
51
- foo: string;
52
- bar: number;
53
- };
54
-
55
- // Imagine that you want a new type `Bar` that comes from `Foo`.
56
- // => {
57
- // [x: string]: string;
58
- // bar: number;
59
- // };
60
-
61
- type Bar = Omit<Foo, 'foo'>;
62
- // This is not working because `Omit` returns only indexed keys.
63
- // => {
64
- // [x: string]: string;
65
- // [x: number]: string;
66
- // }
67
-
68
- // One solution is to save the indexed signatures to new type.
69
- type FooIndexSignature = PickIndexSignature<Foo>;
70
- // => {
71
- // [x: string]: string;
72
- // }
73
-
74
- // Get a new type without index signatures.
75
- type FooWithoutIndexSignature = OmitIndexSignature<Foo>;
76
- // => {
77
- // foo: string;
78
- // bar: number;
79
- // }
80
-
81
- // At this point we can use Omit to get our new type.
82
- type BarWithoutIndexSignature = Omit<FooWithoutIndexSignature, 'foo'>;
83
- // => {
84
- // bar: number;
85
- // }
86
-
87
- // And finally we can merge back the indexed signatures.
88
- type BarWithIndexSignature = Simplify<BarWithoutIndexSignature & FooIndexSignature>;
89
- // => {
90
- // [x: string]: string;
91
- // bar: number;
92
- // }
93
- ```
94
-
95
43
  @see OmitIndexSignature
96
44
  @category Object
97
45
  */
@@ -6,7 +6,7 @@ type ReplaceOptions = {
6
6
  Represents a string with some or all matches replaced by a replacement.
7
7
 
8
8
  Use-case:
9
- - `snake-case-path` to `dotted.path.notation`
9
+ - `kebab-case-path` to `dotted.path.notation`
10
10
  - Changing date/time format: `01-08-2042` → `01/08/2042`
11
11
  - Manipulation of type properties, for example, removal of prefixes
12
12
 
@@ -34,6 +34,6 @@ export type SetOptional<BaseType, Keys extends keyof BaseType> =
34
34
  // Pick just the keys that are readonly from the base type.
35
35
  Except<BaseType, Keys> &
36
36
  // Pick the keys that should be mutable from the base type and make them mutable.
37
- Partial<HomomorphicPick<BaseType, Keys & KeysOfUnion<BaseType>>>
37
+ Partial<HomomorphicPick<BaseType, Keys>>
38
38
  >
39
39
  : never;
@@ -35,6 +35,6 @@ export type SetReadonly<BaseType, Keys extends keyof BaseType> =
35
35
  BaseType extends unknown
36
36
  ? Simplify<
37
37
  Except<BaseType, Keys> &
38
- Readonly<HomomorphicPick<BaseType, Keys & KeysOfUnion<BaseType>>>
38
+ Readonly<HomomorphicPick<BaseType, Keys>>
39
39
  >
40
40
  : never;
@@ -1,6 +1,10 @@
1
+ import type {IsAny} from './is-any';
1
2
  import type {NonRecursiveType, StringToNumber} from './internal';
2
3
  import type {Paths} from './paths';
4
+ import type {SetRequired} from './set-required';
3
5
  import type {SimplifyDeep} from './simplify-deep';
6
+ import type {UnionToTuple} from './union-to-tuple';
7
+ import type {RequiredDeep} from './required-deep';
4
8
  import type {UnknownArray} from './unknown-array';
5
9
 
6
10
  /**
@@ -28,19 +32,37 @@ type SomeRequiredDeep = SetRequiredDeep<Foo, 'a' | `c.${number}.d`>;
28
32
  // d: number // Is now required
29
33
  // }[]
30
34
  // }
35
+
36
+ // Set specific indices in an array to be required.
37
+ type ArrayExample = SetRequiredDeep<{a: [number?, number?, number?]}, 'a.0' | 'a.1'>;
38
+ //=> {a: [number, number, number?]}
31
39
  ```
32
40
 
33
41
  @category Object
34
42
  */
35
- export type SetRequiredDeep<BaseType, KeyPaths extends Paths<BaseType>> =
36
- BaseType extends NonRecursiveType
43
+ export type SetRequiredDeep<BaseType, KeyPaths extends Paths<BaseType>> = IsAny<KeyPaths> extends true
44
+ ? SimplifyDeep<RequiredDeep<BaseType>>
45
+ : SetRequiredDeepHelper<BaseType, UnionToTuple<KeyPaths>>;
46
+
47
+ /**
48
+ Internal helper for {@link SetRequiredDeep}.
49
+
50
+ Recursively transforms the `BaseType` by applying {@link SetRequiredDeepSinglePath} for each path in `KeyPathsTuple`.
51
+ */
52
+ type SetRequiredDeepHelper<BaseType, KeyPathsTuple extends UnknownArray> =
53
+ KeyPathsTuple extends [infer KeyPath, ...infer RestPaths]
54
+ ? SetRequiredDeepHelper<SetRequiredDeepSinglePath<BaseType, KeyPath>, RestPaths>
55
+ : BaseType;
56
+
57
+ /**
58
+ Makes a single path required in `BaseType`.
59
+ */
60
+ type SetRequiredDeepSinglePath<BaseType, KeyPath> = BaseType extends NonRecursiveType
37
61
  ? BaseType
38
- : SimplifyDeep<(
39
- BaseType extends UnknownArray
40
- ? {}
41
- : {[K in keyof BaseType as K extends (KeyPaths | StringToNumber<KeyPaths & string>) ? K : never]-?: BaseType[K]}
42
- ) & {
43
- [K in keyof BaseType]: Extract<KeyPaths, `${K & (string | number)}.${string}`> extends never
44
- ? BaseType[K]
45
- : SetRequiredDeep<BaseType[K], KeyPaths extends `${K & (string | number)}.${infer Rest extends Paths<BaseType[K]>}` ? Rest : never>
46
- }>;
62
+ : KeyPath extends `${infer Property}.${infer RestPath}`
63
+ ? {
64
+ [Key in keyof BaseType]: Property extends `${Key & (string | number)}`
65
+ ? SetRequiredDeepSinglePath<BaseType[Key], RestPath>
66
+ : BaseType[Key];
67
+ }
68
+ : SetRequired<BaseType, (KeyPath | StringToNumber<KeyPath & string>) & keyof BaseType>;
@@ -43,7 +43,7 @@ export type SetRequired<BaseType, Keys extends keyof BaseType> =
43
43
  // Pick just the keys that are optional from the base type.
44
44
  Except<BaseType, Keys> &
45
45
  // Pick the keys that should be required from the base type and make them required.
46
- Required<HomomorphicPick<BaseType, Keys & KeysOfUnion<BaseType>>>
46
+ Required<HomomorphicPick<BaseType, Keys>>
47
47
  >;
48
48
 
49
49
  /**
@@ -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
package/source/split.d.ts CHANGED
@@ -22,8 +22,14 @@ array = split(items, ',');
22
22
  export type Split<
23
23
  S extends string,
24
24
  Delimiter extends string,
25
+ > = SplitHelper<S, Delimiter>;
26
+
27
+ type SplitHelper<
28
+ S extends string,
29
+ Delimiter extends string,
30
+ Accumulator extends string[] = [],
25
31
  > = S extends `${infer Head}${Delimiter}${infer Tail}`
26
- ? [Head, ...Split<Tail, Delimiter>]
27
- : S extends Delimiter
28
- ? []
29
- : [S];
32
+ ? SplitHelper<Tail, Delimiter, [...Accumulator, Head]>
33
+ : Delimiter extends ''
34
+ ? Accumulator
35
+ : [...Accumulator, S];
@@ -1,5 +1,5 @@
1
+ import type {IsNumericLiteral} from './is-literal';
1
2
  import type {IsNegative} from './numeric';
2
- import type {Subtract} from './subtract';
3
3
 
4
4
  /**
5
5
  Returns a new string which contains the specified number of copies of a given string, just like `String#repeat()`.
@@ -28,16 +28,20 @@ stringRepeat('=', 3);
28
28
  export type StringRepeat<
29
29
  Input extends string,
30
30
  Count extends number,
31
- > = number extends Count
32
- ? Input extends ''
33
- ? ''
34
- : string
35
- : IsNegative<Count> extends true
31
+ > = StringRepeatHelper<Input, Count>;
32
+
33
+ type StringRepeatHelper<
34
+ Input extends string,
35
+ Count extends number,
36
+ Counter extends never[] = [],
37
+ Accumulator extends string = '',
38
+ > =
39
+ IsNegative<Count> extends true
36
40
  ? never
37
- : Count extends 0
41
+ : Input extends ''
38
42
  ? ''
39
- : string extends Input
40
- ? string
41
- : StringRepeat<Input, Subtract<Count, 1>> extends infer R extends string
42
- ? `${Input}${R}`
43
- : never;
43
+ : Count extends Counter['length']
44
+ ? Accumulator
45
+ : IsNumericLiteral<Count> extends false
46
+ ? string
47
+ : StringRepeatHelper<Input, Count, [...Counter, never], `${Accumulator}${Input}`>;
@@ -31,7 +31,7 @@ export type StringSlice<
31
31
  Start extends number = 0,
32
32
  End extends number = StringToArray<S>['length'],
33
33
  > = string extends S
34
- ? string[]
34
+ ? string
35
35
  : ArraySlice<StringToArray<S>, Start, End> extends infer R extends readonly string[]
36
36
  ? Join<R, ''>
37
37
  : never;