Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type {ReadonlyDeep} from './source/readonly-deep.d.ts';
export type {LiteralUnion} from './source/literal-union.d.ts';
export type {Promisable} from './source/promisable.d.ts';
export type {Arrayable} from './source/arrayable.d.ts';
export type {Optional} from './source/optional.d.ts';
export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts';
export type {InvariantOf} from './source/invariant-of.d.ts';
export type {SetOptional} from './source/set-optional.d.ts';
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ Click the type names for complete docs.
- [`ConditionalSimplify`](source/conditional-simplify.d.ts) - Simplifies a type while including and/or excluding certain types from being simplified.
- [`ConditionalSimplifyDeep`](source/conditional-simplify-deep.d.ts) - Recursively simplifies a type while including and/or excluding certain types from being simplified.
- [`ExclusifyUnion`](source/exclusify-union.d.ts) - Ensure mutual exclusivity in object unions by adding other members’ keys as `?: never`.
- [`Optional`](source/optional.d.ts) - Create a type that represents either the value or `undefined`, while stripping `null` from the type.

### Type Guard

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

Expand Down
31 changes: 31 additions & 0 deletions source/optional.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
Create a type that represents either the value or `undefined`, while stripping `null` from the type.

Use-cases:
- Enforcing the practice of using `undefined` instead of `null` as the "absence of value" marker.
- Converting APIs that return `null` (DOM, JSON, legacy libraries) to use `undefined` consistently.

@example
```
import type {Optional} from 'type-fest';

// Adds `undefined` to the type
type MaybeNumber = Optional<number>;
//=> number | undefined

// Strips `null` from the type
type NullableString = Optional<string | null>;
//=> string | undefined

type Config = {
name: string;
description: Optional<string>;
};
```

@category Utilities
*/
// Uses `Exclude` instead of a conditional type so that `Optional<null>` resolves to `undefined` rather than `never`.
export type Optional<Value> = Exclude<Value, null> | undefined;

export {};
46 changes: 46 additions & 0 deletions test-d/optional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {expectType} from 'tsd';
import type {Optional} from '../index.d.ts';

// Basic
expectType<string | undefined>({} as Optional<string>);
expectType<{foo: string} | undefined>({} as Optional<{foo: string}>);
expectType<'foo' | undefined>({} as Optional<'foo'>);
expectType<42 | undefined>({} as Optional<42>);
expectType<boolean | undefined>({} as Optional<boolean>);
expectType<string | number | undefined>({} as Optional<string | number>);
expectType<(() => void) | undefined>({} as Optional<() => void>);

// Strips null
expectType<string | undefined>({} as Optional<string | null>);
expectType<string | undefined>({} as Optional<string | null | undefined>);
expectType<number | boolean | undefined>({} as Optional<number | null | boolean>);
expectType<true | undefined>({} as Optional<true | null>);

// Already undefined (idempotent)
expectType<string | undefined>({} as Optional<string | undefined>);

// Pure null becomes undefined (null is stripped, undefined remains)
declare const pureNull: Optional<null>;
expectType<undefined>(pureNull);

// Null | undefined becomes undefined
declare const nullOrUndefined: Optional<null | undefined>;
expectType<undefined>(nullOrUndefined);

// Pure undefined stays undefined
declare const pureUndefined: Optional<undefined>;
expectType<undefined>(pureUndefined);

// Nested Optional is idempotent
expectType<string | undefined>({} as Optional<Optional<string>>);

// Void
declare const voidOptional: Optional<void>;
expectType<void | undefined>(voidOptional);

// Edge cases
expectType<any>({} as Optional<any>);
declare const neverOptional: Optional<never>;
expectType<undefined>(neverOptional);
// `unknown | undefined` simplifies to `unknown`
expectType<unknown>({} as Optional<unknown>);
Loading