diff --git a/index.d.ts b/index.d.ts index 39fd84c06..039024441 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,6 +34,7 @@ export type {OmitIndexSignature} from './source/omit-index-signature.d.ts'; export type {PickIndexSignature} from './source/pick-index-signature.d.ts'; export type {PartialDeep, PartialDeepOptions} from './source/partial-deep.d.ts'; export type {UnwrapPartial} from './source/unwrap-partial.d.ts'; +export type {UnwrapRequired} from './source/unwrap-required.d.ts'; export type {RequiredDeep} from './source/required-deep.d.ts'; export type {PickDeep} from './source/pick-deep.d.ts'; export type {OmitDeep} from './source/omit-deep.d.ts'; diff --git a/readme.md b/readme.md index 5a20e8c3b..53e8124e6 100644 --- a/readme.md +++ b/readme.md @@ -135,6 +135,7 @@ Click the type names for complete docs. - [`PartialOnUndefinedDeep`](source/partial-on-undefined-deep.d.ts) - Create a deep version of another type where all keys accepting `undefined` type are set to optional. - [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`. - [`UnwrapPartial`](source/unwrap-partial.d.ts) - Revert the `Partial` modifier on an object type. +- [`UnwrapRequired`](source/unwrap-required.d.ts) - Revert the `Required` modifier on an object type. - [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of another type. - [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. - [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). @@ -534,6 +535,8 @@ There are many advanced types most users don't know about. email: 'ex@mple.com', }); ``` + + `Required` can be reverted with [`UnwrapRequired`](source/unwrap-required.d.ts). - [`Readonly`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) - Make all properties in `T` readonly. diff --git a/source/unwrap-required.d.ts b/source/unwrap-required.d.ts new file mode 100644 index 000000000..3b70d8411 --- /dev/null +++ b/source/unwrap-required.d.ts @@ -0,0 +1,37 @@ +import type {MapsSetsOrArrays, NonRecursiveType} from './internal/type.d.ts'; + +/** +Revert the `Required` modifier on an object type. + +Use-case: Infer the underlying type `T` when only `Required` is available or the original type may not be directly accessible. + +@example +``` +import type {UnwrapRequired} from 'type-fest'; + +type ContactFormData = Required<{ + email: string; + message?: string; +}>; + +type DraftContactFormData = UnwrapRequired; +//=> {email: string; message?: string} +``` + +Note: + - If the provided type isn’t of the form `Required`, `UnwrapRequired` simply returns the input type. + - `UnwrapRequired` doesn't work with arrays, if instantiated with arrays, it simply returns the input type. + +@category Object +*/ +export type UnwrapRequired = + RequiredObjectType extends NonRecursiveType | MapsSetsOrArrays + ? RequiredObjectType + : _UnwrapRequired; + +type _UnwrapRequired = + RequiredObjectType extends Required + ? ObjectType + : RequiredObjectType; + +export {}; diff --git a/test-d/unwrap-required.ts b/test-d/unwrap-required.ts new file mode 100644 index 000000000..9ba7b588b --- /dev/null +++ b/test-d/unwrap-required.ts @@ -0,0 +1,73 @@ +import {expectType} from 'tsd'; +import type {UnwrapRequired} from '../index.d.ts'; + +type TestType = { + a?: string; + b: number; +}; + +expectType({} as UnwrapRequired>); + +// `UnwrapRequired` preserves optional properties +type AnotherTestType = { + c?: boolean; + d?: 'literal'; +}; + +type TestTypeWithOptionalProps = TestType & AnotherTestType; + +expectType({} as UnwrapRequired>); + +// `UnwrapRequired` preserves nested `Required` properties +type TestTypeWithRequiredProp = TestType & { + c: Required; +}; + +expectType({} as UnwrapRequired>); + +// `UnwrapRequired` preserves readonly properties +type TestTypeWithReadonlyProps = { + readonly a?: string; + readonly b: number; + readonly c?: boolean; +}; + +expectType({} as UnwrapRequired>); + +// `UnwrapRequired` works with methods +type TestTypeWithMethod = { + a?: string; + c?(): void; +}; + +expectType({} as UnwrapRequired>); + +// `UnwrapRequired` works with union types +type RequiredUnionType = Required | Required; + +expectType({} as UnwrapRequired); +expectType({} as UnwrapRequired | AnotherTestType>); +expectType({} as UnwrapRequired | string>); +expectType>({} as UnwrapRequired | Map>); +expectType | string>({} as UnwrapRequired | Map | string>); + +// `UnwrapRequired` works with unknown types +expectType({} as UnwrapRequired>); +expectType({} as UnwrapRequired>); + +// `UnwrapRequired` has no effect on non-required types +expectType({} as UnwrapRequired); +expectType<{a: string; b: number}>({} as UnwrapRequired<{a: string; b: number}>); +expectType({} as UnwrapRequired); +expectType({} as UnwrapRequired); +expectType>({} as UnwrapRequired>); +expectType>({} as UnwrapRequired>); +expectType>({} as UnwrapRequired>); +expectType>({} as UnwrapRequired>); +expectType>({} as UnwrapRequired>); +expectType>({} as UnwrapRequired>); +expectType({} as UnwrapRequired); +expectType({} as UnwrapRequired); +expectType>({} as UnwrapRequired>); +expectType({} as UnwrapRequired); +expectType<() => string>({} as UnwrapRequired<() => string>);