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

@verifyhash/deep-equal

v0.1.0

Published

Zero-dependency structural deep equality for Node.js: plain objects, arrays (sparse-hole aware), Date, RegExp, Map/Set with structural object keys, ArrayBuffer/TypedArray/DataView (byte-wise), boxed primitives, NaN-equals-NaN with +0/-0 distinction, and c

Readme

@verifyhash/deep-equal

Zero-dependency structural deep equality for Node.js. One function, one boolean, and a precisely documented rule for every awkward case: NaN, ±0, sparse arrays, Map/Set with object keys, typed arrays, boxed primitives, prototype mismatches — and cyclic structures, which terminate instead of blowing the stack.

Who it's for: anyone writing tests, cache-invalidation checks, or change-detection who is tired of JSON.stringify(a) === JSON.stringify(b) (which breaks on key order, undefined, NaN, Map, Set, cycles, and typed arrays — all of which this handles correctly).

Install

npm install @verifyhash/deep-equal

Zero runtime dependencies. CommonJS, works on any maintained Node.js. TypeScript declarations ship in the package (index.d.ts).

Example

const { deepEqual } = require('@verifyhash/deep-equal');

deepEqual({ a: 1, b: [NaN] }, { b: [NaN], a: 1 });            // true  (key order irrelevant, NaN === NaN)
deepEqual(new Map([[{ id: 1 }, 'x']]),
          new Map([[{ id: 1 }, 'x']]));                        // true  (object keys match structurally)
deepEqual(new Set([NaN, 1]), new Set([1, NaN]));               // true
deepEqual(0, -0);                                              // false (+0 and -0 are distinct)
deepEqual([, 1], [undefined, 1]);                              // false (a hole is not an explicit undefined)
deepEqual(new Float64Array(1), new Uint8Array(8));             // false (same bytes, different type)

// Cycles terminate and compare correctly:
const a = { name: 'node' }; a.self = a;
const b = { name: 'node' }; b.self = b;
deepEqual(a, b);                                               // true

API

deepEqual(a, b) => boolean

Returns true iff a and b are structurally equal under these rules:

| Category | Rule | |---|---| | Primitives | Object.is semantics: NaN equals NaN (unlike ===), and +0 does NOT equal -0 (unlike ===). Chosen because a deep-equal that says NaN !== NaN is useless in tests, and collapsing ±0 silently hides real sign bugs (1/x flips infinity sign). | | null / undefined | Distinct from each other and from everything else. { a: undefined } does NOT equal {}. | | Plain objects | Own enumerable string keys only; key order irrelevant. Symbol keys and non-enumerable properties are ignored — two objects differing only in symbol-keyed props are equal. | | Prototypes | Both sides must share the same prototype object (identity). A plain object never equals a class instance with the same fields; Object.create(null) objects only equal other null-proto objects. This is the strict rule Node's assert.deepStrictEqual family uses; it keeps [] vs {} and Map vs Set false for free. | | Arrays | Length + elements. Sparse holes are preserved: [, 1] equals [, 1] but not [undefined, 1]. Extra own enumerable props on an array are compared too. | | Date | By getTime(). Two Invalid Dates (both NaN) are equal. | | RegExp | By source and flags. lastIndex is ignored. | | Map | Equal size, and every [key, value] entry matches an unused entry of the other map — keys and values both compared structurally, so {id:1} keys match across maps. Insertion order irrelevant. | | Set | Equal size, every member structurally matches an unused member. NaN in a Set works (Sets use SameValueZero internally). Insertion order irrelevant. | | ArrayBuffer / TypedArrays / DataView | Byte-wise, and only against the same type: a Float64Array never equals a Uint8Array even over identical bytes. A subarray() view compares only its own window, not the whole backing buffer. | | Boxed primitives | new Number(3) equals new Number(3) (unwrapped via valueOf, Object.is semantics) but never equals primitive 3 — object vs primitive is a type mismatch. | | Functions | Reference equality only. Two distinct closures with identical source are not equal. | | Cycles | Self-referential and mutually-referential structures terminate via an in-progress pair memo (WeakMap). Equality is coinductive: two cycles are equal when their infinite unrollings agree — so a 1-cycle a→a equals a 2-cycle b→b'→b when every payload matches. A cyclic structure never equals an acyclic one of different shape. |

Honest limits

  • Symbol-keyed and non-enumerable properties are invisible to the comparison, by design. If those carry meaning for you, this is the wrong tool.
  • Map/Set matching of object keys/members uses a greedy scan, not a full bipartite matcher. With multiple structurally-equal-but-distinct candidate keys mapping to different values, a pathological arrangement could greedily mis-pair and report false where a smarter matcher finds a pairing. Sizes and normal data never hit this.
  • Object-keyed Map/Set comparison is O(n·m) in the worst case (every key of one map scanned against the other). Fine for the hundreds of entries typical in tests/config; do not diff two million-entry object-keyed maps with it.
  • Getters are invoked (values are read as a consumer would see them), and cross-realm objects (e.g. from node:vm) fail the prototype-identity rule.
  • Floats in typed arrays compare byte-wise: Float64Array [0] vs [-0] differ (different bit patterns), and two NaNs with different bit payloads would differ — plain-number NaNs (outside typed arrays) always compare equal.

Running the tests

cd deep-equal
node test/index.test.js

231 deterministic golden-vector checks (every table row is asserted in both argument orders), no timers, no network, exit code 0 on success.

License

MIT