TypeScript validation library with type-safe validators
npm install sibyl-ts

> _A TypeScript validation library inspired by the Sibyl System from Psycho-Pass - precise, unwavering judgment for your data._
- Type-safe: Full TypeScript support with automatic type inference
- Comprehensive validators: String, number, boolean, date, array, object, tuple, union, and more
- String validators: Email, URL, UUID, IP address validation
- Safe judgment: tryJudge() method returns results without throwing
- Detailed errors: Rich error messages with path information
- Composable: Build complex validators from simple ones
- Dual module support: Works with both ESM and CommonJS
- Installation
- Quick Start
- Usage
- Basic Validation
- Object Validation
- Safe Judgment
- Union Types
- Optional and Nullable
- Available Validators
- Primitive Validators
- String Validators
- Complex Validators
- Literal & Enum Validators
- Modifier Validators
- Type Inference
- Real World Examples
- TypeScript Configuration
- Error Handling
- Troubleshooting
- Contributing
- License
- Changelog
``bashnpm
npm install sibyl-ts
Quick Start
$3
`typescript
import { str, num, obj, array, email } from 'sibyl-ts';
`$3
`typescript
const { str, num, obj, array, email } = require('sibyl-ts');
`Usage
$3
`typescript
import { str, num, bool } from 'sibyl-ts';// String validation
const nameValidator = str({ minLen: 2, maxLen: 50 });
const inspector = nameValidator.judge('Akane Tsunemori'); // Returns: "Akane Tsunemori"
// Number validation (Crime Coefficient)
const crimeCoefficientValidator = num({ min: 0, max: 300 });
const coefficient = crimeCoefficientValidator.judge(45); // Returns: 45
// Boolean validation
const isEnforcerValidator = bool();
const isEnforcer = isEnforcerValidator.judge(true); // Returns: true
`$3
`typescript
import { str, num, obj, array, email, date, bool } from 'sibyl-ts';// Define a complex object validator for MWPSB personnel
const inspectorValidator = obj({
name: str({ minLen: 2, maxLen: 50 }),
crimeCoefficient: num({ min: 0, max: 300 }),
email: email(),
roles: array(str()),
profile: obj({
joinedAt: date(),
isLatentCriminal: bool(),
}),
});
// Type is automatically inferred!
const inspector = inspectorValidator.judge({
name: 'Akane Tsunemori',
crimeCoefficient: 28,
email: 'akane.tsunemori@mwpsb.go.jp',
roles: ['inspector', 'unit-one'],
profile: {
joinedAt: new Date('2112-04-01'),
isLatentCriminal: false,
},
});
// Type: { name: string; crimeCoefficient: number; email: string; roles: string[]; profile: { joinedAt: Date; isLatentCriminal: boolean } }
`$3
Use
tryJudge() when you want to handle validation errors gracefully without throwing exceptions:`typescript
import { num } from 'sibyl-ts';const crimeCoefficientValidator = num({ min: 0, max: 300 });
// tryJudge() returns a result object instead of throwing
const result = crimeCoefficientValidator.tryJudge(45);
if (result.type === 'success') {
console.log(
Crime Coefficient: ${result.data}); // "Crime Coefficient: 45"
console.log('Target is not a threat. Trigger will be locked.');
} else {
console.error('Validation failed:', result.issues);
}// Example with invalid data
const enforcerCheck = crimeCoefficientValidator.tryJudge(350);
if (enforcerCheck.type === 'error') {
console.error(enforcerCheck.issues);
// [{ message: "Value 350 is greater than max 300", path: [] }]
console.log('Enforcement mode: Lethal Eliminator');
}
// Compare with judge() which throws exceptions
try {
crimeCoefficientValidator.judge(500); // Throws!
} catch (error) {
console.error('Exception thrown!');
}
`When to use:
-
judge() - When you want exceptions thrown (typical validation)
- tryJudge() - When you want to handle errors gracefully (user input, APIs)$3
`typescript
import { union, str, num, lit } from 'sibyl-ts';// Dominator mode can be lethal or non-lethal
const dominatorModeValidator = union([lit('paralyzer'), lit('eliminator'), lit('decomposer')]);
dominatorModeValidator.judge('paralyzer'); // OK
dominatorModeValidator.judge('eliminator'); // OK
dominatorModeValidator.judge('stun'); // Error!
`$3
`typescript
import { str, optional, nullable, nullish } from 'sibyl-ts';// Some inspectors may not have an enforcer assigned
const enforcerNameValidator = optional(str()); // string | undefined
enforcerNameValidator.judge('Shinya Kogami'); // OK
enforcerNameValidator.judge(undefined); // OK
// Crime coefficient can be null for non-citizens
const crimeCoefficientValidator = nullable(num()); // number | null
crimeCoefficientValidator.judge(120); // OK
crimeCoefficientValidator.judge(null); // OK
// Hue color can be nullish
const hueColorValidator = nullish(str()); // string | null | undefined
hueColorValidator.judge('clear'); // OK
hueColorValidator.judge(null); // OK
hueColorValidator.judge(undefined); // OK
`Available Validators
$3
####
str(options?)String validation with optional constraints.
`typescript
import { str } from 'sibyl-ts';// Basic string
const nameValidator = str();
nameValidator.judge('Akane Tsunemori'); // ✓
// With length constraints
const enforcerIdValidator = str({ minLen: 5, maxLen: 10 });
enforcerIdValidator.judge('ENF-001'); // ✓
enforcerIdValidator.judge('E1'); // ✗ Too short
// With pattern (regex)
const idPatternValidator = str({ pattern: /^[A-Z]{3}-\d{3}$/ });
idPatternValidator.judge('INS-042'); // ✓
`Options:
-
minLen?: number - Minimum string length
- maxLen?: number - Maximum string length
- pattern?: string - Regex pattern to match
- startsWith?: string - Ensure string starts with a specific prefix
- endsWith?: string - Ensure string ends with a specific suffix
- trim?: boolean - Trim whitespace before validation
- coerce?: boolean - Coerce non-string values to strings---
####
num(options?)Number validation with optional min/max values.
`typescript
import { num } from 'sibyl-ts';// Crime Coefficient (0-300)
const coefficientValidator = num({ min: 0, max: 300 });
coefficientValidator.judge(85); // ✓
coefficientValidator.judge(350); // ✗ Too high
// Age with coercion
const ageValidator = num({ min: 18, coerce: true });
ageValidator.judge('25'); // ✓ Coerced to 25
ageValidator.judge(30); // ✓
`Options:
-
min?: number - Minimum value
- max?: number - Maximum value
- int?: boolean - Ensure value is an integer
- positive?: boolean - Ensure value is positive (> 0)
- negative?: boolean - Ensure value is negative (< 0)
- coerce?: boolean - Coerce strings to numbers---
####
bool(options?)Boolean validation.
`typescript
import { bool } from 'sibyl-ts';// Strict boolean
const isLatentValidator = bool();
isLatentValidator.judge(true); // ✓
isLatentValidator.judge(false); // ✓
isLatentValidator.judge(1); // ✗ Not a boolean
// With coercion
const activeFlagValidator = bool({ coerce: true });
activeFlagValidator.judge('true'); // ✓ Coerced to true
activeFlagValidator.judge(1); // ✓ Coerced to true
activeFlagValidator.judge(0); // ✓ Coerced to false
`Options:
-
coerce?: boolean - Coerce truthy/falsy values to boolean---
####
date(options?)Date validation.
`typescript
import { date } from 'sibyl-ts';// Basic date validation
const joinDateValidator = date();
joinDateValidator.judge(new Date('2112-04-01')); // ✓
joinDateValidator.judge('2112-04-01'); // ✓ String is auto-converted
// With min/max constraints
const recentDateValidator = date({
min: new Date('2110-01-01'),
max: new Date('2115-12-31'),
});
recentDateValidator.judge(new Date('2112-04-01')); // ✓
recentDateValidator.judge(new Date('2100-01-01')); // ✗ Before min
`Expected input: JavaScript
Date object or valid date stringOptions:
-
min?: Date - Minimum date
- max?: Date - Maximum date---
####
unknown()Passthrough validator that accepts any value. Useful for data that hasn't been scanned by the Sibyl System yet.
`typescript
import { unknown } from 'sibyl-ts';const unscannedEvidenceValidator = unknown();
unscannedEvidenceValidator.judge('mysterious device'); // ✓
unscannedEvidenceValidator.judge({ threatLevel: 'unknown' }); // ✓
`Type:
unknown---
$3
####
email()Email address validation (RFC 5322).
`typescript
import { email } from 'sibyl-ts';const emailValidator = email();
emailValidator.judge('akane@mwpsb.go.jp'); // ✓
emailValidator.judge('kogami@enforcer'); // ✗ Invalid format
`---
####
url()URL validation.
`typescript
import { url } from 'sibyl-ts';const urlValidator = url();
urlValidator.judge('https://sibyl-system.jp'); // ✓
urlValidator.judge('not-a-url'); // ✗
`---
####
uuid()UUID validation (v4).
`typescript
import { uuid } from 'sibyl-ts';const sessionIdValidator = uuid();
sessionIdValidator.judge('550e8400-e29b-41d4-a716-446655440000'); // ✓
sessionIdValidator.judge('not-a-uuid'); // ✗
`---
####
ip()IP address validation (IPv4 and IPv6).
`typescript
import { ip } from 'sibyl-ts';const ipValidator = ip();
ipValidator.judge('192.168.1.1'); // ✓ IPv4
ipValidator.judge('2001:0db8::1'); // ✓ IPv6
ipValidator.judge('999.999.999.999'); // ✗ Invalid
`---
$3
####
array(elementValidator, options?)Array validation with typed elements.
`typescript
import { array, str } from 'sibyl-ts';// Array of enforcer names
const enforcersValidator = array(str());
enforcersValidator.judge(['Shinya Kogami', 'Nobuchika Ginoza']); // ✓
enforcersValidator.judge(['Valid', 123]); // ✗ Element 1 is not a string
// With length constraints
const rolesValidator = array(str(), { minLen: 1, maxLen: 5 });
rolesValidator.judge(['inspector', 'analyst']); // ✓
rolesValidator.judge([]); // ✗ Too short
`Options:
-
minLen?: number - Minimum array length
- maxLen?: number - Maximum array length---
####
obj(shape)Object validation with typed properties.
`typescript
import { obj, str, num } from 'sibyl-ts';const enforcerValidator = obj({
name: str(),
coefficient: num({ min: 100, max: 300 }),
division: str(),
});
enforcerValidator.judge({
name: 'Shinya Kogami',
coefficient: 180,
division: 'Unit 1',
}); // ✓
enforcerValidator.judge({
name: 'Kogami',
// Missing required fields
}); // ✗
`Expected input: Object matching the shape definition
---
####
partial(objectValidator)Creates a validator where all properties of an object are optional.
`typescript
import { obj, partial, str, num } from 'sibyl-ts';// Define an enforcer profile
const enforcerProfileValidator = obj({
name: str(),
coefficient: num({ min: 100, max: 300 }),
division: str(),
yearsOfService: num(),
});
// Make all properties optional for partial updates
const partialEnforcerValidator = partial(enforcerProfileValidator);
// All fields are now optional
partialEnforcerValidator.judge({
coefficient: 195, // Update only the coefficient
}); // ✓
partialEnforcerValidator.judge({
name: 'Shinya Kogami',
division: 'Unit 1',
}); // ✓ Update name and division
partialEnforcerValidator.judge({}); // ✓ Empty object is valid
`Parameters:
-
objectValidator - An object validator created with obj()---
####
omit(objectValidator, keys)Creates a validator that omits specific properties from an object validator.
`typescript
import { obj, omit, str, num, bool, email } from 'sibyl-ts';// Full inspector profile
const inspectorValidator = obj({
name: str(),
crimeCoefficient: num({ min: 0, max: 300 }),
email: email(),
isLatentCriminal: bool(),
securityClearance: str(),
});
// Public profile - omit sensitive fields
const publicProfileValidator = omit(inspectorValidator, [
'crimeCoefficient',
'isLatentCriminal',
'securityClearance',
]);
// Only name and email are required
publicProfileValidator.judge({
name: 'Akane Tsunemori',
email: 'akane.tsunemori@mwpsb.go.jp',
}); // ✓
publicProfileValidator.judge({
name: 'Akane',
email: 'akane@mwpsb.go.jp',
crimeCoefficient: 28, // ✗ This field should not be present
}); // ✗
`Parameters:
-
objectValidator - An object validator created with obj()
- keys - Array of property names to omit---
####
pick(objectValidator, keys)Creates a validator that picks only specific properties from an object validator.
`typescript
import { obj, pick, str, num, email } from 'sibyl-ts';// Full inspector profile
const inspectorValidator = obj({
name: str(),
crimeCoefficient: num({ min: 0, max: 300 }),
email: email(),
securityClearance: str(),
});
// Basic profile - pick only non-sensitive fields
const basicProfileValidator = pick(inspectorValidator, ['name', 'email']);
// Only name and email are kept
const result = basicProfileValidator.judge({
name: 'Akane Tsunemori',
email: 'akane.tsunemori@mwpsb.go.jp',
crimeCoefficient: 28,
securityClearance: 'Level 1',
});
// result: { name: 'Akane Tsunemori', email: 'akane.tsunemori@mwpsb.go.jp' }
`Parameters:
-
objectValidator - An object validator created with obj()
- keys - Array of property names to pick---
####
tuple([...validators])Fixed-length array (tuple) validation.
`typescript
import { tuple, str, num } from 'sibyl-ts';// [name, coefficient] pair
const personTupleValidator = tuple([str(), num()]);
personTupleValidator.judge(['Akane Tsunemori', 28]); // ✓
personTupleValidator.judge(['Akane', 28, 'extra']); // ✗ Wrong length
personTupleValidator.judge(['Akane']); // ✗ Missing element
`Expected input: Array with exact length matching validators array
---
####
union([...validators])Union type validation (OR logic).
`typescript
import { union, str, num, lit } from 'sibyl-ts';// Can be string OR number
const idValidator = union([str(), num()]);
idValidator.judge('INS-001'); // ✓
idValidator.judge(42); // ✓
idValidator.judge(true); // ✗ Not in union
// Dominator modes
const modeValidator = union([lit('paralyzer'), lit('eliminator')]);
modeValidator.judge('paralyzer'); // ✓
`Expected input: Value matching at least one validator in the array
---
####
intersection([...validators])Intersection type validation (AND logic). Combines multiple validators and deeply merges the results of object validators.
`typescript
import { intersection, obj, str, num } from 'sibyl-ts';// Combine multiple object schemas
const personValidator = obj({ name: str() });
const employeeValidator = obj({ role: str(), salary: num() });
// Resulting type is flattened: { name: string; role: string; salary: number }
const employeeProfileValidator = intersection([personValidator, employeeValidator]);
employeeProfileValidator.judge({
name: 'Akane Tsunemori',
role: 'Inspector',
salary: 100000,
}); // ✓
`Expected input: Value matching ALL validators in the array
---
####
record(keyValidator, valueValidator)Record/dictionary validation.
`typescript
import { record, str, num } from 'sibyl-ts';// Map of enforcer names to coefficients
const coefficientsValidator = record(str(), num());
coefficientsValidator.judge({
Kogami: 180,
Ginoza: 87,
Masaoka: 150,
}); // ✓
coefficientsValidator.judge({
Kogami: '180', // ✗ Value should be number
}); // ✗
`Expected input: Object with dynamic keys/values matching validators
---
$3
####
lit(value)Exact value validation.
`typescript
import { lit } from 'sibyl-ts';const roleValidator = lit('inspector');
roleValidator.judge('inspector'); // ✓
roleValidator.judge('enforcer'); // ✗ Not exact match
// Works with numbers, booleans too
const statusCodeValidator = lit(200);
statusCodeValidator.judge(200); // ✓
statusCodeValidator.judge(404); // ✗
`Expected input: Exact value specified
---
####
nativeEnum(enumObject)Native TypeScript enum validation.
`typescript
import { nativeEnum } from 'sibyl-ts';enum Division {
CriminalInvestigation = 'CI',
Enforcement = 'ENF',
Analysis = 'AN',
}
const divisionValidator = nativeEnum(Division);
divisionValidator.judge(Division.Enforcement); // ✓ 'ENF'
divisionValidator.judge('ENF'); // ✓
divisionValidator.judge('INVALID'); // ✗
`Expected input: Enum value or string matching enum
---
$3
####
optional(validator, defaultValue?)Makes a validator optional (allows
undefined).`typescript
import { optional, str } from 'sibyl-ts';// Without default value
const nicknameValidator = optional(str());
nicknameValidator.judge('Ko'); // ✓
nicknameValidator.judge(undefined); // ✓
nicknameValidator.judge(null); // ✗ Use nullable() for null
// With default value
const rankValidator = optional(str(), 'Inspector');
rankValidator.judge('Enforcer'); // ✓ 'Enforcer'
rankValidator.judge(undefined); // ✓ 'Inspector' (default)
`Type:
T | undefined (or T if default value provided)Parameters:
-
validator - The validator to make optional
- defaultValue? - Optional default value when undefined---
####
nullable(validator)Makes a validator nullable (allows
null).`typescript
import { nullable, num } from 'sibyl-ts';const previousCoefficientValidator = nullable(num());
previousCoefficientValidator.judge(150); // ✓
previousCoefficientValidator.judge(null); // ✓ No previous record
previousCoefficientValidator.judge(undefined); // ✗
`Type:
T | null---
####
undef()Explicitly validates
undefined.`typescript
import { undef } from 'sibyl-ts';const undefValidator = undef();
undefValidator.judge(undefined); // ✓
undefValidator.judge(null); // ✗
undefValidator.judge(false); // ✗
`Type:
undefined---
####
nil()Explicitly validates
null.`typescript
import { nil } from 'sibyl-ts';const nilValidator = nil();
nilValidator.judge(null); // ✓
nilValidator.judge(undefined); // ✗
nilValidator.judge(false); // ✗
`Type:
null---
####
nullish(validator)Makes a validator nullish (allows
null AND undefined).`typescript
import { nullish, str } from 'sibyl-ts';const hueColorValidator = nullish(str());
hueColorValidator.judge('clear'); // ✓
hueColorValidator.judge(null); // ✓
hueColorValidator.judge(undefined); // ✓
`Type:
T | null | undefined---
####
refine(validator, predicate, message)Refine a validation with a custom predicate function.
`typescript
import { refine, num } from 'sibyl-ts';// Ensure crime coefficient is an integer
const integerCoefficientValidator = refine(
num({ min: 0, max: 300 }),
(val) => Number.isInteger(val),
'Crime Coefficient must be an integer'
);
integerCoefficientValidator.judge(100); // ✓
integerCoefficientValidator.judge(100.5); // ✗ Crime Coefficient must be an integer
`Parameters:
-
validator - The base validator
- predicate - Function returning true if valid, false otherwise
- message - Custom error message when predicate fails---
####
transform(validator, transformFn)Transform a value after validation.
`typescript
import { transform, str } from 'sibyl-ts';// Transform string to uppercase
const upperCaseValidator = transform(str(), (val) => val.toUpperCase());
upperCaseValidator.judge('hello'); // 'HELLO'
// Transform string to number
const numberParserValidator = transform(str(), (val) => parseInt(val, 10));
numberParserValidator.judge('123'); // 123
`Parameters:
-
validator - The base validator
- transformFn - Function that takes the validated value and returns the transformed valueType Inference
Extract TypeScript types from validators using
ExtractValidatorType:`typescript
import { obj, str, num, array, ExtractValidatorType } from 'sibyl-ts';// Define an inspector validator
const inspectorValidator = obj({
name: str(),
crimeCoefficient: num({ min: 0, max: 300 }),
roles: array(str()),
});
// Extract the type
type Inspector = ExtractValidatorType;
// { name: string; crimeCoefficient: number; roles: string[] }
// Use in your application
function processInspector(inspector: Inspector) {
console.log(
Processing ${inspector.name});
}
`Real World Examples
$3
`typescript
import { obj, str, num, email, optional } from 'sibyl-ts';const registrationFormValidator = obj({
username: str({ minLen: 3, maxLen: 20, pattern: /^[a-zA-Z0-9_]+$/ }),
email: email(),
password: str({ minLen: 8 }),
age: num({ min: 18 }),
bio: optional(str({ maxLen: 500 })),
});
function handleFormSubmit(formData: unknown) {
const result = registrationFormValidator.tryJudge(formData);
if (result.type === 'error') {
return { errors: result.issues };
}
return { user: result.data };
}
`$3
`typescript
import { obj, str, num, array, nullable } from 'sibyl-ts';const apiResponseValidator = obj({
status: str(),
data: obj({
users: array(
obj({
id: num(),
name: str(),
email: str(),
lastLogin: nullable(str()),
})
),
pagination: obj({
page: num(),
totalPages: num(),
}),
}),
});
async function fetchUsers() {
const response = await fetch('/api/users');
const json = await response.json();
return apiResponseValidator.judge(json); // Type-safe response!
}
`$3
`typescript
import { obj, str, num, transform } from 'sibyl-ts';const envValidator = obj({
NODE_ENV: str({ pattern: /^(development|production|test)$/ }),
PORT: transform(str(), (val) => parseInt(val, 10)),
DATABASE_URL: str({ minLen: 1 }),
API_KEY: str({ minLen: 32 }),
});
// Validate at startup
const env = envValidator.judge(process.env);
console.log(
Server starting on port ${env.PORT});
`TypeScript Configuration
For best results, enable strict mode in your
tsconfig.json:`json
{
"compilerOptions": {
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true
}
}
`Error Handling
Validators throw detailed errors when validation fails:
`typescript
import { JudgmentError } from 'sibyl-ts';try {
const validator = num({ min: 0, max: 300 });
validator.judge(350); // Crime coefficient too high!
} catch (error) {
if (error instanceof JudgmentError) {
console.log(error.issues);
// [{ message: "Value 350 is greater than max 300", path: [] }]
}
}
`Troubleshooting
$3
If you encounter module resolution errors:
1. Ensure Node.js version >= 16.0.0
2. For ESM: Make sure your
package.json has "type": "module"
3. For TypeScript: Set "moduleResolution": "node" in tsconfig.json`Make sure TypeScript strict mode is enabled and you're using TypeScript 5.0+.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
ISC - see LICENSE file for details.
See CHANGELOG.md for release history.