equalkit
v0.1.1
Published
Tiny, type-safe deep equal and deep clone — handles Map, Set, Date, RegExp, typed arrays, and circular references. Zero dependencies.
Maintainers
Readme
equalkit
Tiny, type-safe deep equal and deep clone — handles
Map,Set,Date,RegExp, typed arrays, and circular references. Zero dependencies.
Two jobs you keep needing and keep pulling lodash (or a half-right gist) for:
is this deeply equal to that, and give me an independent copy. equalkit
does both correctly — including the modern data types and the cyclic structures
that trip up fast-deep-equal (stack overflow) and structuredClone (throws on
functions) — in one zero-dependency package.
import { equal, clone } from "equalkit";
equal({ a: [1, 2], when: new Date(0) }, { a: [1, 2], when: new Date(0) }); // true
const draft = clone(state); // deep, independent copy — mutate freelyWhy equalkit?
equalandclonein one tiny module. Consistent type coverage across both.- Real-world types. Plain & class objects (prototype preserved on clone),
arrays,
Date,RegExp,Map,Set,ArrayBuffer, typed arrays,DataView. - Cycle-safe. Both functions tolerate circular references —
equalwon't overflow,clonerewires the cycle to the copy and keeps shared references shared. - Sensible primitive rules.
NaNequalsNaN;+0equals-0. clonenever throws. Functions and symbols are carried over by reference instead of crashing the waystructuredClonedoes.- Zero dependencies, ESM + CJS + types, fully typed (
clone<T>(x): T).
Install
npm install equalkit
# or: pnpm add equalkit / yarn add equalkit / bun add equalkitequal(a, b) → boolean
Deep structural equality.
import { equal } from "equalkit";
equal({ a: 1, b: 2 }, { b: 2, a: 1 }); // true (key order ignored)
equal([1, 2], [2, 1]); // false (array order matters)
equal(new Map([["x", 1]]), new Map([["x", 1]])); // true
equal(new Uint8Array([1, 2]), new Uint8Array([1, 2])); // true
equal(NaN, NaN); // true- Objects compare own enumerable keys, including symbols.
aandbmust share a constructor ({x:1}≠new Point(...)).Mapkeys andSetmembers are matched by identity (standard semantics);Mapvalues are compared deeply.
clone(value) → typeof value
Deep, independent copy.
import { clone } from "equalkit";
const a = { list: [1, 2], meta: new Map([["v", 1]]) };
const b = clone(a);
b.list.push(3); // a.list stays [1, 2]
b.meta.set("v", 99); // a.meta unchanged- Preserves the prototype of class instances (the copy is still
instanceof). - Copies typed arrays /
ArrayBufferinto fresh buffers. - Circular references are rewired to the clone; shared references stay shared.
- Functions and symbols are kept by reference (not deep-copied).
Notes
equalis reference-equal fast-pathed and recurses only into objects.- Need
Map/Setcompared by deep member value rather than identity? That's a deliberately different (and ambiguous, O(n²)) operation — out of scope here.
Contributors ✨
This project follows the all-contributors specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the emoji key for how each contribution is recognized, and open a PR or issue to get involved.
Thanks goes to these wonderful people:
License
MIT © Tung Tran
