quantique-field-validator
v2.0.6
Published
Enterprise-grade dynamic field validator for Indian and international form fields.
Maintainers
Readme
quantique-field-validator
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
- What is this?
- Why use it?
- Installation
- Quick start — 30 seconds
- How it works
- All 25 validator types
- ValidationRules — complete reference
- ErrorMessages — custom error text
- Custom validators
- Tree-shakeable named exports
- TypeScript types
- Security: ReDoS protection
- Framework integration examples
- Real-world example — vehicle registration form
- 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-validatorRequirements: 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.jsrequire(), 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 * ason 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 toimport dynamicValidator from(default) orimport { 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 letter5. 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 typeThis 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 digitsFormat: [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 charactersFormat: [A-Z]{5}[0-9]{4}[A-Z] — e.g. ABCDE1234F
When used with
quantique-formsandfieldType: '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 00Format: \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 digitsFormat: [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 uppercaseFormat: [A-Z]{2} — exactly 2 uppercase letters
When used with
quantique-formsandfieldType: '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 allowedFormat: \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 totalFormat: 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 validurl
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 protocolip
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 messagefalse→ fail with the default customValidation messagetrueor{ 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 autocomplete12. 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__esModuleas an enumerable property onmodule.exports, which causes Babel's_interopRequireWildcardto return the function itself instead of wrapping it in a plain object - Fixed
dynamicValidator is not a functionerror when usingrequire('quantique-field-validator')(value, type)— CJS build now exports the function asmodule.exportsdirectly (with named exports as properties) - Fixed non-string values (e.g.
9876543210as a JS number from an API response) were reaching validators as-is due to a TypeScript-onlyas stringcast;dynamicValidatornow coerces all input tostringat the entry point viaString(value) - Fixed three pre-existing test expectations corrected to match documented validator behaviour:
- Chassis I/O/Q rejection documented as opt-in via custom
regexrule (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)
- Chassis I/O/Q rejection documented as opt-in via custom
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
chassisvalidator: regex updated to(?=.*[A-Z])[A-Z0-9]{5,17}— all-digit strings (e.g.000000000000) are now correctly rejected - Fixed
enginevalidator: regex updated to(?=.*[A-Za-z])[A-Za-z0-9]{2,25}— all-digit strings (e.g.00000000000000000000) are now correctly rejected - Added
regCodevalidator — exactly 2 uppercase alphabets (e.g.MH,DL) - Added
regNumbervalidator — 1–4 digit numeric string - Added
policyNumbervalidator — starts withP, alphanumeric, max 10 characters
v2.0.3
- Fixed webpack 5 compatibility: added
"default"export condition topackage.jsonexports — 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
