Motivation
validate-this ships no type definitions today (no types/typings field, no bundled .d.ts), so TypeScript consumers fall back to any — losing field-name checking and rule-chaining safety. A definition is quite achievable, with one well-understood limitation around runtime-registered custom validators.
What types nicely
Generics over the value shape make field references type-safe, and nested validation can recurse into child object / array-element shapes:
type ArrayElement<T> = T extends readonly (infer E)[] ? E : never;
type ValidationErrors<V> = Partial<Record<keyof V & string, string[]>>;
// Chainable rule surface — built-ins here; custom rules merged in (see below).
interface Rules {
required(): this;
// ...other built-ins
}
interface Validator<V> {
validate(...fields: (keyof V & string)[]): Rules; // field names checked vs V
validateChild<K extends keyof V>(
field: K,
build: (v: Validator<NonNullable<V[K]>>) => void,
): void;
validateChildren<K extends keyof V>(
field: K,
build: (v: Validator<ArrayElement<NonNullable<V[K]>>>) => void,
): void;
}
export function validator<V extends object>(
values: V,
build: (v: Validator<V>) => void,
translate?: (message: string, field: string) => string,
): ValidationErrors<V>;
type RuleFn<V = unknown> = (value: unknown, values: V) => string | void;
export function defineValidator(def: {
name: string;
rule: RuleFn | ((...args: any[]) => RuleFn); // plain or higher-order
}): void;
Consumers teach TS about their defineValidator-registered rules via declaration merging:
declare module "validate-this" {
interface Rules {
isValidEmail(): this;
minLength(n: number): this; // higher-order rule
}
}
Limitations (worth documenting)
- Custom validators can't be auto-discovered.
defineValidator({ name: "isValidEmail" }) adds .isValidEmail() at runtime; the type system can't see that. Declaration merging on Rules (above) bridges it, but there's no fully-automatic path given the global-registration design.
- Per-field value narrowing is lost in custom rules. Because
validate(a, b, c) applies one chain to many fields, a rule's value can't be narrowed to a single field's type — unknown is the honest typing.
Question
Ship the definitions in-repo (add a types field + bundled .d.ts), or publish a separate @types/validate-this? Happy to open a PR with a full .d.ts (built-in rules filled in) if there's interest.
Motivation
validate-thisships no type definitions today (notypes/typingsfield, no bundled.d.ts), so TypeScript consumers fall back toany— losing field-name checking and rule-chaining safety. A definition is quite achievable, with one well-understood limitation around runtime-registered custom validators.What types nicely
Generics over the value shape make field references type-safe, and nested validation can recurse into child object / array-element shapes:
Consumers teach TS about their
defineValidator-registered rules via declaration merging:Limitations (worth documenting)
defineValidator({ name: "isValidEmail" })adds.isValidEmail()at runtime; the type system can't see that. Declaration merging onRules(above) bridges it, but there's no fully-automatic path given the global-registration design.validate(a, b, c)applies one chain to many fields, a rule'svaluecan't be narrowed to a single field's type —unknownis the honest typing.Question
Ship the definitions in-repo (add a
typesfield + bundled.d.ts), or publish a separate@types/validate-this? Happy to open a PR with a full.d.ts(built-in rules filled in) if there's interest.