1.2.1 • Published 8 months ago

ts-tagged-union v1.2.1

Weekly downloads
-
License
CC0-1.0
Repository
github
Last release
8 months ago

A modern TypeScript library designed to reduce boilerplate for tagged unions, also known as discriminated unions.
This library is also an implementation of algebraic data types.

Features

  • Effortlessly defines tagged union types, encompassing even recursive ones
  • Generates following helper functions for each tagged union type (without code generation 👍)
    1. Data constructors
    2. Pattern matching functions
    3. Type guard functions (type predicates)
  • Works on both browsers and Node.js
  • 0 dependencies

Basic example

Here is an example of defining a simple tagged union type and creating its values.

import { type TaggedUnion, createHelperFunctions } from 'ts-tagged-union'

// Define a tagged union type
export type Color = TaggedUnion<{
  rgb: { r: number; g: number; b: number }
  primary: {}
  secondary: {}
}>

// Get helper functions for the type
export const Color = createHelperFunctions<Color>()

// Create object with a data constructor
const rgb = Color.rgb({ r: 255, g: 31, b: 0 })
const primary = Color.primary() // {} can be omitted

console.log(rgb) // { r: 255, g: 31, b: 0, [Symbol(defaultTagKey)]: 'rgb' }
console.log(primary) // { [Symbol(defaultTagKey)]: 'primary' }

Pattern matching

To perform pattern matching with exhaustiveness checking, use the match function.

const color = Math.random() < 0.5 ? Color.primary() : Color.secondary()

const cssColor = Color.match(color, {
  rgb: ({ r, g, b }) => `rgb(${r}, ${g}, ${b})`,
  primary: () => '#C0FFEE', 
  secondary: () => 'blue',
})

The third argument serves as a so-called default case, as follows.

const isAchromatic = Color.match(
  color,
  { rgb: ({ r, g, b }) => r === g && g === b },
  (other) => false,
)

To perform pattern matching without exhaustiveness checking, use the matchPartial instead.

Type guard functions

Type guard functions are available as the is and isNot properties, as shown below.

if (Color.is.rgb(color)) {
  // Here, the variable is narrowed to the rgb variant type.
  console.log(color.r, color.g, color.b)
}

if (Color.isNot.secondary(color)) {
  // Here, the variable is narrowed to the rgb or primary variant type.
  console.log(color)
}

Custom tag key

The key of the property used to distinguish each variant is called tag key.
You can specify a tag key as the second argument to TaggedUnion<T> as follows.

// Define a tagged union type with a custom tag key, 'status'
type Response = TaggedUnion<
  {
    Success: { payload: Blob }
    Failure: { message: string }
  },
  'status' // Either a string literal or symbol type
>
// You need to provide the tag key as an argument due to TypeScript specifications.
const Response = createHelperFunctions<Response>('status')

const failure = Response.Failure({ message: 'Not found' })
console.log(failure.status) // Failure
console.log(Response.tagKey) // status

Adapters for tagged union types defined without using this library

createHelperFunctions and other utilities do not work for tagged union types without a tag-key-pointer.
The tag-key-pointer is a special hidden property that specifies which property is a tag.
It exists only at the type level, so it does not affect runtime.

The type defined with TaggedUnion<T> has the tag-key-pointer property.
To manually add it to an existing type, use AddTagKeyPointer as follows.

import { type AddTagKeyPointer, createHelperFunctions } from 'ts-tagged-union'

type RawTaggedUnion =
  | { type: 'circle', radius: number }
  | { type: 'rect', width: number; height: number }

type Shape = AddTagKeyPointer<RawTaggedUnion, 'type'>
const Shape = createHelperFunctions<Shape>('type')

If you need to remove the tag-key-pointer, use RemoveTagKeyPointer.

Other utilities

There are also several other utilities.

TagKeyOf<T>

Get the tag key of the given tagged union type.

type Response = TaggedUnion<
  {
    Success: { payload: Blob }
    Failure: { message: string }
  },
  'status'
>

type TagKey = TagKeyOf<Response> // 'status'

VariantOf<T, Tag>

Extract the variant type with the specific tag from a tagged union type.

type Response = TaggedUnion<
  {
    Success: { payload: Blob }
    Failure: { message: string }
  },
  'status'
>

type Variant = VariantOf<Response, 'Failure'> // { status: 'Failure', message: string }

PayloadOf<T, Tag>

Extract the payload type of the variant with the specific tag from a tagged union type.

type Response = TaggedUnion<
  {
    Success: { payload: Blob }
    Failure: { message: string }
  },
  'status'
>

type Payload = PayloadOf<Response, 'Failure'> // { message: string }
1.2.1

8 months ago

1.2.0

10 months ago

1.1.1

11 months ago

1.1.0

12 months ago

1.0.0

12 months ago

0.6.1

12 months ago

0.6.0

12 months ago

0.5.0

12 months ago

0.4.0

12 months ago

0.3.9

12 months ago

0.3.8

12 months ago

0.3.7

12 months ago

0.3.6

12 months ago

0.3.5

12 months ago

0.3.4

12 months ago

0.3.3

12 months ago

0.3.2

12 months ago

0.3.1

12 months ago

0.3.0

12 months ago

0.2.0

1 year ago

0.1.0

1 year ago