first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
# FieldValidator — `@suaveplan/rules/validators/field`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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 `.withX` returns a new `FieldValidator`.
- Sync + async stages; `validateSync` throws `RULES.VALIDATOR_INVALID` when 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
```ts
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
```ts
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 new `FieldValidator`.
- `validate(value, options?)` → `Promise<FieldValidatorResult>` where `FieldValidatorResult = { valid: boolean; errors: readonly string[] }`. Honours `options.signal`.
- `validateSync(value)` → `FieldValidatorResult`. Throws `RulesError(RULES.VALIDATOR_INVALID)` when the chain has any async stage.
- `isAsync()` → `true` when 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) — `FieldValidator` is a multi-predicate aggregator, not a transform pipeline. For transforms, use `SchemaValidator` with the `transforms` field.
- `validate` always 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`) is `O(stages)` and runs once on `validateSync`. Callers that want fast-path sync validation should check `isAsync()` first.
- `custom` accepts a fn returning `true | false | string`. `false` falls back to either the supplied `message` or 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.