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

@cook-step/schema-validator

v0.1.0

Published

JSON Schema validation and compatibility checker for CookStep

Readme

@cook-step/schema-validator

JSON Schema validation and compatibility checker with TypeScript-like error messages.

Features

  • 🔍 Deep Compatibility Checking - Validates if schemas are compatible at every level
  • 📝 TypeScript-like Errors - Clear, actionable error messages with suggestions
  • High Performance - Built on AJV, the fastest JSON Schema validator
  • 🎯 Zero False Positives - Conservative validation ensures reliability
  • 📊 Detailed Reports - Errors, warnings, and suggestions for fixing issues
  • 🔧 Fully Typed - Complete TypeScript support with strict types
  • Battle-tested - 306 tests with 79% code coverage
  • 🎨 AJV-aligned - Behavior matches AJV's JSON Schema interpretation exactly

Installation

npm install @cook-step/schema-validator
# or
pnpm add @cook-step/schema-validator
# or
yarn add @cook-step/schema-validator

Quick Start

import { SchemaCompatibilityValidator } from "@cook-step/schema-validator";

const validator = new SchemaCompatibilityValidator();

// Define source schema (what your output produces)
const sourceSchema = {
  type: "object",
  properties: {
    email: { type: "string" },
    age: { type: "number", minimum: 0 },
  },
};

// Define target schema (what the input expects)
const targetSchema = {
  type: "object",
  properties: {
    email: { type: "string", format: "email" }, // Requires email format!
    age: { type: "number", minimum: 18 }, // Requires age >= 18!
  },
  required: ["email", "age"],
};

// Check compatibility
const result = validator.validateCompatibility(sourceSchema, targetSchema);

if (!result.compatible) {
  console.log(validator.formatResult(result));
  // ❌ Schemas are incompatible (2 errors)
  //
  // Errors:
  //   ❌ String must have format 'email' but no format is specified
  //      Path: /properties/email
  //      Code: INCOMPATIBLE_FORMAT
  //      💡 Suggestion: Add format: 'email' to the source schema
  //
  //   ❌ Range [0, ∞] violates constraint minimum: 18
  //      Path: /properties/age
  //      Code: RANGE_VIOLATION
  //      💡 Suggestion: Set minimum to 18
}

Core Concepts

Schema Compatibility

Two schemas are compatible when data that passes the source schema will also pass the target schema:

  • Source Schema: The schema of data being produced (output)
  • Target Schema: The schema of data being consumed (input)
  • Compatible: Source ⊆ Target (source is a subset of target)

Important Compatibility Rules

This validator follows strict JSON Schema semantics aligned with AJV behavior:

1. Required Properties

Properties that exist but are not marked as required in source are incompatible with target that requires them:

// ❌ Incompatible - source can produce {} without config
const source = {
  type: "object",
  properties: {
    config: { type: "string" },
  },
  // config is NOT required
};
const target = {
  type: "object",
  properties: {
    config: { type: "string" },
  },
  required: ["config"], // config IS required
};

2. Exclusive Bounds (Numbers)

Exclusive bounds work correctly with real numbers, not just integers:

// ❌ Incompatible - source can produce 0.1, 0.5, 0.999...
const source = { type: "number", exclusiveMinimum: 0 }; // > 0
const target = { type: "number", minimum: 1 }; // >= 1

3. Empty Schema ({}) Behavior

  • An empty schema {} accepts ANY value (string, number, boolean, null, object, array)
  • Therefore, {} is incompatible with schemas that have type constraints
  • This is the same as true schema which also accepts everything
// ❌ Incompatible - source can produce any type, target only accepts strings
const source = {};
const target = { type: "string" };

4. Missing Constraints are Incompatible

When source lacks constraints that target requires, they are incompatible:

// ❌ Incompatible - source can produce negative numbers
const source = { type: "number" };
const target = { type: "number", minimum: 0 };

// ❌ Incompatible - source can produce empty strings
const source = { type: "string" };
const target = { type: "string", minLength: 5 };

3. Exclusive Bounds for Real Numbers

Exclusive bounds work with real numbers, not just integers:

// exclusiveMinimum: 0 means > 0 (includes 0.1, 0.5, 0.999...)
// minimum: 1 means >= 1
// ❌ Incompatible - source can produce 0.1, 0.5, 0.999 which target rejects
const source = { type: "number", exclusiveMinimum: 0 };
const target = { type: "number", minimum: 1 };

4. Const Values are Highly Restrictive

When source has const, it only accepts that specific value:

// ✅ Compatible - source only produces 42, which target accepts
const source = { const: 42 };
const target = { type: "number", minimum: 40, maximum: 50 };

// ❌ Incompatible - different const values
const source = { const: "a" };
const target = { const: "b" };

5. Boolean Schemas

  • true schema accepts everything (equivalent to {})
  • false schema rejects everything (compatible with any target as empty set)
// ❌ Incompatible - true accepts everything, target only strings
const source = true;
const target = { type: "string" };

// ✅ Compatible - false rejects everything (empty set ⊆ any set)
const source = false;
const target = { type: "string" };

6. OneOf Requires Exactly One Match

The oneOf keyword requires values to match exactly one schema - not zero, not multiple:

// Target oneOf rejects values that match:
// - NONE of the schemas (gap problem)
// - MULTIPLE schemas (overlap problem)

// Example of gap: strings of length 4 match neither schema
{
  oneOf: [{ minLength: 5 }, { maxLength: 3 }];
}

// Example of overlap: all strings match both schemas
{
  oneOf: [{ type: "string" }, { type: "string", minLength: 0 }];
}

Validation Coverage

The validator comprehensively checks:

Primitive Types:

  • String (formats, patterns, length constraints)
  • Number/Integer (ranges, exclusive bounds, multipleOf)
  • Boolean (const, enum)
  • Null
  • Arrays (items, tuples, length, uniqueItems, contains)
  • Objects (properties, required, additionalProperties, dependencies)

Advanced Features:

  • Composite schemas (allOf, anyOf, oneOf, not)
  • Conditional schemas (if/then/else)
  • Nested schemas (recursive validation)
  • Mixed constraints (combining multiple validation rules)
  • Const and enum values
  • Pattern properties
  • Property dependencies

Error Types

The validator distinguishes between:

  • Errors: Breaking incompatibilities that will cause validation failures
  • Warnings: Non-breaking issues that should be addressed for best practices

API Reference

SchemaCompatibilityValidator

class SchemaCompatibilityValidator {
  constructor(options?: ValidationOptions);

  validateCompatibility(
    sourceSchema: JSONSchemaInput,
    targetSchema: JSONSchemaInput,
    options?: ValidationOptions,
  ): CompatibilityResult;

  formatResult(result: CompatibilityResult): string;

  testWithData(
    sourceSchema: JSONSchemaInput,
    targetSchema: JSONSchemaInput,
    testData?: any,
  ): boolean;
}

ValidationOptions

interface ValidationOptions {
  strict?: boolean; // Strict validation mode
  allowCoercion?: boolean; // Allow type coercion
  validateFormats?: boolean; // Validate string formats
  maxDepth?: number; // Max recursion depth
  allErrors?: boolean; // Collect all errors
}

CompatibilityResult

interface CompatibilityResult {
  compatible: boolean;
  errors: CompatibilityError[];
  warnings: CompatibilityWarning[];
  metadata?: {
    duration: number;
    schemasAnalyzed: number;
  };
}

Validation Examples

String Validation

// Format validation
const source = { type: "string" };
const target = { type: "string", format: "email" };
// ❌ Incompatible - source doesn't guarantee email format

// Pattern validation
const source = { type: "string", pattern: "^[a-z]+$" };
const target = { type: "string", pattern: "^[A-Z]+$" };
// ❌ Incompatible - patterns don't match

// Length constraints
const source = { type: "string", maxLength: 10 };
const target = { type: "string", maxLength: 5 };
// ❌ Incompatible - source allows longer strings

Number Validation

// Range validation
const source = { type: "number", minimum: 0, maximum: 100 };
const target = { type: "number", minimum: 10, maximum: 90 };
// ❌ Incompatible - source range exceeds target range

// Multiple validation
const source = { type: "number", multipleOf: 10 };
const target = { type: "number", multipleOf: 5 };
// ✅ Compatible - all multiples of 10 are multiples of 5

Object Validation

// Required properties
const source = {
  type: "object",
  properties: {
    name: { type: "string" },
  },
};
const target = {
  type: "object",
  properties: {
    name: { type: "string" },
    email: { type: "string" },
  },
  required: ["name", "email"],
};
// ❌ Incompatible - missing required property 'email'

// Additional properties
const source = {
  type: "object",
  additionalProperties: true,
};
const target = {
  type: "object",
  additionalProperties: false,
};
// ❌ Incompatible - target forbids additional properties

Array Validation

// Items validation
const source = {
  type: "array",
  items: { type: "string" },
};
const target = {
  type: "array",
  items: { type: "number" },
};
// ❌ Incompatible - item types don't match

// Length constraints
const source = {
  type: "array",
  minItems: 0,
  maxItems: 5,
};
const target = {
  type: "array",
  minItems: 2,
};
// ❌ Incompatible - source allows fewer items than target requires

Composite Schemas

// anyOf validation
const source = { type: "string" };
const target = {
  anyOf: [{ type: "string" }, { type: "number" }],
};
// ✅ Compatible - source matches one of the anyOf schemas

// oneOf validation - REQUIRES EXACTLY ONE MATCH
const source = { type: "string" };
const target = {
  oneOf: [
    { type: "string", minLength: 5 },
    { type: "string", maxLength: 3 },
  ],
};
// ❌ Incompatible - source can produce strings of length 4 that match NONE
// Also incompatible if source can produce values matching MULTIPLE schemas

// oneOf with overlapping schemas
const source = { type: "string", minLength: 5 };
const target = {
  oneOf: [
    { type: "string", minLength: 10 },
    { type: "string", maxLength: 20 },
  ],
};
// ❌ Incompatible - strings of length 10-20 match BOTH schemas
// oneOf rejects values that match multiple schemas!

// allOf validation
const source = {
  type: "object",
  properties: {
    name: { type: "string" },
  },
};
const target = {
  allOf: [
    {
      type: "object",
      properties: {
        name: { type: "string" },
      },
    },
    {
      type: "object",
      required: ["email"],
    },
  ],
};
// ❌ Incompatible - source doesn't satisfy all schemas in allOf

Error Codes

| Code | Description | | --------------------------------- | ----------------------------------- | | INCOMPATIBLE_TYPE | Type mismatch between schemas | | TYPE_MISMATCH | Cannot convert between types | | INCOMPATIBLE_FORMAT | String format mismatch | | PATTERN_MISMATCH | Regular expression pattern mismatch | | STRING_LENGTH_VIOLATION | String length constraint violation | | RANGE_VIOLATION | Numeric range constraint violation | | MULTIPLE_OF_MISMATCH | Multiple constraint mismatch | | MISSING_REQUIRED_PROPERTY | Required property missing in source | | PROPERTY_TYPE_MISMATCH | Property type incompatibility | | ADDITIONAL_PROPERTIES_FORBIDDEN | Additional properties not allowed | | ARRAY_ITEMS_MISMATCH | Array items incompatibility | | ARRAY_LENGTH_VIOLATION | Array length constraint violation | | UNIQUE_ITEMS_MISMATCH | Unique items constraint mismatch | | ENUM_VALUES_MISMATCH | Enum values incompatibility | | CONST_VALUE_MISMATCH | Const value mismatch |

Advanced Usage

Custom Formats

const validator = new SchemaCompatibilityValidator();

// Add custom format
validator.addFormat("customId", (value) => {
  return /^ID-\d{6}$/.test(value);
});

// Now you can validate with custom format
const source = { type: "string", format: "customId" };
const target = { type: "string", format: "customId" };
// ✅ Compatible

Testing with Real Data

const validator = new SchemaCompatibilityValidator();

// Test if specific data would pass both schemas
const testData = {
  email: "[email protected]",
  age: 25,
};

const compatible = validator.testWithData(sourceSchema, targetSchema, testData);
// Returns true if data passes both schemas

Boolean Schemas

The validator handles boolean schemas correctly:

// true schema - allows everything
const source = true;
const target = { type: "string" };
// ❌ Incompatible - true allows everything, target only strings

// false schema - allows nothing
const source = false;
const target = { type: "string" };
// ✅ Compatible - false allows nothing (empty set ⊆ any set)

AJV Alignment

This validator is designed to be fully compatible with AJV's JSON Schema interpretation. Key alignments include:

Strict Subset Validation

The validator ensures that the source schema produces values that are a strict subset of what the target schema accepts. This means:

  • If source can produce any value that target rejects → Incompatible
  • Empty schemas ({} or true) that accept everything are incompatible with constrained schemas
  • Missing constraints in source when target has them → Incompatible

Real Number Support

Unlike some validators that assume integer arithmetic, this validator correctly handles real numbers:

  • exclusiveMinimum: 0 includes decimals like 0.1, 0.5, 0.999
  • Range comparisons work with floating-point precision
  • Exclusive bounds are properly validated for continuous number ranges

Const and Enum Optimization

When schemas use const or enum, the validator:

  • Skips unnecessary constraint validation (range, pattern, length)
  • Validates only the actual value compatibility
  • Correctly infers types from const/enum values

Test Coverage

All compatibility rules are validated against actual AJV behavior to ensure correctness. The validator passes 250+ test cases covering:

  • Primitive types and their coercion
  • String formats and patterns
  • Number ranges with exclusive bounds
  • Const and enum values
  • Boolean schemas
  • Complex nested structures

Testing & Quality

  • 306 comprehensive tests covering edge cases and real-world scenarios
  • 79% code coverage (87% branch coverage)
  • Core validators coverage:
    • String validation: 98.46%
    • Number validation: 86.01%
    • Object validation: 80.99%
    • Array validation: 79.51%
  • All behavior validated against actual AJV implementation

Performance

  • Validation typically completes in < 1ms for simple schemas
  • Complex nested schemas with 100+ properties: ~5-10ms
  • Uses AJV internally for optimal performance
  • Caches compiled schemas for repeated validations

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © Anderson D. Rosa