Typescript Snippets

Typescript Snippets


This document contains a collection of useful, interesting and sometimes quirky snippets of TypeScript I’ve gathered over the years. Some of these patterns are not unique to TypeScript, but in these examples they should be also type-safe (hopefully).

Builder Pattern

The following snippet creates a typed object by progressively adding properties, this can be useful for defining different errors in an application.

CAVEAT: In order for the this in register() method to work correctly, the definition cannot use an arrow function.

type ErrorRegistry<T extends string> = Record<T, number> & {
  register<K extends string>(error: K, code: number): ErrorRegistry<K | T>
}

function createErrorRegistry<T extends string>(domain: T) {
  const errorRegistry = {
    register<K extends T>(error: K, code: number) {
      return {
        ...this,
        [error]: code,
      } as ErrorRegistry<T | K>
    },
  } as ErrorRegistry<T>
  return errorRegistry
}

const output = createErrorRegistry('app')
  .register('linking', 100)
  .register('billing', 200)
  .register('surface', 200)

Typescript Playground Link

Branded Types

The following snippet defines a utility type Brand<any, string> which can be used to create an alias of an existing type, which is more strict than the original type.

declare const BrandSymbol: unique symbol

type Brand<T, Description extends string> = T & {
  [BrandSymbol]: Description
}

// example usage

type UserId = Brand<number, 'UserId'>

function trackUser(userId: UserId) {
  console.log('user:', userId)
}

const userOne = 123 as UserId
const userTwo = 123

trackUser(userOne) // ok
trackUser(userTwo) // error

Typescript Playground Link

Monad*

The following is a monad-like type which has support for operatings over elements of an array, or the array itself.

/**
 * This class facilitates the chaining of operations on a value,
 * each task will either return a new Monad or MonadIterable.
 */
export class Monad<I> {
  constructor(public value: I) {}

  public pipe<O>(operation: (value: I) => O[]): MonadIterable<O[]>

  public pipe<O>(operation: (value: I) => O): Monad<O>

  public pipe<O>(operation: (value: I) => O): Monad<O> | MonadIterable<O[]> {
    const result = operation(this.value)
    return Array.isArray(result) ? new MonadIterable(result) : new Monad(result)
  }

  public print(label?: string): Monad<I> {
    console.log(`${label}:` ?? 'value:', this.value)
    return this
  }
}

/**
 * This is an extension of the Monad class that allows for operations
 * on iterable values. The reduce method will convert back to
 * a basic Monad.
 */
export class MonadIterable<I extends any[]> extends Monad<any> {
  public iterate<O>(
    operation: (value: I[number], index?: number, array?: I) => O
  ): MonadIterable<O[]> {
    return new MonadIterable(this.value.map(operation))
  }

  public filter<O>(
    operation: (value: I[number], index?: number, array?: I) => O
  ): MonadIterable<I> {
    return new MonadIterable(this.value.filter(operation))
  }

  public reduce<O>(
    operation: (prev: O, value: I[number], index?: number, array?: I) => O,
    initialValue: Partial<O> = {}
  ): Monad<O> {
    return new Monad(this.value.reduce(operation, initialValue))
  }

  /**
   * Converts an iterable monad back into a normal monad.
   */
  public combine<O>(operation: (value: I) => O): Monad<O> {
    return new Monad(operation(this.value))
  }
}

Try it on the TypeScript playground!

Define Class via Enum (WIP)

NOTE: This has only been tested on TypeScript 5.4.5 and is still expiramental, see here for example.

This allows us to define an enum which will auto-magically be created into a class definition, and then extended. This is great for a simple ORM with SQLite!

// SPECIAL TYPES

type EnumClass<T> = {
  new (...args: any[]): T
}

type Writeable<T> = { -readonly [P in keyof T]: T[P] }

type EnumProps<T> = {
  [key in keyof T]: number | string | boolean | Date
}

// THE MAGIC

namespace Class {
  export function classFromEnum<E, T>(enumSchema: E): EnumClass<T> {
    return class {
      constructor(args: EnumProps<E>) {
        let hiddenState: EnumProps<E> = Object.assign({}, args)

        for (const property in enumSchema) {
          console.log(`defining property ${property}`)

          Object.defineProperty(this, property, {
            enumerable: true,
            configurable: true,
            writable: true,
            get value() {
              return hiddenState[property]
            },
            set value(nextValue) {
              hiddenState[property] = nextValue
            },
          })
        }
      }
    } as {
      new (args: EnumProps<E>): T
    }
  }
}

// EXAMPLE UASGES

enum UserSchema {
  userId = 'INTEGER NON NULL',
  firstName = 'TEXT NON NULL',
  lastName = 'TEXT NON NULL',
}

type SchemaBinding = {
  userId: number
  firstName: string
  lastName: string
}

class User extends Class.classFromEnum<typeof UserSchema, SchemaBinding>(
  UserSchema
) {
  setName(fullName: string) {
    const [firstName, lastName] = fullName.split(' ')
    this.firstName = firstName
    this.lastName = lastName
  }
}

const myUser = new User({
  userId: 123,
  firstName: 'Bob',
  lastName: 'Smitch',
  extra: true,
})

myUser.setName('Colin Teahan')

console.log(myUser.firstName) // Colin

Try it on the TypeScript Playground