Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add ConditionalKeys, ConditionalPick and ConditionalExcept types (
#76)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
ifiokjr and sindresorhus committed Feb 14, 2020
1 parent ae34d9e commit 8a908a3
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 0 deletions.
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);

0 comments on commit 8a908a3

Please sign in to comment.