This package provides a fluent validation API built around a small set of composable check classes.
Many flows are asynchronous. Any API that evaluates promised checks or loads binary data should be awaited before you build the final result.
If you need stable codes or translated result text, see coded-results.md. The main API works fine without that layer.
validateClass(...)Use validateClass(input, ClassType, options?) when a class definition should drive validation directly.
By default, it uses hybrid behavior:
Type-only property declarations such as name!: string or age?: number do not create runtime instance properties in JavaScript, so inference cannot see them unless decorators registered metadata for them. A default value such as name = '' or active = false does create a runtime property, which is why inference currently follows initialized fields.
Example:
import { required, string, type, validateClass } from '@samatawy/checks';
class PersonDto {
@required()
@type.string()
@string.minLength(2)
name!: string;
active = false;
}
const check = await validateClass({
name: 'Ada',
active: true,
}, PersonDto, {
noExtraFields: true,
});
const result = check.result({ language: 'en' });
Notes:
{ skip: 'inference' } for decorator-only behavior{ skip: 'decorators' } for inference-only behaviormatchesType(...) calls also accept the same options shapeObjectCheckUse ObjectCheck when the input itself is an object or when a field should contain an object.
Common methods:
ObjectCheck.for(data) starts a root object validationnotEmpty(options?) rejects empty objectsobject(options?) asserts the value is an object and not an arrayrequired(name, options?) starts a required field validationoptional(name) starts an optional field validationconditional(name, condition, options?) requires a field only when the predicate returns truenoExtraFields(options?) flags undeclared keys in the final object resultcheck(fn) applies nested rules and aggregates results asynchronouslyallOf(fn) applies a nested group of object rules where all returned checks must passanyOf(branches) applies alternative object branches where at least one branch must passoneOf(branches) applies alternative object branches where exactly one branch must passnot(fn, options?) applies a negated object branch that must failisTrue(fn, options?) applies a custom object-level predicate and supports async predicatesresult(options?) returns the current result or a formatted final result depending on the options you passExample:
import { ObjectCheck } from '@samatawy/checks';
const check = await ObjectCheck.for(input)
.notEmpty()
.check(person => [
person.required('name').string().minLength(2).maxLength(100),
person.optional('age').number().atLeast(0).atMost(150),
person.required('address').object().check(address => [
address.required('street').string(),
address.required('city').string()
])
]);
const result = check.result({ language: 'en' });
Composition example:
import { ObjectCheck } from '@samatawy/checks';
const input = {
profile: {
name: ' Ada ',
age: '37'
}
};
const check = await ObjectCheck.for(input)
.check(root => [
root.required('profile').object().anyOf([
profile => [
profile.required('name').string().trim().minLength(3)
],
profile => [
profile.required('age').number({ tolerant: true }).greaterThan(17)
]
])
]);
const result = check.result({ language: 'en' });
Notes:
allOf(fn) keeps the same callback shape as check(fn)anyOf(...) and oneOf(...) accept an array of branch functions, not a single callbackanyOf(...) and oneOf(...) branches are replayed on the real object, so normal mutations such as trim() or tolerant parsing affect the original input consistentlynot(...) evaluates its branch on isolated data and never replays mutations onto the original inputFieldCheckFieldCheck bridges from an object field to a specific value type.
Common methods:
required(options?)allOf(fn) applies a group of field rules where all returned checks must passanyOf(branches) applies alternative field branches where at least one branch must passoneOf(branches) applies alternative field branches where exactly one branch must passnot(fn, options?) applies a negated field branch that must failequals(value, options?) compares the field value using strict equality by default and supports lax matching with tolerant: trueobject()array()file()image()string()email()url()number()date()uuid()ulid()boolean()Notes:
file() returns Promise<FileCheck>image() returns Promise<ImageCheck>email() returns EmailCheckurl() returns UrlCheckuuid() returns UUIDCheckuuid(options?) accepts the same UUID version filter supported by UUIDCheck.version(...)ulid() returns UUIDCheck in ULID modeField composition example:
import { ObjectCheck } from '@samatawy/checks';
const input = {
value: '37'
};
const check = await ObjectCheck.for(input)
.check(root => [
root.required('value').anyOf([
field => [field.number({ tolerant: true }).greaterThan(10)],
field => [field.string().minLength(5)]
])
]);
const result = check.result({ language: 'en' });
ArrayCheckUse ArrayCheck for array-specific validation.
Common methods:
ArrayCheck.for(data) starts a root array validationarray(options?) ensures the value is an arraynotEmpty(options?) rejects empty arraysminLength(length, options?)maxLength(length, options?)noDuplicates(key?, options?) rejects duplicate primitive values, duplicate repeated object references, or duplicate object values by a selected object keymatchesType(ClassType, options?) validates each array element against one decorated class definitioncheck(fn) applies array-level checks and synthetic child results asynchronouslyallOf(fn) applies a group of array rules where all returned checks must passanyOf(branches) applies alternative array branches where at least one branch must passoneOf(branches) applies alternative array branches where exactly one branch must passnot(fn, options?) applies a negated array branch that must failcheckEach(fn) validates each item using ArrayItemCheck and supports promised checkscontains(fn, options?) succeeds when a bounded number of items match one nested item rule without reporting non-matching item errors individuallyisTrue(fn, options?) applies a custom predicate to the array value and supports async predicatesisTrueEach(fn, options?) runs a custom predicate on every item and supports async predicatesresult(options?) returns the current result or a formatted final result depending on the options you passExample:
import { ObjectCheck } from '@samatawy/checks';
const check = await ObjectCheck.for(input)
.check(person => [
person.optional('children').array().maxLength(10)
.checkEach(child => [
child.object(),
child.required('name').string(),
child.optional('age').number().atLeast(0).atMost(17)
])
]);
const result = check.result({ flattened: true, language: 'en' });
Composition example:
import { ObjectCheck } from '@samatawy/checks';
const input = {
tags: [' Ada ', ' Bob ']
};
const check = await ObjectCheck.for(input)
.check(root => [
root.required('tags').array().anyOf([
tags => [
tags.checkEach(item => [item.string().trim().minLength(2)])
],
tags => [tags.maxLength(1)]
])
]);
const result = check.result({ language: 'en' });
Notes:
contains(...) is the array-level “some items must match” helper; use it instead of checkEach(...) when non-matching items are expected and should not each produce their own errormatchesType(...) on ArrayCheck is shorthand for checkEach(item => [item.matchesType(...)])item.array().matchesType(...) refers to a nested-array case where the current item value is itself an array and each nested element must match the class definitionArrayItemCheckRepresents one element in an array.
Common methods:
object()required(name, options?)optional(name)conditional(name, condition, options?)array()string()number()date()boolean()allOf(fn) applies a group of item rules where all returned checks must passanyOf(branches) applies alternative item branches where at least one branch must passoneOf(branches) applies alternative item branches where exactly one branch must passnot(fn, options?) applies a negated item branch that must failequals(value, options?) compares the current item value using strict equality by default and supports lax matching with tolerant: trueItem composition example:
import { ObjectCheck } from '@samatawy/checks';
const input = {
values: [' Ada ']
};
const check = await ObjectCheck.for(input)
.check(root => [
root.required('values').array().checkEach(item => [
item.anyOf([
entry => [entry.string().trim().minLength(2)],
entry => [entry.number().greaterThan(10)]
])
])
]);
const result = check.result({ language: 'en' });
FileCheckUse FileCheck for binary or data-URL style inputs.
Creation:
await FileCheck.for(key, data)await field.file()Methods:
mimeType(expectedMime, options?)notEmpty(options?)minSize(minBytes, options?)maxSize(maxBytes, options?)Supported inputs include Blob, File, Uint8Array, ArrayBuffer, Node Buffer, and data: URLs.
ImageCheckImageCheck extends FileCheck with image-specific validation.
Creation:
await ImageCheck.for(key, data)await field.image()Methods:
isImage(options?)minWidth(minWidth, options?)minHeight(minHeight, options?)maxWidth(maxWidth, options?)maxHeight(maxHeight, options?)StringCheck, EmailCheck, and UrlCheck share common string comparison methods through the internal StringBaseCheck.
NumberCheck and DateCheck expose numeric and date comparison helpers. ValueCheck is the shared base for value-level fluent behavior and is usually not needed directly in application code.
Composition helpers such as allOf(...), anyOf(...), oneOf(...), and not(...) are intentionally documented on ObjectCheck, FieldCheck, ArrayCheck, and ArrayItemCheck, where branching still happens before a final fixed type is chosen.
Selected methods:
StringCheck.trim()StringCheck.minLength(length, options?)StringCheck.maxLength(length, options?)StringCheck.equals(value, options?)StringCheck.equalsOneOf(values, options?)StringCheck.startsWith(prefix, options?)StringCheck.endsWith(suffix, options?)StringCheck.contains(substring, options?)StringCheck.pattern(regex, options?)StringCheck.uuid(options?)StringCheck.ulid(options?)StringCheck.email(options?)StringCheck.url(options?)StringCheck.isBase64(options?)StringCheck.isSHA256(options?)StringCheck.isMD5(options?)StringCheck.isHexadecimal(options?)StringCheck.isAlphanumeric(options?)StringCheck.isAscii(options?)StringCheck.hasMultibyte(options?)StringCheck.hasUpperCase(minCount?, options?)StringCheck.hasLowerCase(minCount?, options?)StringCheck.hasDigit(minCount?, options?)StringCheck.hasSpecialCharacter(minCount?, options?)StringCheck.noSpecialCharacters(chars?, options?)StringCheck.noSpaces(options?)StringCheck.maxWords(count, options?)UUIDCheck.version(version, options?)UUIDCheck.isULID(options?)NumberCheck.integer(options?)NumberCheck.greaterThan(value, options?)NumberCheck.atLeast(value, options?)NumberCheck.atMost(value, options?)DateCheck.after(value, options?)DateCheck.before(value, options?)DateCheck.sameDay(value, options?)ValueCheck.isTrue(fn, options?)ValueCheck.result(options?)uuid() validates immediately as soon as you enter the UUID checker, like email() and url(). Use field.uuid({ version }) or string().uuid({ version }) when you want the shortcut form, or uuid().version(4) / uuid().version([4, 7]) when you want to narrow an already-valid UUID to one version or a small allowed set.
Use ulid() when the value must be a ULID. Like uuid(), it validates immediately when you enter the specialized checker, so field.ulid() and string().ulid() already perform the base ULID validity check.
result(options?) is the main output API.
For object and array checks:
result({ language }) returns the merged nested result treeresult({ language, catalog }) resolves coded messages with an explicit result catalog instead of ResultCatalog.globalresult({ flattened: true, language }) returns flattened hints, warnings, and errorsresult({ nested: true, language }) returns an input-shaped projection under inputresult({ validated: 'partial', language }) returns a cloned validated value with invalid descendants removed and valid siblings preservedresult({ validated: 'strict', language }) returns a cloned validated value where any invalid descendant removes the whole parent branchresult({ raw: true, nested: true, flattened: true, language }) returns all projections at onceFor value-level checks:
result({ language }) returns the finalized single resultresult() returns the current single result state without extra projectionsExample:
const output = check.result({
raw: true,
nested: true,
validated: 'partial',
flattened: true,
language: 'en'
}) as any;
console.log(output.raw);
console.log(output.input);
console.log(output.validated);
console.log(output.errors);
Notes:
catalog lets final result formatting resolve coded messages from a specific ResultCatalog; without it, the package uses ResultCatalog.globalvalidated uses the current normalized input value, so coercions or mutations performed by checks are reflected in the output'partial' is the default mode to prefer when you want to keep valid object fields or array items even if siblings fail'strict' is useful when a parent object or array should be considered unusable as soon as one descendant is invalidSingleResultRepresents one validation outcome.
interface SingleResult {
valid: boolean;
field?: string | number | null | undefined;
hint?: string | string[];
warn?: string | string[];
err?: string | string[];
code?: string | number;
}
ResultSetAdds nested and flattened result collections.
interface ResultSet extends SingleResult {
input?: any;
results?: IResult[];
hints?: string[];
warnings?: string[];
errors?: string[];
}
CheckOptionsUse these to customize output when a check fails.
interface CheckOptions {
hint?: string | string[];
warn?: string | string[];
err?: string | string[];
code?: string | number;
catalog?: IResultCatalog;
}
Use inline hint, warn, or err for direct messages.
Use code when you want the message level and translations to come from a ResultCatalog. If you do not pass catalog, the package uses ResultCatalog.global.
StringCheckOptionsAdds case sensitivity control.
interface StringCheckOptions extends CheckOptions {
case?: 'sensitive' | 'insensitive';
}
ResultOptionsControls how final output is shaped.
interface ResultOptions {
language?: string;
catalog?: IResultCatalog;
raw?: boolean;
nested?: boolean;
validated?: 'partial' | 'strict';
flattened?: boolean;
}
ClassValidationOptionsControls how validateClass(...) and matchesType(...) source their rules.
interface ClassValidationOptions {
noExtraFields?: boolean;
noExtraFieldsOptions?: CheckOptions;
result?: ResultOptions;
skip?: 'decorators' | 'inference';
}
Notes:
skip to use hybrid behaviorskip: 'inference' to require explicit decorators onlyskip: 'decorators' to ignore decorator metadata and rely on inferred checks from runtime instance properties, which usually means initialized class fieldsInstall just the core package if you only need object, array, string, number, or date validation:
npm install @samatawy/checks
Install the optional peer dependencies when using file or image validation:
npm install @samatawy/checks file-type probe-image-size