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

ascertain

v3.2.1

Published

0-Deps, simple, fast, for browser and node js object schema validator

Readme

Ascertain

Zero-dependency, high-performance schema validator for Node.js and browsers.

Coverage Status Build Status NPM version Downloads Snyk

Table of Contents

Features

  • Zero dependencies - Minimal footprint, no external dependencies
  • High performance - Compiles schemas to optimized JS functions (~6x faster than dynamic validation)
  • Type-safe - Full TypeScript support with type inference
  • Flexible schemas - AND, OR, optional, tuple, discriminated operators
  • Type casting - Built-in parsers for numbers (hex, octal, binary), dates, JSON, base64
  • Object validation - Validate keys/values with $keys, $values, $strict
  • Partial validation - createValidator validates subsets with type narrowing
  • Detailed errors - Clear error messages with paths for debugging
  • Standard Schema v1 - Interoperable with tRPC, TanStack Form, and other ecosystem tools

Install

npm install ascertain

Quick Start

import { ascertain, or, optional } from 'ascertain';

ascertain({
  name: String,
  age: Number,
  role: or('admin', 'user'),
  email: optional(String),
}, userData);

Performance

Ascertain compiles schemas into optimized JavaScript functions. Compiled validators run ~6x faster than dynamic validation.

import { compile } from 'ascertain';

// Compile once
const validateUser = compile(userSchema);

// Validate many (no recompilation)
validateUser(user1);
validateUser(user2);

| When to use | Function | Speed | |-------------|----------|-------| | Repeated validation (API handlers, loops) | compile() | Fastest | | One-off validation | ascertain() | Convenient |

Benchmark

| Library | Mode | Valid (ops/s) | Invalid (ops/s) | |---------|------|---------------|-----------------| | Ascertain | first-error | 322M | 84M | | Ascertain | all-errors | 325M | 36M | | AJV | first-error | 92M | 65M | | AJV | all-errors | 92M | 30M | | Zod | all-errors | 62M | 72K |

Benchmark source: src/__bench__/benchmark.ts

Run it locally:

pnpm build
pnpm bench

Notes:

  • Maintainer-run benchmark, not an independent study
  • Measures valid and invalid paths for compiled validators
  • Compares the current checkout, published ascertain, AJV, and Zod
  • Results vary by CPU, Node.js version, and workload, so treat the numbers as directional

Schema Reference

| Schema | Validates | Example | |--------|-----------|---------| | String, Number, Boolean | Type check | { age: Number } | | Date, Array, Object | Instance check | { created: Date } | | Function | Any callable | { handler: Function } | | Primitives | Exact value | { status: 'active' } | | RegExp | Pattern match | { email: /^.+@.+$/ } | | [Schema] | Array of type | { tags: [String] } | | { key: Schema } | Object shape | { user: { name: String } } | | or(a, b, ...) | Any match | or(String, Number) | | and(a, b, ...) | All match | and(Date, { toJSON: Function }) | | optional(s) | Nullable | optional(String) | | tuple(a, b) | Fixed array | tuple(Number, Number) | | discriminated(schemas, key) | Tagged union | discriminated([{ type: 'a' }, { type: 'b' }], 'type') |

Special Symbols

import { $keys, $values, $strict } from 'ascertain';

const schema = {
  [$keys]: /^[a-z]+$/,   // Validate all keys
  [$values]: Number,      // Validate all values
  [$strict]: true,        // No extra properties
};

Type Casting

Parse strings into typed values (environment variables, query params):

import { as } from 'ascertain';

as.number('42')        // 42
as.number('3.14')      // 3.14
as.number('0xFF')      // 255 (hex)
as.number('0o77')      // 63 (octal)
as.number('0b1010')    // 10 (binary)
as.number('1e10')      // 10000000000

as.boolean('true')     // true
as.boolean('1')        // true

as.time('500ms')       // 500
as.time('30s')         // 30000
as.time('5m')          // 300000
as.time('2h')          // 7200000
as.time('1d')          // 86400000

as.date('2024-12-31')  // Date object
as.array('a,b,c', ',') // ['a', 'b', 'c']
as.json('{"x":1}')     // { x: 1 }
as.base64('dGVzdA==')  // 'test'

Invalid values return TypeError for deferred validation:

const config = {
  port: as.number(process.env.PORT),  // TypeError if invalid
  host: as.string(process.env.HOST),
};

// Errors surface with clear paths
ascertain({ port: Number, host: String }, config);
// → TypeError: "Invalid value undefined, expected a string"

Patterns

Batch Validation

Compile once, validate many:

const validateUser = compile(userSchema);

const results = users.map((user, i) => {
  if (validateUser(user)) {
    return { index: i, valid: true };
  }
  return { index: i, valid: false, error: validateUser.issues[0].message };
});

Discriminated Unions

Use discriminated() for efficient tagged union validation. Instead of trying each variant like or(), it checks the discriminant field first and only validates the matching variant:

import { compile, discriminated } from 'ascertain';

const messageSchema = discriminated([
  { type: 'email', address: String },
  { type: 'sms', phone: String },
  { type: 'push', token: String },
], 'type');

const validate = compile(messageSchema);

validate({ type: 'email', address: '[email protected]' }); // true
validate({ type: 'sms', phone: '123456' });                // true
validate({ type: 'push', token: 123 });                    // false
validate({ type: 'unknown' });                             // false

Discriminant values must be string, number, or boolean literals.

Conditional Rules

Use or() and and() for complex conditions:

const schema = {
  type: or('email', 'sms'),
  // Conditional: email requires address, sms requires phone
  contact: or(
    and({ type: 'email' }, { address: String }),
    and({ type: 'sms' }, { phone: String }),
  ),
};

Schema Composition

Build schemas from reusable parts:

const addressSchema = {
  street: String,
  city: String,
  zip: /^\d{5}$/,
};

const personSchema = {
  name: String,
  address: addressSchema,
};

const companySchema = {
  name: String,
  headquarters: addressSchema,
  employees: [personSchema],
};

Versioned Schemas

Version schemas as modules:

// schemas/user.v1.ts
export const userSchemaV1 = { name: String, email: String };

// schemas/user.v2.ts
export const userSchemaV2 = {
  ...userSchemaV1,
  phone: optional(String),
  createdAt: Date,
};

// api/handler.ts
import { userSchemaV2 } from './schemas/user.v2';
const validate = compile(userSchemaV2);

Config Validation

Validate only what each module needs:

import { createValidator, as } from 'ascertain';

const config = {
  app: { name: as.string(process.env.APP_NAME), port: as.number(process.env.PORT) },
  db: { host: as.string(process.env.DB_HOST), pool: as.number(process.env.DB_POOL) },
  cache: { ttl: as.time(process.env.CACHE_TTL) },
};

const validate = createValidator(config);

// Each module validates only what it needs
const { db } = validate({
  db: { host: String, pool: Number },
});

db.host;   // string - validated and typed
db.pool;   // number - validated and typed
// db.xxx  // TypeScript error - property doesn't exist

// cache not validated = not accessible
// cache.ttl  // TypeScript error - cache not in returned type

Compile Options

By default compile() stops at the first validation error (fastest for invalid data). Pass { allErrors: true } to collect all errors:

import { compile } from 'ascertain';

const schema = { name: String, age: Number, active: Boolean };

// First-error mode (default) - stops at first failure
const validate = compile(schema);
if (!validate({ name: 123, age: 'bad', active: 'no' })) {
  console.log(validate.issues.length); // 1
  console.log(validate.issues[0].path); // ['name']
}

// All-errors mode - collects every failure
const validateAll = compile(schema, { allErrors: true });
if (!validateAll({ name: 123, age: 'bad', active: 'no' })) {
  console.log(validateAll.issues.length); // 3
}

Standard Schema

Wrap a schema for Standard Schema v1 compliance, enabling interoperability with tRPC, TanStack Form, and other ecosystem libraries:

import { standardSchema, or, optional } from 'ascertain';

const userValidator = standardSchema({
  name: String,
  age: Number,
  role: or('admin', 'user'),
  email: optional(String),
});

// Use as regular validator (throws on error)
userValidator({ name: 'Alice', age: 30, role: 'admin' });

// Use Standard Schema interface (returns result object)
const result = userValidator['~standard'].validate(unknownData);
if (result.issues) {
  console.log(result.issues);
} else {
  console.log(result.value);
}

Complete Example

import { compile, or, optional, and, tuple, $keys, $values, $strict, as } from 'ascertain';

const schema = {
  id: Number,
  name: String,
  email: /^[^@]+@[^@]+$/,
  status: or('active', 'inactive', 'pending'),
  role: optional(or('admin', 'user')),

  profile: {
    bio: optional(String),
    avatar: optional(String),
  },

  settings: {
    [$keys]: /^[a-z_]+$/,
    [$values]: or(String, Number, Boolean),
    [$strict]: true,
  },

  coordinates: optional(tuple(Number, Number)),
  createdAt: and(Date, { toISOString: Function }),

  retries: as.number(process.env.MAX_RETRIES),
  timeout: as.time(process.env.TIMEOUT),
};

const validate = compile(schema);
if (!validate(data)) {
  console.error(validate.issues);
}

License

MIT - Ivan Zakharchanka