or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

Deep-EQL

Deep-EQL is an improved deep equality testing library for Node.js and browsers that performs recursive comparison of object keys and values beyond referential equality. It handles complex scenarios including circular references, special object types, and provides advanced customization options.

Package Information

  • Package Name: deep-eql
  • Package Type: npm
  • Language: JavaScript (ES modules)
  • Installation: npm install deep-eql

Core Imports

import deepEqual from "deep-eql";
import { MemoizeMap } from "deep-eql";

Basic Usage

import deepEqual from "deep-eql";

// Basic object comparison
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
console.log(deepEqual(obj1, obj2)); // true

// Handles special cases like NaN and ±0
console.log(deepEqual(NaN, NaN)); // true
console.log(deepEqual(-0, +0)); // false

// Works with complex nested structures
const complex1 = {
  array: [1, 2, { nested: true }],
  date: new Date('2023-01-01'),
  regex: /test/gi,
  set: new Set([1, 2, 3])
};
const complex2 = {
  array: [1, 2, { nested: true }],
  date: new Date('2023-01-01'),
  regex: /test/gi,
  set: new Set([1, 2, 3])
};
console.log(deepEqual(complex1, complex2)); // true

Architecture

Deep-EQL uses a multi-layered comparison strategy:

  • Simple Equality: Fast path for primitives, NaN, and ±0 using Object.is semantics
  • Type Detection: Advanced type detection using Symbol.toStringTag and Object.prototype.toString
  • Memoization: Circular reference detection using WeakMap (or FakeMap fallback)
  • Type-Specific Comparison: Specialized comparison logic for each supported type
  • Extensible Design: Custom comparator support for advanced use cases

Capabilities

Deep Equality Comparison

Primary function for comparing two values with deep equality semantics.

/**
 * Assert deeply nested sameValue equality between two objects of any type
 * @param {any} leftHandOperand - First value to compare
 * @param {any} rightHandOperand - Second value to compare
 * @param {DeepEqualOptions} [options] - Optional configuration
 * @returns {boolean} - true if deeply equal, false otherwise
 */
function deepEqual(leftHandOperand, rightHandOperand, options);

interface DeepEqualOptions {
  /** Custom equality comparator function */
  comparator?: (left: any, right: any) => boolean | null;
  /** Memoization object for circular reference handling, or false to disable */
  memoize?: MemoizeMap | false;
}

Usage Examples:

import deepEqual from "deep-eql";

// Basic usage
deepEqual({ a: 1 }, { a: 1 }); // true
deepEqual([1, 2, 3], [1, 2, 3]); // true

// With custom comparator
deepEqual(
  { timestamp: new Date('2023-01-01') },
  { timestamp: new Date('2023-01-02') },
  {
    comparator: (left, right) => {
      if (left instanceof Date && right instanceof Date) {
        return left.getFullYear() === right.getFullYear();
      }
      return null; // Fall back to default comparison
    }
  }
); // true

// Disable memoization for performance in simple cases
deepEqual(simpleObj1, simpleObj2, { memoize: false });

Memoization Map

Memoization implementation for circular reference detection and performance optimization.

/**
 * Memoization map implementation that uses WeakMap when available,
 * otherwise falls back to FakeMap for environments without WeakMap support
 */
const MemoizeMap: typeof WeakMap;

interface MemoizeMapInterface {
  /**
   * Retrieve cached comparison result
   * @param {any} key - The key to look up
   * @returns {any} - The cached value or undefined
   */
  get(key: any): any;

  /**
   * Store comparison result in cache
   * @param {any} key - The key to store under
   * @param {any} value - The value to store
   */
  set(key: any, value: any): void;
}

Usage Examples:

import { MemoizeMap } from "deep-eql";

// Create custom memoization map
const memoMap = new MemoizeMap();

// Use with deepEqual for consistent caching across calls
deepEqual(obj1, obj2, { memoize: memoMap });
deepEqual(obj1, obj3, { memoize: memoMap }); // Reuses cache

Supported Types and Special Handling

Primitive Types

  • String, Number, Boolean: Wrapper objects compared by valueOf()
  • null, undefined: Direct comparison
  • Symbol: Reference equality only
  • Function: Reference equality only

Object Types

Standard Objects

  • Plain Objects: Compares all own and inherited enumerable properties and symbols
  • Arrays: Element-by-element comparison with length check
  • Arguments: Treated distinctly from Arrays, compared like arrays

Date and RegExp

  • Date: Compared using valueOf() method
  • RegExp: Compared by string representation (source, flags)

Collections

  • Set/Map: Compared by entries, handles iteration order
  • WeakSet/WeakMap: Reference equality only (cannot iterate)

Typed Arrays

All typed array types supported with element-by-element comparison:

  • Int8Array, Uint8Array, Uint8ClampedArray
  • Int16Array, Uint16Array
  • Int32Array, Uint32Array
  • Float32Array, Float64Array

Binary Data

  • ArrayBuffer: Compared as Uint8Array representation
  • DataView: Compared as Uint8Array of the underlying buffer

Advanced Types

  • Error: Only name, message, and code properties compared
  • Generator: Consumed and compared by yielded values
  • Promise: Reference equality only
  • Temporal Objects: Uses native equals() method or specialized comparison

Temporal API Support

Full support for TC39 Temporal API proposal:

// Temporal objects with .equals() method
deepEqual(
  Temporal.PlainDate.from('2023-01-01'),
  Temporal.PlainDate.from('2023-01-01')
); // Uses .equals() method

// Temporal.Duration uses total nanoseconds
deepEqual(
  Temporal.Duration.from({ hours: 1 }),
  Temporal.Duration.from({ minutes: 60 })
); // true

// Temporal.TimeZone and Calendar use string representation
deepEqual(
  Temporal.TimeZone.from('America/New_York'),
  Temporal.TimeZone.from('America/New_York')
); // true

Equality Rules

Object.is Semantics

  • NaN === NaN (true, unlike standard equality)
  • -0 !== +0 (false, unlike standard equality)
  • All other primitives follow standard equality

Property Enumeration

  • All own enumerable properties included
  • All inherited enumerable properties included
  • All own enumerable Symbol properties included

Error Object Handling

Only specific properties compared regardless of enumerability:

  • name property
  • message property
  • code property

Arguments vs Arrays

Arguments objects are treated as distinct from Arrays:

function example() {
  deepEqual([], arguments); // false
  deepEqual([], Array.from(arguments)); // true
}

Circular Reference Handling

Automatic detection and handling of circular references:

const obj1 = { name: 'test' };
obj1.self = obj1;

const obj2 = { name: 'test' };
obj2.self = obj2;

deepEqual(obj1, obj2); // true - handles circular references

Error Handling

The library gracefully handles comparison failures and edge cases:

  • Type coercion: No automatic type coercion performed
  • Property access errors: Safely handles objects with problematic property access
  • Iterator consumption: Safely consumes iterators and generators
  • Memory management: Uses WeakMap to avoid memory leaks in memoization

Performance Considerations

  • Fast path: Quick checks for simple equality cases
  • Memoization: Caches complex object comparisons to handle circular references
  • Type dispatch: Efficient type-specific comparison strategies
  • Early termination: Stops comparison as soon as inequality is determined

Optimization Examples:

// Fast path for primitives and simple references
deepEqual(5, 5); // Immediate return
deepEqual("hello", "hello"); // Immediate return

// Memoization for complex objects
const complexObj = { /* large nested structure */ };
deepEqual(complexObj, complexObj); // Cached after first comparison

// Disable memoization for simple comparisons to avoid overhead
deepEqual(simpleObj1, simpleObj2, { memoize: false });