Skip to content

Commit

Permalink
Add ConditionalKeys, ConditionalPick and ConditionalExcepttypes
Browse files Browse the repository at this point in the history
  • Loading branch information
ifiokjr committed Feb 9, 2020
1 parent 7fe70fd commit 7f437b2
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 0 deletions.
3 changes: 3 additions & 0 deletions index.d.ts
Expand Up @@ -16,6 +16,9 @@ export {Opaque} from './source/opaque';
export {SetOptional} from './source/set-optional';
export {SetRequired} from './source/set-required';
export {PromiseValue} from './source/promise-value';
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 @@ -75,6 +75,9 @@ Click the type names for complete docs.
- [`SetOptional`](source/set-optional.d.ts) - Create a type that makes the given keys optional.
- [`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` type.
- [`ConditionalKeys`](source/conditional-keys.d.ts) - Extract all keys from a shape where values extend the provided `Condition` type.
- [`ConditionalPick`](source/conditional-pick.d.ts) - Like `Pick` except select properties from a given shape when the values extend the provided `Condition` type.
- [`ConditionalExcept`](source/conditional-except.d.ts) - Like `Omit` except remove properties from a given shape only when the values extend the provided `Condition` type.

### Miscellaneous

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

/**
Exclude the keys from any shape which matches the provided `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; }
```
A simpler and more contrived example is below.
@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 provided 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 as shown 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 provided base type.
[Key in keyof Base]:
// Pick only keys with types extending the `Condition` type provided.
Base[Key] extends Condition
// Retain this key since the condition passes.
? Key
// Discard this key since the condition fails.
: never;

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

/**
Pick the keys from any typescript shape which match the provided `Condition`.
This is useful when you want to create a new type from a specific subset from an already created 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; }
```
A simpler and more contrived example is below.
@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 7f437b2

Please sign in to comment.