Home

Banner

Decorator Validation

A TypeScript decorator-driven validation and model framework. It lets you:

  • Define validation rules with declarative decorators on model properties (e.g., @required, @min, @pattern).
  • Build, validate, serialize, and hash models with pluggable registries and algorithms.
  • Extend validation via a registry of Validator classes and utilities.
  • Optionally expose validation as MCP tools for automation workflows.

Licence GitHub language count GitHub top language

Build & Test CodeQLSnyk Analysis Pages builder .github/workflows/release-on-tag.yaml

Open Issues Closed Issues Pull Requests Maintained

Forks Stars Watchers

Node Version NPM Version

Documentation available here

Decorator Validation – Detailed Description

Decorator Validation is a TypeScript library that centers on two complementary pillars:

  1. Declarative validation via decorators
  • A rich set of property decorators such as @required, @min, @max, @minLength, @maxLength, @pattern, @email, @url, @type, @equals, @greaterThan, @greaterThanOrEqual, @lessThan, @lessThanOrEqual, @step, @list, @diff, @date, @password, as well as an async() flag helper.
  • Each decorator writes strongly-typed metadata using reflect-metadata and a common Validation metadata keying convention, so validators can later interpret the rules consistently.
  • Decorators are defined in src/validation/decorators.ts and are backed by concrete Validator classes in src/validation/Validators/* that implement the actual validation logic.
  1. A model system tailored for validation, building, hashing, and serialization
  • Models are ordinary classes marked with @model() (src/model/decorators.ts). The decorator replaces the constructor, binds the model prototype utilities, runs a global builder (if registered), and tags reflection metadata for identification.
  • Additional class-level decorators configure algorithms:
    • @hashedBy(algorithm, ...args) to define model hashing implementation.
    • @serializedBy(serializer, ...args) to define serialization strategy.
    • @description(text) to attach human-readable documentation to a class, property, or method.
  • The Model class (src/model/Model.ts) provides:
    • A ModelRegistryManager for registering and retrieving model constructors by name, enabling rebuild/deserialize flows.
    • Validation integration (model/validation.ts) that runs the registered validators against metadata collected by decorators.
    • Utility methods and metadata helpers to identify models, fetch metadata, compare instances, and orchestrate hashing/serialization strategies.

Core runtime architecture

  • Validation namespace (src/validation/Validation.ts):
    • Manages a pluggable IValidatorRegistry so custom Validator implementations can be registered, migrated, and queried.
    • Exposes helper utilities: Validation.key(key) for reflect-metadata keying, Validation.keys() to list available validator keys, register(...) and get(...) to manage validators, decorator registration to link a metadata key to its decorator for dynamic use.
  • Validator classes (src/validation/Validators/*):
    • BaseValidator.ts defines common behaviors; concrete validators (RequiredValidator, MinValidator, PatternValidator, etc.) implement validate and message/typing logic.
    • ValidatorRegistry.ts stores Validator instances keyed by ValidationKeys constants.
    • constants.ts defines DEFAULT_ERROR_MESSAGES, DEFAULT_PATTERNS, and ValidationKeys (e.g., REQUIRED, MIN, MAX...).
    • decorators.ts contains decorator sugar for directly registering standard validators and building metadata using Decoration/Reflection utilities.
  • Utilities (src/utils/*):
    • strings, dates, types, serialization, hashing, registry, decorators, Decoration helper, and a PathProxy to traverse nested properties and apply metadata.

Intent of the library

  • Provide a cohesive, decorator-first developer experience for enforcing validation constraints on model classes.
  • Ensure that validation, model lifecycle (build/serialize/hash), and metadata are consistent and extensible through registries.
  • Allow advanced composition (custom validators, alternative registries), and integration into automation flows (MCP tools).

Design principles

  • Declarative over imperative: Constraints live next to the properties they validate.
  • Extensibility: Registries and helper factories allow swapping implementations without changing consumer code.
  • Type safety: Metadata and decorators are typed; validators advertise supported types; utility functions use narrow types where practical.
  • Separation of concerns: Decorators express intent; Validator classes implement behavior; Model provides lifecycle utilities.

How to Use Decorator Validation

This guide shows concrete, TypeScript-accurate examples for the main public APIs exported by the library. Examples are inspired by patterns used across the repo tests and typical usage of decorator-driven validation and models.

Notes

  • Import paths assume a consumer importing from the package entry and submodules as re-exported by src/index.ts.
  • All snippets are valid TypeScript.

Model decorators and model lifecycle

@model()

Description: Marks a class as a model. The constructor is wrapped to bind model utilities and run a global builder (if any).

import { model, Model, ModelArg } from "@decaf-ts/decorator-validation";

@model()
class User extends Model {
  @prop()
  id!: string;
  @prop()
  name!: string;
  
  constructor(arg?: ModelArg<User>) {
    super(arg);
  }
}

const u = new User();

Extending from the Model class is optional but highly recommended. Regardless, if decorated with @model(), the constructor signature must be compatible.

When using a class for validation, eg hasErrors() only, @model() is not required.

Model construction

When a class is decorated with @model(), the framework invokes a model building function right after the instance is constructed. This builder initializes your model from the argument you pass to the constructor.

There are two builder strategies:

  • Model.fromObject: Accepts a plain object and performs a more permissive, best-effort mapping.
    • Does not enforce nested Model classes
  • Model.fromModel (default): does the same as fromObject, but also instantiates nested Model classes

Your model constructors should accept an optional argument and pass it to super so the builder can use it.

You can change between builder functions by using:

import { required, Model, model, ModelArg } from "@decaf-ts/decorator-validation";

@model()
class Child extends Model {
  @required()
  name!: string;
  
  constructor(arg?: ModelArg<Child>) {
    super(arg);
  }
}

@model()
class Parent extends Model {
  @required()
  name!: string;
  @required()
  child!: Child;
  constructor(arg?: ModelArg<Parent>) {
    super(arg);
  }
}
// Default builder is Model.fromModel

let parent = new Parent({
  child: {
    name: "child"
  }
})

parent.child instanceof Child; // true

Model.setBuilder(Model.fromObject);

parent = new Parent({
  child: {
    name: "child"
  }
})

parent.child instanceof Child; // false

@hashedBy(algorithm, ...args)

Description: Declares which hashing strategy to use when hashing model instances.

import { model, hashedBy, ModelArg, prop } from "@decaf-ts/decorator-validation";

@model()
@hashedBy("sha256", "utf8")
class FileInfo extends Model {
  @prop()
  path!: string;
  @prop()
  size!: number;
  
  constructor(arg?: ModelArg<FileInfo>) {
    super(arg)
  }
}

@serializedBy(serializer, ...args)

Description: Declares which serializer to use for (de)serializing model instances.

import { prop, ModelArg, model, serializedBy } from "@decaf-ts/decorator-validation";

@model()
@serializedBy("json")
class Settings extends Model {
  @prop()
  theme!: string;
  @prop()
  locale!: string;
  
  constructor(arg?: ModelArg<Settings>){
    super(arg)
  }
}

@description(text)

Description: Applies textual documentation metadata to a class, property, or method.

import { model, description, Model } from "@decaf-ts/decorator-validation";

@model()
@description("Represents an application user")
class User extends Model {
  @description("Unique identifier")
  id!: string;
  // ...
}

Validation decorators (property-level)

Each decorator writes metadata for a corresponding Validator. Use them on model fields.

@prop()

Description: Registers a property with the model system. This is required for model construction so the property participates in metadata, serialization, hashing, validation discovery, etc.

Important

  • All other property decorators (e.g., @required, @min, @email, etc.) already apply @prop under the hood.
  • Therefore, you only need to use @prop when a property has no other decorators.

@required(message?)

Description: Field must be present and non-empty.

import { Model, required, model } from "@decaf-ts/decorator-validation";

@model()
class User extends Model {
  @required()
  username!: string;
  //...
}

@min(value, message?) and @max(value, message?)

Description: Numeric or date boundaries.

import { model } from "@decaf-ts/decorator-validation";
import { min } from "@decaf-ts/decorator-validation";
import { max } from "@decaf-ts/decorator-validation";

@model()
class Product extends Model {
  @min(0)
  @max(1000)
  price!: number;
}

@minLength(n, message?) and @maxLength(n, message?)

Description: String length boundaries.

import { model, Model } from "@decaf-ts/decorator-validation";
import { minLength, maxLength } from "@decaf-ts/decorator-validation";

@model()
class Credentials extends Model {
  @minLength(8)
  @maxLength(64)
  password!: string;
}

@pattern(regex | string, message?)

Description: String must match a pattern.

import { model } from "@decaf-ts/decorator-validation";
import { pattern, model } from "@decaf-ts/decorator-validation";

@model()
class Vehicle extends Model {
  @pattern(/^[A-Z]{2}-\d{2}-[A-Z]{2}$/)
  plate!: string;
}

@email(message?)

Description: Must be a valid email.

import { model } from "@decaf-ts/decorator-validation";
import { email } from "@decaf-ts/decorator-validation";

@model()
class Contact extends Model {
  @email()
  email!: string;
}

@url(message?)

Description: Must be a valid URL.

import { model } from "@decaf-ts/decorator-validation";
import { url } from "@decaf-ts/decorator-validation";

@model()
class Link extends Model {
  @url()
  href!: string;
}

@type(T, message?)

Description: Enforces a runtime type match (e.g., Number, String, Date).

import { model } from "@decaf-ts/decorator-validation";
import { type } from "@decaf-ts/decorator-validation";

@model()
class Measurement extends Model {
  @type(Number)
  value!: number;
}

@equals(otherValueOrPath, message?)

Description: Value must equal the provided value or another property.

import { model } from "@decaf-ts/decorator-validation";
import { equals } from "@decaf-ts/decorator-validation";

@model()
class Confirmation extends Model {
  password!: string;
  @equals(":password")
  confirm!: string;
}

@greaterThan(x) / @greaterThanOrEqual(x) / @lessThan(x) / @lessThanOrEqual(x)

Description: Numeric or date comparisons.

import { model } from "@decaf-ts/decorator-validation";
import { greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual } from "@decaf-ts/decorator-validation";

@model()
class Range extends Model {
  @greaterThan(0)
  @lessThanOrEqual(100)
  ratio!: number;
}

@step(step, message?)

Description: Numeric step constraints.

import { model, Model } from "@decaf-ts/decorator-validation";
import { step } from "@decaf-ts/decorator-validation";

@model()
class Slider extends Model {
  @step(0.5)
  value!: number;
}

@list(values, message?)

Description: Constrains value to be one of the provided list.

import { model, Model } from "@decaf-ts/decorator-validation";
import { list } from "@decaf-ts/decorator-validation";

@model()
class ThemeSettingm extends Model {
  @list(["light", "dark", "system"]) 
  theme!: "light" | "dark" | "system";
}

@diff(propertyPath, message?)

Description: Must be different from another property.

import { model, Model } from "@decaf-ts/decorator-validation";
import { diff } from "@decaf-ts/decorator-validation";

@model()
class Credentials extends Model {
  username!: string;
  @diff(":username")
  password!: string;
}

@date({ min?, max? }, message?)

Description: Date constraints for a date-typed field.

import { model, Model } from "@decaf-ts/decorator-validation";
import { date } from "@decaf-ts/decorator-validation";

@model()
class Booking extends Model {
  @date({ min: new Date("2025-01-01"), max: new Date("2025-12-31") })
  start!: Date;
}

@password(options?, message?)

Description: Password strength constraints (e.g., min length, uppercase, digits, symbols) depending on validator configuration.

import { model } from "@decaf-ts/decorator-validation";
import { password, Model } from "@decaf-ts/decorator-validation";

@model()
class Account extends Model{
  @password({ minLength: 10 })
  password!: string;
}

async()

Description: Marks a model as involving async validation rules (decorator flag helper).

import { model } from "@decaf-ts/decorator-validation";
import { async } from "@decaf-ts/decorator-validation";

@model()
@async()
class Signup {
  // fields that may use async validators
}

Running validation

Use the model validation utilities to evaluate rules defined by decorators.

import { model, required, email, validate, Model } from "@decaf-ts/decorator-validation";

@model()
class Contact extends Model {
  @required()
  @email()
  email!: string;
  
  constructor(arg?: ModelArg<Contact>) {
    super(arg);
  }
}

const c = new Contact({
  email: "not-an-email"
});

let errs = c.hasErrors(); // resolves to a list of errors or undefined

@async()
@model()
class User extends Model {
  @required()
  @email()
  email!: string;
  
  constructor(arg?: ModelArg<User>) {
    super(arg);
  }
}

const u = new User({
  email: "not-an-email"
})

errs = await u.hasErrors(); // resolves to a list of errors or undefined

Validation registry APIs (Validation)

Validation.setRegistry(registry, migration?)

Description: Swap the active validator registry and optionally migrate validators.

import { Validation, ValidatorRegistry } from "@decaf-ts/decorator-validation";

const custom = new ValidatorRegistry();
Validation.setRegistry(custom, v => v); // trivial migration

Validation.register(...validators)

Description: Register one or more Validator implementations or definitions.

import { Validation, Validator, validator } from "@decaf-ts/decorator-validation";

@validator("ALWAYS_OK")
class AlwaysOk extends Validator {
  hasErrors(...args: any[]) { return []; }
}

Validation.register(new AlwaysOk());

Validation.get(key)

Description: Retrieve a registered validator by key.

import { Validation, ValidationKeys } from "@decaf-ts/decorator-validation";

const requiredValidator = Validation.get(ValidationKeys.REQUIRED);

Validation.key(k) and Validation.keys()

Description: Build a reflect-metadata key or list all registered keys.

import { Validation } from "@decaf-ts/decorator-validation";

const metaKey = Validation.key("REQUIRED");
const allKeys = Validation.keys();

Notes on tests and validity

  • Patterns here reflect common test patterns found across the monorepo (e.g., model decoration, decorator application, registry customization).
  • Each snippet is valid TypeScript and aligns with the re-exports provided by the package entry.

Related

decaf-ts Reflection

Social

LinkedIn

Languages

TypeScript JavaScript NodeJS ShellScript

Getting help

If you have bug reports, questions or suggestions please create a new issue.

Contributing

I am grateful for any contributions made to this project. Please read this to get started.

Supporting

The first and easiest way you can support it is by Contributing. Even just finding a typo in the documentation is important.

Financial support is always welcome and helps keep both me and the project alive and healthy.

So if you can, if this project in any way. either by learning something or simply by helping you save precious time, please consider donating.

License

This project is released under the MIT License.

By developers, for developers...