sindresorhus / type-fest

A collection of essential TypeScript types
Creative Commons Zero v1.0 Universal
14.25k stars 541 forks source link

MaybeUndefined (Partial - but with required properties) #363

Open devinrhode2 opened 2 years ago

devinrhode2 commented 2 years ago

This is a modified version of built-in Partial<:

/**
 * Mark all properties as maybe undefined (`T | undefined`)
 *
 * With `Partial`, properties can be omitted, they are optional.
 * With `MaybeUndefined` all properties are required, but may be explicitly set to `undefined`.
 *
 * This is most useful if `compilerOptions.exactOptionalPropertyTypes` is set to `true`.
 * This option was introduced in TS v4.4:
 *   https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes
 */
type MaybeUndefined<T> = {
  [P in keyof T]: T[P] | undefined
}

Here's the built-in/native Partial:

/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

Upvote & Fund

Fund with Polar

devinrhode2 commented 2 years ago

Example showing why this is useful:

export type SafeEnv = {
  /** It's the FOO thing you know? */
  FOO: string
  /** It's the BAR thing you know? */
  BAR: string
  /** It's the BAZ thing you know? */
  BAZ: string  
  /** It's the BAO thing you know? */
  BAO: string
  /** It's the BOO thing you know? */
  BOO: string // just added
}

export type RawEnv = Partial<SafeEnv>

export const rawEnv: RawEnv = {
  FOO: process.env['FOO'],
  BAR: process.env['BAR'],
  BAZ: process.env['BAZ'],
  BAO: process.env['BAO'],
  // Forgot to add BOO!! It will _always_ be undefined :(
}

// Later, `rawEnv` will be verified and transformed into a `safeEnv` object. But that's irrelevant to this thread.

In this state, BOO will always be undefined, but there are no typescript errors.

Once we turn on exactOptionalProperties, then we get an error, basically along the lines of: FOO: string | undefined is not assignable to FOO?: string. The full error is like:

Type '{ FOO: string | undefined; BAR: string | undefined; BAZ: string | undefined; BAO: string | undefined; BOO: string | undefined; }' is not assignable to type 'Partial<SafeEnv>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
  Types of property 'FOO' are incompatible.
    Type 'string | undefined' is not assignable to type 'string'.
      Type 'undefined' is not assignable to type 'string'.ts(2375)

The solution to this error is to use MaybeUndefined.

Then, we'll get our desired outcome! An error that BOO is missing!

Property 'BOO' is missing in type '{
  FOO: string | undefined;
  BAR: string | undefined;
  BAZ: string | undefined;
  BAO: string | undefined;
}' but required in type 'MaybeUndefined<SafeEnv>'