@awish10122004/validator
v1.0.1
Published
A validation library inspired by yup with support for string, number, boolean, bigint, date, null, and undefined types
Maintainers
Readme
My Validator
TypeScript validation library inspired by Yup, with a clear API, type coercion, and strong error handling. This README consolidates all docs and explains the concrete implementations across schema/, factories/, error/, and types/, plus the GoF patterns in use (Abstract Factory, Builder [fluent immutable], Composite, Template Method, Prototype).
Features
- ✅ Type Schemas:
string(),number(),boolean(),bigint(),date(),nullType(),undefinedType() - 🔄 Nullable & Optional:
.nullable(),.optional(),.required(),.notRequired() - 🎯 Type Coercion: Built-in transforms,
.strict()to disable coercion - ⚡ Fluent API: Chain methods; immutable cloning under the hood
- 🛡️ Type Safe: Strong TypeScript generics and narrowing
- 🧩 Composite:
ObjectSchema,ArraySchemavalidate nested structures - 🏭 Abstract Factory: Preconfigured factories for contexts (strict/lenient/db/prod)
- 🧪 Custom Tests: Add rules with
test(); custom transforms withtransform() - 🚨 Robust Errors:
ErrorHandlerwith aggregated field errors
Installation
npm install
npm run buildQuick Start
import { string, number, boolean, bigint, date } from "my-validator";
// String validation
const emailSchema = string().required().email();
await emailSchema.validate("[email protected]"); // ✅ Passes
// Number validation
const ageSchema = number().required().min(0).max(120).integer();
await ageSchema.validate(25); // ✅ Passes
// Boolean validation
const acceptedSchema = boolean().required().isTrue();
await acceptedSchema.validate(true); // ✅ PassesAPI Reference
String Schema
string()
.min(length: number) // Minimum length
.max(length: number) // Maximum length
.length(length: number) // Exact length
.matches(regex: RegExp) // Pattern matching
.email() // Email validation
.url() // URL validation
.trim() // Trim whitespace
.lowercase() // Convert to lowercase
.uppercase() // Convert to uppercase
.required(message?: string) // Non-null, non-undefined
.nullable(message?: string) // Allow null
.optional(message?: string) // Allow undefined
.nonNullable(message?: string) // Disallow null
.defined(message?: string) // Disallow undefinedNumber Schema
number()
.min(value: number) // Minimum value
.max(value: number) // Maximum value
.lessThan(value: number) // Strictly less than
.moreThan(value: number) // Strictly greater than
.positive() // Must be positive
.negative() // Must be negative
.integer() // Must be integer
.round() // Round to nearest integer
.truncate() // Truncate to integer
.required/.nullable/.optional/.nonNullable/.definedBoolean Schema
boolean()
.isTrue() // Must be true
.isFalse(); // Must be false
// plus required/nullable/optional/nonNullable/definedBigInt Schema
bigint()
.min(value: bigint) // Minimum value
.max(value: bigint) // Maximum value
.lessThan(value: bigint) // Strictly less than
.moreThan(value: bigint) // Strictly greater than
.positive() // Must be positive
.negative() // Must be negative
// plus required/nullable/optional/nonNullable/definedDate Schema
date()
.min(date: Date) // Minimum date
.max(date: Date) // Maximum date
// plus required/nullable/optional/nonNullable/definedNull and Undefined Types
import { nullType, undefinedType } from "my-validator";
nullType(); // Only accepts null
undefinedType(); // Only accepts undefinedNullable and Optional
All schemas support nullable and optional modifiers:
// Nullable - accepts null
const schema1 = string().nullable();
schema1.validate(null); // ✅ Passes
// Optional - accepts undefined
const schema2 = number().optional();
schema2.validate(undefined); // ✅ Passes
// Required - rejects both null and undefined
const schema3 = string().required();
schema3.validate(null); // ❌ Fails
schema3.validate(undefined); // ❌ Fails
// Not Required - accepts both null and undefined
const schema4 = string().notRequired();
schema4.validate(null); // ✅ Passes
schema4.validate(undefined); // ✅ PassesType Coercion
By default, schemas attempt to coerce values to the correct type:
// Coercion enabled (default)
number().validate("42"); // ✅ Returns 42
boolean().validate("true"); // ✅ Returns true
date().validate("2023-12-13"); // ✅ Returns Date object
// Strict mode - no coercion
number().strict().validate("42"); // ❌ Fails - must be a numberDefault Values
Set default values for undefined inputs:
const schema = string().default("default value");
schema.cast(undefined); // Returns 'default value'
// Function defaults
const schema2 = date().default(() => new Date());
schema2.cast(undefined); // Returns current dateValidation Methods
// Synchronous validation
schema.validateSync(value);
// Asynchronous validation
await schema.validate(value);
// Check validity without throwing
schema.isValidSync(value); // Returns boolean
await schema.isValid(value); // Returns Promise<boolean>
// Cast without validation
schema.cast(value);
// Add custom transform (runs during cast unless strict)
schema.transform((value) => /* transform */ value);
// Add custom test (runs during validate)
schema.test({
name: "myRule",
message: "Custom validation failed",
test: (value) => /* boolean check */ true,
skipAbsent: true, // optional; skip when value is null/undefined
});Custom Tests
Add custom validation logic:
const schema = string().test({
name: "customTest",
message: "Custom validation failed",
test: (value) => value !== "forbidden",
});
// Or shorthand
const schema2 = string().test(
"customTest",
"Custom error",
(value) => value !== "forbidden"
);Error Handling
import { ErrorHandler } from "my-validator";
try {
await schema.validate(invalidValue);
} catch (error) {
if (error instanceof ErrorHandler) {
console.log(error.message); // summarised message or count
console.log(error.value); // offending value
console.log(error.type); // schema type name
console.log(error.getFieldErrors()); // flat Record<path, message>
console.log(error.getAllMessages()); // string[] of messages
}
}Implementation Details
Architecture & Internals
Conforms to a clean base-class design:
- Base Class
Schema<TValue>: Core logic in [schema/Schema.ts]; subclasses implement type-specific checks and rules. - Immutability via
clone(): Each mutator returns a new instance;inMutationMode()enables batch mutable updates internally. - Transform Pipeline: Coercion runs in
cast()unless.strict(true). - Validation Queue: Rules added via
test()run after casting;skipAbsenthelps optional/nullable flows. - Spec Object: Flags like
coerce,strict,nullable,optional,default,labeldrive behavior.
Key Methods
matchesType(v)— Type check honoring.nullable()/.optional()clone(spec?)— Prototype-style cloning with spec overridelabel(label)— Attach a human-readable label for messagesinMutationMode(fn)— Temporarily allow internal mutation for setuptransform(fn)— Add aValueTransformertest(config)— Add aValidationRulecast(value)— Run transforms and apply defaultsvalidate(value)/validateSync(value)— Cast then test; throwErrorHandleron failureisValid(value)/isValidSync(value)— Return boolean; catchErrorHandlerdefault(def)— Value or thunk used when input isundefinedstrict(isStrict)— Disable transforms/coercion when true
Examples
Complex Validation
const userSchema = object({
name: string().required().min(2),
email: string().required().email(),
age: number().required().positive().integer().max(120),
website: string().url().nullable(),
acceptTerms: boolean().required().isTrue(),
accountValue: bigint().optional().positive(),
createdAt: date().default(() => new Date()),
});Dynamic Validation
const createSchema = (minAge: number) => {
return number().required().min(minAge).integer();
};
const adultSchema = createSchema(18);
const seniorSchema = createSchema(65);🏗️ Design Patterns (GoF)
The implementation uses the following core patterns, backed by the code in this repository:
1) Abstract Factory
Centralized creation of preconfigured schemas for different contexts (strict/lenient/db/prod) via factories/SchemaFactory.ts.
import {
strictFactory,
lenientFactory,
databaseFactory,
productionFactory,
} from "my-validator";
const strict = strictFactory();
const email = strict.createString().email().label("Email");
const id = strict
.createString()
.matches(/^[a-zA-Z0-9_-]+$/)
.label("ID");
const form = lenientFactory();
const price = form.createNumber().positive().round().label("Price");Internals: SchemaFactoryProvider (Singleton) caches StrictSchemaFactory, LenientSchemaFactory, DatabaseSchemaFactory, ProductionSchemaFactory.
2) Builder (Fluent Immutable)
Each method on Schema returns a new schema configured with the requested rule — an immutable builder style:
const passwordSchema = string()
.min(8)
.matches(/[A-Z]/)
.matches(/[a-z]/)
.matches(/[0-9]/)
.required();This provides step-by-step construction (builder) with fluent chaining while keeping instances immutable.
3) Composite
ObjectSchema (composite of field schemas) and ArraySchema (composite of item schema) validate nested structures by delegating to child schemas.
const userSchema = object({
name: string().min(2).required(),
tags: array(string()).min(1),
});4) Template Method
Schema.validateSync() defines the validation algorithm (cast → type check → run tests). Subclasses like StringSchema, NumberSchema, etc. customize parts via type check and specific rule methods, but the overall control flow remains in the base class.
5) Prototype
Schema.clone() creates new instances copying configuration (spec, transforms, tests). Composite schemas also clone their inner members (ArraySchema.innerType, ObjectSchema.shape) when overriding clone().
Types & Internals
Located in types/common.ts:
ValueTransformer<T>—(value: any) => anyused by.transform()ValidationRule<T>—{ name?: string; message: string; test: (value: T | null | undefined) => boolean; skipAbsent?: boolean }ValidationTest— runtime form of a rule
Error class in error/ErrorHandler.ts (bilingual comments):
- Aggregates errors (
errors: string[],inner: ErrorHandler[]) - Static
isError(err)type guard - Helpers:
getFieldErrors()returningRecord<string,string>,getAllMessages()returningstring[]
Factories in factories/SchemaFactory.ts:
StrictSchemaFactory,LenientSchemaFactory,DatabaseSchemaFactory,ProductionSchemaFactorySchemaFactoryProviderwith helpers:strictFactory(),lenientFactory(),databaseFactory(),productionFactory()
Schemas in schema/:
StringSchema,NumberSchema,BooleanSchema,BigIntSchema,DateSchema,NullSchema,UndefinedSchema,ArraySchema,ObjectSchema- Common modifiers:
.required(),.nullable(),.optional(),.nonNullable(),.defined(),.notRequired() - Selected rules per type (e.g.,
matches(),email(),url()on strings; numeric bounds on numbers; date bounds on dates; boolean truthiness; bigint bounds)
Index exports in src/index.ts:
- Re-exports
ErrorHandler - Types:
ValidationRule,ValueTransformer,Optional - Helper types:
InferTypeSchema - All schemas and creators from
schema/index(string,number,boolean,bigint,date,nullType,undefinedType,array,object)
Helper types in types/object.helper.ts:
ObjectShape— map of field name →SchemaInferShape<TShape>— derive object output type from a shapeInferType<T>— derive type from a singleSchemaInferTypeSchema<TSchema>— derive a fully prettified type including nested arrays/objects
Example:
import { object, string, number } from "my-validator";
import type { InferTypeSchema } from "my-validator";
const userSchema = object({
name: string().required().min(2),
age: number().required().min(0).integer(),
});
type User = InferTypeSchema<typeof userSchema>;
// User => { name: string; age: number }Requirements Coverage
Mapped to REQUIREMENTS.md statements:
- Notifications when data is invalid:
ErrorHandlerwith aggregated messages and paths. - Combine validations: fluent chaining of multiple
.test()and built-in rules. - Regular expressions:
StringSchema.matches(regex). - Custom validation:
Schema.test()accepts custom predicates. - Base classes for basic data types: concrete schemas in
schema/. - Processing layer:
validateSync()andvalidate()execute transforms and rules. - Declarative-style support: Abstract Factory presets and labels provide consistent configurations (TypeScript lacks runtime reflection; decorators not used here).
If you need attribute-based declarations (like .NET/Java), consider adding TypeScript decorators that translate metadata into schema construction — kept out-of-scope for this core library.
Try It
Build and run quick checks:
npm install
npm run buildMinimal usage in a Node REPL or script:
import { string, number, object, array, strictFactory } from "my-validator";
const schema = object({
name: string().required().min(2),
age: number().required().min(0).integer(),
tags: array(string()).min(1),
});
schema.validateSync({ name: "An", age: 20, tags: ["dev"] });
// Use factories
const strict = strictFactory();
strict.createEmail().validateSync("[email protected]");