@practical-fp/union-types

A Typescript library for creating discriminating union types.

Usage no npm install needed!

<script type="module">
  import practicalFpUnionTypes from 'https://cdn.skypack.dev/@practical-fp/union-types';
</script>

README

Union Types

NPM version badge Bundle size badge Dependency count badge Tree shaking support badge License badge

A Typescript library for creating discriminating union types. Requires Typescript 4.2 or higher.

Typescript Handbook on discriminating union types

Example

import { impl, Variant, match } from "@practical-fp/union-types"

type Shape =
    | Variant<"Circle", { radius: number }>
    | Variant<"Square", { sideLength: number }>

const { Circle, Square } = impl<Shape>()

function getArea(shape: Shape) {
    return match(shape)
        .with(Circle, ({ radius }) => Math.PI * radius ** 2)
        .with(Square, ({ sideLength }) => sideLength ** 2)
        .done()
}

const circle = Circle({ radius: 5 })
const area = getArea(circle)

Installation

$ npm install @practical-fp/union-types

Usage

Defining a discriminating union type

import { Variant } from "@practical-fp/union-types"

type Shape =
    | Variant<"Circle", { radius: number }>
    | Variant<"Square", { sideLength: number }>

This is equivalent to the following type:

type Shape =
    | { type: "Circle", value: { radius: number } }
    | { type: "Square", value: { sideLength: number } }

Creating an implementation

import { impl } from "@practical-fp/union-types"

const { Circle, Square } = impl<Shape>()

impl<>() can only be used if your environment has full support for Proxies.

Circle.is and Square.is can be used to check if a shape is a circle or a square. They also act as type guards.

const shapes: Shape[] = [circle, square]
const sideLengths = shapes.filter(Square.is).map(square => square.value.sideLength)

Generics

impl<>() also supports generic union types.

In case the variant type uses unconstrained generics, unknown needs to be passed as its type arguments.

import { impl, Variant } from "@practical-fp/union-types"

type Result<T, E> =
    | Variant<"Ok", T>
    | Variant<"Err", E>

const { Ok, Err } = impl<Result<unknown, unknown>>()

In case the variant type uses constrained generics, the constraint type needs to be passed as its type arguments.

import { impl, Variant } from "@practical-fp/union-types"

type Result<T extends object, E> =
    | Variant<"Ok", T>
    | Variant<"Err", E>

const { Ok, Err } = impl<Result<object, unknown>>()