Skip to content

Commit

Permalink
Add OptionalKeysOf, HasOptionalKeys, RequiredKeysOf, `HasRequir…
Browse files Browse the repository at this point in the history
…edKeys` types (#405)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
Menecats and sindresorhus committed Jul 3, 2022
1 parent 1483de3 commit f0b1c3f
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 0 deletions.
4 changes: 4 additions & 0 deletions index.d.ts
Expand Up @@ -57,6 +57,10 @@ export {
export {StringKeyOf} from './source/string-key-of';
export {Exact} from './source/exact';
export {ReadonlyTuple} from './source/readonly-tuple';
export {OptionalKeysOf} from './source/optional-keys-of';
export {HasOptionalKeys} from './source/has-optional-keys';
export {RequiredKeysOf} from './source/required-keys-of';
export {HasRequiredKeys} from './source/has-required-keys';

// Template literal types
export {CamelCase} from './source/camel-case';
Expand Down
4 changes: 4 additions & 0 deletions readme.md
Expand Up @@ -192,6 +192,10 @@ Click the type names for complete docs.
- [`StringKeyOf`](source/string-key-of.d.ts) - Get keys of the given type as strings.
- [`Schema`](source/schema.d.ts) - Create a deep version of another object type where property values are recursively replaced into a given value type.
- [`Exact`](source/exact.d.ts) - Create a type that does not allow extra properties.
- [`OptionalKeysOf`](source/optional-keys-of.d.ts) - Extract all optional keys from the given type.
- [`HasOptionalKeys`](source/has-optional-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any optional fields.
- [`RequiredKeysOf`](source/required-keys-of.d.ts) - Extract all required keys from the given type.
- [`HasRequiredKeys`](source/has-required-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any required fields.

### JSON

Expand Down
21 changes: 21 additions & 0 deletions source/has-optional-keys.d.ts
@@ -0,0 +1,21 @@
import {OptionalKeysOf} from './optional-keys-of';

/**
Creates a type that represents `true` or `false` depending on whether the given type has any optional fields.
This is useful when you want to create an API whose behavior depends on the presence or absence of optional fields.
@example
```
import type {HasOptionalKeys, OptionalKeysOf} from 'type-fest';
type UpdateService<Entity extends object> = {
removeField: HasOptionalKeys<Entity> extends true
? (field: OptionalKeysOf<Entity>) => Promise<void>
: never
}
```
@category Utilities
*/
export type HasOptionalKeys<BaseType extends object> = OptionalKeysOf<BaseType> extends never ? false : true;
59 changes: 59 additions & 0 deletions source/has-required-keys.d.ts
@@ -0,0 +1,59 @@
import {RequiredKeysOf} from './required-keys-of';

/**
Creates a type that represents `true` or `false` depending on whether the given type has any required fields.
This is useful when you want to create an API whose behavior depends on the presence or absence of required fields.
@example
```
import type {HasRequiredKeys} from 'type-fest';
type GeneratorOptions<Template extends object> = {
prop1: number;
prop2: string;
} & (HasRequiredKeys<Template> extends true
? {template: Template}
: {template?: Template});
interface Template1 {
optionalSubParam?: string;
}
interface Template2 {
requiredSubParam: string;
}
type Options1 = GeneratorOptions<Template1>;
type Options2 = GeneratorOptions<Template2>;
const optA: Options1 = {
prop1: 0,
prop2: 'hi'
};
const optB: Options1 = {
prop1: 0,
prop2: 'hi',
template: {}
};
const optC: Options1 = {
prop1: 0,
prop2: 'hi',
template: {
optionalSubParam: 'optional value'
}
};
const optD: Options2 = {
prop1: 0,
prop2: 'hi',
template: {
requiredSubParam: 'required value'
}
};
```
@category Utilities
*/
export type HasRequiredKeys<BaseType extends object> = RequiredKeysOf<BaseType> extends never ? false : true;
38 changes: 38 additions & 0 deletions source/optional-keys-of.d.ts
@@ -0,0 +1,38 @@
/**
Extract all optional keys from the given type.
This is useful when you want to create a new type that contains different type values for the optional keys only.
@example
```
import type {OptionalKeysOf, Except} from 'type-fest';
interface User {
name: string;
surname: string;
luckyNumber?: number;
}
const REMOVE_FIELD = Symbol('remove field symbol');
type UpdateOperation<Entity extends object> = Except<Partial<Entity>, OptionalKeysOf<Entity>> & {
[Key in OptionalKeysOf<Entity>]?: Entity[Key] | typeof REMOVE_FIELD;
};
const update1: UpdateOperation<User> = {
name: 'Alice'
};
const update2: UpdateOperation<User> = {
name: 'Bob',
luckyNumber: REMOVE_FIELD
};
```
@category Utilities
*/
export type OptionalKeysOf<BaseType extends object> = Exclude<{
[Key in keyof BaseType]: BaseType extends Record<Key, BaseType[Key]>
? never
: Key
}[keyof BaseType], undefined>;
29 changes: 29 additions & 0 deletions source/required-keys-of.d.ts
@@ -0,0 +1,29 @@
/**
Extract all required keys from the given type.
This is useful when you want to create a new type that contains different type values for the required keys only or use the list of keys for validation purposes, etc...
@example
```
import type {RequiredKeysOf} from 'type-fest';
declare function createValidation<Entity extends object, Key extends RequiredKeysOf<Entity> = RequiredKeysOf<Entity>>(field: Key, validator: (value: Entity[Key]) => boolean): ValidatorFn;
interface User {
name: string;
surname: string;
luckyNumber?: number;
}
const validator1 = createValidation<User>('name', value => value.length < 25);
const validator2 = createValidation<User>('surname', value => value.length < 25);
```
@category Utilities
*/
export type RequiredKeysOf<BaseType extends object> = Exclude<{
[Key in keyof BaseType]: BaseType extends Record<Key, BaseType[Key]>
? Key
: never
}[keyof BaseType], undefined>;
29 changes: 29 additions & 0 deletions test-d/has-optional-keys.ts
@@ -0,0 +1,29 @@
import {expectType} from 'tsd';
import type {HasOptionalKeys} from '../index';

type TestType1 = {
a: string;
b?: boolean;
};

type TestType2 = {
a?: string;
b?: boolean;
};

type TestType3 = {
a: string;
b: boolean;
};

type HasOptionalKeys1 = HasOptionalKeys<TestType1>;
type HasOptionalKeys2 = HasOptionalKeys<TestType2>;
type HasOptionalKeys3 = HasOptionalKeys<TestType3>;

declare const test1: HasOptionalKeys1;
declare const test2: HasOptionalKeys2;
declare const test3: HasOptionalKeys3;

expectType<true>(test1);
expectType<true>(test2);
expectType<false>(test3);
29 changes: 29 additions & 0 deletions test-d/has-required-keys.ts
@@ -0,0 +1,29 @@
import {expectType} from 'tsd';
import type {HasRequiredKeys} from '../index';

type TestType1 = {
a: string;
b?: boolean;
};

type TestType2 = {
a?: string;
b?: boolean;
};

type TestType3 = {
a: string;
b: boolean;
};

type HasRequiredKeys1 = HasRequiredKeys<TestType1>;
type HasRequiredKeys2 = HasRequiredKeys<TestType2>;
type HasRequiredKeys3 = HasRequiredKeys<TestType3>;

declare const test1: HasRequiredKeys1;
declare const test2: HasRequiredKeys2;
declare const test3: HasRequiredKeys3;

expectType<true>(test1);
expectType<false>(test2);
expectType<true>(test3);
29 changes: 29 additions & 0 deletions test-d/optional-keys-of.ts
@@ -0,0 +1,29 @@
import {expectType} from 'tsd';
import type {OptionalKeysOf} from '../index';

type TestType1 = {
a: string;
b?: boolean;
};

type TestType2 = {
a?: string;
b?: boolean;
};

type TestType3 = {
a: string;
b: boolean;
};

type OptionalKeysOf1 = OptionalKeysOf<TestType1>;
type OptionalKeysOf2 = OptionalKeysOf<TestType2>;
type OptionalKeysOf3 = OptionalKeysOf<TestType3>;

declare const test1: OptionalKeysOf1;
declare const test2: OptionalKeysOf2;
declare const test3: OptionalKeysOf3;

expectType<'b'>(test1);
expectType<'a' | 'b'>(test2);
expectType<never>(test3);
29 changes: 29 additions & 0 deletions test-d/required-keys-of.ts
@@ -0,0 +1,29 @@
import {expectType} from 'tsd';
import type {RequiredKeysOf} from '../index';

type TestType1 = {
a: string;
b?: boolean;
};

type TestType2 = {
a?: string;
b?: boolean;
};

type TestType3 = {
a: string;
b: boolean;
};

type RequiredKeysOf1 = RequiredKeysOf<TestType1>;
type RequiredKeysOf2 = RequiredKeysOf<TestType2>;
type RequiredKeysOf3 = RequiredKeysOf<TestType3>;

declare const test1: RequiredKeysOf1;
declare const test2: RequiredKeysOf2;
declare const test3: RequiredKeysOf3;

expectType<'a'>(test1);
expectType<never>(test2);
expectType<'a' | 'b'>(test3);

0 comments on commit f0b1c3f

Please sign in to comment.