Skip to content
On this page

Exploring TigerStyle! programming in Bun/TypeScript


<- return to blog

tigerstyle! in bun / typescript

So, I have been recently getting Ziggy With It™ and along the way while watching one of the ThePrimeagen's videos I discovered something called "TigerStyle Programming".

drawing

What the f*** is TigerStyle?

This programming martial-art-style came from the creators of TigerBeetle - a high performance, fault-tolerant, mission critical database designed for debit / credit card transactions. Surely the creators of a highly fault-tolerant financial transactions database have some wisdom to impart on us.

This handy little article by Dave Gauer broadly describes what TigerStyle is. You can also watch Joran Greef's talk "TigerStyle! (Or How To Design Safer Systems in Less Time)".

tldr - TigerStyle is all about programming the negative space.

Programming the negative space is all about using assertions to ensure your world-view is correct while turning your coffee in to beautiful, beautiful code.

Normally - most programmers use assertions in their test functions. In addition to tests, TigerStyle is about putting assertions directly in your running code to ensure it fails, shuts down, or the error is handled properly so your application does not keep running with a faulty state.

NASA recommends using a minimum of two runtime assertions per function, as defined in The Power of 10: Rules for Developing Safety-Critical Code .

You should always run your assertions in production.

The TypeScript flying tiger kick


Bringing TigerStyle to TypeScript is extremely simple with bun:test. Since it is built-in to Bun, we don't need to worry about downloading any packages.

A simple import { expect } from 'bun:test' will do.

Consider the following code:

const doMoreStuff = async (fruit: string): Promise<boolean> => {
    console.log("This is a fruit, I swear!")
    return true
}

const doStuff = async () => {
    const isFruit: boolean = await doMoreStuff('apple')
    console.log(isFruit)
}

doStuff()

This code is extremly loose and fruit could be anything. Perhaps we want to ensure that when we doMoreStuff() with fruit, it is a specific fruit.

We could use switch cases or if statements to handle potential edge cases and control flow, but here instead we going to make some simple assertions with a try..catch. This creates a worldview, and throws an error when that worldview is challenged. I find this is much neater and requires much less code.

Let's make some simple assertions:

import { expect } from 'bun:test'

const doMoreStuff = async (fruit: string): Promise<boolean> => {
    try {
        expect(fruit).toBeOneOf(["apple", "banana", "orange"])
        console.log("This is a fruit, I swear!")
        return true
    } catch {
        console.log("I don't like this fruit so it isn't one.")
        return false
    }
}

const doStuff = async () => {
    const isFruit: boolean = await doMoreStuff('apple')
    expect(isFruit).toBe(true)
    console.log(isFruit)
}

doStuff()

Now, we are asserting our worldview as we create our code. We want our program to throw an error when our worldview is challenged as soon as possible so that error can be discovered or handled. It makes it significantly easier to debug your code.

Using Zod

Zod provides an easy way to assert your data at a finer level. For a simple example, we can parse and assert our function argument to be a UUID.

import { expect } from "bun:test"
import { z } from "zod"

const doMoreStuff = async (uuid: string): Promise<boolean> => {
    try {
        z.string().uuid().parse(uuid)
        return true
    } catch {
        return false
    }
}

const doStuff = async () => {
    const isUUID: boolean = await doMoreStuff(crypto.randomUUID())
    expect(isUUID).toBe(true)
    console.log(isUUID)
}

doStuff()

Congrats

You now possess some TypeScript TigerStyle wisdom!