Skip to content

contract()

Wrap any function with runtime input and output validation predicates.

contract() wraps a function with runtime validation. It checks inputs before calling the original function and checks outputs before returning. The wrapped function preserves the original type signature.

Signature

function contract<TArgs extends unknown[], TReturn>(
  fn: (...args: TArgs) => TReturn,
  options: ContractOptions<TArgs, TReturn>,
): (...args: TArgs) => TReturn

ContractOptions

interface ContractOptions<TArgs extends unknown[], TReturn> {
  name: string
  input?: Predicate<TArgs[0]> | { [K in keyof TArgs]: Predicate<TArgs[K]> }
  output?: Predicate<Awaited<TReturn>>
  mode?: Mode
  onViolation?: ViolationHandler
}
PropertyTypeRequiredDefaultDescription
namestringyesIdentifier included in violation reports
inputPredicate | Predicate[]noSingle predicate for the first argument, or a tuple of predicates for each argument
outputPredicatenoValidates the return value. For async functions, validates the awaited result
modeModeno"warn"Enforcement level: "strict", "warn", or "silent"
onViolationViolationHandlernoCustom handler called when a violation occurs in warn mode

Return Value

A function with the same signature as fn. In silent mode, contract() returns the original fn reference with zero overhead.

Behavior Per Mode

strict — Throws a VowwchViolationError on the first failing predicate. For input violations, fn is never called. For output violations, the return value is discarded.

warn — Calls onViolation (or console.error if none is provided) and continues execution. The function runs normally and returns its result regardless of validation failures.

silent — Returns the original function unchanged. No predicates are evaluated and no wrapper is created.

Single vs Tuple Input Predicates

A single predicate validates only the first argument:

import { contract } from "vowwch"

const isString = (v: unknown): v is string => typeof v === "string"

const greet = contract((name: string) => `hello ${name}`, {
  name: "greet",
  input: isString,
  mode: "strict",
})

A tuple of predicates validates each argument by position:

import { contract } from "vowwch"

const isNumber = (v: unknown): v is number => typeof v === "number"
const isString = (v: unknown): v is string => typeof v === "string"

const repeat = contract((text: string, count: number) => text.repeat(count), {
  name: "repeat",
  input: [isString, isNumber],
  mode: "strict",
})

repeat("ab", 3) // "ababab"
repeat(42 as any, 3) // throws VowwchViolationError

Output Validation

The output predicate validates the return value. For async functions, the predicate runs against the resolved value after the Promise settles.

import { contract } from "vowwch"

const isPositive = (v: unknown): v is number => typeof v === "number" && v > 0

const divide = contract((a: number, b: number) => a / b, {
  name: "divide",
  output: isPositive,
  mode: "warn",
  onViolation: (v) => console.error(`[${v.name}] bad output: ${v.actual}`),
})

divide(10, 3) // 3.333...
divide(10, -3) // -3.333..., logs violation and returns result

Async Functions

contract() detects async functions automatically. Input predicates run synchronously before the call. The output predicate runs after the Promise resolves.

import { contract, defineGuard } from "vowwch"
import { z } from "zod"

const isUser = defineGuard((v) => z.object({ id: z.string(), name: z.string() }).parse(v))

const fetchUser = contract(
  async (id: string) => {
    const res = await fetch(`/api/users/${id}`)
    return res.json()
  },
  { name: "fetchUser", output: isUser, mode: "warn" },
)

const user = await fetchUser("abc-123")

Strict Mode Error Handling

In strict mode, catch VowwchViolationError to inspect the violation details:

import { contract } from "vowwch"

const isPositive = (v: unknown): v is number => typeof v === "number" && v > 0

const double = contract((n: number) => n * 2, { name: "double", input: isPositive, mode: "strict" })

try {
  double(-1)
} catch (err) {
  const violation = (err as any).violation
  console.log(violation.name) // "double"
  console.log(violation.side) // "input"
  console.log(violation.actual) // -1
}

Warn Mode with Custom Handler

import { contract } from "vowwch"
import type { Violation } from "vowwch"

const violations: Violation[] = []

const abs = contract((n: number) => Math.abs(n), {
  name: "abs",
  input: (v: unknown): v is number => typeof v === "number",
  output: (v: unknown): v is number => typeof v === "number" && v >= 0,
  mode: "warn",
  onViolation: (v) => violations.push(v),
})

Edge Cases

No predicates — When neither input nor output is provided, the function passes through without any wrapping overhead.

const double = contract((n: number) => n * 2, { name: "double" })

double(5) // 10, no validation performed

Silent mode returns original function — The returned reference is identical to fn, so === comparison holds and there is zero runtime cost.

const fn = (n: number) => n * 2
const wrapped = contract(fn, { name: "double", mode: "silent" })

fn === wrapped // true

Output predicate receives Awaited<TReturn> — When wrapping an async function, the output predicate receives the unwrapped value, not the Promise.

Violation timestamps — Each violation includes a timestamp (from Date.now()) and a stack trace captured at the call site.