4.9 KiB
FieldValidator — @suaveplan/rules/validators/field
(no submodules)
Purpose
FieldValidator is a chainable, immutable-per-call validator for a single value. Every withX method returns a fresh validator with the new stage appended, so preset chains can be shared across requests without aliasing concerns. validateSync(value) runs purely-sync chains; validate(value, options?) returns a Promise and threads AbortSignal through every stage. The module ships 12 preset chains covering the common shapes (email, password, username, phone, URL, UUID, date, integer, age, currency, percentage, positive number).
The chain is the natural fit when validating one value at a time (form field, query parameter, header). For object-tree validation, prefer SchemaValidator; for plug-in-style reusable validators with a registry, prefer CustomValidator.
Features
- Immutable chain — every
.withXreturns a newFieldValidator. - Sync + async stages;
validateSyncthrowsRULES.VALIDATOR_INVALIDwhen invoked on an async chain. - 9 builder methods:
required,minLength,maxLength,min,max,pattern,oneOf,custom,customAsync. - 12 preset factories.
- OTel histogram (
rules.validator.validate.duration_ms{kind:"field"}) + counter (rules.validator.validations_total{kind:"field",outcome}).
Usage: Basic Example
import { field } from "@suaveplan/rules/validators/field";
const validator = field().required().minLength(3).maxLength(20);
const result = await validator.validate("ada");
result.valid; // true
Usage: Advanced Example
import {
field,
passwordFieldValidator,
usernameFieldValidator,
} from "@suaveplan/rules/validators/field";
// Compose a preset with caller-specific extensions
const adminPasswordValidator = passwordFieldValidator()
.minLength(12, "admin passwords must be at least 12 chars")
.customAsync(async (value) => {
const breached = await checkHibp(value as string);
return breached ? "password appears in known-breach corpus" : true;
});
const username = usernameFieldValidator();
expect((await username.validate("ada42")).valid).toBe(true);
declare function checkHibp(password: string): Promise<boolean>;
declare const expect: { (received: unknown): { toBe(v: unknown): void } };
API Reference
class FieldValidator
FieldValidator.create()/createFieldValidator()/field()— entry points.required(message?),minLength(min, message?),maxLength(max, message?),min(min, message?),max(max, message?),pattern(re, message?),oneOf(values, message?),custom(fn, message?),customAsync(fn, message?)— each returns a newFieldValidator.validate(value, options?)→Promise<FieldValidatorResult>whereFieldValidatorResult = { valid: boolean; errors: readonly string[] }. Honoursoptions.signal.validateSync(value)→FieldValidatorResult. ThrowsRulesError(RULES.VALIDATOR_INVALID)when the chain has any async stage.isAsync()→truewhen the chain contains any async stage.
Preset chains
| Factory | Behaviour |
|---|---|
emailFieldValidator() |
required + email regex |
urlFieldValidator() |
required + ^https?:// |
uuidFieldValidator() |
required + RFC-4122 8-4-4-4-12 |
phoneFieldValidator() |
required + ^\+?[\d\s\-().]{7,}$ |
usernameFieldValidator() |
required + ^[a-z0-9_]{3,32}$ (case-insensitive) |
passwordFieldValidator() |
required + minLength 8 + upper + lower + digit |
dateFieldValidator() |
instanceof Date && !NaN |
integerFieldValidator() |
Number.isInteger |
ageValidator() |
required + range 0..150 |
currencyValidator() |
required + non-negative number |
percentageValidator() |
required + range 0..100 |
positiveNumberValidator() |
required + > 0 |
Implementation Notes
- Every stage runs against the original input value (not the previous stage's output) —
FieldValidatoris a multi-predicate aggregator, not a transform pipeline. For transforms, useSchemaValidatorwith thetransformsfield. validatealways records every stage that fails — it does not short-circuit. This makes the error list complete in a single pass, which matches the convention used by Zod and Joi.- A pre-aborted signal short-circuits the loop and records the synthetic
"validation aborted"error. Subsequent stages do not run. - The async-stage detector (
isAsync) isO(stages)and runs once onvalidateSync. Callers that want fast-path sync validation should checkisAsync()first. customaccepts a fn returningtrue | false | string.falsefalls back to either the suppliedmessageor the default"custom validation failed"; a string return is treated as the error message verbatim.- Preset chains are factory functions (not module-level constants) so consumers get an independent instance per call and can chain further without affecting other callers.