diff --git a/index.d.ts b/index.d.ts index 34dd2b549..e2ac6c3ff 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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'; diff --git a/readme.md b/readme.md index 277a8d040..6eed5e359 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/source/conditional-except.d.ts b/source/conditional-except.d.ts new file mode 100644 index 000000000..ac506ccf1 --- /dev/null +++ b/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; +//=> {run: () => void} +``` + +@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..eb074dc5d --- /dev/null +++ b/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; +//=> '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; +//=> 'a' | 'c' +``` +*/ +export type ConditionalKeys = 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] +>; diff --git a/source/conditional-pick.d.ts b/source/conditional-pick.d.ts new file mode 100644 index 000000000..cecc3df14 --- /dev/null +++ b/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; +//=> {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; +//=> {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..e6c3f5ee6 --- /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..8aa401609 --- /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);