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,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.

View File

@@ -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.