Skip to content

createContractor()

Create a contractor with shared default options for all contracts it produces.

createContractor() builds a contractor object preconfigured with default mode and onViolation settings. Every contract created through the contractor inherits those defaults, with per-contract options taking precedence.

Signature

function createContractor(defaults: ContractorDefaults): { contract: typeof contract }

ContractorDefaults

interface ContractorDefaults {
  mode?: Mode
  onViolation?: ViolationHandler
}
PropertyTypeRequiredDefaultDescription
modeModeno"warn"Default enforcement mode for all contracts
onViolationViolationHandlernoDefault violation handler for all contracts

Return Value

An object with a single contract method. This method has the same signature as the standalone contract() function. Options provided on individual contract() calls override the contractor defaults.

const { contract } = createContractor({ mode: "strict" })

How Merging Works

The contractor merges defaults with per-contract options using a shallow override. If a contract specifies mode or onViolation, that value is used instead of the default. If the contract omits them, the defaults apply.

import { createContractor } from "vowwch"

const { contract } = createContractor({
  mode: "warn",
  onViolation: (v) => console.error(v),
})

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

const a = contract((s: string) => s.toUpperCase(), { name: "a", input: isString })
// mode: "warn", onViolation: console.error (from defaults)

const b = contract((s: string) => s.toLowerCase(), { name: "b", input: isString, mode: "strict" })
// mode: "strict" (overridden), onViolation: console.error (from defaults)

Shared Error Reporting

Use a contractor to centralize violation reporting across your application:

import { createContractor, defineGuard } from "vowwch"
import type { Violation } from "vowwch"

const violations: Violation[] = []

const { contract } = createContractor({
  mode: "warn",
  onViolation: (v) => {
    violations.push(v)
    fetch("/api/violations", {
      method: "POST",
      body: JSON.stringify({
        name: v.name,
        side: v.side,
        actual: v.actual,
        timestamp: v.timestamp,
        stack: v.stack,
      }),
    })
  },
})

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

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

const triple = contract((n: number) => n * 3, { name: "triple", output: isPositive })

double(-1) // -2, violation reported
triple(-1) // -3, violation reported
console.log(violations.length) // 2

Environment-Based Mode

A single createContractor() call at app startup can set the mode for an entire module based on the environment:

import { createContractor } from "vowwch"

const { contract } = createContractor({
  mode: process.env.NODE_ENV === "production" ? "silent" : "strict",
})

const isObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null

const parseConfig = contract((raw: string) => JSON.parse(raw), {
  name: "parseConfig",
  output: isObject,
})

In development, parseConfig throws on invalid output. In production, the contract is a zero-cost passthrough.

Overriding Defaults Per Contract

Individual contracts can escalate or relax the mode regardless of the contractor default:

import { createContractor } from "vowwch"

const { contract } = createContractor({ mode: "warn" })

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

const critical = contract((data: string) => JSON.parse(data), { name: "critical", mode: "strict" })

const optional = contract((data: string) => data.trim(), {
  name: "optional",
  input: isString,
  mode: "silent",
})

critical throws on violations despite the contractor default of "warn". optional skips validation entirely.

When to Use createContractor

Centralized config — When multiple modules share the same mode or violation handler, a contractor avoids repeating options on every call.

Team conventions — Export a pre-configured contractor from a shared module so all team members use consistent settings.

Environment switching — Set the mode once based on NODE_ENV or a feature flag rather than scattering conditionals across every contract.

Error tracking integration — Wire a single onViolation handler to Sentry, Datadog, or a custom logging service and every contract created through the contractor reports through that channel.

Gotchas

The return value is { contract }, not a function. Destructure it before use:

const { contract } = createContractor({ mode: "strict" })

Calling createContractor({}) is valid. The resulting contractor behaves identically to the standalone contract() with all defaults.

The contractor does not deep-merge nested objects. Only mode and onViolation are supported as defaults. Input and output predicates are always specified per-contract.