Skip to main content
The @ooneex/validation component is a type-safe validation layer. You describe the shape of your data once with Assert({...}), and the same schema both narrows your TypeScript types and validates values at runtime. The package provides Assert, scope, union, and intersection, adds a set of ready-made constraints (email, URL, port, country code, and more), and exposes an abstract Validation class for building your own reusable validators. When a schema is attached to a route, the framework runs it for you before the controller is reached.

Why this component

  • One schema, two jobs. An Assert({...}) definition is both a runtime validator and a static TypeScript type — no duplicate interfaces to keep in sync.
  • Concise schema syntax. Assert, scope, union, and intersection give you a full string-based syntax (string.email, number >= 0, 'a' | 'b', optional keys with ?).
  • Automatic route validation. Schemas passed to @Route.http as params, queries, or payload are validated before the controller runs; invalid requests produce an error response automatically.
  • Built-in constraints. Pre-built validators cover common shapes — email, URL, port, hostname, locale, currency, country code, hex color, names, IDs — so you do not re-write the same rules.
  • Custom validators. Extend the abstract Validation class with getConstraint and getErrorMessage to package any rule (plus a friendly message) as a reusable class.

How it works

A schema is a function. Calling it with data returns either the validated value or an Assert.errors object. The Validation class wraps that flow into a { isValid, message } result, and the routing layer applies your schemas to incoming requests automatically.
StageWhat happens
DefineAssert({...}) builds a schema that is both a runtime validator and a static type.
ValidateCalling the schema returns the parsed value, or an instance of Assert.errors with a .summary describing the failure.
Route inputSchemas on @Route.http (params, queries, payload) run before the controller; a failure yields an error response and the controller is skipped.
Custom ruleA Validation subclass turns a schema and an optional message into a validate(data) method returning { isValid, message? }.

Defining schemas

Use Assert to describe an object. Each value is a schema expression — a primitive, a constrained primitive, a numeric range, a literal union, or an array. Mark a key optional by suffixing it with ?.
import { Assert } from "@ooneex/validation";

const UserSchema = Assert({
  name: "string > 0",          // non-empty string
  email: "string.email",       // valid email
  age: "number.integer >= 0",  // non-negative integer
  "phone?": "string",          // optional
  role: "'admin' | 'user'",    // literal union
  tags: "string[]",            // array of strings
});

const result = UserSchema({ name: "John", email: "john@example.com", age: 30, role: "user", tags: [] });

if (result instanceof Assert.errors) {
  console.error(result.summary); // human-readable failure description
} else {
  console.log(result); // fully typed, validated value
}
For related types, group them in a scope and export() the result; combine schemas with union and intersection.
import { scope } from "@ooneex/validation";

const $ = scope({
  profile: {
    firstName: "string > 0",
    lastName: "string > 0",
    bio: "string | null",
  },
  user: {
    id: "string.uuid",
    email: "string.email",
    profile: "profile",
  },
}).export();

const user = $.user({
  id: crypto.randomUUID(),
  email: "a@b.com",
  profile: { firstName: "Jane", lastName: "Doe", bio: null },
});

Validation in routing

The most common place you use validation is on a route. Pass schemas to @Route.http and the framework validates the request before your controller’s index runs — so inside the controller the data is already guaranteed to match.
import { Route } from "@ooneex/routing";
import { Assert } from "@ooneex/validation";
import type { ContextType, IController } from "@ooneex/controller";

const CreateUserPayload = Assert({
  name: "string > 0",
  email: "string.email",
  password: "string >= 8",
});

const UserIdParam = Assert({
  id: "string.uuid",
});

@Route.http({
  name: "api.users.create",
  path: "/api/users",
  method: "POST",
  payload: CreateUserPayload,
})
export class UserCreateController implements IController {
  public async index(context: ContextType) {
    // Already validated — invalid requests never reach here
    const { name, email } = context.payload;
    return context.response.json({ user: { name, email } });
  }
}
  • params validates path parameters (e.g. :id in the path).
  • queries validates the query string.
  • payload validates the request body.
If any of them fail, the request is rejected with an error response and the controller is never invoked. See Routing for the full route options, Request for how input reaches the context, and Response for the shape of the returned errors.

Built-in constraints

Import these from @ooneex/validation/constraints. Each is a Validation subclass — construct it and call validate(data) to get a { isValid, message? } result.
import { AssertEmail, AssertPort } from "@ooneex/validation/constraints";

const result = new AssertEmail().validate("user@example.com");
console.log(result.isValid); // true
ConstraintValidates
AssertEmailA valid email address.
AssertUrlA URL (1–2083 chars, http/https, no .. or trailing dots); also offers validateStrict requiring a protocol.
AssertYoutubeUrlA YouTube watch URL (youtube.com/watch?v=… or youtu.be/…).
AssertHostnameA hostname or IPv4 address (also accepts an empty string).
AssertPortAn integer port between 1 and 65535.
AssertHexaColorA 3- or 6-digit hex color (e.g. #fff, #A1B2C3).
AssertCountryCodeA 2-letter uppercase ISO 3166-1 alpha-2 code.
AssertCurrencyA valid ISO 4217 currency code (3 uppercase letters).
AssertLocaleA supported locale code from @ooneex/translation.
AssertAppEnvA valid application environment value.
AssertNameA name (1–50 chars, letters/numbers/spaces/-'., trimmed).
AssertFirstNameA first name (1–50 chars, letters, spaces, hyphens, apostrophes).
AssertLastNameA last name (1–50 chars, letters, spaces, hyphens, apostrophes).
AssertIdA 25-character lowercase hexadecimal ID.
AssertDescriptionFree text between 1 and 5000 characters.
AssertChatQueryChat text (1–2000 chars) with no HTML, scripts, or unsafe protocols.

Custom validators

To package a rule of your own, extend the abstract Validation class. It requires two methods and gives you validate for free.
public abstract getConstraint(): AssertType;       // the schema to check against
public abstract getErrorMessage(): string | null;  // custom message, or null for the default summary
validate(data, constraint?) calls the schema; on failure it returns your getErrorMessage() (falling back to the schema’s summary), otherwise { isValid: true }.
import { Validation, Assert } from "@ooneex/validation";
import type { AssertType } from "@ooneex/validation";

class PositiveNumberValidator extends Validation {
  public getConstraint(): AssertType {
    return Assert("number > 0");
  }

  public getErrorMessage(): string | null {
    return "Value must be a positive number";
  }
}

const result = new PositiveNumberValidator().validate(42);
console.log(result.isValid); // true
This is the same pattern the built-in constraints use, and the same one you reach for when validating Entity fields with a shared, named rule.

Inline assertions and utilities

Assert builds a schema you can call immediately to check a value — useful for one-off assertions without defining a named schema.
import { Assert } from "@ooneex/validation";

const out = Assert("string.email")("john@example.com");
const isInvalid = out instanceof Assert.errors;
jsonSchemaToTypeString(schema) converts a JSON Schema object into a readable TypeScript-style type string — handling primitives, arrays, objects with required/optional keys, and anyOf/oneOf/allOf unions and intersections. Pair it with a schema’s toJsonSchema() for documentation or AI-output descriptions.
import { Assert, jsonSchemaToTypeString } from "@ooneex/validation";

const ProductSchema = Assert({ name: "string", price: "number" });
console.log(jsonSchemaToTypeString(ProductSchema.toJsonSchema())); // "{ name: string; price: number }"

Types

  • AssertType — alias for the schema Type; the return type of getConstraint and the type of any schema.
  • IAssert — the interface implemented by every validator: getConstraint, getErrorMessage, and validate.
  • ValidationResultType — the result of validate: { isValid: boolean; message?: string }.

Best practices

  • Validate at the edge. Attach schemas to @Route.http (params, queries, payload) so bad input is rejected before any business logic runs.
  • Define schemas once. Export a schema and reuse it for routing, custom validators, and JSON Schema generation — never re-describe the same shape.
  • Prefer built-in constraints. Use AssertEmail, AssertPort, and friends instead of re-writing common rules; extend Validation only for genuinely new ones.
  • Give helpful messages. Return a clear getErrorMessage() so failures explain what was expected rather than leaning on the raw summary.
  • Mark optionals explicitly. Use the ? key suffix for optional fields so the static type and runtime check agree.
See Routing, Request, Response, and Entity for the surfaces that consume these schemas.