Skip to content

Commit 93728b5

Browse files
authored
Add IsOptionalKeyOf, IsReadonlyKeyOf, IsRequiredKeyOf, and IsWritableKeyOf types (#1189)
1 parent d52d5e7 commit 93728b5

14 files changed

+730
-20
lines changed

index.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,18 @@ export type {Subtract} from './source/subtract.d.ts';
9797
export type {KeyAsString} from './source/key-as-string.d.ts';
9898
export type {Exact} from './source/exact.d.ts';
9999
export type {ReadonlyTuple} from './source/readonly-tuple.d.ts';
100-
export type {OptionalKeysOf} from './source/optional-keys-of.d.ts';
101100
export type {OverrideProperties} from './source/override-properties.d.ts';
101+
export type {OptionalKeysOf} from './source/optional-keys-of.d.ts';
102+
export type {IsOptionalKeyOf} from './source/is-optional-key-of.d.ts';
102103
export type {HasOptionalKeys} from './source/has-optional-keys.d.ts';
103104
export type {RequiredKeysOf} from './source/required-keys-of.d.ts';
105+
export type {IsRequiredKeyOf} from './source/is-required-key-of.d.ts';
104106
export type {HasRequiredKeys} from './source/has-required-keys.d.ts';
105107
export type {ReadonlyKeysOf} from './source/readonly-keys-of.d.ts';
108+
export type {IsReadonlyKeyOf} from './source/is-readonly-key-of.d.ts';
106109
export type {HasReadonlyKeys} from './source/has-readonly-keys.d.ts';
107110
export type {WritableKeysOf} from './source/writable-keys-of.d.ts';
111+
export type {IsWritableKeyOf} from './source/is-writable-key-of.d.ts';
108112
export type {HasWritableKeys} from './source/has-writable-keys.d.ts';
109113
export type {Spread} from './source/spread.d.ts';
110114
export type {IsInteger} from './source/is-integer.d.ts';

readme.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ Click the type names for complete docs.
151151
- [`KeyAsString`](source/key-as-string.d.ts) - Get keys of the given type as strings.
152152
- [`Schema`](source/schema.d.ts) - Create a deep version of another object type where property values are recursively replaced into a given value type.
153153
- [`Exact`](source/exact.d.ts) - Create a type that does not allow extra properties.
154-
- [`OptionalKeysOf`](source/optional-keys-of.d.ts) - Extract all optional keys from the given type.
155154
- [`KeysOfUnion`](source/keys-of-union.d.ts) - Create a union of all keys from a given type, even those exclusive to specific union members.
155+
- [`OptionalKeysOf`](source/optional-keys-of.d.ts) - Extract all optional keys from the given type.
156156
- [`HasOptionalKeys`](source/has-optional-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any optional fields.
157157
- [`RequiredKeysOf`](source/required-keys-of.d.ts) - Extract all required keys from the given type.
158158
- [`HasRequiredKeys`](source/has-required-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any required fields.
@@ -205,6 +205,10 @@ Click the type names for complete docs.
205205
- [`IsUppercase`](source/is-uppercase.d.ts) - Returns a boolean for whether the given string literal is uppercase.
206206
- [`IsOptional`](source/is-optional.d.ts) - Returns a boolean for whether the given type includes `undefined`.
207207
- [`IsNullable`](source/is-nullable.d.ts) - Returns a boolean for whether the given type includes `null`.
208+
- [`IsOptionalKeyOf`](source/is-optional-key-of.d.ts) - Returns a boolean for whether the given key is an optional key of type.
209+
- [`IsRequiredKeyOf`](source/is-required-key-of.d.ts) - Returns a boolean for whether the given key is a required key of type.
210+
- [`IsReadonlyKeyOf`](source/is-readonly-key-of.d.ts) - Returns a boolean for whether the given key is a readonly key of type.
211+
- [`IsWritableKeyOf`](source/is-writable-key-of.d.ts) - Returns a boolean for whether the given key is a writable key of type.
208212

209213
### JSON
210214

source/is-optional-key-of.d.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type {IsAny} from './is-any.d.ts';
2+
3+
/**
4+
Returns a boolean for whether the given key is an optional key of type.
5+
6+
This is useful when writing utility types or schema validators that need to differentiate `optional` keys.
7+
8+
@example
9+
```
10+
import type {IsOptionalKeyOf} from 'type-fest';
11+
12+
interface User {
13+
name: string;
14+
surname: string;
15+
16+
luckyNumber?: number;
17+
}
18+
19+
interface Admin {
20+
name: string;
21+
surname?: string
22+
}
23+
24+
type T1 = IsOptionalKeyOf<User, 'luckyNumber'>;
25+
//=> true
26+
27+
type T2 = IsOptionalKeyOf<User, 'name'>;
28+
//=> false
29+
30+
type T3 = IsOptionalKeyOf<User, 'name' | 'luckyNumber'>;
31+
//=> boolean
32+
33+
type T4 = IsOptionalKeyOf<User | Admin, 'name'>;
34+
//=> false
35+
36+
type T5 = IsOptionalKeyOf<User | Admin, 'surname'>;
37+
//=> boolean
38+
```
39+
40+
@category Type Guard
41+
@category Utilities
42+
*/
43+
export type IsOptionalKeyOf<Type extends object, Key extends keyof Type> =
44+
IsAny<Type | Key> extends true ? never
45+
: Key extends keyof Type
46+
? Type extends Record<Key, Type[Key]>
47+
? false
48+
: true
49+
: false;

source/is-readonly-key-of.d.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type {IsEqual} from './is-equal.d.ts';
2+
import type {IsAny} from './is-any.d.ts';
3+
4+
/**
5+
Returns a boolean for whether the given key is a readonly key of type.
6+
7+
This is useful when writing utility types or schema validators that need to differentiate `readonly` keys.
8+
9+
@example
10+
```
11+
import type {IsReadonlyKeyOf} from 'type-fest';
12+
13+
interface User {
14+
name: string;
15+
surname: string;
16+
17+
readonly id: number;
18+
}
19+
20+
interface Admin {
21+
name: string;
22+
id: string
23+
}
24+
25+
type T1 = IsReadonlyKeyOf<User, 'id'>;
26+
//=> true
27+
28+
type T2 = IsReadonlyKeyOf<User, 'name'>;
29+
//=> false
30+
31+
type T3 = IsReadonlyKeyOf<User, 'name' | 'id'>;
32+
//=> boolean
33+
34+
type T4 = IsReadonlyKeyOf<User | Admin, 'name'>;
35+
//=> false
36+
37+
type T5 = IsReadonlyKeyOf<User | Admin, 'id'>;
38+
//=> boolean
39+
```
40+
41+
@category Type Guard
42+
@category Utilities
43+
*/
44+
export type IsReadonlyKeyOf<Type extends object, Key extends keyof Type> =
45+
IsAny<Type | Key> extends true ? never
46+
: Key extends unknown // For distributing `Key`
47+
? Type extends unknown // For distributing `Type`
48+
? IsEqual<
49+
{[K in Key]: Type[Key]},
50+
{readonly [K in Key]: Type[Key]}
51+
>
52+
: never // Should never happen
53+
: never; // Should never happen

source/is-required-key-of.d.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts';
2+
import type {Not} from './internal/type.d.ts';
3+
import type {IsAny} from './is-any.d.ts';
4+
5+
/**
6+
Returns a boolean for whether the given key is a required key of type.
7+
8+
This is useful when writing utility types or schema validators that need to differentiate `required` keys.
9+
10+
@example
11+
```
12+
import type {IsRequiredKeyOf} from 'type-fest';
13+
14+
interface User {
15+
name: string;
16+
surname: string;
17+
18+
luckyNumber?: number;
19+
}
20+
21+
interface Admin {
22+
name: string;
23+
surname?: string
24+
}
25+
26+
type T1 = IsRequiredKeyOf<User, 'name'>;
27+
//=> true
28+
29+
type T2 = IsRequiredKeyOf<User, 'luckyNumber'>;
30+
//=> false
31+
32+
type T3 = IsRequiredKeyOf<User, 'name' | 'luckyNumber'>;
33+
//=> boolean
34+
35+
type T4 = IsRequiredKeyOf<User | Admin, 'name'>;
36+
//=> true
37+
38+
type T5 = IsRequiredKeyOf<User | Admin, 'surname'>;
39+
//=> boolean
40+
```
41+
42+
@category Type Guard
43+
@category Utilities
44+
*/
45+
export type IsRequiredKeyOf<Type extends object, Key extends keyof Type> =
46+
IsAny<Type | Key> extends true ? never
47+
: Key extends keyof Type
48+
? Not<IsOptionalKeyOf<Type, Key>>
49+
: false;

source/is-writable-key-of.d.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type {IsReadonlyKeyOf} from './is-readonly-key-of.d.ts';
2+
import type {Not} from './internal/type.d.ts';
3+
import type {IsAny} from './is-any.d.ts';
4+
5+
/**
6+
Returns a boolean for whether the given key is a writable key of type.
7+
8+
This is useful when writing utility types or schema validators that need to differentiate `writable` keys.
9+
10+
@example
11+
```
12+
import type {IsWritableKeyOf} from 'type-fest';
13+
14+
interface User {
15+
name: string;
16+
surname: string;
17+
18+
readonly id: number;
19+
}
20+
21+
interface Admin {
22+
name: string;
23+
id: string
24+
}
25+
26+
type T1 = IsWritableKeyOf<User, 'name'>;
27+
//=> true
28+
29+
type T2 = IsWritableKeyOf<User, 'id'>;
30+
//=> false
31+
32+
type T3 = IsWritableKeyOf<User, 'name' | 'id'>;
33+
//=> boolean
34+
35+
type T4 = IsWritableKeyOf<User | Admin, 'name'>;
36+
//=> true
37+
38+
type T5 = IsWritableKeyOf<User | Admin, 'id'>;
39+
//=> boolean
40+
```
41+
42+
@category Type Guard
43+
@category Utilities
44+
*/
45+
export type IsWritableKeyOf<Type extends object, Key extends keyof Type> =
46+
IsAny<Type | Key> extends true ? never
47+
: Key extends keyof Type
48+
? Not<IsReadonlyKeyOf<Type, Key>>
49+
: false;

source/optional-keys-of.d.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts';
2+
13
/**
24
Extract all optional keys from the given type.
35
@@ -31,9 +33,12 @@ const update2: UpdateOperation<User> = {
3133
3234
@category Utilities
3335
*/
34-
export type OptionalKeysOf<BaseType extends object> =
35-
BaseType extends unknown // For distributing `BaseType`
36-
? (keyof {
37-
[Key in keyof BaseType as BaseType extends Record<Key, BaseType[Key]> ? never : Key]: never
38-
}) & (keyof BaseType) // Intersect with `keyof BaseType` to ensure result of `OptionalKeysOf<BaseType>` is always assignable to `keyof BaseType`
36+
export type OptionalKeysOf<Type extends object> =
37+
Type extends unknown // For distributing `Type`
38+
? (keyof {[Key in keyof Type as
39+
IsOptionalKeyOf<Type, Key> extends false
40+
? never
41+
: Key
42+
]: never
43+
}) & keyof Type // Intersect with `keyof Type` to ensure result of `OptionalKeysOf<Type>` is always assignable to `keyof Type`
3944
: never; // Should never happen

source/readonly-keys-of.d.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {WritableKeysOf} from './writable-keys-of.d.ts';
1+
import type {IsReadonlyKeyOf} from './is-readonly-key-of.d.ts';
22

33
/**
44
Extract all readonly keys from the given type.
@@ -12,6 +12,7 @@ import type {ReadonlyKeysOf} from 'type-fest';
1212
interface User {
1313
name: string;
1414
surname: string;
15+
1516
readonly id: number;
1617
}
1718
@@ -24,7 +25,12 @@ const update1: UpdateResponse<User> = {
2425
2526
@category Utilities
2627
*/
27-
export type ReadonlyKeysOf<T extends object> =
28-
T extends unknown // For distributing `T`
29-
? Exclude<keyof T, WritableKeysOf<T>>
28+
export type ReadonlyKeysOf<Type extends object> =
29+
Type extends unknown // For distributing `Type`
30+
? (keyof {[Key in keyof Type as
31+
IsReadonlyKeyOf<Type, Key> extends false
32+
? never
33+
: Key
34+
]: never
35+
}) & keyof Type // Intersect with `keyof Type` to ensure result of `ReadonlyKeysOf<Type>` is always assignable to `keyof Type`
3036
: never; // Should never happen

source/required-keys-of.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const validator2 = createValidation<User>('surname', value => value.length < 25)
2424
2525
@category Utilities
2626
*/
27-
export type RequiredKeysOf<BaseType extends object> =
28-
BaseType extends unknown // For distributing `BaseType`
29-
? Exclude<keyof BaseType, OptionalKeysOf<BaseType>>
27+
export type RequiredKeysOf<Type extends object> =
28+
Type extends unknown // For distributing `Type`
29+
? Exclude<keyof Type, OptionalKeysOf<Type>>
3030
: never; // Should never happen

source/writable-keys-of.d.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {IsEqual} from './is-equal.d.ts';
1+
import type {ReadonlyKeysOf} from './readonly-keys-of.d.ts';
22

33
/**
44
Extract all writable keys from the given type.
@@ -12,6 +12,7 @@ import type {WritableKeysOf} from 'type-fest';
1212
interface User {
1313
name: string;
1414
surname: string;
15+
1516
readonly id: number;
1617
}
1718
@@ -25,9 +26,7 @@ const update1: UpdateRequest<User> = {
2526
2627
@category Utilities
2728
*/
28-
export type WritableKeysOf<T extends object> =
29-
T extends unknown // For distributing `T`
30-
? (keyof {
31-
[P in keyof T as IsEqual<{[Q in P]: T[P]}, {readonly [Q in P]: T[P]}> extends false ? P : never]: never
32-
}) & keyof T // Intersect with `keyof T` to ensure result of `WritableKeysOf<T>` is always assignable to `keyof T`
29+
export type WritableKeysOf<Type extends object> =
30+
Type extends unknown // For distributing `Type`
31+
? Exclude<keyof Type, ReadonlyKeysOf<Type>>
3332
: never; // Should never happen

0 commit comments

Comments
 (0)