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 ConditionalKeys, ConditionalPick and ConditionalExcept types #76

Merged
merged 10 commits into from Feb 14, 2020
3 changes: 3 additions & 0 deletions index.d.ts
Expand Up @@ -17,6 +17,9 @@ export {SetOptional} from './source/set-optional';
export {SetRequired} from './source/set-required';
export {PromiseValue} from './source/promise-value';
export {AsyncReturnType} from './source/async-return-type';
export {ConditionalExcept} from './source/conditional-except';
export {ConditionalKeys} from './source/conditional-keys';
export {ConditionalPick} from './source/conditional-pick';

// Miscellaneous
export {PackageJson} from './source/package-json';
Expand Down
3 changes: 3 additions & 0 deletions readme.md
Expand Up @@ -76,6 +76,9 @@ Click the type names for complete docs.
- [`SetRequired`](source/set-required.d.ts) - Create a type that makes the given keys required.
- [`PromiseValue`](source/promise-value.d.ts) - Returns the type that is wrapped inside a `Promise`.
- [`AsyncReturnType`](source/async-return-type.d.ts) - Unwrap the return type of a function that returns a `Promise`.
- [`ConditionalKeys`](source/conditional-keys.d.ts) - Extract keys from a shape where values extend the given `Condition` type.
- [`ConditionalPick`](source/conditional-pick.d.ts) - Like `Pick` except it selects properties from a shape where the values extend the given `Condition` type.
- [`ConditionalExcept`](source/conditional-except.d.ts) - Like `Omit` except it removes properties from a shape where the values extend the given `Condition` type.

### Miscellaneous

Expand Down
43 changes: 43 additions & 0 deletions source/conditional-except.d.ts
@@ -0,0 +1,43 @@
import {Except} from './except';
import {ConditionalKeys} from './conditional-keys';

/**
Exclude keys from a shape that matches the given `Condition`.

This is useful when you want to create a new type with a specific set of keys from a shape. For example, you might want to exclude all the primitive properties from a class and form a new shape containing everything but the primitive properties.

@example
```
import {Primitive, ConditionalExcept} from 'type-fest';

class Awesome {
name: string;
successes: number;
failures: bigint;

run() {}
}

type ExceptPrimitivesFromAwesome = ConditionalExcept<Awesome, Primitive>;
//=> {run: () => void}
```

@example
```
import {ConditionalExcept} from 'type-fest';

interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}

type NonStringKeysOnly = ConditionalExcept<Example, string>;
//=> {b: string | number; c: () => void; d: {}}
```
*/
export type ConditionalExcept<Base, Condition> = Except<
Base,
ConditionalKeys<Base, Condition>
>;
43 changes: 43 additions & 0 deletions source/conditional-keys.d.ts
@@ -0,0 +1,43 @@
/**
Extract the keys from a type where the value type of the key extends the given `Condition`.

Internally this is used for the `ConditionalPick` and `ConditionalExcept` types.

@example
```
import {ConditionalKeys} from 'type-fest';

interface Example {
a: string;
b: string | number;
c?: string;
d: {};
}

type StringKeysOnly = ConditionalKeys<Example, string>;
//=> 'a'
```

To support partial types, make sure your `Condition` is a union of undefined (for example, `string | undefined`) as demonstrated below.

@example
```
type StringKeysAndUndefined = ConditionalKeys<Example, string | undefined>;
//=> 'a' | 'c'
```
*/
export type ConditionalKeys<Base, Condition> = NonNullable<
// Wrap in `NonNullable` to strip away the `undefined` type from the produced union.
{
// Map through all the keys of the given base type.
[Key in keyof Base]:
// Pick only keys with types extending the given `Condition` type.
Base[Key] extends Condition
// Retain this key since the condition passes.
? Key
// Discard this key since the condition fails.
: never;

// Convert the produced object into a union type of the keys which passed the conditional test.
}[keyof Base]
>;
42 changes: 42 additions & 0 deletions source/conditional-pick.d.ts
@@ -0,0 +1,42 @@
import {ConditionalKeys} from './conditional-keys';

/**
Pick keys from the shape that matches the given `Condition`.

This is useful when you want to create a new type from a specific subset of an existing type. For example, you might want to pick all the primitive properties from a class and form a new automatically derived type.

@example
```
import {Primitive, ConditionalPick} from 'type-fest';

class Awesome {
name: string;
successes: number;
failures: bigint;

run() {}
}

type PickPrimitivesFromAwesome = ConditionalPick<Awesome, Primitive>;
//=> {name: string; successes: number; failures: bigint}
```

@example
```
import {ConditionalPick} from 'type-fest';

interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}

type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
```
*/
export type ConditionalPick<Base, Condition> = Pick<
Base,
ConditionalKeys<Base, Condition>
>;
28 changes: 28 additions & 0 deletions test-d/conditional-except.ts
@@ -0,0 +1,28 @@
import {expectType} from 'tsd';
import {ConditionalExcept, Primitive} from '..';

class Awesome {
name!: string;
successes!: number;
failures!: bigint;

run(): void {
// Empty
}
}

interface Example {
a: string;
b?: string | number;
c?: string;
d: Record<string, unknown>;
}

declare const exampleConditionalExcept: ConditionalExcept<Example, string>;
expectType<{b?: string | number; c?: string; d: Record<string, unknown>}>(exampleConditionalExcept);

declare const awesomeConditionalExcept: ConditionalExcept<Awesome, Primitive>;
expectType<{run: () => void}>(awesomeConditionalExcept);

declare const exampleConditionalExceptWithUndefined: ConditionalExcept<Example, string | undefined>;
expectType<{b?: string | number; d: Record<string, unknown>}>(exampleConditionalExceptWithUndefined);
15 changes: 15 additions & 0 deletions test-d/conditional-keys.ts
@@ -0,0 +1,15 @@
import {expectType} from 'tsd';
import {ConditionalKeys} from '..';

interface Example {
a: string;
b?: string | number;
c?: string;
d: Record<string, unknown>;
}

declare const exampleConditionalKeys: ConditionalKeys<Example, string>;
expectType<'a'>(exampleConditionalKeys);

declare const exampleConditionalKeysWithUndefined: ConditionalKeys<Example, string | undefined>;
expectType<'a' | 'c'>(exampleConditionalKeysWithUndefined);
28 changes: 28 additions & 0 deletions test-d/conditional-pick.ts
@@ -0,0 +1,28 @@
import {expectType} from 'tsd';
import {ConditionalPick, Primitive} from '..';

class Awesome {
name!: string;
successes!: number;
failures!: bigint;

run(): void {
// Empty
}
}

interface Example {
a: string;
b?: string | number;
c?: string;
d: Record<string, unknown>;
}

declare const exampleConditionalPick: ConditionalPick<Example, string>;
expectType< {a: string}>(exampleConditionalPick);

declare const awesomeConditionalPick: ConditionalPick<Awesome, Primitive>;
expectType<{name: string; successes: number; failures: bigint}>(awesomeConditionalPick);

declare const exampleConditionalPickWithUndefined: ConditionalPick<Example, string | undefined>;
expectType<{a: string; c?: string}>(exampleConditionalPickWithUndefined);