Skip to content

Add TypeScript type definitions #166

Description

@saxymofo

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions