type-fest 4.32.0 → 4.33.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "type-fest",
3
- "version": "4.32.0",
3
+ "version": "4.33.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
@@ -370,6 +370,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
370
370
  - `SetEntry` - See [`IterableElement`](source/iterable-element.d.ts)
371
371
  - `SetValues` - See [`IterableElement`](source/iterable-element.d.ts)
372
372
  - `PickByTypes` - See [`ConditionalPick`](source/conditional-pick.d.ts)
373
+ - `HomomorphicOmit` - See [`Except`](source/except.d.ts)
373
374
 
374
375
  ## Tips
375
376
 
@@ -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>;
@@ -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>;
@@ -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';
@@ -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
  /**
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;