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.
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>( 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>( 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 * 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( (value: string) => value.includes('@'), () => ({ field: 'email', message: 'invalid email', }), )(email), V.fromPredicate( (value: string) => value.length >= 8, () => ({ field: 'password', message: 'too short', }), )(password),] as const)
R.match({ err: (errors) => showErrors(errors), ok: ([validEmail, validPassword]: [string, string]) => submit(validEmail, validPassword),})(signup)Result compatibility
Section titled “Result compatibility”Validation is structurally a Result, so use existing Result transforms and extraction:
R.map(normalize)(validation)R.match({ err: showErrors, ok: submit })(validation)R.getOrElse(() => fallback)(validation)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.