/** * Compiles a column's validation declaration into an ordered list of rules, and * runs it. * * Editors collect values; this engine decides whether a value may be committed. * Keeping that decision here — rather than in each editor — is what makes a * React editor, an Angular editor and a built-in editor behave identically on * the same column. * * @packageDocumentation */ import type { ColumnDef } from '../../types/column.types'; import type { RowNode } from '../../types/row.types'; import type { RowValidatorFn, ValidationContext, ValidationResult, ValidatorFactory, ValidatorFn } from '../types/validation.types'; import { ValidatorRegistry } from './validator-registry'; /** * A column's rules, split by how they must be executed. * * The split is the whole reason an ordinary edit stays synchronous: with the * asynchronous rules held separately, {@link ValidationEngine.validate} can see * that `async` is empty and return a result directly instead of a promise. A * single flat list would force every caller to `await`, which means every cell * commit would cost at least a microtask and the grid could no longer close an * editor in the same frame the user pressed Enter. */ export interface CompiledValidation { /** Rules that normally answer immediately, in guaranteed execution order. */ readonly sync: readonly ValidatorFn[]; /** Rules that always answer with a promise, run only after `sync` all passed. */ readonly async: readonly ValidatorFn[]; } /** * Compiles and runs column validation. * * ### Compilation is memoised per column * Every rule a column declares is turned into a closure exactly once, keyed on * the `ColumnDef` **object** in a `WeakMap`. Editing 100,000 rows of a column * compiles its rules once, not 100,000 times, and the cache is released with the * column definition itself — no disposal call, no leak. Anything that mutates a * column's validation in place must call {@link invalidate}. * * ### Synchronous stays synchronous * {@link validate} returns a `ValidationResult` directly whenever every rule * answered directly. See {@link CompiledValidation} for why that matters. * * @example * ```ts * const engine = new ValidationEngine(); * engine.registerValidator('iban', (config) => * config === true ? ({ value, label }) => * isIban(value) ? { valid: true } : { valid: false, message: `${label} is not an IBAN` } * : null, * ); * const result = engine.validate(context); * ``` */ export declare class ValidationEngine { private readonly registry; /** * Compiled rule sets, keyed on the column definition object. * * A `WeakMap` and not a `Map`: columns are created and discarded with the grid * (and with every `setColumnDefs`), and a strong cache would pin every column * definition — and every closure over it — for the lifetime of the engine. */ private cache; /** * @param registry - Rule factories to compile through. Defaults to a fresh * registry holding every built-in rule. */ constructor(registry?: ValidatorRegistry); /** * The registry this engine compiles through, for inspection or for sharing * with a second engine. * * Mutating it directly bypasses cache invalidation; prefer * {@link registerValidator}. */ getRegistry(): ValidatorRegistry; /** * Adds a rule and drops every compiled rule set. * * The invalidation is the point. Columns that already declared `iban` compiled * to "no such rule, ignore it"; without clearing the cache, registering the * rule afterwards would silently do nothing for exactly the columns that * wanted it most. * * @param name - Key columns declare the rule under in `validation`. * @param factory - Builds the rule from the column's configuration. */ registerValidator(name: string, factory: ValidatorFactory): void; /** * The rules `colDef` validates with, compiled once and cached. * * ### Sources, weakest first * 1. **Type-implied** — `type: 'email'` contributes `email: true`. See * `impliedValidationFor`. * 2. **Legacy column fields** — `required`, `min`, `max`, `validatorFn`. * 3. **`colDef.validation`** — the declarative form, which wins every key. * * Later sources overwrite earlier ones key by key, so an author can always * override what their column type implied, and the modern `validation` block * always beats the legacy field it replaces. * * @param colDef - The column to compile. * @returns The cached compiled rule set. The same object is returned for the * same column until {@link invalidate} is called, so callers may compare it * by identity. */ compile(colDef: ColumnDef): CompiledValidation; /** * Drops cached rule sets so the next {@link compile} rebuilds them. * * Call after mutating a column's `validation` in place, or after changing the * registry behind the engine's back. * * @param colDef - The single column to forget. Omit to forget all of them. */ invalidate(colDef?: ColumnDef): void; /** * Validates one value against its column's rules. * * Stops at the **first** failure: the user is shown the most specific thing * wrong with their input, and the remaining rules — which may include a * network round trip — are never run. * * ### The synchronous fast path * When every rule answers directly (the case for all declarative rules), this * returns a `ValidationResult`, not a promise. An ordinary keystroke-to-commit * must not wait a microtask: the grid closes the editor, writes the cell and * moves focus in the same task, and forcing that through `await` would put a * visible frame between Enter and the committed cell. * * A synchronous rule is nevertheless *allowed* to return a promise — the type * permits it, and a custom rule may hit a cache that is sometimes warm. The * moment one does, execution continues on the asynchronous path from that * rule onward, preserving order. * * @param context - The value under test and everything about its cell. * @returns The first failure, or {@link VALID}. Synchronously where possible. */ validate(context: ValidationContext): ValidationResult | Promise; /** * Runs a whole-row rule, for cross-field constraints no single column can * express ("end date must be after start date"). * * Thin by design: row rules are author-supplied and the engine has nothing to * add to them. It exists as a seam so every caller — row-mode commit and * `GridApi.validateRow` alike — goes through one place, which is where * instrumentation or race-guarding will land when it is needed. A missing or * malformed validator answers {@link VALID} rather than throwing, because a * misconfigured row rule must not make a grid uneditable. * * @param data - The row's data with pending edits already applied. * @param node - The row node, for identity and position. * @param validator - The rule to run. * @returns One result, or a `field -> result` map attributing failures to * individual cells. Synchronous when the validator is. */ validateRow(data: Readonly>, node: RowNode | null, validator: RowValidatorFn): ValidationResult | Readonly> | Promise>>; /** * Builds a column's rule list. Called once per column; see {@link compile}. * * Rules are appended in four phases — the declarative core in * {@link RULE_ORDER}, then any registered rules the column declared that the * core does not name, then `validate`, then `validateAsync` — which is what * makes the reported failure predictable for a given column. */ private build; /** Resolves one rule name through the registry, or `null` when unknown/disabled. */ private instantiate; /** * Finishes a validation that turned asynchronous partway through the * synchronous list. * * Order is preserved exactly as if every rule had been awaited from the start, * so a column's reported failure never depends on whether a custom rule * happened to hit a warm cache. */ private resume; /** Runs rules in sequence — never in parallel — stopping at the first failure. */ private runAll; } //# sourceMappingURL=validation-engine.d.ts.map