Skip to content

incognito-dev07/incog-validate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@incogdev/validate

Fast, type-safe validation library with async, conditional, nested schema, pipeline, batch, and benchmark support. Zero dependencies. Works in Node.js and browser.

Installation

npm install @incogdev/validate

Quick Start

import { validate } from '@incogdev/validate';

const result = validate('user@example.com')
  .required()
  .email()
  .isValid();

console.log(result); // true

Table of Contents

Basic Rules

Import individual rules for simple validation:

import { rules } from '@incogdev/validate';

// Email validation
rules.email('john@example.com');     // true
rules.email('invalid-email');        // false

// Phone number (international format)
rules.phone('+1234567890');          // true
rules.phone('+1 (555) 123-4567');    // true
rules.phone('abc123');               // false

// URL validation
rules.url('https://google.com');     // true
rules.url('http://localhost:3000');  // true
rules.url('not-a-url');              // false

// Check if value is not empty
rules.notEmpty('hello');             // true
rules.notEmpty('');                  // false
rules.notEmpty(null);                // false
rules.notEmpty(undefined);           // false

// Number validation
rules.isNumber(42);                  // true
rules.isNumber('3.14');              // true
rules.isNumber('abc');               // false

// Integer validation
rules.isInt(42);                     // true
rules.isInt(3.14);                   // false
rules.isInt('100');                  // true

// Boolean validation
rules.isBoolean(true);               // true
rules.isBoolean(false);              // true
rules.isBoolean('true');             // true
rules.isBoolean('false');            // true
rules.isBoolean('yes');              // false

// String length
rules.minLength('hello', 3);         // true
rules.minLength('hi', 3);            // false
rules.maxLength('hello', 10);        // true
rules.maxLength('hello world', 5);   // false

// Number range
rules.between(50, 0, 100);           // true
rules.between(150, 0, 100);          // false

// Regex pattern matching
rules.matches('abc123', /^[a-z]{3}\d{3}$/);  // true
rules.matches('abc', /^[a-z]{3}\d{3}$/);     // false

// Value must be in allowed list
rules.oneOf('apple', ['apple', 'banana', 'orange']);     // true
rules.oneOf('grape', ['apple', 'banana', 'orange']);     // false

// Strong password (min 8 chars, uppercase, lowercase, number, special char)
rules.strongPassword('Pass123!');    // true
rules.strongPassword('weak');        // false
rules.strongPassword('password123'); // false

// Postal code by country
rules.postalCode('90210');           // true (US default)
rules.postalCode('90210-1234');      // true (US zip+4)
rules.postalCode('M5V 2T6', 'CA');   // true (Canada)
rules.postalCode('SW1A 1AA', 'UK');  // true (UK)

// Credit card (Luhn algorithm)
rules.creditCard('4532015112830366');  // true (Visa test number)
rules.creditCard('1234567890123456');  // false

// IP address
rules.ip('192.168.1.1');               // true (IPv4 default)
rules.ip('::1');                       // true (IPv6)
rules.ip('999.999.999.999');           // false

Chainable Validator

The chainable validator allows multiple validations on the same value:

import { validate } from '@incogdev/validate';

const validator = validate('john@example.com')
  .required()
  .email()
  .minLength(5)
  .maxLength(100);

console.log(validator.isValid());   // true
console.log(validator.getErrors()); // []

Checking Validation Result

const validator = validate('invalid')
  .required()
  .email();

if (validator.isValid()) {
  console.log('Email is valid!');
} else {
  console.log('Errors:', validator.getErrors());
}
// Output: Errors: ['Invalid email address']

Available Chainable Methods

Method Description
.email() Validates email format
.phone() Validates phone number
.url() Validates URL
.required() Value cannot be empty
.minLength(n) Minimum character length
.maxLength(n) Maximum character length
.isNumber() Must be a number
.strongPassword() Password strength check
.between(min, max) Number between range
.matches(regex) Matches regex pattern
.oneOf([...]) Value must be in array
.custom(fn, msg) Custom validation function

Method Chaining Order

Methods can be chained in any order. All validations run regardless of previous failures:

const result = validate('')
  .required()      // fails
  .email()         // fails
  .minLength(3)    // fails
  .isValid();      // false

console.log(validator.getErrors());
// ['Field is required', 'Invalid email address', 'Must be at least 3 characters']

Getting the Transformed Value

After pipeline transforms, you can get the final value:

const validator = validate('  hello@world.com  ')
  .pipe(v => v.trim())
  .pipe(v => v.toLowerCase())
  .email();

console.log(validator.getValue()); // 'hello@world.com'

Custom Error Messages

Override default error messages:

import { validate } from '@incogdev/validate';

const validator = validate('', {
  required: 'Username cannot be empty!',
  email: 'Please enter a valid email address'
})
  .required()
  .email();

console.log(validator.getErrors());
// ['Username cannot be empty!', 'Please enter a valid email address']

Available Error Message Keys

Key Default Message
email "Invalid email address"
phone "Invalid phone number"
url "Invalid URL"
required "Field is required"
minLength "Must be at least X characters"
maxLength "Must be at most X characters"
isNumber "Must be a number"
strongPassword "Password must contain uppercase, lowercase, number, and symbol"
between "Must be between X and Y"
matches "Value does not match required pattern"
oneOf "Value must be one of: ..."

Schema Validation

Validate entire objects with a schema definition.

Basic Schema

import { schema } from '@incogdev/validate';

const userSchema = schema({
  name: { type: 'string', required: true },
  email: { type: 'email', required: true },
  age: { type: 'number' }
});

const result = userSchema.validate({
  name: 'John Doe',
  email: 'john@example.com',
  age: 25
});

console.log(result.valid);   // true
console.log(result.errors);  // {}

Schema with Length Constraints

const userSchema = schema({
  username: { 
    type: 'string', 
    required: true, 
    min: 3, 
    max: 20 
  },
  bio: { 
    type: 'string', 
    max: 200 
  }
});

const result = userSchema.validate({
  username: 'jo',              // too short
  bio: 'a'.repeat(201)         // too long
});

console.log(result.valid);  // false
console.log(result.errors);
// {
//   username: ['username must be at least 3 characters'],
//   bio: ['bio must be at most 200 characters']
// }

Schema with Pattern Validation

const productSchema = schema({
  sku: { 
    type: 'string', 
    required: true,
    pattern: /^[A-Z]{3}-\d{4}$/,
    patternMessage: 'SKU must be format XXX-0000'
  }
});

const result = productSchema.validate({ sku: 'ABC-123' });
console.log(result.valid);   // false
console.log(result.errors.sku[0]);

Schema with Custom Validation

const orderSchema = schema({
  total: {
    type: 'number',
    custom: (value) => value > 0,
    customMessage: 'Total must be greater than 0'
  }
});

const result = orderSchema.validate({ total: -10 });
console.log(result.valid);   // false
console.log(result.errors.total[0]);  // "Total must be greater than 0"

Schema Types Reference

Type Description Validation
'string' Any string typeof value === 'string'
'number' Any number typeof value === 'number'
'boolean' Boolean typeof value === 'boolean'
'email' Email format Uses rules.email()
'phone' Phone number Uses rules.phone()
'url' URL format Uses rules.url()

Schema Options Reference

Option Type Description
type string One of the types above
required boolean If true, value cannot be empty
min number Minimum length (strings) or value (numbers)
max number Maximum length (strings) or value (numbers)
pattern RegExp Regex pattern to match
patternMessage string Custom message for pattern failure
custom function Custom validation function
customMessage string Custom message for custom validation
message string Custom message for required failure

Custom Rules

Create your own validation rules using the .custom() method.

Basic Custom Rule

import { validate } from '@incogdev/validate';

const validator = validate(7)
  .custom((value) => value % 2 === 0, 'Number must be even');

console.log(validator.isValid());  // false
console.log(validator.getErrors());  // ['Number must be even']

Reusable Custom Rule Function

function isDivisibleBy(divisor) {
  return (value) => value % divisor === 0;
}

const validator = validate(10)
  .custom(isDivisibleBy(3), 'Must be divisible by 3');

console.log(validator.isValid());  // false

Combining with Built-in Rules

const validator = validate('abc123')
  .required()
  .minLength(3)
  .custom((value) => /^\w+$/.test(value), 'Only letters, numbers, and underscores')
  .isValid();

Async Validation

Validate values asynchronously (e.g., checking if email exists in database).

Using validateAsync

import { validateAsync } from '@incogdev/validate';

async function checkEmail(email) {
  const { valid, errors } = await validateAsync(email, (v) =>
    v.required().email()
  );
  
  if (!valid) {
    console.log('Invalid email:', errors);
  }
  return valid;
}

Using .customAsync() with Chainable Validator

import { validate } from '@incogdev/validate';

async function validateUser(email) {
  const validator = validate(email)
    .required()
    .email()
    .customAsync(async (value) => {
      // Simulate database check
      const exists = await db.users.findOne({ email: value });
      return !exists;
    }, 'Email already taken');
  
  const isValid = await validator.isValidAsync();
  
  return {
    valid: isValid,
    errors: validator.getErrors()
  };
}

Using createAsyncValidator

import { createAsyncValidator } from '@incogdev/validate';

const validator = createAsyncValidator('test@example.com')
  .addRule(v => v.required().email())
  .addAsyncRule(async (value) => {
    const exists = await checkDatabase(value);
    return !exists;
  }, 'Email already exists');

const result = await validator.validate();
console.log(result.valid, result.errors);

Conditional Validation

Apply validation rules only when certain conditions are met.

Using conditional

import { conditional } from '@incogdev/validate';

const password = 'weak';

const result = conditional(password, [
  {
    condition: (v) => v.length < 8,
    rules: (v) => v.custom(() => false, 'Password must be at least 8 characters')
  },
  {
    condition: (v) => /^[a-z]+$/.test(v),
    rules: (v) => v.custom(() => false, 'Password must contain uppercase or numbers')
  }
]);

console.log(result.valid);  // false
console.log(result.errors); // ['Password must be at least 8 characters', 'Password must contain uppercase or numbers']

Using .if() and .unless() with Chainable Validator

import { validate } from '@incogdev/validate';

const isAdmin = true;
const password = 'admin123';

const validator = validate(password)
  .required()
  .minLength(5)
  .if(() => isAdmin, (v) => v.strongPassword())
  .unless(() => isAdmin, (v) => v.minLength(3));

console.log(validator.isValid());  // false if admin and password not strong

Using createConditionalValidator

import { createConditionalValidator } from '@incogdev/validate';

const result = createConditionalValidator('user@example.com')
  .if(v => v.includes('admin'), v => v.email())
  .if(v => v.length > 10, v => v.minLength(5))
  .validate();

console.log(result.valid, result.errors);

Nested Schema

Validate deeply nested objects.

Basic Nested Schema

import { nestedSchema } from '@incogdev/validate';

const userSchema = nestedSchema({
  name: { type: 'string', required: true, min: 2 },
  address: {
    street: { type: 'string', required: true },
    city: { type: 'string', required: true },
    zipCode: { type: 'string', pattern: /^\d{5}$/, patternMessage: 'Invalid zip code' }
  }
});

const result = userSchema.validate({
  name: 'John',
  address: {
    street: '123 Main St',
    city: '',
    zipCode: '1234'
  }
});

console.log(result.valid);  // false
console.log(result.errors);
// {
//   'address.city': ['city is required'],
//   'address.zipCode': ['Invalid zip code']
// }

Using deepValidate for Custom Path Prefixing

import { deepValidate } from '@incogdev/validate';

const schemaDef = {
  email: { type: 'email', required: true }
};

const result = deepValidate({ email: 'invalid' }, schemaDef, 'user');
console.log(result.errors);
// { 'user.email': ['user.email must be a email'] }

Partial Validation

const userSchema = nestedSchema({
  name: { type: 'string', required: true },
  email: { type: 'email', required: true }
});

// Validate only provided fields
const partialResult = userSchema.partial({ email: 'test@example.com' });
console.log(partialResult.valid);  // true (name not required in partial mode)

Pipeline / Transform

Transform values before validation.

Basic Pipeline

import { createPipeline } from '@incogdev/validate';

const result = await createPipeline('  HELLO@WORLD.com  ')
  .pipe(v => v.trim())           // Remove whitespace
  .pipe(v => v.toLowerCase())    // Convert to lowercase
  .validate(v => v.email())      // Validate email
  .execute();

console.log(result.valid);   // true
console.log(result.value);   // 'hello@world.com'

Multiple Transforms and Validations

const pipeline = createPipeline('  User@Example.com  ')
  .pipe(v => v.trim())
  .pipe(v => v.toLowerCase())
  .validate(v => v.required().email())
  .validate(v => v.custom(v => v.includes('user'), 'Must contain "user"'));

const result = await pipeline.execute();
console.log(result.valid);   // true
console.log(result.value);   // 'user@example.com'

Batch Validation

Validate multiple fields at once.

Basic Batch Validation

import { batchValidate, batchResultSummary } from '@incogdev/validate';

const results = batchValidate([
  { field: 'email', value: 'test@example.com', rules: (v) => v.required().email() },
  { field: 'password', value: 'weak', rules: (v) => v.required().strongPassword() },
  { field: 'age', value: 25, rules: (v) => v.required().isNumber().between(0, 120) }
]);

const summary = batchResultSummary(results);
console.log(summary.allValid);     // false
console.log(summary.validCount);   // 2 (email and age are valid)
console.log(summary.invalidCount); // 1 (password invalid)
console.log(summary.errors);
// { password: ['Password must contain uppercase, lowercase, number, and symbol'] }

Async Batch Validation

import { batchValidateAsync } from '@incogdev/validate';

const results = await batchValidateAsync([
  { field: 'email', value: 'test@example.com', rules: (v) => v.email() },
  { field: 'unique', value: 'taken', rules: (v) => v.customAsync(async (val) => {
    return await checkUniqueness(val);
  }, 'Value already taken') }
]);

console.log(results);

Accessing Individual Results

const results = batchValidate([
  { field: 'email', value: 'invalid', rules: (v) => v.email() }
]);

for (const result of results) {
  console.log(`${result.field}: ${result.valid ? '✓' : '✗'}`);
  if (!result.valid) {
    console.log(`  Errors: ${result.errors.join(', ')}`);
  }
}

Benchmarking

Measure performance of your validations.

Basic Benchmark

import { benchmark, compareBenchmarks } from '@incogdev/validate';

const result = benchmark(() => {
  validate('test@example.com')
    .required()
    .email()
    .minLength(5)
    .isValid();
}, 10000, 'email-validation');

console.log(`${result.opsPerSecond} ops/sec`);
console.log(`${result.averageTimeMs.toFixed(4)}ms average`);

Comparing Multiple Benchmarks

const results = [
  benchmark(() => validate('test@example.com').email().isValid(), 10000, 'email-only'),
  benchmark(() => validate('test@example.com').required().email().minLength(5).maxLength(100).isValid(), 10000, 'full-validation'),
  benchmark(() => rules.email('test@example.com'), 10000, 'rules-direct')
];

compareBenchmarks(results);

Using Benchmark Suite

import { createBenchmarkSuite } from '@incogdev/validate';

const suite = createBenchmarkSuite()
  .add('email validation', () => {
    rules.email('test@example.com');
  }, 10000)
  .add('chainable validation', () => {
    validate('test@example.com').email().isValid();
  }, 10000)
  .add('full validation', () => {
    validate('test@example.com')
      .required()
      .email()
      .minLength(5)
      .maxLength(100)
      .isValid();
  }, 10000);

const results = suite.runAndCompare();

Async Benchmark

import { benchmarkAsync } from '@incogdev/validate';

const result = await benchmarkAsync(async () => {
  await validateAsync('test@example.com', v => v.required().email());
}, 1000, 'async-validation');

console.log(`${result.opsPerSecond} async ops/sec`);

Benchmark Result Properties

Property Type Description
name string Benchmark name
operations number Number of operations run
totalTimeMs number Total time in milliseconds
averageTimeMs number Average time per operation
opsPerSecond number Operations per second

TypeScript Support

Full TypeScript support with type inference.

Basic Types

import { validate, rules, schema, Validator } from '@incogdev/validate';

// Rules return boolean
const isValid: boolean = rules.email('test@example.com');

// Validator returns typed instance
const validator: Validator<string> = validate('test')
  .required()
  .minLength(3);

// Get typed value
const value: string = validator.getValue();

Typed Schema with Interface

interface User {
  name: string;
  email: string;
  age?: number;
}

const userSchema = schema<User>({
  name: { type: 'string', required: true, min: 2 },
  email: { type: 'email', required: true },
  age: { type: 'number' }
});

const user: User = { name: 'John', email: 'john@example.com' };
const result = userSchema.validate(user);

Generic Validator

function validateUserData<T extends { email: string }>(data: T) {
  const emailValid = validate(data.email).required().email().isValid();
  return { ...data, emailValid };
}

Async with Types

interface ValidationResult<T> {
  valid: boolean;
  errors: string[];
  value: T;
}

async function validateEmail(value: string): Promise<ValidationResult<string>> {
  return await validateAsync(value, v => v.required().email());
}

Framework Examples

Express.js

import { validate } from '@incogdev/validate';

app.post('/register', (req, res) => {
  const { email, password, username } = req.body;
  
  const emailValid = validate(email).required().email();
  const passwordValid = validate(password).required().strongPassword();
  const usernameValid = validate(username).required().minLength(3).maxLength(20);
  
  if (!emailValid.isValid() || !passwordValid.isValid() || !usernameValid.isValid()) {
    return res.status(400).json({
      errors: {
        email: emailValid.getErrors(),
        password: passwordValid.getErrors(),
        username: usernameValid.getErrors()
      }
    });
  }
  
  // Proceed with registration
  res.json({ message: 'User registered' });
});

Schema Validation with Request Body

app.post('/product', (req, res) => {
  const productSchema = schema({
    name: { type: 'string', required: true, min: 1, max: 100 },
    price: { type: 'number', required: true, min: 0 },
    category: { type: 'string', required: true }
  });
  
  const result = productSchema.validate(req.body);
  if (!result.valid) {
    return res.status(400).json({ errors: result.errors });
  }
  
  // Proceed
});

React Hook Form Integration

import { useState } from 'react';
import { validate } from '@incogdev/validate';

function RegistrationForm() {
  const [formData, setFormData] = useState({ email: '', password: '' });
  const [errors, setErrors] = useState({});
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const emailValid = validate(formData.email).required().email();
    const passwordValid = validate(formData.password).required().strongPassword();
    
    const newErrors = {};
    if (!emailValid.isValid()) newErrors.email = emailValid.getErrors()[0];
    if (!passwordValid.isValid()) newErrors.password = passwordValid.getErrors()[0];
    
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }
    
    // Submit form
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
      />
      {errors.email && <span className="error">{errors.email}</span>}
      
      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
      />
      {errors.password && <span className="error">{errors.password}</span>}
      
      <button type="submit">Register</button>
    </form>
  );
}

Next.js API Route

import { NextResponse } from 'next/server';
import { validate, schema } from '@incogdev/validate';

export async function POST(request: Request) {
  const body = await request.json();
  
  const userSchema = schema({
    name: { type: 'string', required: true, min: 2 },
    email: { type: 'email', required: true },
    password: { type: 'string', required: true, min: 8 }
  });
  
  const result = userSchema.validate(body);
  
  if (!result.valid) {
    return NextResponse.json({ errors: result.errors }, { status: 400 });
  }
  
  // Create user
  return NextResponse.json({ success: true });
}

Fastify Plugin

import fastify from 'fastify';
import { validate } from '@incogdev/validate';

const app = fastify();

app.post('/login', async (req, reply) => {
  const { email, password } = req.body;
  
  const validator = validate(email).required().email();
  
  if (!validator.isValid()) {
    return reply.status(400).send({ error: validator.getErrors() });
  }
  
  return { success: true };
});

API Reference

CommonJS Import

const { rules, validate, schema } = require('@incogdev/validate');

ESM Import

import { rules, validate, schema } from '@incogdev/validate';

// Import specific features
import { validateAsync } from '@incogdev/validate/async';
import { conditional } from '@incogdev/validate/conditional';
import { nestedSchema } from '@incogdev/validate/nested';
import { createPipeline } from '@incogdev/validate/pipeline';
import { batchValidate } from '@incogdev/validate/batch';
import { benchmark } from '@incogdev/validate/benchmark';

Browser CDN

<script type="importmap">
  {
    "imports": {
      "@incogdev/validate": "https://unpkg.com/@incogdev/validate@4.0.0/dist/browser.js"
    }
  }
</script>
<script type="module">
  import { validate } from '@incogdev/validate';
  
  const result = validate('test@example.com').email().isValid();
  console.log(result);
</script>

About

Fast and type-safe javascript/typescript validation library with multiple features

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors