From 034c7129cde7098cf94e66db59c66ea32654bd26 Mon Sep 17 00:00:00 2001 From: derodero24 Date: Sun, 8 Feb 2026 19:24:26 +0900 Subject: [PATCH 1/5] Add `SetNullable` type --- index.d.ts | 1 + readme.md | 1 + source/set-nullable.d.ts | 43 ++++++++++++++++++++++++++++++++++++++++ test-d/set-nullable.ts | 30 ++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 source/set-nullable.d.ts create mode 100644 test-d/set-nullable.ts diff --git a/index.d.ts b/index.d.ts index 8a9926d8d..b04095430 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,6 +50,7 @@ export type {SetReadonly} from './source/set-readonly.d.ts'; export type {SetRequired} from './source/set-required.d.ts'; export type {SetRequiredDeep} from './source/set-required-deep.d.ts'; export type {SetNonNullable} from './source/set-non-nullable.d.ts'; +export type {SetNullable} from './source/set-nullable.d.ts'; export type {SetNonNullableDeep} from './source/set-non-nullable-deep.d.ts'; export type {ValueOf} from './source/value-of.d.ts'; export type {AsyncReturnType} from './source/async-return-type.d.ts'; diff --git a/readme.md b/readme.md index 47e9fee30..642dd5d00 100644 --- a/readme.md +++ b/readme.md @@ -133,6 +133,7 @@ Click the type names for complete docs. - [`SetReadonly`](source/set-readonly.d.ts) - Create a type that makes the given keys readonly. - [`SetRequired`](source/set-required.d.ts) - Create a type that makes the given keys required. - [`SetRequiredDeep`](source/set-required-deep.d.ts) - Like `SetRequired` except it selects the keys deeply. +- [`SetNullable`](source/set-nullable.d.ts) - Create a type that makes the given keys nullable. - [`SetNonNullable`](source/set-non-nullable.d.ts) - Create a type that makes the given keys non-nullable. - [`SetNonNullableDeep`](source/set-non-nullable-deep.d.ts) - Create a type that makes the specified keys non-nullable (removes `null` and `undefined`), supports deeply nested key paths, and leaves all other keys unchanged. - [`ValueOf`](source/value-of.d.ts) - Create a union of the given object's values, and optionally specify which keys to get the values from. diff --git a/source/set-nullable.d.ts b/source/set-nullable.d.ts new file mode 100644 index 000000000..dbd1162a4 --- /dev/null +++ b/source/set-nullable.d.ts @@ -0,0 +1,43 @@ +/** +Create a type that makes the given keys nullable, where the remaining keys are kept as is. + +If no keys are given, all keys will be made nullable. + +Use-case: You want to define a single model where the only thing that changes is whether or not some or all of the keys are nullable. + +@example +``` +import type {SetNullable} from 'type-fest'; + +type Foo = { + a: number; + b: string; + c?: boolean; +}; + +type SomeNullable = SetNullable; +// type SomeNullable = { +// a: number | null; +// b: string; +// c?: boolean | null; +// } + +type AllNullable = SetNullable; +// type AllNullable = { +// a: number | null; +// b: string | null; +// c?: boolean | null; +// } +``` + +@see {@link SetNonNullable} + +@category Object +*/ +export type SetNullable = { + [Key in keyof BaseType]: Key extends Keys + ? BaseType[Key] | null + : BaseType[Key]; +}; + +export {}; diff --git a/test-d/set-nullable.ts b/test-d/set-nullable.ts new file mode 100644 index 000000000..93dd9aef8 --- /dev/null +++ b/test-d/set-nullable.ts @@ -0,0 +1,30 @@ +import {expectNotAssignable, expectType} from 'tsd'; +import type {SetNullable} from '../index.d.ts'; + +// Make one key nullable. +declare const variation1: SetNullable<{a: number; b: string}, 'a'>; +expectType<{a: number | null; b: string}>(variation1); + +// Make multiple keys nullable. +declare const variation2: SetNullable<{a: number; b: string; c: boolean}, 'a' | 'c'>; +expectType<{a: number | null; b: string; c: boolean | null}>(variation2); + +// Preserve optional modifier. +declare const variation3: SetNullable<{a: number; b?: string}, 'b'>; +expectType<{a: number; b?: string | null}>(variation3); + +// Key already nullable remains unchanged. +declare const variation4: SetNullable<{a: number | null; b: string}, 'a'>; +expectType<{a: number | null; b: string}>(variation4); + +// Fail if type changes even if nullable is right. +declare const variation5: SetNullable<{a: number; b: string}, 'b'>; +expectNotAssignable<{a: string; b: string | null}>(variation5); + +// Make all keys nullable when `Keys` generic is not passed. +declare const variation6: SetNullable<{a: number; b: string; c: boolean}>; +expectType<{a: number | null; b: string | null; c: boolean | null}>(variation6); + +// Preserve readonly modifier. +declare const variation7: SetNullable<{readonly a: number; b: string}, 'a'>; +expectType<{readonly a: number | null; b: string}>(variation7); From 244b33c3f1c28a1f3b6a1723e37c2968ce29cc6f Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee Date: Mon, 9 Feb 2026 15:23:42 +0530 Subject: [PATCH 2/5] fix: JSDoc twoslash comment --- source/set-nullable.d.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/source/set-nullable.d.ts b/source/set-nullable.d.ts index dbd1162a4..327c21b72 100644 --- a/source/set-nullable.d.ts +++ b/source/set-nullable.d.ts @@ -15,19 +15,11 @@ type Foo = { c?: boolean; }; -type SomeNullable = SetNullable; -// type SomeNullable = { -// a: number | null; -// b: string; -// c?: boolean | null; -// } +type SomeNullable = SetNullable; // Optionality is preserved for `c` +//=> {a: number | null; b: string; c?: boolean | null} type AllNullable = SetNullable; -// type AllNullable = { -// a: number | null; -// b: string | null; -// c?: boolean | null; -// } +//=> {a: number | null; b: string | null; c?: boolean | null} ``` @see {@link SetNonNullable} From f59d605de9a1c5791dfee5c56c670abf0e8c16c2 Mon Sep 17 00:00:00 2001 From: derodero24 Date: Mon, 9 Feb 2026 21:14:39 +0900 Subject: [PATCH 3/5] `SetNullable`: Add tests for unions, index signatures, and `any`/`never` keys --- test-d/set-nullable.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test-d/set-nullable.ts b/test-d/set-nullable.ts index 93dd9aef8..97f9a926e 100644 --- a/test-d/set-nullable.ts +++ b/test-d/set-nullable.ts @@ -28,3 +28,28 @@ expectType<{a: number | null; b: string | null; c: boolean | null}>(variation6); // Preserve readonly modifier. declare const variation7: SetNullable<{readonly a: number; b: string}, 'a'>; expectType<{readonly a: number | null; b: string}>(variation7); + +// Works with unions. +declare const variation8: SetNullable<{a: '1'; b: string; c: boolean} | {a: '2'; b: string; c: boolean}, 'a' | 'b'>; +expectType<{a: '1' | null; b: string | null; c: boolean} | {a: '2' | null; b: string | null; c: boolean}>(variation8); + +// Works with index signatures. +declare const variation9: SetNullable<{[k: string]: unknown; a: number; b: string}, 'a'>; +expectType<{[k: string]: unknown; a: number | null; b: string}>(variation9); + +// Makes all keys nullable when `Keys` is `any`. +declare const variation10: SetNullable<{readonly a: number; b?: string; c: boolean}, any>; +expectType<{readonly a: number | null; b?: string | null; c: boolean | null}>(variation10); + +// Does nothing when `Keys` is `never`. +declare const variation11: SetNullable<{a: number; readonly b?: string; readonly c: boolean}, never>; +expectType<{a: number; readonly b?: string; readonly c: boolean}>(variation11); + +// Preserves existing `undefined` when adding `null`. +declare const variation12: SetNullable<{a: string | undefined; b: number}, 'a'>; +expectType<{a: string | undefined | null; b: number}>(variation12); + +// Works with unions where only some keys are shared across branches. +declare const variation13: SetNullable<{a: number; c: boolean} | {a: string; d: boolean}, 'a'>; +expectType<{a: number | null; c: boolean} | {a: string | null; d: boolean}>(variation13); + From 66aa3fc94dc5cd53cc2f67a70a3556347ff638e8 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee Date: Sun, 15 Mar 2026 14:19:08 +0530 Subject: [PATCH 4/5] test: add case --- test-d/set-nullable.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-d/set-nullable.ts b/test-d/set-nullable.ts index 97f9a926e..103ded981 100644 --- a/test-d/set-nullable.ts +++ b/test-d/set-nullable.ts @@ -37,6 +37,9 @@ expectType<{a: '1' | null; b: string | null; c: boolean} | {a: '2' | null; b: st declare const variation9: SetNullable<{[k: string]: unknown; a: number; b: string}, 'a'>; expectType<{[k: string]: unknown; a: number | null; b: string}>(variation9); +declare const variation9a: SetNullable<{[k: Uppercase]: number; A: number; b: string}, Uppercase>; +expectType<{[k: Uppercase]: number | null; A: number | null; b: string}>(variation9a); + // Makes all keys nullable when `Keys` is `any`. declare const variation10: SetNullable<{readonly a: number; b?: string; c: boolean}, any>; expectType<{readonly a: number | null; b?: string | null; c: boolean | null}>(variation10); From 605dda1d40c7215f85cddfc85f0bf48c6b5ddeb2 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee Date: Sun, 15 Mar 2026 14:19:28 +0530 Subject: [PATCH 5/5] fix: remove extra new line at end --- test-d/set-nullable.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test-d/set-nullable.ts b/test-d/set-nullable.ts index 103ded981..9202885f8 100644 --- a/test-d/set-nullable.ts +++ b/test-d/set-nullable.ts @@ -55,4 +55,3 @@ expectType<{a: string | undefined | null; b: number}>(variation12); // Works with unions where only some keys are shared across branches. declare const variation13: SetNullable<{a: number; c: boolean} | {a: string; d: boolean}, 'a'>; expectType<{a: number | null; c: boolean} | {a: string | null; d: boolean}>(variation13); -