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

compare-guard

v1.0.0

Published

Safe, chainable comparison methods for direct values and potentially undefined or null values in nested object paths

Readme

compare-guard

npm version License: MIT

Safe, chainable comparison methods for direct values and potentially undefined or null values in nested object paths.

Features

  • Simple Value Comparisons - Compare strings, numbers, booleans, and more directly
  • Safe Path Resolution - Resolve nested paths without throwing errors
  • Security First - Prototype pollution protection, input validation, and safe property access
  • Deep Equality - Compare objects and arrays deeply with deepEq()
  • Fluent API - Chainable comparison methods
  • Type Safety - Full TypeScript support with type inference
  • Zero Dependencies - Lightweight and fast
  • Multiple Conditions - Combine conditions with all(), any(), and none()
  • Flexible Paths - Support both dot notation strings and array paths
  • Utility Methods - isEmpty(), isEmail(), isUrl(), isJson(), and more
  • Tree-shakable - ESM + CommonJS support

Installation

npm install compare-guard

Quick Start

import { compare, all, any } from 'compare-guard';

// Simple value comparisons (NEW!)
if (compare('hello').eq('hello')) {
  console.log('Strings match!');
}

if (compare(42).gt(10)) {
  console.log('Number is greater than 10!');
}

if (compare(true).isTruthy()) {
  console.log('Value is truthy!');
}

// Nested object path comparisons
const user = {
  profile: {
    email: '[email protected]',
    age: 25,
  },
  active: true,
};

if (compare(user, 'profile.email').eq('[email protected]')) {
  console.log('Email matches!');
}

// Check if path exists
if (compare(user, 'profile.email').exists()) {
  console.log('Email exists!');
}

// Multiple conditions
if (all([
  compare(user, 'profile.email').isString(),
  compare(user, 'profile.age').isNumber(),
  compare(user, 'profile.age').gte(18),
  compare(user, 'active').isTruthy(),
])) {
  console.log('All validations passed!');
}

API Reference

compare(value) or compare(obj, path, options?)

Creates a Comparator instance for direct value comparisons or nested object paths.

Simple Value Comparison:

compare(value)  // Compare value directly

Nested Object Path:

compare(obj, path, options?)

Parameters:

  • value - Direct value to compare (for simple comparisons)
  • obj - The object to traverse (for nested paths)
  • path - Path as string (dot notation) or array ['data', 'users', 0, 'name']
  • options - Optional configuration
    • defaultValue - Default value if path doesn't exist
    • throwOnError - Whether to throw errors (default: false)

Returns: Comparator instance

Examples:

// Simple value comparison
compare('text').eq('text')
compare(42).gt(10)
compare(true).isTruthy()

// Nested object path
compare(obj, 'deep.nested.property').eq('value')
compare(obj, ['data', 'users', 0, 'name']).exists()

Path Formats

The library supports multiple path formats:

// Dot notation
compare(obj, 'data.users[0].name')

// Array format
compare(obj, ['data', 'users', 0, 'name'])

// Mixed (in array format)
compare(obj, ['data', 'users', 0, 'profile', 'email'])

Comparator Methods

Existence Checks

compare(obj, 'path').exists()           // Path exists and is not null/undefined
compare(obj, 'path').isNull()           // Value is strictly null
compare(obj, 'path').isUndefined()      // Value is undefined
compare(obj, 'path').isNullOrUndefined() // Value is null or undefined

Type Checks

compare(obj, 'path').isString()         // Value is a string
compare(obj, 'path').isNumber()         // Value is a number
compare(obj, 'path').isBoolean()        // Value is a boolean
compare(obj, 'path').isArray()          // Value is an array
compare(obj, 'path').isObject()         // Value is an object (not null/array)

Equality Checks

compare(obj, 'path').eq(value)          // Strict equality (===)
compare(obj, 'path').neq(value)         // Not equal (!==)
compare(obj, 'path').oneOf(...values)   // Value is one of the provided values
compare(obj, 'path').notOneOf(...values) // Value is not one of the provided values

Truthiness Checks

compare(obj, 'path').isTruthy()         // Value is truthy
compare(obj, 'path').isFalsy()          // Value is falsy

Numeric Comparisons

compare(obj, 'path').gt(n)              // Greater than
compare(obj, 'path').gte(n)             // Greater than or equal
compare(obj, 'path').lt(n)              // Less than
compare(obj, 'path').lte(n)             // Less than or equal
compare(obj, 'path').between(min, max)  // Between min and max (inclusive)

String Operations

compare(obj, 'path').matches(regex)     // String matches regex pattern
compare(obj, 'path').startsWith(prefix) // String starts with prefix
compare(obj, 'path').endsWith(suffix)   // String ends with suffix
compare(obj, 'path').contains(substring) // String contains substring

Array Operations

compare(obj, 'path').includes(value)    // Array contains value
compare(obj, 'path').lengthGt(n)        // Array/string length > n
compare(obj, 'path').lengthLt(n)        // Array/string length < n
compare(obj, 'path').lengthEq(n)        // Array/string length === n

Object Operations

compare(obj, 'path').hasProperty(prop)        // Object has property
compare(obj, 'path').hasAllProperties(...props) // Object has all properties
compare(obj, 'path').hasAnyProperty(...props)  // Object has any property

Custom Validation

compare(obj, 'path').validate((value) => {
  // Your custom validation logic
  return value > 0 && value < 100;
})

Deep Equality

compare(obj1).deepEq(obj2)  // Deep comparison for objects/arrays
compare(obj, 'nested.data').deepEq({ a: 1, b: 2 })

Utility Methods

compare(value).isEmpty()      // Empty string, array, object, null, undefined
compare(value).isNotEmpty()   // Not empty
compare(value).isPositive()   // Positive number
compare(value).isNegative()   // Negative number
compare(value).isInteger()    // Integer number
compare(value).isFinite()     // Finite number (not Infinity/NaN)
compare(value).isDate()       // Valid Date object
compare(value).isEmail()      // Valid email format
compare(value).isUrl()        // Valid URL
compare(value).isJson()       // Valid JSON string

String Utilities

compare(value).isBlank()              // Empty or whitespace-only string
compare(value).isNumeric()             // String contains only digits
compare(value).isAlphaNumeric()       // String is alphanumeric
compare(value).isUppercase()          // String is all uppercase
compare(value).isLowercase()          // String is all lowercase
compare(value).equalsIgnoreCase(str)  // Case-insensitive string equality

Date/Time Comparisons

compare(date).isBefore(otherDate)     // Date is before another date
compare(date).isAfter(otherDate)       // Date is after another date
compare(date).isToday()                // Date is today
compare(date).isInPast()               // Date is in the past
compare(date).isInFuture()            // Date is in the future
compare(date).isSameDay(otherDate)     // Dates are the same day

Number Utilities

compare(value).isEven()                // Number is even
compare(value).isOdd()                 // Number is odd
compare(value).isDivisibleBy(n)        // Number is divisible by n
compare(value).isMultipleOf(n)          // Number is multiple of n (alias)
compare(value).inRange(min, max)       // Number in range (alias for between)

Array Utilities

compare(value).isEmptyArray()          // Value is empty array
compare(value).hasMinLength(n)         // Array/string has min length
compare(value).hasMaxLength(n)         // Array/string has max length
compare(value).every(predicate)        // All elements match predicate
compare(value).some(predicate)         // Any element matches predicate

String Format Validators

compare(value).isHexColor()            // Valid hex color (#FF0000 or #fff)
compare(value).isUUID()                // Valid UUID (v4 format)
compare(value).isIP()                  // Valid IP address (IPv4/IPv6)
compare(value).isPhone()               // Valid phone number format

Comparison Helpers

compare(value).isCloseTo(expected, precision)  // Number close to expected (floating point)
compare(value).isInstanceOf(Class)             // Value is instance of class

Type Coercion Helpers

compare(value).asString()    // Convert to string (empty string for null/undefined)
compare(value).asNumber()    // Convert to number (NaN if invalid)
compare(value).asInteger()  // Convert to integer (NaN if invalid)

Conditional Functions

all(conditions)

Returns true if ALL conditions pass.

all([
  compare(obj1, 'path1').eq('value1'),
  compare(obj2, 'path2').gt(10),
  compare(obj3, 'path3').exists(),
])

any(conditions)

Returns true if ANY condition passes.

any([
  compare(obj, 'path1').eq('value1'),
  compare(obj, 'path2').eq('value2'),
])

none(conditions)

Returns true if NONE of the conditions pass.

none([
  compare(obj, 'path1').eq('value1'),
  compare(obj, 'path2').eq('value2'),
])

Usage Examples

Simple Value Comparisons

import { compare, all } from 'compare-guard';

// String comparisons
if (compare('hello').eq('hello')) {
  // Strings match
}

if (compare('[email protected]').matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
  // Valid email format
}

if (compare('password123').lengthGt(8)) {
  // Password is long enough
}

// Number comparisons
if (compare(42).gt(10) && compare(42).lt(100)) {
  // Number is between 10 and 100
}

if (compare(age).gte(18) && compare(age).lte(120)) {
  // Valid age range
}

// Boolean comparisons
if (compare(isActive).isTruthy()) {
  // Value is truthy
}

// Type checks
if (compare(value).isString()) {
  // Value is a string
}

if (compare(value).isNumber()) {
  // Value is a number
}

// Array checks
if (compare([1, 2, 3]).includes(2)) {
  // Array contains value
}

if (compare(items).lengthGt(0)) {
  // Array is not empty
}

// Multiple conditions
const isValid = all([
  compare(email).isString(),
  compare(email).matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
  compare(age).isNumber(),
  compare(age).gte(18),
]);

Form Validation

import { compare, all } from 'compare-guard';

const user = {
  profile: {
    email: '[email protected]',
    age: 25,
  },
  consent: true,
};

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const isValid = all([
  compare(user, 'profile.email').isString(),
  compare(user, 'profile.email').matches(emailRegex),
  compare(user, 'profile.age').isNumber(),
  compare(user, 'profile.age').gte(18),
  compare(user, 'consent').isTruthy(),
]);

if (isValid) {
  // Process valid user
}

API Response Handling

import { compare } from 'compare-guard';

const response = {
  data: {
    users: [
      { active: true, name: 'Alice' },
    ],
  },
};

// Safe to check nested paths
if (compare(response, 'data.users[0].active').eq(true)) {
  console.log('User is active');
}

// Safe even if path doesn't exist
if (compare(response, 'data.users[0].email').exists()) {
  console.log('Email exists');
} else {
  console.log('Email not found - no error thrown!');
}

Configuration Validation

import { compare, all, any } from 'compare-guard';

const config = {
  api: {
    url: 'https://api.example.com',
    timeout: 5000,
    retries: 3,
  },
  features: {
    enabled: true,
    flags: ['feature1', 'feature2'],
  },
};

// Validate all required fields
const isConfigValid = all([
  compare(config, 'api.url').isString(),
  compare(config, 'api.url').startsWith('https://'),
  compare(config, 'api.timeout').isNumber().gte(1000),
  compare(config, 'api.retries').isNumber().between(0, 10),
  compare(config, 'features.enabled').isBoolean(),
  compare(config, 'features.flags').isArray(),
  compare(config, 'features.flags').lengthGt(0),
]);

// Check if any feature flag is set
const hasFeatures = any([
  compare(config, 'features.flags').includes('feature1'),
  compare(config, 'features.flags').includes('feature2'),
  compare(config, 'features.flags').includes('feature3'),
]);

Default Values

import { compare } from 'compare-guard';

const data = {};

// Use default value if path doesn't exist
const result = compare(data, 'missing.path', { defaultValue: 'default' });
console.log(result.getValue()); // 'default'

// Check with default value
if (compare(data, 'count', { defaultValue: 0 }).gt(10)) {
  // This will compare 0 > 10, which is false
}

Custom Validation

import { compare, all } from 'compare-guard';

const product = {
  price: 99.99,
  stock: 50,
};

const isValidProduct = all([
  compare(product, 'price').isNumber(),
  compare(product, 'price').validate((price) => price > 0 && price < 10000),
  compare(product, 'stock').isNumber(),
  compare(product, 'stock').gte(0),
]);

Complex Nested Structures

import { compare, all } from 'compare-guard';

const order = {
  customer: {
    id: 123,
    address: {
      street: '123 Main St',
      city: 'New York',
      zip: '10001',
    },
  },
  items: [
    { id: 1, quantity: 2, price: 10.99 },
    { id: 2, quantity: 1, price: 5.99 },
  ],
  total: 27.97,
};

// Validate complex structure
const isValidOrder = all([
  compare(order, 'customer.id').isNumber(),
  compare(order, 'customer.address.zip').isString(),
  compare(order, 'customer.address.zip').matches(/^\d{5}$/),
  compare(order, 'items').isArray(),
  compare(order, 'items').lengthGt(0),
  compare(order, 'items[0].quantity').isNumber(),
  compare(order, 'items[0].quantity').gt(0),
  compare(order, 'total').isNumber(),
  compare(order, 'total').gte(0),
]);

Mixed Simple and Nested Comparisons

You can mix simple value comparisons with nested object path comparisons:

import { compare, all } from 'compare-guard';

const user = { email: '[email protected]', age: 25 };
const status = 'active';
const isVerified = true;

// Mix simple and nested comparisons
const isValid = all([
  // Simple value comparisons
  compare(status).oneOf('active', 'inactive', 'pending'),
  compare(isVerified).isTruthy(),
  
  // Nested object comparisons
  compare(user, 'email').isString(),
  compare(user, 'email').matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
  compare(user, 'age').isNumber(),
  compare(user, 'age').gte(18),
]);

Advanced Validation Examples

import { compare, all } from 'compare-guard';

// Form validation with new features
const formData = {
  username: 'JohnDoe123',
  email: '[email protected]',
  age: 25,
  phone: '+1-555-123-4567',
  password: 'SecurePass123',
  birthDate: new Date('1998-01-15'),
  tags: ['developer', 'typescript'],
};

const isValid = all([
  // String validations
  compare(formData.username).isAlphaNumeric(),
  compare(formData.username).hasMinLength(3),
  compare(formData.username).hasMaxLength(20),
  
  // Email validation
  compare(formData.email).isEmail(),
  
  // Number validations
  compare(formData.age).isInteger(),
  compare(formData.age).inRange(18, 120),
  compare(formData.age).isEven(), // Example: require even age
  
  // Phone validation
  compare(formData.phone).isPhone(),
  
  // Password strength
  compare(formData.password).hasMinLength(8),
  compare(formData.password).isNotEmpty(),
  compare(formData.password).some((char) => /[A-Z]/.test(char)), // Has uppercase
  
  // Date validations
  compare(formData.birthDate).isInPast(),
  compare(formData.birthDate).isBefore(new Date()), // Must be before today
  
  // Array validations
  compare(formData.tags).isArray(),
  compare(formData.tags).hasMinLength(1),
  compare(formData.tags).every((tag) => compare(tag).isString()),
]);

// UUID validation
const id = '550e8400-e29b-41d4-a716-446655440000';
if (compare(id).isUUID()) {
  // Valid UUID
}

// Hex color validation
const color = '#FF5733';
if (compare(color).isHexColor()) {
  // Valid hex color
}

// Floating point comparison
const result = 0.1 + 0.2;
if (compare(result).isCloseTo(0.3, 0.01)) {
  // Numbers are close enough (handles floating point precision)
}

// Type coercion
const userInput = '42';
const age = compare(userInput).asInteger();
if (compare(age).isInteger() && compare(age).gte(18)) {
  // Valid age
}

TypeScript Support

Full TypeScript support with type inference:

import { compare, Path, CompareOptions } from 'compare-guard';

interface User {
  name: string;
  age: number;
  email: string;
}

const user: User = {
  name: 'Alice',
  age: 30,
  email: '[email protected]',
};

// Type-safe path access
const name = compare(user, 'name' as Path);
const age = compare(user, 'age' as Path);

Security Features

Prototype Pollution Protection

The library blocks access to dangerous properties (__proto__, constructor, prototype) to prevent prototype pollution attacks:

const obj = { data: 'value' };

// All blocked for security
compare(obj, '__proto__').exists();        // Returns false
compare(obj, 'constructor').exists();     // Returns false
compare(obj, 'prototype').exists();        // Returns false
compare(obj, 'data.__proto__').exists();   // Returns false

Input Validation

  • Path length limit: Maximum 10,000 characters
  • Path depth limit: Maximum 100 segments
  • Type validation: Paths must be strings or arrays
// These will throw errors
normalizePath('a'.repeat(10001));  // Too long
normalizePath(Array(101).fill('a')); // Too deep
normalizePath(null);                // Invalid type

Safe Property Access

The library uses Object.prototype.hasOwnProperty.call() to ensure only own properties are accessed, preventing access to inherited properties from the prototype chain.

Error Handling

The library never throws errors for missing properties**. It returns false for invalid comparisons instead:

const obj = {};

// Safe - no error thrown
compare(obj, 'missing.deep.path').exists(); // Returns false
compare(obj, 'missing.path').eq('value');  // Returns false
compare(obj, 'missing.path').gt(10);       // Returns false

Note: Input validation errors (invalid path types, excessive length/depth) will throw errors for security reasons.

Performance

  • Zero dependencies
  • Fast path resolution
  • Tree-shakable (only import what you use)
  • No runtime overhead for type checks
  • Efficient regex patterns for validators
  • Minimal memory footprint

Complete Feature List

Core Features

  • ✅ Safe nested object path resolution
  • ✅ Simple value comparisons
  • ✅ Deep equality comparisons
  • ✅ Multiple condition combiners (all(), any(), none())

Type Checks (15+ methods)

  • isString(), isNumber(), isBoolean(), isArray(), isObject()
  • isNull(), isUndefined(), isNullOrUndefined()
  • isInteger(), isFinite(), isDate()
  • isInstanceOf()

String Operations (15+ methods)

  • Basic: startsWith(), endsWith(), contains(), matches()
  • Utilities: isEmpty(), isBlank(), isNumeric(), isAlphaNumeric()
  • Case: isUppercase(), isLowercase(), equalsIgnoreCase()
  • Format: isEmail(), isUrl(), isJson(), isHexColor(), isUUID(), isIP(), isPhone()

Number Operations (10+ methods)

  • Comparisons: gt(), gte(), lt(), lte(), between(), inRange()
  • Checks: isPositive(), isNegative(), isEven(), isOdd()
  • Math: isDivisibleBy(), isMultipleOf(), isCloseTo()

Date Operations (6 methods)

  • isBefore(), isAfter(), isToday(), isInPast(), isInFuture(), isSameDay()

Array Operations (8+ methods)

  • Length: lengthGt(), lengthLt(), lengthEq(), hasMinLength(), hasMaxLength()
  • Content: includes(), isEmptyArray(), every(), some()

Object Operations (3 methods)

  • hasProperty(), hasAllProperties(), hasAnyProperty()

Type Coercion (3 methods)

  • asString(), asNumber(), asInteger()

Total: 70+ comparison methods!

Browser Support

Works in all modern browsers and Node.js environments:

  • Node.js 14+
  • Chrome, Firefox, Safari, Edge (latest versions)

Contributing

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

License

MIT

Related Packages

  • lodash.get - Similar path resolution but without comparison methods
  • jsonpath - JSONPath implementation

Why compare-guard?

Unlike other packages, compare-guard provides:

  1. Fluent API - Chainable comparison methods
  2. Safety First - Never throws errors for missing paths
  3. Multiple Conditions - Built-in all(), any(), none() helpers
  4. Type Safe - Full TypeScript support
  5. Zero Dependencies - Lightweight and fast

Perfect for validation, configuration checking, and safe object traversal!