first commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# validation
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`validation.ts` is the iteration layer that runs every rule attached to a
|
||||
field or form and aggregates the resulting `ValidationError` array. It
|
||||
honours `AbortSignal` for cancellation (so callers can race a slow async
|
||||
custom rule against a debounce timer) and emits two OpenTelemetry
|
||||
instruments — `form_builder.validation.run_total` counter and
|
||||
`form_builder.validation.duration_ms` histogram — on every invocation so
|
||||
operations dashboards can watch validation latency and error rates.
|
||||
|
||||
The engine deliberately knows nothing about the rule semantics; it
|
||||
delegates to `runRule` from `../validators/validators.ts`. That keeps the
|
||||
two layers independently testable: the validators cover rule semantics, the
|
||||
engine covers iteration, cancellation, and observability.
|
||||
|
||||
## Features
|
||||
|
||||
- `runFieldValidation(field, value, values, signal?)` — single field.
|
||||
- `runFormValidation(fields, values, signal?)` — every field.
|
||||
- `AbortSignal` short-circuits both functions and returns `[]`.
|
||||
- Async-safe: awaits any promise returned by `runRule`.
|
||||
- OTel counter with `{ scope, result }` attributes and histogram with
|
||||
`{ scope }`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { runFieldValidation } from "@suaveplan/form-builder";
|
||||
|
||||
const errors = await runFieldValidation(
|
||||
{ id: "name", name: "name", label: "Name", type: "text", validations: [{ type: "required" }] },
|
||||
"",
|
||||
{ name: "" }
|
||||
);
|
||||
// errors[0]?.rule === "required"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { runFormValidation } from "@suaveplan/form-builder";
|
||||
|
||||
const controller = new AbortController();
|
||||
const promise = runFormValidation(fields, values, controller.signal);
|
||||
controller.abort();
|
||||
const errors = await promise; // []
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `runFieldValidation(field, value, values, signal?, clock?): Promise<readonly ValidationError[]>`.
|
||||
- `runFormValidation(fields, values, signal?, clock?): Promise<readonly ValidationError[]>`.
|
||||
- `Clock = () => number` — injectable for deterministic histograms.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Both functions check the signal before every rule (not just at the start)
|
||||
so a long-running form aborts promptly. Histogram durations clamp at zero
|
||||
to defend against monotonic-clock skew under fake clocks. Counters use the
|
||||
`{ scope: "field"|"form", result: "ok"|"errors" }` attribute pair so
|
||||
dashboards can split error rate by scope. We intentionally do not throw
|
||||
on individual rule failures — validation failures are data, not
|
||||
exceptions.
|
||||
@@ -0,0 +1,75 @@
|
||||
# validators
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`validators.ts` provides the eleven built-in rule runners used by the
|
||||
validation engine. Each runner takes the candidate value, the surrounding
|
||||
`FormValues` (so `equalsField` can cross-reference), and the originating
|
||||
field id; it returns either `null` (passed) or a `ValidationError` shaped
|
||||
`{ fieldId, rule, message }`. `custom` rules may return a
|
||||
`Promise<boolean>`, in which case `runRule` also returns a promise — the
|
||||
upstream engine awaits the result transparently.
|
||||
|
||||
This file is the only place where rule semantics live. Adding a new
|
||||
built-in rule means adding a case here, extending `BUILT_IN_RULES`, and
|
||||
updating the Zod schema; everything else (the engine, the Zod generator,
|
||||
the builder) consumes the rule kind opaquely.
|
||||
|
||||
## Features
|
||||
|
||||
- Eleven built-in rules: `required`, `minLength`, `maxLength`, `min`,
|
||||
`max`, `pattern`, `email`, `url`, `oneOf`, `equalsField`, `custom`.
|
||||
- `required` treats `null`, `undefined`, empty string, and empty array as
|
||||
absent.
|
||||
- `pattern` accepts a string source; constructed into `RegExp` per call.
|
||||
- `url` validates via the standard `URL` constructor.
|
||||
- `custom` supports synchronous boolean predicates and async predicates.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { runRule } from "@suaveplan/form-builder";
|
||||
|
||||
const error = runRule(
|
||||
{ type: "required" },
|
||||
"",
|
||||
{},
|
||||
"name"
|
||||
);
|
||||
// error.rule === "required"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { runRule } from "@suaveplan/form-builder";
|
||||
|
||||
const asyncCustom = runRule(
|
||||
{
|
||||
type: "custom",
|
||||
value: async (v: unknown) => v !== "blocked",
|
||||
},
|
||||
"blocked",
|
||||
{},
|
||||
"username"
|
||||
);
|
||||
// asyncCustom is a Promise<ValidationError | null>
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `runRule(rule, value, values, fieldId): ValidationError | null |
|
||||
Promise<ValidationError | null>`.
|
||||
- `CustomRulePredicate = (value, values) => boolean | Promise<boolean>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Unknown rule kinds return `null` — the engine treats unknown rules as
|
||||
no-ops so a registry mismatch never crashes a hot loop. `equalsField`
|
||||
compares with `!==` (strict equality), which is the right behaviour for
|
||||
the canonical "password confirmation" use case. `url` constructs a `URL`
|
||||
inside a try/catch; this is cheap and avoids regex traps for IPv6 hosts.
|
||||
Reference in New Issue
Block a user