Lucas

The problem

Most resilience libraries in the TypeScript ecosystem are tied to HTTP clients (axios-retry, p-retry, Polly.js) or require wrapping your function in a class with a .execute() ceremony. What if you just want to wrap any async function — a database query, an internal service call, a file operation — with retry logic, a timeout, and a circuit breaker, without pulling in heavy dependencies?

Enter houhou.

What is houhou?

Houhou is a zero-dependency TypeScript library (~500 LOC) that wraps any async function with composable resilience policies. The wrapped function keeps the exact same signature — you call it like the original.

import { task } from 'houhou'

const charge = task(chargeCard)
  .retry(3)
  .timeout(10_000)
  .fallback(() => ({ status: 'pending' }))

await charge(account, amount)

Enter fullscreen mode Exit fullscreen mode

Policies at a glance

Retry

Re-execute on failure with fixed or exponential backoff:

task(fetchUser).retry(3) // shorthand

task(fetchUser).retry({
  attempts: 5,
  backoff: 'exponential',
  jitter: true,
  delay: 500
})

Enter fullscreen mode Exit fullscreen mode

Timeout

Reject if the function doesn't complete in time:

task(fetchUser).timeout(5000)

Enter fullscreen mode Exit fullscreen mode

Fallback

Run an alternative function on failure:

task(fetchUser).fallback(() => loadFromCache(id))

Enter fullscreen mode Exit fullscreen mode

Circuit Breaker

Prevent repeated calls to an unhealthy service:

task(queryDb).circuitBreaker({
  failureThreshold: 5,
  successThreshold: 2,
  resetTimeout: 30_000
})

Enter fullscreen mode Exit fullscreen mode

Delay

Wait before execution:

task(syncData).delay(1000)

Enter fullscreen mode Exit fullscreen mode

Policy ordering matters

Policies are nested: the last method called wraps the previous ones. Execution order is reverse of declaration order.

task(fn).retry(3).timeout(1000)
// → timeout wraps retry
// → function runs → retry on failure (up to 3 times) → 1s total timeout
// → if the timeout fires, there are no more retries

Enter fullscreen mode Exit fullscreen mode

task(fn).timeout(1000).retry(3)
// → retry wraps timeout
// → function runs → 1s timeout → if timeout fires, retry catches it
// → the whole cycle repeats up to 3 times

Enter fullscreen mode Exit fullscreen mode

Type safety — policy lock

Each method is lockable. TypeScript prevents configuring the same policy twice at compile time, and a runtime Set guard enforces it at runtime:

const t = task(fn).retry(3)

// @ts-expect-error — 'retry' is already locked
t.retry(2) // throws at runtime too

Enter fullscreen mode Exit fullscreen mode

Cancellation with AbortSignal

Houhou uses AbortController to cancel operations when a timeout fires or an external signal is provided. The AbortSignal is passed as the last argument to your function:

const fn = (url: string, signal?: AbortSignal) => fetch(url, { signal })

task(fn).timeout(5000)('https://api.example.com')
// → timeout fires → controller.abort() → fetch is cancelled

Enter fullscreen mode Exit fullscreen mode

You can also pass your own external signal:

const controller = new AbortController()
const promise = task(fn).timeout(5000).retry(3)('url', controller.signal)

controller.abort() // cancels everything

Enter fullscreen mode Exit fullscreen mode

Composition example

Policies chain fluently in any order:

const resilient = task(callApi)
  .retry({ attempts: 3, backoff: 'exponential' })
  .timeout(5000)
  .fallback(loadFromCache)
  .circuitBreaker({ failureThreshold: 5, successThreshold: 2, resetTimeout: 30_000 })
  .delay(100)

Enter fullscreen mode Exit fullscreen mode

The name

"houhou" means "method" or "way of doing" in Japanese — fitting for a library about how you execute your functions.

Try it

npm install houhou
# or
pnpm add houhou
yarn add houhou

Enter fullscreen mode Exit fullscreen mode

GitHub: https://github.com/smokeeaasd/houhou
npm: https://npmjs.com/package/houhou