From e55306d0c19548c39b5bec61112cf814637667de Mon Sep 17 00:00:00 2001 From: Dom Porada Date: Thu, 23 Apr 2026 16:53:56 +0200 Subject: [PATCH 1/9] Introduce `UnwrapRequired` type --- index.d.ts | 1 + source/unwrap-required.d.ts | 15 +++++++++ test-d/unwrap-required.ts | 65 +++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 source/unwrap-required.d.ts create mode 100644 test-d/unwrap-required.ts 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/source/unwrap-required.d.ts b/source/unwrap-required.d.ts new file mode 100644 index 000000000..71b2a68b2 --- /dev/null +++ b/source/unwrap-required.d.ts @@ -0,0 +1,15 @@ +/** +Revert the `Required` modifier on an object type. + +@category Object +*/ +export type UnwrapRequired = + RequiredObjectType extends Required + ? ( + Required extends RequiredObjectType + ? ObjectType + : RequiredObjectType + ) + : RequiredObjectType; + +export {}; diff --git a/test-d/unwrap-required.ts b/test-d/unwrap-required.ts new file mode 100644 index 000000000..873409f18 --- /dev/null +++ b/test-d/unwrap-required.ts @@ -0,0 +1,65 @@ +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>); + +// `UnwrapRequired` works with arrays and tuples +type TestTuple = [string?, number?, boolean?]; + +expectType<[string, number, boolean]>({} as UnwrapRequired>); +expectType({} as UnwrapRequired); +expectType({} as UnwrapRequired); + +// `UnwrapRequired` works with unknown types +expectType({} as UnwrapRequired>); +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}>); From 4d2a5adf958842a828dbc7cc869887f3780727e9 Mon Sep 17 00:00:00 2001 From: Dom Porada Date: Fri, 24 Apr 2026 16:44:19 +0200 Subject: [PATCH 2/9] Add documentation --- readme.md | 3 +++ source/unwrap-required.d.ts | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) 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 index 71b2a68b2..54e842d94 100644 --- a/source/unwrap-required.d.ts +++ b/source/unwrap-required.d.ts @@ -1,6 +1,23 @@ /** 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 `Required`, `UnwrapRequired` has no effect on the original type. + @category Object */ export type UnwrapRequired = From 6ac5aa3171da989603c5520b7785d3b9ba885b60 Mon Sep 17 00:00:00 2001 From: Dom Porada Date: Sat, 25 Apr 2026 13:26:35 +0200 Subject: [PATCH 3/9] Refactor tests --- test-d/unwrap-required.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/test-d/unwrap-required.ts b/test-d/unwrap-required.ts index 873409f18..9e9655a3a 100644 --- a/test-d/unwrap-required.ts +++ b/test-d/unwrap-required.ts @@ -48,18 +48,11 @@ type RequiredUnionType = Required | Required; expectType({} as UnwrapRequired); expectType({} as UnwrapRequired | AnotherTestType>); -// `UnwrapRequired` works with arrays and tuples -type TestTuple = [string?, number?, boolean?]; - -expectType<[string, number, boolean]>({} as UnwrapRequired>); -expectType({} as UnwrapRequired); -expectType({} as UnwrapRequired); - // `UnwrapRequired` works with unknown types expectType({} as UnwrapRequired>); 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); From 4be8ff90488721e20b5abc141f56d289dd94435d Mon Sep 17 00:00:00 2001 From: Dom Porada Date: Mon, 27 Apr 2026 17:21:25 +0200 Subject: [PATCH 4/9] Simplify type --- source/unwrap-required.d.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/source/unwrap-required.d.ts b/source/unwrap-required.d.ts index 54e842d94..da2fe53cf 100644 --- a/source/unwrap-required.d.ts +++ b/source/unwrap-required.d.ts @@ -22,11 +22,7 @@ Note: If the provided type isn’t of `Required`, `UnwrapRequired` has no eff */ export type UnwrapRequired = RequiredObjectType extends Required - ? ( - Required extends RequiredObjectType - ? ObjectType - : RequiredObjectType - ) + ? ObjectType : RequiredObjectType; export {}; From c844694939675685befdcfd9743ab89db6a2caef Mon Sep 17 00:00:00 2001 From: Dom Porada Date: Mon, 27 Apr 2026 19:00:05 +0200 Subject: [PATCH 5/9] Add note on required tuples --- source/unwrap-required.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/unwrap-required.d.ts b/source/unwrap-required.d.ts index da2fe53cf..5ea91769d 100644 --- a/source/unwrap-required.d.ts +++ b/source/unwrap-required.d.ts @@ -16,7 +16,9 @@ type DraftContactFormData = UnwrapRequired; //=> {email: string; message?: string} ``` -Note: If the provided type isn’t of `Required`, `UnwrapRequired` has no effect on the original type. +Note 1: If the provided type isn’t of `Required`, `UnwrapRequired` has no effect on the original type. + +Note 2: `Required` is lossy for tuples with optional elements. `UnwrapRequired` cannot recover the original tuple type in these cases. Object types with optional properties are unaffected by this limitation (and work as expected with `UnwrapRequired`). @category Object */ From ca9c34c5b6bb01594b352e5a2a7de034c7d7ee6b Mon Sep 17 00:00:00 2001 From: Dom Porada Date: Tue, 28 Apr 2026 17:54:56 +0200 Subject: [PATCH 6/9] Handle additional cases --- source/unwrap-required.d.ts | 6 +++++- test-d/unwrap-required.ts | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/source/unwrap-required.d.ts b/source/unwrap-required.d.ts index 5ea91769d..435ff5ee6 100644 --- a/source/unwrap-required.d.ts +++ b/source/unwrap-required.d.ts @@ -24,7 +24,11 @@ Note 2: `Required` is lossy for tuples with optional elements. `UnwrapRequire */ export type UnwrapRequired = RequiredObjectType extends Required - ? ObjectType + ? ( + Required extends RequiredObjectType + ? ObjectType + : RequiredObjectType + ) : RequiredObjectType; export {}; diff --git a/test-d/unwrap-required.ts b/test-d/unwrap-required.ts index 9e9655a3a..296600db4 100644 --- a/test-d/unwrap-required.ts +++ b/test-d/unwrap-required.ts @@ -56,3 +56,7 @@ expectType({} as UnwrapRequired>); 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<() => string>({} as UnwrapRequired<() => string>); From 35489507c7dfd60862019b3362578dc500b96376 Mon Sep 17 00:00:00 2001 From: Dom Porada Date: Sat, 2 May 2026 14:19:10 +0200 Subject: [PATCH 7/9] Refactor type --- source/unwrap-required.d.ts | 17 +++++++++++------ test-d/unwrap-required.ts | 11 +++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/source/unwrap-required.d.ts b/source/unwrap-required.d.ts index 435ff5ee6..88ca6b064 100644 --- a/source/unwrap-required.d.ts +++ b/source/unwrap-required.d.ts @@ -1,3 +1,5 @@ +import type {MapsSetsOrArrays, NonRecursiveType} from './internal/type.d.ts'; + /** Revert the `Required` modifier on an object type. @@ -18,17 +20,20 @@ type DraftContactFormData = UnwrapRequired; Note 1: If the provided type isn’t of `Required`, `UnwrapRequired` has no effect on the original type. -Note 2: `Required` is lossy for tuples with optional elements. `UnwrapRequired` cannot recover the original tuple type in these cases. Object types with optional properties are unaffected by this limitation (and work as expected with `UnwrapRequired`). +Note 2: `Required` is lossy for arrays and tuples with optional elements, so `UnwrapRequired` has no effect on array and tuple types. Object types with optional properties are unaffected by this limitation (and work as expected with `UnwrapRequired`). @category Object */ export type UnwrapRequired = + // Isolate members where `Required` produces unwanted results (accounting for unions) + Extract extends infer PreservedType + // Recombine the preserved types with the unwrapped members + ? PreservedType | _UnwrapRequired> + : RequiredObjectType; + +type _UnwrapRequired = RequiredObjectType extends Required - ? ( - Required extends RequiredObjectType - ? ObjectType - : RequiredObjectType - ) + ? ObjectType : RequiredObjectType; export {}; diff --git a/test-d/unwrap-required.ts b/test-d/unwrap-required.ts index 296600db4..9ba7b588b 100644 --- a/test-d/unwrap-required.ts +++ b/test-d/unwrap-required.ts @@ -47,6 +47,9 @@ 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>); @@ -56,7 +59,15 @@ expectType({} as UnwrapRequired>); 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>); From 667dbf5d638bcd4bf44fa24272059b665b853c2e Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Tue, 5 May 2026 20:50:25 +0530 Subject: [PATCH 8/9] refactor: simply exit conditional --- source/unwrap-required.d.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/source/unwrap-required.d.ts b/source/unwrap-required.d.ts index 88ca6b064..2916594b3 100644 --- a/source/unwrap-required.d.ts +++ b/source/unwrap-required.d.ts @@ -25,11 +25,9 @@ Note 2: `Required` is lossy for arrays and tuples with optional elements, so @category Object */ export type UnwrapRequired = - // Isolate members where `Required` produces unwanted results (accounting for unions) - Extract extends infer PreservedType - // Recombine the preserved types with the unwrapped members - ? PreservedType | _UnwrapRequired> - : RequiredObjectType; + RequiredObjectType extends NonRecursiveType | MapsSetsOrArrays + ? RequiredObjectType + : _UnwrapRequired; type _UnwrapRequired = RequiredObjectType extends Required From bce51b2e4962de77d28bfb024a41796f0d281bac Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Tue, 5 May 2026 20:54:29 +0530 Subject: [PATCH 9/9] doc: simplify note text --- source/unwrap-required.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/unwrap-required.d.ts b/source/unwrap-required.d.ts index 2916594b3..3b70d8411 100644 --- a/source/unwrap-required.d.ts +++ b/source/unwrap-required.d.ts @@ -18,9 +18,9 @@ type DraftContactFormData = UnwrapRequired; //=> {email: string; message?: string} ``` -Note 1: If the provided type isn’t of `Required`, `UnwrapRequired` has no effect on the original type. - -Note 2: `Required` is lossy for arrays and tuples with optional elements, so `UnwrapRequired` has no effect on array and tuple types. Object types with optional properties are unaffected by this limitation (and work as expected with `UnwrapRequired`). +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 */