npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

quantique-field-validator

v2.0.6

Published

Enterprise-grade dynamic field validator for Indian and international form fields.

Readme

quantique-field-validator

npm version license typescript esm+cjs node

Enterprise-grade dynamic field validator for Indian and international form fields.
One function call. 25 validator types. Full TypeScript support. Dual ESM + CJS package. ReDoS-safe. Works everywhere — webpack 4/5, Vite, Rollup, Parcel, Next.js, and Node.js.


Table of contents

  1. What is this?
  2. Why use it?
  3. Installation
  4. Quick start — 30 seconds
  5. How it works
  6. All 25 validator types
  7. ValidationRules — complete reference
  8. ErrorMessages — custom error text
  9. Custom validators
  10. Tree-shakeable named exports
  11. TypeScript types
  12. Security: ReDoS protection
  13. Framework integration examples
  14. Real-world example — vehicle registration form
  15. Changelog

1. What is this?

quantique-field-validator is a single-entry-point validation library. You pass a value, a type string, and optional rules — you get back { isValid: boolean, error: string }. That's it.

import dynamicValidator from 'quantique-field-validator';

dynamicValidator('ABCDE1234F', 'pan')
// { isValid: true, error: '' }

dynamicValidator('ABC', 'pan')
// { isValid: false, error: 'Please enter a valid pan' }

No configuration files. No classes to instantiate. No schema builders. Just a function call.


2. Why use it?

| Problem | This library | |---|---| | Different validators in every project | One package, one API, used across all your apps | | Indian-specific formats (PAN, GST, Aadhaar, IFSC…) built from scratch each time | All 9 Indian formats built in and production-tested | | Vehicle forms needing chassis, engine, reg code validation | Built-in with correct character and format rules | | Hand-writing regex that breaks on edge cases | Every regex is hardened and tested against hundreds of samples | | User-supplied regex crashing the server (ReDoS) | All rules.regex strings are compiled with a ReDoS guard | | Webpack 5 "not exported under the conditions" errors | Dual ESM + CJS package — works in every bundler | | dynamicValidator is not a function after upgrading | CJS build now exports the function directly — all call patterns work | | API prefill data (lowercase PAN, numeric mobile) failing validation | Values are coerced and normalised before validation — no manual conversion needed | | Emoji characters sneaking into database fields | All validators silently reject emoji by default | | Tight bundle requirements | Tree-shakeable named exports — import only what you need | | All-digit chassis/engine numbers passing validation | Both validators require at least one letter |


3. Installation

npm install quantique-field-validator
# or
yarn add quantique-field-validator
# or
pnpm add quantique-field-validator

Requirements: Node.js ≥ 18, TypeScript 5.x (optional — fully typed, no TS required)

Module format

This package ships as a dual CJS + ESM package:

{
  "exports": {
    ".": {
      "import":  "./dist/esm/index.js",
      "require": "./dist/index.js",
      "default": "./dist/index.js"
    }
  }
}
  • ESM (dist/esm/index.js) — used by Vite, Rollup, Next.js app router, and any bundler that uses the "import" condition
  • CJS (dist/index.js) — used by webpack, Jest, Node.js require(), and older toolchains

You do not need to configure anything — your bundler picks the right format automatically.

CJS call patterns — all supported

The CJS build exports the function as the module itself, so every common import style works:

// ESM default import (Vite, Rollup, Next.js, webpack 5) — recommended
import dynamicValidator from 'quantique-field-validator';
dynamicValidator(value, type, rules);

// Named import (tree-shakeable — preferred for large bundles)
import { dynamicValidator } from 'quantique-field-validator';

// Namespace import (webpack 4 / CRA with Babel — also supported)
import * as dynamicValidator from 'quantique-field-validator';
dynamicValidator(value, type, rules);         // ✓ callable in webpack 4 / CRA

// CJS require — direct call (webpack 4 / CRA / Jest)
const dynamicValidator = require('quantique-field-validator');
dynamicValidator(value, type, rules);         // ✓ callable directly

// CJS require — named access
const { dynamicValidator } = require('quantique-field-validator');

import * as on webpack 5 / native ESM: ES module namespace objects are never callable by spec. If your project uses webpack 5 with native ESM (not Babel), switch to import dynamicValidator from (default) or import { dynamicValidator } from (named). Both work in every bundler.

Upgrading from v1.x? Older versions exported the function as module.exports = fn (bare CJS). v2.x kept the same call signature AND added named exports — require('quantique-field-validator') is still callable directly.


4. Quick start — 30 seconds

import dynamicValidator from 'quantique-field-validator';

// ── Basic usage ──────────────────────────────────────────────────────────────
dynamicValidator('[email protected]', 'email')
// { isValid: true, error: '' }

// ── Required field ───────────────────────────────────────────────────────────
dynamicValidator('', 'email', { required: true })
// { isValid: false, error: 'This field is required' }

// ── With rules ───────────────────────────────────────────────────────────────
dynamicValidator('9876543210', 'mobile')
// { isValid: true, error: '' }

dynamicValidator('1234567890', 'mobile')
// { isValid: false, error: 'Please enter a valid mobile' }
// ↑ Fails: Indian mobile must start with 6–9

// ── Custom error message ─────────────────────────────────────────────────────
dynamicValidator('1234', 'mobile', {
  errorMessages: { invalid: 'Enter a valid 10-digit Indian mobile number.' },
})
// { isValid: false, error: 'Enter a valid 10-digit Indian mobile number.' }

// ── All-digit engine number is rejected ─────────────────────────────────────
dynamicValidator('00000000000000000000', 'engine')
// { isValid: false, error: 'Invalid engine number' }
// ↑ Fails: engine numbers must contain at least one letter

5. How it works

The dynamicValidator function

dynamicValidator(value, type, rules?, customValidator?)

| Parameter | Type | Required | Description | |---|---|---|---| | value | unknown | Yes | Raw input value (usually a string) | | type | ValidatorType | Yes | Which validator to run — see the full list below | | rules | ValidationRules | No | Optional constraints: required, minLength, regex, etc. | | customValidator | CustomValidatorFn | No | External custom validator — runs before type dispatch |

Returns ValidationResult:

{ isValid: boolean; error: string }

Validation order

Every call follows this exact sequence:

1. Emoji check         → reject if emoji found in the value
2. Required check      → reject empty if required: true
3. Empty passthrough   → if empty and not required, return { isValid: true }
4. customValidator     → runs if provided as the 4th argument
5. rules.customValidator → runs if provided inline in rules
6. Type validator      → the specific validator for the given type

This means empty + optional = always valid. Add required: true to change this.


6. All 25 validator types

Generic types


string

Generic string. Use when no specific format applies.

| Rule | Effect | |---|---| | minLength | Minimum character count | | maxLength | Maximum character count | | regex | Custom pattern override |

dynamicValidator('hello world', 'string')
// { isValid: true, error: '' }

dynamicValidator('hi', 'string', { minLength: 5 })
// { isValid: false, error: 'Minimum length should be 5' }

dynamicValidator('hello world', 'string', { maxLength: 5 })
// { isValid: false, error: 'Maximum length should be 5' }

dynamicValidator('abc-123', 'string', { regex: '^[a-z]+-[0-9]+$' })
// { isValid: true, error: '' }

alphanumeric

Lowercase letters and digits only (a–z, 0–9). No spaces, no uppercase.

dynamicValidator('abc123', 'alphanumeric')
// { isValid: true, error: '' }

dynamicValidator('ABC123', 'alphanumeric')
// { isValid: false, error: 'Value must be alphanumeric' }
// ↑ uppercase not allowed by default

dynamicValidator('abc 123', 'alphanumeric')
// { isValid: false, error: 'Value must be alphanumeric' }
// ↑ spaces not allowed

// Allow uppercase too with a custom regex
dynamicValidator('ABC123', 'alphanumeric', { regex: '^[A-Za-z0-9]+$' })
// { isValid: true, error: '' }

number

Whole (integer) number strings. No spaces, no decimals.

dynamicValidator('42', 'number')
// { isValid: true, error: '' }

dynamicValidator('3.14', 'number')
// { isValid: false, error: 'Please enter a valid number' }

dynamicValidator('150', 'number', { minValue: 1, maxValue: 100 })
// { isValid: false, error: 'Maximum value should be 100' }

float

Decimal number strings. Supports precision and whole-digit limits.

dynamicValidator('3.14', 'float')
// { isValid: true, error: '' }

dynamicValidator('3.14159', 'float', { decimalPrecision: 2 })
// { isValid: false, error: 'Only up to 2 digits allowed after the decimal point' }

dynamicValidator('12345.99', 'float', { maxWholeDigits: 4 })
// { isValid: false, error: 'Only up to 4 digits allowed before the decimal point' }

// Price field: max ₹99,999.99 with 2 decimal places
dynamicValidator('49999.50', 'float', {
  maxValue: 99999.99,
  decimalPrecision: 2,
  maxWholeDigits: 5,
})
// { isValid: true, error: '' }

date

Date strings with format detection, age checks, and relative past/future constraints.

Default format: DD/MM/YYYY

// Basic date validation (DD/MM/YYYY)
dynamicValidator('25/12/1990', 'date')
// { isValid: true, error: '' }

// Different format
dynamicValidator('2024-12-25', 'date', { format: 'YYYY-MM-DD' })
// { isValid: true, error: '' }

// Age gate — must be at least 18
dynamicValidator('01/01/2010', 'date', { minAge: 18 })
// { isValid: false, error: 'User must be at least 18 years old' }

dynamicValidator('01/01/1990', 'date', { minAge: 18 })
// { isValid: true, error: '' }

// Insurance policy — must start within next 30 days
dynamicValidator('01/07/2026', 'date', { minPlus: 1, maxPlus: 30, unit: 'days' })
// { isValid: true, error: '' }

// Vehicle registration — must be in the past
dynamicValidator('01/01/2020', 'date', { maxMinus: 1, unit: 'days' })
// { isValid: true, error: '' }

| Rule | Effect | |---|---| | format | Date format string: DD/MM/YYYY, MM-DD-YYYY, etc. Default: DD/MM/YYYY | | minAge | Person must be at least this many years old | | maxAge | Person must be at most this many years old | | minMinus | Date must be no more than N units in the past | | maxMinus | Date must be at least N units in the past | | minPlus | Date must be at least N units in the future | | maxPlus | Date must be no more than N units in the future | | unit | Time unit: day, days, month, months, year, years |


name

Personal or corporate name. Allows letters, spaces, and common name punctuation.

dynamicValidator('Jane Smith', 'name')
// { isValid: true, error: '' }

dynamicValidator('Jane123', 'name')
// { isValid: false, error: 'Invalid name' }

// Corporate name (allows numbers and extra chars)
dynamicValidator('Tata Motors Ltd.', 'name', { type: 'corporate' })
// { isValid: true, error: '' }

firstName / middleName / lastName

Single-word name fields. Letters only — no spaces, no digits, no punctuation.

dynamicValidator('John', 'firstName')
// { isValid: true, error: '' }

dynamicValidator('John Paul', 'firstName')
// { isValid: false, error: 'Please enter a valid firstName' }
// ↑ spaces not allowed in single-name fields

dynamicValidator('Kumar', 'middleName')
// { isValid: true, error: '' }

address

Postal or street address. Allows letters, digits, spaces, and common punctuation: , . / - # & :

dynamicValidator('123, MG Road, Mumbai - 400001', 'address')
// { isValid: true, error: '' }

dynamicValidator('Plot #12, Sector-5, Noida', 'address')
// { isValid: true, error: '' }

dynamicValidator('Flat @ Tower', 'address')
// { isValid: false, error: 'Please enter a valid address' }
// ↑ '@' is not in the allowed set

dynamicValidator('', 'address')
// { isValid: false, error: 'Please enter a valid address' }
// ↑ blank/whitespace-only address is always invalid

dynamicValidator('123 Main St', 'address', { minLength: 10, maxLength: 200 })
// { isValid: true, error: '' }

Indian identity & financial


aadhaar

12-digit Aadhaar number. First digit must be 2–9 (no leading 0 or 1).

dynamicValidator('234567890123', 'aadhaar')
// { isValid: true, error: '' }

dynamicValidator('123456789012', 'aadhaar')
// { isValid: false, error: 'Please enter a valid aadhaar' }
// ↑ starts with 1 — not a valid Aadhaar

dynamicValidator('23456789012', 'aadhaar')
// { isValid: false, error: 'Please enter a valid aadhaar' }
// ↑ only 11 digits

Format: [2-9][0-9]{11} — 12 digits, first digit 2–9


pan

Indian PAN card. 5 uppercase letters + 4 digits + 1 uppercase letter = 10 characters.

dynamicValidator('ABCDE1234F', 'pan')
// { isValid: true, error: '' }

dynamicValidator('abcde1234f', 'pan')
// { isValid: false, error: 'Please enter a valid pan' }
// ↑ must be uppercase

dynamicValidator('ABCD1234F', 'pan')
// { isValid: false, error: 'Please enter a valid pan' }
// ↑ only 9 characters

Format: [A-Z]{5}[0-9]{4}[A-Z] — e.g. ABCDE1234F

When used with quantique-forms and fieldType: 'pan', the library auto-uppercases input and caps at 10 characters automatically.


gst

Indian GSTIN (15 characters). Format: 2-digit state code + PAN + 3-digit entity suffix.

dynamicValidator('22AAAAA0000A1Z5', 'gst')
// { isValid: true, error: '' }

dynamicValidator('22AAAAA0000A1Z', 'gst')
// { isValid: false, error: 'Please enter a valid gst' }
// ↑ only 14 characters

dynamicValidator('00AAAAA0000A1Z5', 'gst')
// { isValid: false, error: 'Please enter a valid gst' }
// ↑ state code can't be 00

Format: \d{2}[A-Z]{5}\d{4}[A-Z][A-Z\d]Z[A-Z\d]


ifsc

Indian bank IFSC (11 characters). First 4 letters = bank code, 5th character always 0, last 6 = branch code.

dynamicValidator('HDFC0001234', 'ifsc')
// { isValid: true, error: '' }

dynamicValidator('SBIN0000123', 'ifsc')
// { isValid: true, error: '' }

dynamicValidator('HDFC1001234', 'ifsc')
// { isValid: false, error: 'Invalid bank IFSC code' }
// ↑ 5th character must be '0'

Format: [A-Z]{4}0[A-Z0-9]{6} — always exactly 11 characters


pincode

Indian postal PIN code. 6 digits. First digit must be 1–9 (no leading zero).

dynamicValidator('400001', 'pincode')
// { isValid: true, error: '' }  — Mumbai

dynamicValidator('110001', 'pincode')
// { isValid: true, error: '' }  — New Delhi

dynamicValidator('012345', 'pincode')
// { isValid: false, error: 'Please enter a valid 6-digit PIN code' }
// ↑ starts with 0

dynamicValidator('40000', 'pincode')
// { isValid: false, error: 'Please enter a valid 6-digit PIN code' }
// ↑ only 5 digits

Format: [1-9][0-9]{5} — 6 digits, first digit 1–9


Vehicle & registration


chassis

Vehicle chassis number. Accepts 5–17 uppercase alphanumeric characters. Must contain at least one letter — all-digit strings are rejected because real chassis numbers always contain letters.

dynamicValidator('MA3ERLF1S00100001', 'chassis')
// { isValid: true, error: '' }  — 17-char VIN

dynamicValidator('MALA051BL3M43956', 'chassis')
// { isValid: true, error: '' }

dynamicValidator('ABC12345', 'chassis')
// { isValid: true, error: '' }  — shorter Indian chassis number

dynamicValidator('000000000000', 'chassis')
// { isValid: false, error: 'Invalid chassis number' }
// ↑ all digits — rejected (must have at least one letter)

dynamicValidator('AB12', 'chassis')
// { isValid: false, error: 'Invalid chassis number' }
// ↑ too short (minimum 5 characters)

Default format: (?=.*[A-Z])[A-Z0-9]{5,17} — 5 to 17 uppercase alphanumeric, at least one letter


engine

Vehicle engine number. Accepts 2–25 alphanumeric characters. Must contain at least one letter — all-digit strings are rejected because real engine numbers always include alphabetic characters.

dynamicValidator('G16B12345', 'engine')
// { isValid: true, error: '' }

dynamicValidator('4JJ1123456', 'engine')
// { isValid: true, error: '' }  — ISUZU 4JJ1 engine

dynamicValidator('AB', 'engine')
// { isValid: true, error: '' }  — minimum 2 characters

dynamicValidator('00000000000000000000', 'engine')
// { isValid: false, error: 'Invalid engine number' }
// ↑ all digits — rejected (must have at least one letter)

dynamicValidator('A', 'engine')
// { isValid: false, error: 'Invalid engine number' }
// ↑ too short (minimum 2 characters)

Default format: (?=.*[A-Za-z])[A-Za-z0-9]{2,25} — 2 to 25 alphanumeric, at least one letter


rto

Indian RTO district code. Supports old and new formats via rules.version.

| rules.version | Accepted format | Example | |---|---|---| | undefined / 'old' | AA-00 | MH-01, DL-08 | | 'new' | AA-00 or AA-00-AA | MH-01-AB | | 'all' | Flexible separators | MH01, MH-01AB |

dynamicValidator('MH-01', 'rto')
// { isValid: true, error: '' }

dynamicValidator('MH-01-AB', 'rto', { version: 'new' })
// { isValid: true, error: '' }

dynamicValidator('MH-01-AB', 'rto')
// { isValid: false, error: 'Invalid RTO code' }
// ↑ new format requires version: 'new'

regCode

Vehicle registration state/UT code. Exactly 2 uppercase alphabets (e.g. MH for Maharashtra, DL for Delhi).

dynamicValidator('MH', 'regCode')
// { isValid: true, error: '' }

dynamicValidator('DL', 'regCode')
// { isValid: true, error: '' }

dynamicValidator('M', 'regCode')
// { isValid: false, error: 'Invalid registration code...' }
// ↑ too short — must be exactly 2 letters

dynamicValidator('MH1', 'regCode')
// { isValid: false, error: 'Invalid registration code...' }
// ↑ digit not allowed

dynamicValidator('mh', 'regCode')
// { isValid: false, error: 'Invalid registration code...' }
// ↑ must be uppercase

Format: [A-Z]{2} — exactly 2 uppercase letters

When used with quantique-forms and fieldType: 'regCode', input is auto-uppercased and capped at exactly 2 characters.


regNumber

Vehicle registration series number. 1–4 digits (1 through 9999).

dynamicValidator('1', 'regNumber')
// { isValid: true, error: '' }

dynamicValidator('9999', 'regNumber')
// { isValid: true, error: '' }

dynamicValidator('10000', 'regNumber')
// { isValid: false, error: 'Invalid registration number...' }
// ↑ 5 digits — too long

dynamicValidator('AB', 'regNumber')
// { isValid: false, error: 'Invalid registration number...' }
// ↑ letters not allowed

Format: \d{1,4} — 1 to 4 digits


policyNumber

Insurance policy number. Must start with P, followed by alphanumeric characters. Total length 2–10 characters.

dynamicValidator('PABC1234', 'policyNumber')
// { isValid: true, error: '' }

dynamicValidator('P1234567', 'policyNumber')
// { isValid: true, error: '' }

dynamicValidator('PA', 'policyNumber')
// { isValid: true, error: '' }  — minimum: P + 1 character

dynamicValidator('1234ABCD', 'policyNumber')
// { isValid: false, error: 'Invalid policy number...' }
// ↑ must start with P

dynamicValidator('PABCDEFGHIJ', 'policyNumber')
// { isValid: false, error: 'Invalid policy number...' }
// ↑ exceeds 10 characters total

dynamicValidator('P', 'policyNumber')
// { isValid: false, error: 'Invalid policy number...' }
// ↑ minimum 2 characters total

Format: P[A-Z0-9]{1,9} — P + 1–9 alphanumeric = 2–10 chars total


Contact & network


mobile

Indian mobile number. 10 digits, first digit must be 6, 7, 8, or 9.

dynamicValidator('9876543210', 'mobile')
// { isValid: true, error: '' }

dynamicValidator('1234567890', 'mobile')
// { isValid: false, error: 'Please enter a valid mobile' }
// ↑ starts with 1 — not a valid Indian number

dynamicValidator('987654321', 'mobile')
// { isValid: false, error: 'Please enter a valid mobile' }
// ↑ only 9 digits

// International number — use a custom regex
dynamicValidator('+14155552671', 'mobile', { regex: '^\\+1[2-9]\\d{9}$' })
// { isValid: true, error: '' }

Format: [6-9]\d{9} — 10 digits, first digit 6–9


email

Standard email address. RFC-compatible format check.

dynamicValidator('[email protected]', 'email')
// { isValid: true, error: '' }

dynamicValidator('[email protected]', 'email')
// { isValid: true, error: '' }

dynamicValidator('notanemail', 'email')
// { isValid: false, error: 'Please enter a valid email' }

dynamicValidator('', 'email')
// { isValid: true, error: '' }
// ↑ empty + not required = always valid

url

HTTP/HTTPS URL only. The protocol (http:// or https://) is required.

dynamicValidator('https://www.example.com', 'url')
// { isValid: true, error: '' }

dynamicValidator('ftp://files.example.com', 'url')
// { isValid: false, error: 'Please enter a valid URL' }
// ↑ only http/https accepted

dynamicValidator('www.example.com', 'url')
// { isValid: false, error: 'Please enter a valid URL' }
// ↑ must include the protocol

ip

IP address — IPv4, IPv6, or both. Use rules.version to restrict.

// IPv4
dynamicValidator('192.168.1.1', 'ip')
// { isValid: true, error: '' }

// IPv6
dynamicValidator('2001:db8::1', 'ip')
// { isValid: true, error: '' }

// Restrict to IPv4 only
dynamicValidator('2001:db8::1', 'ip', { version: 'v4' })
// { isValid: false, error: 'Invalid IP address' }

// Restrict to IPv6 only
dynamicValidator('192.168.1.1', 'ip', { version: 'v6' })
// { isValid: false, error: 'Invalid IP address' }

Security


password

Password validator with three configurable strength levels.

| rules.strength | Requirements | |---|---| | 'basic' (default) | At least one letter AND one digit | | 'medium' | Uppercase + lowercase + digit | | 'strong' | Uppercase + lowercase + digit + special character |

// Basic (default) — letter + digit
dynamicValidator('hello1', 'password')
// { isValid: true, error: '' }

dynamicValidator('hello', 'password')
// { isValid: false, error: 'Password must contain at least one letter and one number' }

// Medium — upper + lower + digit
dynamicValidator('Hello1', 'password', { strength: 'medium' })
// { isValid: true, error: '' }

// Strong — upper + lower + digit + special char
dynamicValidator('Hello1!', 'password', { strength: 'strong' })
// { isValid: true, error: '' }

// Combine strength with minimum length
dynamicValidator('Hello123!', 'password', { strength: 'strong', minLength: 8 })
// { isValid: true, error: '' }

custom

Regex-based validation with a user-supplied pattern.

// Basic pattern match
dynamicValidator('ABC-123', 'custom', { regex: '^[A-Z]{3}-[0-9]{3}$' })
// { isValid: true, error: '' }

// Custom error message
dynamicValidator('XY-99', 'custom', {
  regex: '^[A-Z]{3}-[0-9]{3}$',
  errorMessages: { invalid: 'Format must be AAA-000 (e.g. MUM-001)' },
})
// { isValid: false, error: 'Format must be AAA-000 (e.g. MUM-001)' }

7. ValidationRules — complete reference

All rules are optional. Pass only what you need.

interface ValidationRules {
  // ── Universal ──────────────────────────────────────────────────────────────
  required?: boolean;          // Reject empty strings. Default: false
  minLength?: number;          // Minimum character count
  maxLength?: number;          // Maximum character count

  // ── Numeric (number, float) ────────────────────────────────────────────────
  minValue?: number;           // Minimum numeric value
  maxValue?: number;           // Maximum numeric value

  // ── Float precision ────────────────────────────────────────────────────────
  decimalPrecision?: number;   // Max digits after the decimal point
  maxWholeDigits?: number;     // Max digits before the decimal point

  // ── Date ───────────────────────────────────────────────────────────────────
  format?: string;             // Date format. Default: 'DD/MM/YYYY'
  minAge?: number;             // Person must be at least this many years old
  maxAge?: number;             // Person must be at most this many years old
  minMinus?: number;           // Date must be no more than N units in the past
  maxMinus?: number;           // Date must be at least N units in the past
  minPlus?: number;            // Date must be at least N units in the future
  maxPlus?: number;            // Date must be no more than N units in the future
  unit?: 'day'|'days'|'month'|'months'|'year'|'years';

  // ── IP address ─────────────────────────────────────────────────────────────
  version?: 'v4' | 'v6';      // Restrict to IPv4 or IPv6. Default: accept both

  // ── Name ───────────────────────────────────────────────────────────────────
  type?: 'user' | 'corporate'; // Name sub-type. Default: 'user'

  // ── Password ───────────────────────────────────────────────────────────────
  strength?: 'basic' | 'medium' | 'strong';

  // ── Custom regex ───────────────────────────────────────────────────────────
  regex?: string;              // Raw regex string — compiled safely at runtime

  // ── Error messages ─────────────────────────────────────────────────────────
  errorMessages?: ErrorMessages;

  // ── Inline custom validator ────────────────────────────────────────────────
  customValidator?: (value: string) => ValidationResult | boolean;
}

8. ErrorMessages — custom error text

Override any default message without changing validation logic:

interface ErrorMessages {
  required?: string;          // Shown when required: true and value is empty
  minLength?: string;         // Shown when value is shorter than minLength
  maxLength?: string;         // Shown when value exceeds maxLength
  minValue?: string;          // Shown when number < minValue
  maxValue?: string;          // Shown when number > maxValue
  invalid?: string;           // Shown when the regex/format check fails
  regex?: string;             // Shown when rules.regex fails
  minAge?: string;            // Shown when date fails minAge
  maxAge?: string;            // Shown when date fails maxAge
  minPlus?: string;           // Shown when date is not far enough in the future
  maxPlus?: string;           // Shown when date is too far in the future
  minMinus?: string;          // Shown when date is too far in the past
  maxMinus?: string;          // Shown when date is not far enough in the past
  decimalPrecision?: string;  // Shown when decimal places exceed the limit
  maxWholeDigits?: string;    // Shown when whole digits exceed the limit
  emoji?: string;             // Shown when emoji characters are detected
  customValidation?: string;  // Default message for customValidator failures
}

Example — fully customised mobile field:

dynamicValidator('123', 'mobile', {
  required: true,
  errorMessages: {
    required: 'Mobile number is required to proceed.',
    invalid:  'Please enter a valid 10-digit Indian mobile number (starts with 6–9).',
  },
})
// { isValid: false, error: 'Please enter a valid 10-digit Indian mobile number (starts with 6–9).' }

9. Custom validators

9.1 External custom validator (4th argument)

Runs before the type-based check. Use for cross-field rules or server-lookups.

const takenEmails = new Set(['[email protected]', '[email protected]']);

dynamicValidator(
  '[email protected]',
  'email',
  { required: true },
  (value) => {
    if (takenEmails.has(value)) {
      return { isValid: false, error: 'This email address is already registered.' };
    }
    return true; // pass through to the type validator
  }
)
// { isValid: false, error: 'This email address is already registered.' }

Return types accepted:

  • { isValid: false, error: 'message' } → fail with that message
  • false → fail with the default customValidation message
  • true or { isValid: true } → pass through to the type validator

9.2 Inline custom validator (rules.customValidator)

dynamicValidator('short', 'string', {
  customValidator: (value) => {
    if (value.length < 10) {
      return { isValid: false, error: 'Must be at least 10 characters for this field.' };
    }
    return true;
  },
})
// { isValid: false, error: 'Must be at least 10 characters for this field.' }

9.3 Custom regex

Overrides the built-in pattern. Compiled safely at runtime with ReDoS protection.

dynamicValidator('ORD-000123', 'custom', {
  regex: '^[A-Z]{3}-[0-9]{6}$',
  errorMessages: { invalid: 'Order number must be format AAA-000000' },
})
// { isValid: true, error: '' }

10. Tree-shakeable named exports

Import individual validators to keep your bundle as small as possible:

import {
  validateEmail,
  validateMobile,
  validateAadhaar,
  validatePanCard,
  validateGST,
  validateBankIFSC,
  validatePincode,
  validateChassisNumber,
  validateEngineNumber,
  validateRTO,
  validateRegCode,
  validateRegNumber,
  validatePolicyNumber,
  validatePassword,
  validateDate,
  validateNumber,
  validateFloatNumber,
  validateName,
  validateFirstMiddleLastName,
  validateAddress,
  validateAlphanumeric,
  validateString,
  validateURL,
  validateIPAddress,
  validateCustom,
  defaultErrorMessages,
} from 'quantique-field-validator';

// Use a named validator directly
const result = validatePanCard('ABCDE1234F', {}, defaultErrorMessages, 'pan');
console.log(result); // { isValid: true, error: '' }

When to use named exports:

  • You need only 1–2 validators and want a minimal bundle
  • You are building your own form library on top of these validators
  • You are unit-testing a specific validator in isolation

11. TypeScript types

All types are exported. Import what you need:

import type {
  ValidationResult,       // { isValid: boolean; error: string }
  ValidationRules,        // All rules (see section 7)
  ErrorMessages,          // Custom error message map (see section 8)
  ValidatorType,          // Union of all 25 type strings
  ValidatorFn,            // Signature of a named validator
  CustomValidatorFn,      // (value: string) => ValidationResult | boolean
  DefaultErrorMessagesFn, // (rules, type) => DefaultMessages
} from 'quantique-field-validator';

ValidatorType

type ValidatorType =
  | 'string' | 'alphanumeric' | 'number' | 'float' | 'date'
  | 'name' | 'firstName' | 'middleName' | 'lastName'
  | 'email' | 'mobile' | 'address' | 'password' | 'url'
  | 'pincode' | 'aadhaar' | 'pan' | 'gst' | 'ifsc'
  | 'chassis' | 'engine' | 'rto'
  | 'regCode' | 'regNumber' | 'policyNumber'
  | 'ip' | 'custom'
  | (string & Record<never, never>);  // allows any string without losing autocomplete

12. Security: ReDoS protection

User-supplied regex strings (via rules.regex) are compiled using a ReDoS guard — tested for catastrophic backtracking patterns before use.

  • Patterns that would cause exponential backtracking are silently rejected — the value is treated as invalid (safe default)
  • Built-in patterns are individually reviewed for linear complexity
  • IPv4/IPv6 patterns use non-overlapping alternation to avoid catastrophic backtracking

Emoji blocking:
All validators run an emoji check before any other rule. Values containing emoji are always rejected.

dynamicValidator('hello😀', 'string')
// { isValid: false, error: 'Emoji characters are not allowed' }

dynamicValidator('MH😂', 'regCode')
// { isValid: false, error: 'Emoji characters are not allowed' }

13. Framework integration examples

React + react-hook-form

import { useForm } from 'react-hook-form';
import dynamicValidator from 'quantique-field-validator';

function VehicleForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  return (
    <form onSubmit={handleSubmit(console.log)}>
      <input
        {...register('pan', {
          required: 'PAN is required',
          validate: (value) => {
            const result = dynamicValidator(value, 'pan');
            return result.isValid || 'Enter a valid PAN (e.g. ABCDE1234F)';
          },
        })}
        placeholder="ABCDE1234F"
      />
      {errors.pan && <span>{errors.pan.message as string}</span>}
    </form>
  );
}

Vue 3 with vee-validate

import { useField } from 'vee-validate';
import dynamicValidator from 'quantique-field-validator';

function useMobileField() {
  return useField('mobile', (value) => {
    if (!value) return 'Mobile number is required';
    const result = dynamicValidator(String(value), 'mobile');
    return result.isValid ? true : result.error;
  });
}

Yup schema integration

import * as yup from 'yup';
import dynamicValidator from 'quantique-field-validator';

const vehicleSchema = yup.object({
  pan: yup
    .string()
    .required('PAN is required')
    .test('pan', 'Enter a valid PAN', (v) => !!v && dynamicValidator(v, 'pan').isValid),

  chassis: yup
    .string()
    .required()
    .test('chassis', 'Enter a valid chassis number', (v) =>
      !!v && dynamicValidator(v, 'chassis').isValid
    ),

  engine: yup
    .string()
    .required()
    .test('engine', 'Engine number must contain at least one letter', (v) =>
      !!v && dynamicValidator(v, 'engine').isValid
    ),
});

Node.js / Express API validation

import express from 'express';
import dynamicValidator from 'quantique-field-validator';

const router = express.Router();

router.post('/register-vehicle', (req, res) => {
  const { chassis, engine, regCode, regNumber, policyNumber } = req.body;

  const validations = [
    { field: 'chassis',      value: chassis,      type: 'chassis' },
    { field: 'engine',       value: engine,       type: 'engine' },
    { field: 'regCode',      value: regCode,      type: 'regCode' },
    { field: 'regNumber',    value: regNumber,    type: 'regNumber' },
    { field: 'policyNumber', value: policyNumber, type: 'policyNumber' },
  ] as const;

  const errors: Record<string, string> = {};

  for (const { field, value, type } of validations) {
    const result = dynamicValidator(value, type, { required: true });
    if (!result.isValid) errors[field] = result.error;
  }

  if (Object.keys(errors).length > 0) {
    return res.status(400).json({ errors });
  }

  res.json({ message: 'Vehicle registered successfully' });
});

14. Real-world example — vehicle registration form

A complete ISUZU-style vehicle registration form validating all fields:

import dynamicValidator from 'quantique-field-validator';

interface VehicleRegistrationForm {
  ownerName:    string;
  mobile:       string;
  pan:          string;
  aadhaar:      string;
  regCode:      string;   // e.g. MH
  regNumber:    string;   // e.g. 01
  chassisNo:    string;
  engineNo:     string;
  rtoCode:      string;   // e.g. MH-01
  policyNumber: string;
  policyExpiry: string;   // DD/MM/YYYY
  purchaseDate: string;   // DD/MM/YYYY
}

function validateVehicleForm(data: VehicleRegistrationForm) {
  const errors: Partial<Record<keyof VehicleRegistrationForm, string>> = {};

  const checks: Array<[keyof VehicleRegistrationForm, string, object]> = [
    ['ownerName',    'name',         { required: true }],
    ['mobile',       'mobile',       { required: true }],
    ['pan',          'pan',          { required: true }],
    ['aadhaar',      'aadhaar',      { required: true }],
    ['regCode',      'regCode',      { required: true }],
    ['regNumber',    'regNumber',    { required: true }],
    ['chassisNo',    'chassis',      { required: true }],
    ['engineNo',     'engine',       { required: true }],
    ['rtoCode',      'rto',          { required: true, version: 'new' }],
    ['policyNumber', 'policyNumber', { required: true }],
    ['policyExpiry', 'date',         { required: true, minPlus: 1, unit: 'days' }],
    ['purchaseDate', 'date',         { required: true, maxMinus: 1, unit: 'days' }],
  ];

  for (const [field, type, rules] of checks) {
    const result = dynamicValidator(data[field], type, rules);
    if (!result.isValid) errors[field] = result.error;
  }

  return { isValid: Object.keys(errors).length === 0, errors };
}

// Valid form — all pass
const result = validateVehicleForm({
  ownerName:    'Rajesh Kumar',
  mobile:       '9876543210',
  pan:          'ABCDE1234F',
  aadhaar:      '234567890123',
  regCode:      'MH',
  regNumber:    '01',
  chassisNo:    'MA3ERLF1S00100001',
  engineNo:     'G16B12345',
  rtoCode:      'MH-01',
  policyNumber: 'P1234567',
  policyExpiry: '01/06/2027',
  purchaseDate: '01/01/2024',
});

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

// Invalid — all-digit chassis/engine fail
const invalid = validateVehicleForm({
  ...result,
  chassisNo: '000000000000',     // rejected — no letters
  engineNo:  '00000000000',      // rejected — no letters
});

console.log(invalid.errors.chassisNo); // 'Invalid chassis number'
console.log(invalid.errors.engineNo);  // 'Invalid engine number'

15. Changelog

v2.0.5

  • Fixed import * as dynamicValidator from 'quantique-field-validator'; dynamicValidator(value, type) throwing "not a function" in webpack 4 / CRA projects — the CJS build now sets __esModule as an enumerable property on module.exports, which causes Babel's _interopRequireWildcard to return the function itself instead of wrapping it in a plain object
  • Fixed dynamicValidator is not a function error when using require('quantique-field-validator')(value, type) — CJS build now exports the function as module.exports directly (with named exports as properties)
  • Fixed non-string values (e.g. 9876543210 as a JS number from an API response) were reaching validators as-is due to a TypeScript-only as string cast; dynamicValidator now coerces all input to string at the entry point via String(value)
  • Fixed three pre-existing test expectations corrected to match documented validator behaviour:
    • Chassis I/O/Q rejection documented as opt-in via custom regex rule (not default)
    • Chassis min-length test now correctly uses a 4-char string (below the 5-char minimum)
    • Engine min-length test now correctly uses a 1-char string (below the 2-char minimum)

v2.0.4

  • Added ESM output (dist/esm/) — package now ships as dual CJS + ESM
    • "import" condition → dist/esm/index.js (for Vite, Rollup, Next.js)
    • "require" condition → dist/index.js (for webpack, Jest, Node.js)
    • Fixes all bundler compatibility issues including webpack 5 and Vite workspace symlinks
  • Fixed chassis validator: regex updated to (?=.*[A-Z])[A-Z0-9]{5,17} — all-digit strings (e.g. 000000000000) are now correctly rejected
  • Fixed engine validator: regex updated to (?=.*[A-Za-z])[A-Za-z0-9]{2,25} — all-digit strings (e.g. 00000000000000000000) are now correctly rejected
  • Added regCode validator — exactly 2 uppercase alphabets (e.g. MH, DL)
  • Added regNumber validator — 1–4 digit numeric string
  • Added policyNumber validator — starts with P, alphanumeric, max 10 characters

v2.0.3

  • Fixed webpack 5 compatibility: added "default" export condition to package.json exports — resolved "not exported under the conditions" error

v2.0.2

  • Initial public release with 22 validator types
  • Full TypeScript support
  • ReDoS-safe regex compilation
  • Tree-shakeable named exports

License

MIT © Saket Brij Sinha