wellformed

Predicates

PredicateRegistry, built-in predicates, and custom predicates

Most users should start with builder methods. They create predicate IR for you.

NeedBuilder
Email, URL, phone, UUIDw.string().email(), .url(), .phone(), .uuid()
SSN, EIN, ITIN, TINw.string().ssn(), .ein(), .itin(), .tin()
Financial identifiersw.string().creditCard(), .iban(), .abaRouting(), .cusip()
Healthcare and reference codesw.string().npi(), .icd10Code(), .usState(), .currencyCode()
Cross-field business rulesw.object(...).when(...), .requireOneOf(...), .requireSum(...)

When to use this page

Use this reference when you need to inspect serialized IR, register a custom predicate, or build validation tooling.

Predicate IR

Predicates are the boolean evaluation units inside constraints. A constraint pairs a predicate with error metadata:

{
  "pred": { "type": "call", "name": "is_email" },
  "error": {
    "code": "INVALID_EMAIL",
    "message": "Enter a valid email address"
  }
}

Work with predicate IR directly for advanced rules, schema tooling, and tests.

Predicate Types

TypeDescription
true / falseConstant predicates
regexRegular expression match
template_literalStructured string scanner made from literal and character-class parts
min_len / max_lenString or array length
rangeNumeric range
existsJSON Pointer exists and is not null or undefined
eqValue at path equals a literal
inValue at path is one of several values
required_withA field is required when another field exists
required_withoutA field is required when another field is absent
exactly_one_ofExactly one path in a set exists
eq_fieldsTwo fields are equal
gt_field / gte_fieldField comparison, greater than
lt_field / lte_fieldField comparison, less than
sum_equalsSum of fields equals another field
sum_equals_valueSum of fields equals a literal value
and / or / notBoolean composition
impliesIf predicate A is true, predicate B must be true
callNamed predicate resolved by PredicateRegistry

PredicateRegistry

Named predicates use the call type and are resolved through a registry:

import { PredicateRegistry, createEvalContext } from "wellformed-ts";

const registry = PredicateRegistry.withBuiltins();
const ctx = createEvalContext(registry);

validate(...) uses built-ins by default. Pass a custom context when a schema calls predicates that are not built in:

const result = validate(schema, data, { context: ctx });

Built-In Predicates

Taxpayer Identifiers

NameDescription
is_tinAny supported US TIN
is_ssnSocial Security Number
is_einEmployer Identification Number
is_itinIndividual Taxpayer Identification Number
is_atinAdoption Taxpayer Identification Number
luhnLuhn checksum

Financial, Payment, and Product Identifiers

NameDescription
is_cusipCUSIP identifier
is_aba_routingABA routing transit number
is_mccMerchant Category Code
is_account_numberAccount number with configurable length and hyphen behavior
is_credit_cardPayment card number with Luhn validation
is_cvvCard security code
is_card_expiryCard expiry in supported month/year formats
is_ibanInternational Bank Account Number
is_bicBIC code
is_swiftSWIFT code alias
is_vinVehicle Identification Number
is_upcUPC barcode
is_eanEAN barcode
is_isbnISBN

Dates and Times

NameDescription
is_dateDate string
is_timeTime string
is_iso_datetimeISO 8601 date-time
is_tax_yearTax year in a configurable range
date_in_rangeDate within min/max bounds
date_beforeDate before a target
date_afterDate after a target
time_beforeTime before a target
time_afterTime after a target
time_in_rangeTime within a range, including overnight ranges

Amounts and Numeric Values

NameDescription
is_non_negativeNumber is greater than or equal to 0
is_positiveNumber is greater than 0
is_negativeNumber is less than 0
is_non_positiveNumber is less than or equal to 0
is_percentagePercentage value
is_money_formatMoney string with currency symbol support
is_money_no_symbolMoney string without a leading currency symbol
is_multiple_ofNumber is a multiple of the configured step
less_than_or_equalNumber is less than or equal to the configured value
greater_than_or_equalNumber is greater than or equal to the configured value
is_decimal_placesNumber or numeric string has at most N decimal places
is_integerInteger number or integer string
is_floatFloating-point number or numeric string
is_u8 / is_u16 / is_u32 / is_u64Unsigned integer range checks
is_i8 / is_i16 / is_i32 / is_i64Signed integer range checks

Reference Data

NameDescription
is_country_codeISO 3166-1 alpha-2 country code
is_country_nameCountry name (e.g. "Canada")
is_currency_codeISO 4217 currency code
is_us_stateUS state or territory code
is_state_nameUS state or territory name (e.g. "Puerto Rico")
is_us_zipUS ZIP code
is_street_addressBasic street-address shape check
is_w2_box12_codeW-2 Box 12 code
is_1099b_code1099-B code
is_filing_statusUS tax filing status (e.g. "mfj", "single")

Contact and Network Fields

NameDescription
phone_numberGeneral phone number
phone_number_usUS phone number
is_phonePhone number alias used by the TypeScript builder
is_emailEmail address
is_urlURL
is_uuidUUID
is_ipIPv4 or IPv6 address
is_cidrCIDR block
is_mac_addressMAC address

Text and String Shape

NameDescription
is_rtlContains right-to-left script characters
is_ltrContains left-to-right script characters
starts_withString starts with configured value
ends_withString ends with configured value
containsString contains configured value
is_alphaASCII letters only
is_digitsASCII digits only
is_alphanumericASCII letters and digits only
is_alpha_spacesASCII letters and spaces, with at least one letter
is_alphanumeric_spacesASCII letters, digits, and spaces, with at least one alphanumeric character
is_name_charsASCII letters, hyphens, and apostrophes, with at least one letter
is_uppercaseUppercase ASCII letters only
is_lowercaseLowercase ASCII letters only
is_title_caseOne uppercase ASCII letter followed by lowercase ASCII letters

Aviation

NameDescription
is_iata_airport_codeIATA airport code
is_icao_airport_codeICAO airport code
is_airport_codeIATA or ICAO airport code
is_iata_airline_codeIATA airline code
is_icao_airline_codeICAO airline code
is_airline_codeIATA or ICAO airline code
is_flight_numberFlight number

Healthcare and Insurance

NameDescription
is_npiNational Provider Identifier
is_dea_numberDEA registration number
is_icd10_codeICD-10 code
is_cpt_codeCPT code
is_hcpcs_codeHCPCS code
is_ndc_codeNDC code

Color, Encoding, and Crypto Shape

NameDescription
is_hex_colorHex color
is_rgb_colorCSS rgb(...) color
is_hsl_colorCSS hsl(...) color
is_base58Base58 string
is_base64Base64 string
is_bitcoin_addressBitcoin address shape
is_ethereum_addressEthereum address shape
is_solana_addressSolana address shape
is_jwtJSON Web Token shape
is_hashHash string with configurable algorithm/length expectations

Custom Predicates

Register custom predicates for organization-specific validation:

const registry = PredicateRegistry.withBuiltins();

registry.register("is_even", (value) => {
  return typeof value === "number" && value % 2 === 0;
});

registry.register("divisible_by", (value, args) => {
  if (typeof value !== "number") return false;
  const divisor = (args as { value: number }).value;
  return value % divisor === 0;
});

Use custom predicates through call predicates:

const schema = w.object({
  count: w.integer(),
}).rule(
  { type: "call", name: "divisible_by", args: { value: 5 } },
  "NOT_DIVISIBLE_BY_5",
  "Count must be divisible by 5",
);

Custom predicate implementations are not serialized into the IR. Every runtime that evaluates a schema must register equivalent implementations for the predicate names it uses.

Direct Evaluation

For advanced tooling, you can evaluate predicate IR directly:

import { createEvalContext, evaluate } from "wellformed-ts";

const ctx = createEvalContext();

evaluate({ type: "regex", pattern: "^\\d+$" }, "123", ctx); // true
evaluate({ type: "range", min: 0, max: 100 }, 50, ctx); // true
evaluate({ type: "exists", path: "/name" }, { name: "Alice" }, ctx); // true

Direct evaluation is useful for schema tooling, tests, and custom rule builders. Application validation should usually go through validate(...) so transforms, type checks, object rules, and error metadata are handled together.

On this page