orange-dragonfly-validator
v1.0.0
Published
Library for input parameters validation
Maintainers
Readme
Orange Dragonfly Validator
A type-safe, zero-dependency validation library for Node.js and TypeScript. Define schemas as plain objects, get validated and strongly-typed data back.
import { parse } from 'orange-dragonfly-validator'
const schema = {
name: { type: 'string', required: true, min: 1, max: 100 },
email: { type: 'string', required: true, special: 'email' },
age: { type: 'integer', min: 0, max: 150 },
} as const
const data = parse(schema, input)
// data: { name: string; email: string; age?: number }Features
- Type inference — output types are inferred from
as constschemas, no manual typing needed - Zero dependencies — nothing to audit, nothing to break
- Dual format — ships ESM and CommonJS with full TypeScript declarations
- Structured errors — machine-readable codes, human-readable messages, contextual parameters
- Nested validation — recursive schemas for objects and arrays of any depth
- JSON Schema interop — two-way conversion between ODValidator schemas and JSON Schema
- Built-in format validators — email, URL, UUID, phone, IPv4, date, datetime, hex color
- Custom transforms — transform values before or instead of validation
Installation
npm install orange-dragonfly-validatorRequires Node.js 18 or later.
Quick Start
parse — validate and throw on failure
import { parse, ODValidatorException } from 'orange-dragonfly-validator'
const schema = {
name: { type: 'string', required: true, pattern: /^[A-Z][a-z]+$/ },
position: { type: 'string', required: true },
term_ends: { type: 'integer', min: 2025 },
} as const
try {
const data = parse(schema, {
name: 'Donald',
position: 'President of the United States',
term_ends: 2021,
})
} catch (e) {
if (e instanceof ODValidatorException) {
console.error(e.info)
// { term_ends: ['Minimal value (length) is 2025. 2021 provided'] }
}
}safeParse — validate without throwing
import { safeParse } from 'orange-dragonfly-validator'
const result = safeParse(schema, input)
if (result.success) {
console.log(result.data.name) // typed as string
} else {
console.log(result.errors) // Record<string, ODValidatorErrorEntry[]>
}ODValidator — class-based usage
import { ODValidator, ODValidatorRules } from 'orange-dragonfly-validator'
const rules = new ODValidatorRules(schema)
const validator = new ODValidator(rules, {
strictMode: true,
exceptionMode: true,
})
validator.validate(input)
console.log(validator.data)
console.log(validator.errors)Schema Reference
A schema is a plain object mapping field names to rule definitions. No field in a rule definition is required — an empty {} accepts any value.
Rule Properties
| Property | Type | Description |
|----------|------|-------------|
| type | string \| string[] | Allowed type(s): 'string', 'number', 'integer', 'array', 'object', 'boolean', 'null', 'function' |
| required | boolean | If true, field must be present in input |
| default | any | Value used when field is missing from input |
| in | unknown[] | Whitelist of allowed values |
| in:public | unknown[] \| boolean | Controls which in values appear in error messages. true exposes the in list; an array overrides it |
| min | number | Minimum value (numbers), length (strings), or item count (arrays) |
| max | number | Maximum value (numbers), length (strings), or item count (arrays) |
| pattern | RegExp \| string | Regex the value must match (strings and numbers) |
| special | string | Built-in format validator name (see Format Validators) |
| transform | function | (value: unknown) => unknown — transforms value before validation |
| apply_transformed | boolean | If true, the transformed value replaces the original in output |
| children | ODValidatorRulesSchema | Nested schema for object properties or array elements |
| per_type | object | Type-specific rule overrides (see Per-Type Rules) |
Meta-Keys
Schemas support special meta-keys that control behavior at the schema level:
const schema = {
'@': { strict: false }, // Schema options
'#': { type: 'string', pattern: /^[a-z_]+$/ }, // Key name validator
'*': { type: 'string', max: 255 }, // Wildcard — applies to all values
// Regular field rules
name: { type: 'string', required: true },
} as const| Key | Purpose |
|-----|---------|
| @ | Schema-level options. Currently supports strict (boolean, default true) — when true, rejects input keys not defined in the schema |
| # | Key validator — validates the property names themselves, not values |
| * | Wildcard rule — applied to every value in the input |
Nested Validation
Use children to validate nested objects and arrays:
const schema = {
users: {
type: 'array',
required: true,
min: 1,
children: {
'*': { // Every array element must match this rule
type: 'object',
children: {
name: { type: 'string', required: true },
role: { type: 'string', in: ['admin', 'user'] },
},
},
},
},
settings: {
type: 'object',
children: {
'@': { strict: false }, // Allow any keys in settings
'#': { pattern: /^[a-z_]+$/ }, // But key names must be lowercase
'*': { type: 'string' }, // And all values must be strings
},
},
} as constPer-Type Rules
When a field accepts multiple types, use per_type to apply different constraints per type:
const schema = {
value: {
type: ['string', 'number'],
per_type: {
string: { min: 1, max: 255 }, // String length 1–255
number: { min: 0, max: 1000 }, // Numeric value 0–1000
},
},
} as constper_type accepts: in, in:public, min, max, pattern, special, transform, apply_transformed, children.
Schema Builder
Use ODValidatorRules.create() for fluent schema construction with full autocomplete:
import { ODValidatorRules, parse } from 'orange-dragonfly-validator'
const rules = ODValidatorRules.create()
.property('name', p => p.required().string().min(1).max(100))
.property('email', p => p.required().string().special('email'))
.property('age', p => p.integer().min(0).max(150))
.property('role', p => p.string().in(['admin', 'user']).default('user'))
.strict()
.complete()
const validator = new ODValidator(rules)
validator.validate(input)The builder can also produce a raw schema for use with parse and safeParse:
const schema = ODValidatorRules.create()
.property('name', p => p.required().string())
.toSchema()
const data = parse(schema, input)Nested Schemas with the Builder
Use .children() and .wildcard() for nested validation:
const rules = ODValidatorRules.create()
.property('users', p => p.required().array().min(1)
.children(c => c
.wildcard(p => p.object()
.children(c => c
.property('name', p => p.required().string())
.property('role', p => p.string().in(['admin', 'user']))
)
)
)
)
.property('settings', p => p.object()
.children(c => c
.strict(false)
.keyValidator(p => p.pattern(/^[a-z_]+$/))
.wildcard(p => p.string().max(255))
)
)
.complete()Multi-Type Fields with the Builder
Use .perType() or chain multiple type methods:
const rules = ODValidatorRules.create()
.property('value', p => p.string().number()
.perType('string', p => p.min(1).max(255))
.perType('number', p => p.min(0).max(1000))
)
.complete()Builder API Reference
ODValidatorSchemaBuilder (returned by ODValidatorRules.create()):
| Method | Description |
|--------|-------------|
| .property(name, configure) | Add a named field rule |
| .wildcard(configure) | Set the * (all values) rule |
| .keyValidator(configure) | Set the # (key name) validator |
| .strict(value?) | Set strict mode (default true) |
| .toSchema() | Return the built ODValidatorRulesSchema |
| .complete() | Return an ODValidatorRules instance |
ODValidatorPropertyBuilder (passed to configure callbacks):
| Method | Description |
|--------|-------------|
| .string(), .number(), .integer(), .boolean(), .array(), .object(), .null() | Add an allowed type |
| .type(t) | Add type(s) by name |
| .required() | Mark as required |
| .default(value) | Set default value |
| .min(n), .max(n) | Set min/max constraint |
| .in(values) | Set allowed values whitelist |
| .inPublic(values) | Control in values in error messages |
| .pattern(p) | Set regex pattern |
| .special(name) | Set built-in format validator |
| .transform(fn) | Set transform function |
| .applyTransformed() | Replace original with transformed value |
| .children(configure) | Set nested schema (receives ODValidatorSchemaBuilder) |
| .perType(type, configure) | Set type-specific overrides |
Type Inference
Schemas declared with as const (or as const satisfies ODValidatorRulesSchema) enable full type inference on validated data:
import { parse, type ODValidatorInfer, type ODValidatorRulesSchema } from 'orange-dragonfly-validator'
const schema = {
name: { type: 'string', required: true },
age: { type: 'integer' },
status: { type: 'string', in: ['active', 'inactive'] },
tags: {
type: 'array',
children: {
'*': { type: 'string' },
},
},
} as const satisfies ODValidatorRulesSchema
// Extract the type without calling parse
type UserInput = ODValidatorInfer<typeof schema>
// {
// name: string
// age?: number
// status?: 'active' | 'inactive'
// tags?: string[]
// }
// parse() returns the same type
const data = parse(schema, input)
data.name // string (required)
data.age // number | undefined (optional)
data.status // 'active' | 'inactive' | undefined
data.tags // string[] | undefinedHow Inference Works
required: truefields become required properties; all others are optionaltypemaps to its TypeScript equivalent ('integer'and'number'both map tonumber)innarrows the type to a literal unionchildrenrecurses for nested objects and arrays
Format Validators
Use the special property to apply built-in format validation:
const schema = {
email: { type: 'string', special: 'email' },
website: { type: 'string', special: 'url' },
id: { type: 'string', special: 'uuid' },
phone: { type: 'string', special: 'phone' },
usPhone: { type: 'string', special: 'us-phone' },
ip: { type: 'string', special: 'ipv4' },
birthday: { type: 'string', special: 'date' },
created: { type: 'string', special: 'datetime' },
color: { type: 'string', special: 'hex-color' },
} as const| Name | Format | Example |
|------|--------|---------|
| email | RFC 5321 | [email protected] |
| phone | E.164 international | +12025551234 |
| us-phone | US format (+1 + 10 digits) | +12025551234 |
| url | HTTP/HTTPS with optional port/path | https://example.com:8080/path |
| uuid | v1–5, case-insensitive | 550e8400-e29b-41d4-a716-446655440000 |
| ipv4 | Four octets (0–255) | 192.168.1.1 |
| date | ISO 8601 date | 2025-02-16 |
| datetime | ISO 8601 with timezone | 2025-02-16T12:34:56Z |
| hex-color | Hex (#RGB, #RRGGBB, #RRGGBBAA) | #FF00FF |
The underlying RegExp patterns are also exported individually (EMAIL_PATTERN, URL_PATTERN, etc.) and as the SPECIAL_VALIDATORS map for custom use.
Note: Built-in format validators use simplified regex patterns optimized for common cases. For example, the
patternwith your own regex.
Transforms
Use transform to modify values before validation. Combine with apply_transformed to include the transformed value in the output:
const schema = {
email: {
type: 'string',
required: true,
special: 'email',
transform: (v) => typeof v === 'string' ? v.toLowerCase().trim() : v,
apply_transformed: true, // Output will contain the lowercased, trimmed email
},
score: {
type: 'number',
transform: (v) => typeof v === 'string' ? parseFloat(v) : v,
apply_transformed: true,
},
} as constWithout apply_transformed, the transform is only used for validation — the original value is preserved in the output.
Error Handling
Error Structure
Every validation error includes a machine-readable code, a human-readable message, and contextual parameters:
interface ODValidatorErrorEntry {
code: string // e.g. 'TYPE_MISMATCH'
message: string // e.g. 'Incorrect type: string required, number provided'
params?: Record<string, unknown> // e.g. { expected: 'string', actual: 'number' }
}
// Errors are keyed by field path
type ODValidatorErrors = Record<string, ODValidatorErrorEntry[]>
// e.g. { 'users.0.name': [{ code, message, params }] }Error Codes
| Code | Meaning |
|------|---------|
| REQUIRED | Required field is missing |
| NOT_ALLOWED | Field is not defined in schema (strict mode) |
| TYPE_MISMATCH | Value type doesn't match type constraint |
| MIN_VIOLATION | Value/length/count is below min |
| MAX_VIOLATION | Value/length/count is above max |
| PATTERN_MISMATCH | Value doesn't match pattern regex |
| INVALID_FORMAT | Value doesn't match special format |
| VALUE_NOT_IN_LIST | Value is not in the in whitelist |
| ARRAY_ELEMENT_NOT_IN_LIST | Array contains elements not in in list |
| CHILDREN_TYPE_ERROR | children applied to non-object/non-array |
Error codes are available as the ErrorCode constant for programmatic comparison.
Exception Types
import { ODValidatorException, ODValidatorRulesException } from 'orange-dragonfly-validator'
// ODValidatorException — input validation failed (expected at runtime)
try {
parse(schema, badInput)
} catch (e) {
if (e instanceof ODValidatorException) {
e.details // ODValidatorErrors — full structured errors
e.info // Record<string, string[]> — simplified (field → messages)
e.message // 'Validation failed'
}
}
// ODValidatorRulesException — schema itself is invalid (programming error)
// Extends ODValidatorException. Thrown by parse, safeParse, and validateSchema.safeParse catches ODValidatorException and returns { success: false, errors } instead — but still throws ODValidatorRulesException since invalid schemas are programming errors.
Custom Error Messages
Provide a messageFormatter to customize or localize error messages:
import { parse, type ODValidatorMessageFormatter } from 'orange-dragonfly-validator'
const messageFormatter: ODValidatorMessageFormatter = (code, params) => {
switch (code) {
case 'REQUIRED': return 'This field is required'
case 'TYPE_MISMATCH': return `Expected ${params.expected}, got ${params.actual}`
case 'MIN_VIOLATION': return `Must be at least ${params.min}`
case 'MAX_VIOLATION': return `Must be at most ${params.max}`
default: return 'Invalid value'
}
}
const data = parse(schema, input, { messageFormatter })JSON Schema Interop
Convert between ODValidator schemas and JSON Schema (draft-07 / 2020-12):
import { fromJsonSchema, toJsonSchema } from 'orange-dragonfly-validator'
// JSON Schema → ODValidator
const { schema, warnings } = fromJsonSchema({
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
age: { type: 'integer', minimum: 0 },
},
required: ['name'],
})
// warnings lists any unsupported features that were skipped
// ODValidator → JSON Schema
const jsonSchema = toJsonSchema(odvSchema)Supported JSON Schema Features
type, properties, required, items, enum, const, default, minimum, maximum, exclusiveMinimum, exclusiveMaximum, minLength, maxLength, minItems, maxItems, pattern, format, additionalProperties, propertyNames.
Unsupported JSON Schema Features
$ref, oneOf, anyOf, allOf, not, if/then/else, dependencies, prefixItems, contains, patternProperties. These generate warnings during conversion and are silently skipped.
Schema Validation
Validate that a plain JSON object is a well-formed ODValidator schema (useful when schemas come from external sources):
import { validateSchema } from 'orange-dragonfly-validator'
try {
const schema = validateSchema(jsonFromExternalSource)
// schema is now typed as ODValidatorRulesSchema
} catch (e) {
// ODValidatorRulesException — schema is malformed
}API Reference
Functions
| Function | Signature | Description |
|----------|-----------|-------------|
| parse | parse<S>(schema, input, options?, errorsPrefix?): ODValidatorInfer<S> | Validate and return typed data. Throws ODValidatorException on failure |
| safeParse | safeParse<S>(schema, input, options?, errorsPrefix?): SafeParseResult<S> | Like parse but returns { success, data } or { success, errors } instead of throwing |
| validateSchema | validateSchema(json): ODValidatorRulesSchema | Validates a JSON object is a well-formed schema. Throws ODValidatorRulesException if not |
| fromJsonSchema | fromJsonSchema(jsonSchema): { schema, warnings } | Convert JSON Schema to ODValidator schema |
| toJsonSchema | toJsonSchema(schema): JsonSchema | Convert ODValidator schema to JSON Schema |
Classes
| Class | Description |
|-------|-------------|
| ODValidator | Core validator with validate(input) method, data/errors properties, and configurable strictMode/exceptionMode |
| ODValidatorRules | Schema container with static validate() and normalize() methods |
| ODValidatorRule | Single rule processor with apply() method |
| ODValidatorException | Validation failure error with details (structured) and info (simplified) |
| ODValidatorRulesException | Schema definition error (extends ODValidatorException) |
Types
| Type | Description |
|------|-------------|
| ODValidatorInfer<S> | Infers TypeScript type from a schema |
| SafeParseResult<S> | Discriminated union: { success: true; data: ODValidatorInfer<S> } \| { success: false; errors: ODValidatorErrors } |
| ODValidatorRulesSchema | Schema object type |
| ODValidatorRuleSchema | Single rule definition type |
| ODValidatorPerTypeRuleSchema | Per-type override rule type |
| ODValidatorOptions | Constructor options for ODValidator |
| ODValidatorErrors | Record<string, ODValidatorErrorEntry[]> |
| ODValidatorErrorEntry | { code: string; message: string; params?: Record<string, unknown> } |
| ODValidatorErrorCode | Union of all error code string literals |
| ODValidatorMessageFormatter | (code: ODValidatorErrorCode, params: Record<string, unknown>) => string |
| ODValidatorValueType | 'string' \| 'number' \| 'integer' \| 'array' \| 'object' \| 'boolean' \| 'function' \| 'null' |
| JsonSchema | JSON Schema type definition |
| FromJsonSchemaResult | { schema: ODValidatorRulesSchema; warnings: string[] } |
Architecture
Input + Schema
│
▼
parse() / safeParse()
│
▼
ODValidatorRules ─── normalizes schema (deep clone, type normalization)
│
▼
ODValidator.process()
│
├── Apply wildcard ('*') rules to all values
├── Validate key names ('#') if defined
├── Apply defaults for missing fields
├── For each field:
│ └── ODValidatorRule.applyRule()
│ ├── transform (if defined)
│ ├── type check
│ ├── per_type overrides (if matched)
│ ├── min / max constraints
│ ├── in (whitelist) check
│ ├── pattern / special format check
│ └── children (recursive)
├── Enforce strict mode (reject undeclared keys)
│
▼
Typed data or structured errors- Fields are validated independently — errors are accumulated (not short-circuited)
- Input is shallow-cloned; the original object is never mutated
- Schema normalization adds
'integer'when'number'is specified, and normalizes single types to arrays
Limitations
- No cross-field dependencies — each field is validated independently. Use
transformor multi-pass validation for dependent fields - No union schemas —
oneOf/anyOf/allOffrom JSON Schema are not supported - No conditional logic —
if/then/elseis not supported - No async validation — all validation is synchronous
- No schema composition — no
pick/omit/partial/mergeutilities; compose schemas manually - No pattern complexity validation —
patternvalues (RegExp or string) are used as-is. Schemas should be defined by developers, not constructed from end-user input. If you accept patterns from untrusted sources, validate them externally to prevent ReDoS attacks
License
ISC
