V (Validation)
Validation is for independent checks where callers need every error in one pass. It uses the same
runtime representation as Result, but its error side is guaranteed to be non-empty.
Validation has two dual operations. Their paired signatures are in the complete dual-call catalogue.
type NonEmptyArray<E> = readonly [E, ...E[]]type Validation<A, E> = Result<A, NonEmptyArray<E>>Constructors
Section titled “Constructors”valid<A>(value: A): Validation<A, never>invalid<E>(error: E): Validation<never, E>fromResult<A, E>(result: Result<A, E>): Validation<A, E>
fromPredicate<A, E>( value: A, predicate: (value: A) => boolean, onFalse: (value: A) => E,): Validation<A, E>
fromPredicate<A, E>( predicate: (value: A) => boolean, onFalse: (value: A) => E,): (value: A) => Validation<A, E>invalid(error) stores one error in a NonEmptyArray. fromResult preserves a success and wraps
one Result error. Type-guard predicates narrow the successful value, and onFalse is lazy.
Accumulation
Section titled “Accumulation”all<T extends readonly Validation<unknown, unknown>[]>( validations: T,): Validation<ValuesOf<T>, ErrorsOf<T>>
traverse<A, B, E>( values: readonly A[], validate: (value: A) => Validation<B, E>,): Validation<B[], E>
traverse<A, B, E>( validate: (value: A) => Validation<B, E>,): (values: readonly A[]) => Validation<B[], E>all preserves heterogeneous tuple types and flattens every validation error in stable input order.
traverse calls the validator for every input, even after failures. Empty inputs return valid([]).
import { pipe } from '@stopcock/fp'import * as R from '@stopcock/fp/result'import * as V from '@stopcock/fp/validation'
type FieldError = { field: string; message: string }
const signup = V.all([ V.fromPredicate( email, (value: string) => value.includes('@'), () => ({ field: 'email', message: 'invalid email', }), ), pipe( password, V.fromPredicate( (value: string) => value.length >= 8, () => ({ field: 'password', message: 'too short', }), ), ),] as const)
R.match(signup, { err: (errors) => showErrors(errors), ok: ([validEmail, validPassword]: [string, string]) => submit(validEmail, validPassword),})Result compatibility
Section titled “Result compatibility”Validation is structurally a Result, so use existing Result transforms and extraction:
R.map(validation, normalize)pipe(validation, R.map(normalize))pipe(validation, R.match({ err: showErrors, ok: submit }))pipe( validation, R.getOrElse(() => fallback),)There is deliberately no flatMap in the validation module. Dependent
sequential checks are fail-fast and belong in R.flatMap; use Validation only
to accumulate checks that can all run independently.