Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OptionalKeysOf, HasOptionalKeys, RequiredKeysOf, HasRequiredKeys types #405

Merged
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export {
export {StringKeyOf} from './source/string-key-of';
export {Exact} from './source/exact';
export {ReadonlyTuple} from './source/readonly-tuple';
export {OptionalKeysOf, HasOptionalKeys} from './source/optional-keys';
export {RequiredKeysOf, HasRequiredKeys} from './source/required-keys';

// Template literal types
export {CamelCase} from './source/camel-case';
Expand Down
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,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.d.ts) - Extract all optional keys from the given type.
- [`HasOptionalKeys`](source/optional-keys.d.ts) - Create a `true`/`false` type depending on the presence or absence of optional fields.
- [`RequiredKeysOf`](source/required-keys.d.ts) - Extract all required keys from the given type.
- [`HasRequiredKeys`](source/required-keys.d.ts) - Create a `true`/`false` type depending on the presence or absence of required fields.
Menecats marked this conversation as resolved.
Show resolved Hide resolved

### JSON

Expand Down
58 changes: 58 additions & 0 deletions source/optional-keys.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
Pick all optional keys from the given base type.

This is useful when you want to create a new type that contains different type values for the optional keys only
Menecats marked this conversation as resolved.
Show resolved Hide resolved

@example
```
import type {OptionalKeysOf, Except} from 'type-fest';

interface User {
name: string;
Menecats marked this conversation as resolved.
Show resolved Hide resolved
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>;

/**
Creates a type that represents `true` or `false` depending on the presence or absence of optional fields.

This is useful when you want to create an API whose behaviour 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;
87 changes: 87 additions & 0 deletions source/required-keys.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
Pick all required keys from the given base 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>;

/**
Creates a type that represents `true` or `false` depending on the presence or absence of required fields.

This is useful when you want to create an API whose behaviour 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;
41 changes: 41 additions & 0 deletions test-d/optional-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {expectType} from 'tsd';
import type {HasOptionalKeys, 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>;

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

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

declare const test4: HasOptionalKeys1;
declare const test5: HasOptionalKeys2;
declare const test6: HasOptionalKeys3;

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

expectType<true>(test4);
expectType<true>(test5);
expectType<false>(test6);
41 changes: 41 additions & 0 deletions test-d/required-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {expectType} from 'tsd';
import type {HasRequiredKeys, 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>;

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

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

declare const test4: HasRequiredKeys1;
declare const test5: HasRequiredKeys2;
declare const test6: HasRequiredKeys3;

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

expectType<true>(test4);
expectType<false>(test5);
expectType<true>(test6);