From dadae295d3c2c930b7e67ff6bcf00daae31dbafe Mon Sep 17 00:00:00 2001 From: Ifiok Jr Date: Tue, 4 Feb 2020 03:13:51 +0100 Subject: [PATCH] Add `ConditionalKeys`, `ConditionalPick` and `ConditionalExcept`types --- index.d.ts | 3 +++ readme.md | 3 +++ source/conditional-except.d.ts | 45 ++++++++++++++++++++++++++++++++++ source/conditional-keys.d.ts | 37 ++++++++++++++++++++++++++++ source/conditional-pick.d.ts | 44 +++++++++++++++++++++++++++++++++ test-d/conditional-except.ts | 28 +++++++++++++++++++++ test-d/conditional-keys.ts | 15 ++++++++++++ test-d/conditional-pick.ts | 28 +++++++++++++++++++++ 8 files changed, 203 insertions(+) create mode 100644 source/conditional-except.d.ts create mode 100644 source/conditional-keys.d.ts create mode 100644 source/conditional-pick.d.ts create mode 100644 test-d/conditional-except.ts create mode 100644 test-d/conditional-keys.ts create mode 100644 test-d/conditional-pick.ts diff --git a/index.d.ts b/index.d.ts index 7ade21dfc..f892d434a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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'; diff --git a/readme.md b/readme.md index c82ea310f..c9acdec1e 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/source/conditional-except.d.ts b/source/conditional-except.d.ts new file mode 100644 index 000000000..52d22a883 --- /dev/null +++ b/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; +// => { 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; +// => { b: string | number; c: () => void; d: {}; } +``` +*/ +export type ConditionalExcept = Except< + Base, + ConditionalKeys +>; diff --git a/source/conditional-keys.d.ts b/source/conditional-keys.d.ts new file mode 100644 index 000000000..de3fa6335 --- /dev/null +++ b/source/conditional-keys.d.ts @@ -0,0 +1,37 @@ +/** +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; +// => 'a' +``` + +To support partial types make sure your `Condition` is a union of undefined as shown below. +*/ +export type ConditionalKeys = 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] +>; diff --git a/source/conditional-pick.d.ts b/source/conditional-pick.d.ts new file mode 100644 index 000000000..53b63fbc1 --- /dev/null +++ b/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; +// => { 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; +// => { a: string; } +``` +*/ +export type ConditionalPick = Pick< + Base, + ConditionalKeys +>; diff --git a/test-d/conditional-except.ts b/test-d/conditional-except.ts new file mode 100644 index 000000000..886501fd1 --- /dev/null +++ b/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; +} + +declare const exampleConditionalExcept: ConditionalExcept; +expectType<{ b?: string | number; c?: string; d: Record }>(exampleConditionalExcept); + +declare const awesomeConditionalExcept: ConditionalExcept; +expectType<{ run: () => void }>(awesomeConditionalExcept); + +declare const exampleConditionalExceptWithUndefined: ConditionalExcept; +expectType<{ b?: string | number; d: Record }>(exampleConditionalExceptWithUndefined); diff --git a/test-d/conditional-keys.ts b/test-d/conditional-keys.ts new file mode 100644 index 000000000..05bc46f37 --- /dev/null +++ b/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; +} + +declare const exampleConditionalKeys: ConditionalKeys; +expectType<'a'>(exampleConditionalKeys); + +declare const exampleConditionalKeysWithUndefined: ConditionalKeys; +expectType<'a' | 'c'>(exampleConditionalKeysWithUndefined); diff --git a/test-d/conditional-pick.ts b/test-d/conditional-pick.ts new file mode 100644 index 000000000..80112ca99 --- /dev/null +++ b/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; +} + +declare const exampleConditionalPick: ConditionalPick; +expectType< { a: string }>(exampleConditionalPick); + +declare const awesomeConditionalPick: ConditionalPick; +expectType<{ name: string; successes: number; failures: bigint }>(awesomeConditionalPick); + +declare const exampleConditionalPickWithUndefined: ConditionalPick; +expectType<{ a: string; c?: string }>(exampleConditionalPickWithUndefined);