Core API

safe.wrap

Wraps a synchronous function to return SafeResult instead of throwing. Automatically preserves function parameter types.


Signatures

safe.wrap<TArgs, T>(fn: (...args: TArgs) => T): (...args: TArgs) => SafeResult<T, Error>
safe.wrap<TArgs, T>(fn: (...args: TArgs) => T, hooks: SafeHooks<T, Error, TArgs>): (...args: TArgs) => SafeResult<T, Error>
safe.wrap<TArgs, T, E>(fn: (...args: TArgs) => T, parseError: (e: unknown) => NonFalsy<E>): (...args: TArgs) => SafeResult<T, E>
safe.wrap<TArgs, T, E>(fn: (...args: TArgs) => T, parseError: (e: unknown) => NonFalsy<E>, hooks: SafeHooks<T, E, TArgs> & { defaultError: E }): (...args: TArgs) => SafeResult<T, E>

Error normalization

When no parseError is provided, non-Error thrown values are automatically normalized to Error instances via new Error(String(e)). The original thrown value is preserved as error.cause.


Basic usage

// Setup: a function that can throw
const divide = (a: number, b: number) => {
  if (b === 0) throw new Error('Division by zero')
  return a / b
}

// Wrap it
const safeDivide = safe.wrap(divide)
const [result, error] = safeDivide(10, 2) // Types inferred automatically!

if (error) {
  console.error('Division failed:', error.message)
  return
}
console.log(result) // 5

Also works with built-in functions:

const safeJsonParse = safe.wrap(JSON.parse)
const [data, error] = safeJsonParse('{"valid": true}')

With error mapping

type MathError = { code: string; operation: string; message: string }

const safeDivide = safe.wrap(
  divide,
  (e): MathError => ({
    code: 'DIVISION_ERROR',
    operation: 'divide',
    message: e instanceof Error ? e.message : 'Unknown error',
  })
)

const [result, error] = safeDivide(10, 0)
if (error) {
  console.error(error.code, error.message) // error is typed as MathError
}

With hooks

For safe.wrap, the hook context contains the original arguments passed to the wrapped function:

const safeDivide = safe.wrap(divide, {
  onSuccess: (result, [a, b]) => {
    console.log(`${a} / ${b} = ${result}`)
    metrics.record('division_success')
  },
  onError: (error, [a, b]) => {
    console.error(`Failed to divide ${a} by ${b}: ${error.message}`)
    metrics.record('division_error')
  },
})

safeDivide(10, 2) // logs: "10 / 2 = 5"
safeDivide(10, 0) // logs: "Failed to divide 10 by 0: Division by zero"

Context in hooks

Unlike safe.sync where context is always [], safe.wrap hooks receive the original function arguments as context. This is useful for logging which inputs caused an error.


With error mapping and hooks

const safeDivide = safe.wrap(
  divide,
  (e): MathError => ({
    code: 'DIVISION_ERROR',
    operation: 'divide',
    message: e instanceof Error ? e.message : 'Unknown error',
  }),
  {
    onSuccess: (result, [a, b]) => {
      audit.log('division', { a, b, result })
    },
    onError: (error, [a, b]) => {
      // error is typed as MathError here
      audit.log('division_failed', { a, b, code: error.code })
      alertIfCritical(error)
    },
  }
)
Previous
What if I don't like tuples?