Skip to content

Commit 9b52980

Browse files
sindresorhussom-sm
andauthored
Add Optional type (#1374)
Co-authored-by: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com>
1 parent b0a606b commit 9b52980

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export type {ReadonlyDeep} from './source/readonly-deep.d.ts';
4343
export type {LiteralUnion} from './source/literal-union.d.ts';
4444
export type {Promisable} from './source/promisable.d.ts';
4545
export type {Arrayable} from './source/arrayable.d.ts';
46+
export type {Optional} from './source/optional.d.ts';
4647
export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts';
4748
export type {InvariantOf} from './source/invariant-of.d.ts';
4849
export type {SetOptional} from './source/set-optional.d.ts';

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ Click the type names for complete docs.
191191
- [`ConditionalSimplify`](source/conditional-simplify.d.ts) - Simplifies a type while including and/or excluding certain types from being simplified.
192192
- [`ConditionalSimplifyDeep`](source/conditional-simplify-deep.d.ts) - Recursively simplifies a type while including and/or excluding certain types from being simplified.
193193
- [`ExclusifyUnion`](source/exclusify-union.d.ts) - Ensure mutual exclusivity in object unions by adding other members’ keys as `?: never`.
194+
- [`Optional`](source/optional.d.ts) - Create a type that represents either the value or `undefined`, while stripping `null` from the type.
194195

195196
### Type Guard
196197

@@ -354,6 +355,7 @@ Click the type names for complete docs.
354355
- `PickByTypes` - See [`ConditionalPick`](source/conditional-pick.d.ts)
355356
- `HomomorphicOmit` - See [`Except`](source/except.d.ts)
356357
- `IfAny`, `IfNever`, `If*` - See [`If`](source/if.d.ts)
358+
- `Maybe`, `Option` - See [`Optional`](source/optional.d.ts)
357359
- `MaybePromise` - See [`Promisable`](source/promisable.d.ts)
358360
- `ReadonlyTuple` - See [`TupleOf`](source/tuple-of.d.ts)
359361

source/optional.d.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
Create a type that represents either the value or `undefined`, while stripping `null` from the type.
3+
4+
Use-cases:
5+
- Enforcing the practice of using `undefined` instead of `null` as the "absence of value" marker.
6+
- Converting APIs that return `null` (DOM, JSON, legacy libraries) to use `undefined` consistently.
7+
8+
@example
9+
```
10+
import type {Optional} from 'type-fest';
11+
12+
// Adds `undefined` to the type
13+
type MaybeNumber = Optional<number>;
14+
//=> number | undefined
15+
16+
// Strips `null` from the type
17+
type NullableString = Optional<string | null>;
18+
//=> string | undefined
19+
20+
type Config = {
21+
name: string;
22+
description: Optional<string>;
23+
};
24+
25+
type Description = Config['description'];
26+
//=> string | undefined
27+
```
28+
29+
@category Utilities
30+
*/
31+
export type Optional<Value> = Exclude<Value, null> | undefined;
32+
33+
export {};

test-d/optional.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {expectType} from 'tsd';
2+
import type {Optional} from '../index.d.ts';
3+
4+
// Basic
5+
expectType<string | undefined>({} as Optional<string>);
6+
expectType<{foo: string} | undefined>({} as Optional<{foo: string}>);
7+
expectType<'foo' | undefined>({} as Optional<'foo'>);
8+
expectType<42 | undefined>({} as Optional<42>);
9+
expectType<boolean | undefined>({} as Optional<boolean>);
10+
expectType<string | number | undefined>({} as Optional<string | number>);
11+
expectType<(() => void) | undefined>({} as Optional<() => void>);
12+
13+
// Strips `null`
14+
expectType<string | undefined>({} as Optional<string | null>);
15+
expectType<string | undefined>({} as Optional<string | null | undefined>);
16+
expectType<number | boolean | undefined>({} as Optional<number | null | boolean>);
17+
expectType<true | undefined>({} as Optional<true | null>);
18+
19+
// Already `undefined` (idempotent)
20+
expectType<string | undefined>({} as Optional<string | undefined>);
21+
22+
// Pure `null` becomes `undefined` (`null` is stripped, `undefined` remains)
23+
declare const pureNull: Optional<null>;
24+
expectType<undefined>(pureNull);
25+
26+
// `null | undefined` becomes `undefined`
27+
declare const nullOrUndefined: Optional<null | undefined>;
28+
expectType<undefined>(nullOrUndefined);
29+
30+
// Pure `undefined` stays `undefined`
31+
declare const pureUndefined: Optional<undefined>;
32+
expectType<undefined>(pureUndefined);
33+
34+
// Nested `Optional` is idempotent
35+
expectType<string | undefined>({} as Optional<Optional<string>>);
36+
37+
// `void`
38+
declare const voidOptional: Optional<void>;
39+
expectType<void | undefined>(voidOptional);
40+
41+
// Edge cases
42+
expectType<any>({} as Optional<any>);
43+
declare const neverOptional: Optional<never>;
44+
expectType<undefined>(neverOptional);
45+
// `unknown | undefined` simplifies to `unknown`
46+
expectType<unknown>({} as Optional<unknown>);

0 commit comments

Comments
 (0)