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

sibyl-ts

v0.7.1

Published

TypeScript validation library with type-safe validators

Readme

sibyl-ts

npm version License: ISC

A TypeScript validation library inspired by the Sibyl System from Psycho-Pass - precise, unwavering judgment for your data.

Features

  • 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

Table of Contents

Installation

# npm
npm install sibyl-ts

# yarn
yarn add sibyl-ts

# pnpm
pnpm add sibyl-ts

Quick Start

ESM (ES Modules)

import { str, num, obj, array, email } from 'sibyl-ts';

CommonJS

const { str, num, obj, array, email } = require('sibyl-ts');

Usage

Basic Validation

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

Object Validation

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: '[email protected]',
  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 } }

Safe Judgment (No Exceptions)

Use tryJudge() when you want to handle validation errors gracefully without throwing exceptions:

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)

Union Types

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!

Optional and Nullable

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

Primitive Validators

str(options?)

String validation with optional constraints.

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.

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.

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.

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 string

Options:

  • 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.

import { unknown } from 'sibyl-ts';

const unscannedEvidenceValidator = unknown();
unscannedEvidenceValidator.judge('mysterious device'); // ✓
unscannedEvidenceValidator.judge({ threatLevel: 'unknown' }); // ✓

Type: unknown


String Validators

email()

Email address validation (RFC 5322).

import { email } from 'sibyl-ts';

const emailValidator = email();
emailValidator.judge('[email protected]'); // ✓
emailValidator.judge('kogami@enforcer'); // ✗ Invalid format

url()

URL validation.

import { url } from 'sibyl-ts';

const urlValidator = url();
urlValidator.judge('https://sibyl-system.jp'); // ✓
urlValidator.judge('not-a-url'); // ✗

uuid()

UUID validation (v4).

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).

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

Complex Validators

array(elementValidator, options?)

Array validation with typed elements.

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.

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.

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.

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: '[email protected]',
}); // ✓

publicProfileValidator.judge({
  name: 'Akane',
  email: '[email protected]',
  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.

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: '[email protected]',
  crimeCoefficient: 28,
  securityClearance: 'Level 1',
});
// result: { name: 'Akane Tsunemori', email: '[email protected]' }

Parameters:

  • objectValidator - An object validator created with obj()
  • keys - Array of property names to pick

tuple([...validators])

Fixed-length array (tuple) validation.

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).

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.

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.

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


Literal & Enum Validators

lit(value)

Exact value validation.

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.

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


Modifier Validators

optional(validator, defaultValue?)

Makes a validator optional (allows undefined).

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).

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.

import { undef } from 'sibyl-ts';

const undefValidator = undef();
undefValidator.judge(undefined); // ✓
undefValidator.judge(null); // ✗
undefValidator.judge(false); // ✗

Type: undefined


nil()

Explicitly validates null.

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).

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.

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.

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 value

Type Inference

Extract TypeScript types from validators using ExtractValidatorType:

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<typeof inspectorValidator>;
// { name: string; crimeCoefficient: number; roles: string[] }

// Use in your application
function processInspector(inspector: Inspector) {
  console.log(`Processing ${inspector.name}`);
}

Real World Examples

Form Validation

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 };
}

API Response Validation

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!
}

Environment Variable Validation

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:

{
  "compilerOptions": {
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true
  }
}

Error Handling

Validators throw detailed errors when validation fails:

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

Module Resolution Issues

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

Type Inference Not Working

Make sure TypeScript strict mode is enabled and you're using TypeScript 5.0+.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

ISC - see LICENSE file for details.

Changelog

See CHANGELOG.md for release history.