@verifyhash/deep-equal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/index.d.ts +34 -0
- package/index.js +216 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 verifyhash
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @verifyhash/deep-equal
|
|
2
|
+
|
|
3
|
+
Zero-dependency structural deep equality for Node.js. One function, one
|
|
4
|
+
boolean, and a precisely documented rule for every awkward case: `NaN`,
|
|
5
|
+
`±0`, sparse arrays, `Map`/`Set` with object keys, typed arrays, boxed
|
|
6
|
+
primitives, prototype mismatches — and cyclic structures, which terminate
|
|
7
|
+
instead of blowing the stack.
|
|
8
|
+
|
|
9
|
+
Who it's for: anyone writing tests, cache-invalidation checks, or
|
|
10
|
+
change-detection who is tired of `JSON.stringify(a) === JSON.stringify(b)`
|
|
11
|
+
(which breaks on key order, `undefined`, `NaN`, `Map`, `Set`, cycles, and
|
|
12
|
+
typed arrays — all of which this handles correctly).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npm install @verifyhash/deep-equal
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Zero runtime dependencies. CommonJS, works on any maintained Node.js.
|
|
21
|
+
TypeScript declarations ship in the package (`index.d.ts`).
|
|
22
|
+
|
|
23
|
+
## Example
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const { deepEqual } = require('@verifyhash/deep-equal');
|
|
27
|
+
|
|
28
|
+
deepEqual({ a: 1, b: [NaN] }, { b: [NaN], a: 1 }); // true (key order irrelevant, NaN === NaN)
|
|
29
|
+
deepEqual(new Map([[{ id: 1 }, 'x']]),
|
|
30
|
+
new Map([[{ id: 1 }, 'x']])); // true (object keys match structurally)
|
|
31
|
+
deepEqual(new Set([NaN, 1]), new Set([1, NaN])); // true
|
|
32
|
+
deepEqual(0, -0); // false (+0 and -0 are distinct)
|
|
33
|
+
deepEqual([, 1], [undefined, 1]); // false (a hole is not an explicit undefined)
|
|
34
|
+
deepEqual(new Float64Array(1), new Uint8Array(8)); // false (same bytes, different type)
|
|
35
|
+
|
|
36
|
+
// Cycles terminate and compare correctly:
|
|
37
|
+
const a = { name: 'node' }; a.self = a;
|
|
38
|
+
const b = { name: 'node' }; b.self = b;
|
|
39
|
+
deepEqual(a, b); // true
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
### `deepEqual(a, b) => boolean`
|
|
45
|
+
|
|
46
|
+
Returns `true` iff `a` and `b` are structurally equal under these rules:
|
|
47
|
+
|
|
48
|
+
| Category | Rule |
|
|
49
|
+
|---|---|
|
|
50
|
+
| 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). |
|
|
51
|
+
| `null` / `undefined` | Distinct from each other and from everything else. `{ a: undefined }` does NOT equal `{}`. |
|
|
52
|
+
| 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. |
|
|
53
|
+
| 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. |
|
|
54
|
+
| Arrays | Length + elements. Sparse **holes are preserved**: `[, 1]` equals `[, 1]` but not `[undefined, 1]`. Extra own enumerable props on an array are compared too. |
|
|
55
|
+
| `Date` | By `getTime()`. Two Invalid Dates (both `NaN`) are equal. |
|
|
56
|
+
| `RegExp` | By `source` and `flags`. `lastIndex` is ignored. |
|
|
57
|
+
| `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. |
|
|
58
|
+
| `Set` | Equal `size`, every member structurally matches an unused member. `NaN` in a Set works (Sets use SameValueZero internally). Insertion order irrelevant. |
|
|
59
|
+
| `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. |
|
|
60
|
+
| 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. |
|
|
61
|
+
| Functions | Reference equality only. Two distinct closures with identical source are not equal. |
|
|
62
|
+
| 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. |
|
|
63
|
+
|
|
64
|
+
### Honest limits
|
|
65
|
+
|
|
66
|
+
- **Symbol-keyed and non-enumerable properties are invisible** to the
|
|
67
|
+
comparison, by design. If those carry meaning for you, this is the wrong
|
|
68
|
+
tool.
|
|
69
|
+
- Map/Set matching of *object* keys/members uses a **greedy** scan, not a full
|
|
70
|
+
bipartite matcher. With multiple structurally-equal-but-distinct candidate
|
|
71
|
+
keys mapping to different values, a pathological arrangement could greedily
|
|
72
|
+
mis-pair and report `false` where a smarter matcher finds a pairing. Sizes
|
|
73
|
+
and normal data never hit this.
|
|
74
|
+
- Object-keyed `Map`/`Set` comparison is **O(n·m)** in the worst case (every
|
|
75
|
+
key of one map scanned against the other). Fine for the hundreds of entries
|
|
76
|
+
typical in tests/config; do not diff two million-entry object-keyed maps
|
|
77
|
+
with it.
|
|
78
|
+
- Getters are **invoked** (values are read as a consumer would see them), and
|
|
79
|
+
cross-realm objects (e.g. from `node:vm`) fail the prototype-identity rule.
|
|
80
|
+
- Floats in typed arrays compare **byte-wise**: `Float64Array [0]` vs `[-0]`
|
|
81
|
+
differ (different bit patterns), and two `NaN`s with different bit payloads
|
|
82
|
+
would differ — plain-number `NaN`s (outside typed arrays) always compare
|
|
83
|
+
equal.
|
|
84
|
+
|
|
85
|
+
## Running the tests
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
cd deep-equal
|
|
89
|
+
node test/index.test.js
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
231 deterministic golden-vector checks (every table row is asserted in both
|
|
93
|
+
argument orders), no timers, no network, exit code 0 on success.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for @verifyhash/deep-equal — zero-dependency structural
|
|
3
|
+
* equality with Map/Set/Date/RegExp/typed-array/cycle support.
|
|
4
|
+
*
|
|
5
|
+
* Hand-written against index.js — every runtime export is declared here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Structural deep equality.
|
|
10
|
+
*
|
|
11
|
+
* Rules (documented in the README):
|
|
12
|
+
* - Primitives compare with Object.is semantics: `NaN` equals `NaN`,
|
|
13
|
+
* `+0` does NOT equal `-0`.
|
|
14
|
+
* - `null` and `undefined` are distinct.
|
|
15
|
+
* - Plain objects: key order irrelevant; only own enumerable STRING keys
|
|
16
|
+
* are compared (symbol keys and non-enumerable properties are ignored).
|
|
17
|
+
* - Objects must share the same prototype (identity) — a plain object never
|
|
18
|
+
* equals a class instance.
|
|
19
|
+
* - Arrays: length + elements; a sparse hole is NOT an explicit `undefined`.
|
|
20
|
+
* - `Date` by `getTime()` (two Invalid Dates are equal), `RegExp` by
|
|
21
|
+
* source + flags.
|
|
22
|
+
* - `Map` / `Set`: equal size and structurally matching entries/members
|
|
23
|
+
* (object keys and members match structurally, not by identity).
|
|
24
|
+
* - `ArrayBuffer`, every TypedArray, and `DataView` compare byte-wise, and
|
|
25
|
+
* only against the same type.
|
|
26
|
+
* - Cyclic (self- or mutually-referential) structures terminate and compare
|
|
27
|
+
* correctly.
|
|
28
|
+
* - Functions are equal only by reference.
|
|
29
|
+
*
|
|
30
|
+
* @param a - first value
|
|
31
|
+
* @param b - second value
|
|
32
|
+
* @returns `true` iff `a` and `b` are structurally equal under the rules above.
|
|
33
|
+
*/
|
|
34
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
package/index.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @verifyhash/deep-equal — zero-dependency structural equality for Node.js.
|
|
5
|
+
*
|
|
6
|
+
* deepEqual(a, b) -> boolean. Handles primitives (NaN equals NaN; +0 and -0
|
|
7
|
+
* are DIFFERENT), plain objects (key order irrelevant, symbol keys ignored),
|
|
8
|
+
* arrays (sparse holes are not the same as explicit undefined), Date, RegExp,
|
|
9
|
+
* Map, Set, ArrayBuffer / every TypedArray / DataView (byte-wise), boxed
|
|
10
|
+
* primitives, and cyclic structures (self- and mutually-referential graphs
|
|
11
|
+
* terminate and compare correctly via an in-progress pair memo).
|
|
12
|
+
*
|
|
13
|
+
* Documented rules (see README for the full rationale):
|
|
14
|
+
* - Numbers compare with Object.is semantics: NaN === NaN is TRUE, +0 vs -0
|
|
15
|
+
* is FALSE.
|
|
16
|
+
* - Objects must share the SAME prototype (identity): a plain object never
|
|
17
|
+
* equals a class instance, even with identical own properties.
|
|
18
|
+
* - Only own ENUMERABLE STRING keys are compared; symbol-keyed and
|
|
19
|
+
* non-enumerable properties are ignored.
|
|
20
|
+
* - Functions are equal only by reference.
|
|
21
|
+
* - Typed arrays / DataView / ArrayBuffer compare BYTE-WISE and only against
|
|
22
|
+
* the same view type (Float64Array never equals a Uint8Array, even over
|
|
23
|
+
* identical bytes).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const toString = Object.prototype.toString;
|
|
27
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
28
|
+
|
|
29
|
+
/** Byte-wise comparison of two Uint8Array views of equal length. */
|
|
30
|
+
function eqBytes(a, b) {
|
|
31
|
+
if (a.length !== b.length) return false;
|
|
32
|
+
for (let i = 0; i < a.length; i++) {
|
|
33
|
+
if (a[i] !== b[i]) return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Own enumerable string-keyed properties: same key set, structurally equal
|
|
40
|
+
* values. Symbol keys and non-enumerable properties are deliberately ignored.
|
|
41
|
+
*/
|
|
42
|
+
function eqOwnKeys(a, b, seen) {
|
|
43
|
+
const keysA = Object.keys(a);
|
|
44
|
+
const keysB = Object.keys(b);
|
|
45
|
+
if (keysA.length !== keysB.length) return false;
|
|
46
|
+
// Membership must be checked against b's ENUMERABLE keys, not hasOwnProperty,
|
|
47
|
+
// so a non-enumerable own property on b can never mask a real mismatch.
|
|
48
|
+
const setB = new Set(keysB);
|
|
49
|
+
for (const k of keysA) {
|
|
50
|
+
if (!setB.has(k)) return false;
|
|
51
|
+
if (!eq(a[k], b[k], seen)) return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Maps: equal size, and every [key, value] entry of `a` matches an unused
|
|
58
|
+
* entry of `b` — keys and values both compared STRUCTURALLY. Uses a fast path
|
|
59
|
+
* through b.get() for SameValueZero-identical keys, then a greedy scan for
|
|
60
|
+
* structural key matches (object keys).
|
|
61
|
+
*/
|
|
62
|
+
function eqMap(a, b, seen) {
|
|
63
|
+
if (a.size !== b.size) return false;
|
|
64
|
+
const usedB = new Set();
|
|
65
|
+
for (const [ka, va] of a) {
|
|
66
|
+
// Fast path: b has an identical (SameValueZero) key with an equal value.
|
|
67
|
+
if (b.has(ka) && !usedB.has(ka) && eq(va, b.get(ka), seen)) {
|
|
68
|
+
usedB.add(ka);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Structural path: find any unused key of b that deep-equals ka with a
|
|
72
|
+
// deep-equal value (needed for object keys, which differ by identity).
|
|
73
|
+
let found = false;
|
|
74
|
+
for (const [kb, vb] of b) {
|
|
75
|
+
if (usedB.has(kb)) continue;
|
|
76
|
+
if (eq(ka, kb, seen) && eq(va, vb, seen)) {
|
|
77
|
+
usedB.add(kb);
|
|
78
|
+
found = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!found) return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Sets: equal size, and every member of `a` structurally matches an unused
|
|
89
|
+
* member of `b`. Fast path via b.has() (SameValueZero — this is what makes
|
|
90
|
+
* NaN-in-a-Set work directly), then a greedy structural scan for objects.
|
|
91
|
+
*/
|
|
92
|
+
function eqSet(a, b, seen) {
|
|
93
|
+
if (a.size !== b.size) return false;
|
|
94
|
+
const usedB = new Set();
|
|
95
|
+
for (const va of a) {
|
|
96
|
+
if (b.has(va) && !usedB.has(va)) {
|
|
97
|
+
usedB.add(va);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let found = false;
|
|
101
|
+
for (const vb of b) {
|
|
102
|
+
if (usedB.has(vb)) continue;
|
|
103
|
+
if (eq(va, vb, seen)) {
|
|
104
|
+
usedB.add(vb);
|
|
105
|
+
found = true;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!found) return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Core recursive comparison. `seen` is a WeakMap<object, WeakSet<object>> of
|
|
116
|
+
* IN-PROGRESS (a, b) pairs: when a cycle re-enters a pair already being
|
|
117
|
+
* compared, we return true (coinductive equality — the cycle itself cannot
|
|
118
|
+
* introduce a difference; any real difference is found elsewhere in the
|
|
119
|
+
* graph). Pairs are removed when their comparison completes, so a `false`
|
|
120
|
+
* verdict is never wrongly memoized as `true` for later sibling comparisons.
|
|
121
|
+
*/
|
|
122
|
+
function eq(a, b, seen) {
|
|
123
|
+
// Object.is: identical references, identical primitives, NaN === NaN true,
|
|
124
|
+
// +0 vs -0 false — exactly the documented primitive semantics.
|
|
125
|
+
if (Object.is(a, b)) return true;
|
|
126
|
+
if (
|
|
127
|
+
a === null || b === null ||
|
|
128
|
+
typeof a !== 'object' || typeof b !== 'object'
|
|
129
|
+
) {
|
|
130
|
+
// Covers null/undefined distinction, primitive vs object (a boxed Number
|
|
131
|
+
// never equals its primitive), and functions (reference-only equality —
|
|
132
|
+
// already handled by Object.is above).
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Documented prototype rule: both sides must share the SAME prototype
|
|
137
|
+
// object. Plain object vs class instance -> false; subclassed Array vs
|
|
138
|
+
// plain Array -> false; also separates [] from {} and Map from Set.
|
|
139
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;
|
|
140
|
+
|
|
141
|
+
const tag = toString.call(a);
|
|
142
|
+
if (tag !== toString.call(b)) return false; // belt-and-braces cross-type guard
|
|
143
|
+
|
|
144
|
+
switch (tag) {
|
|
145
|
+
case '[object Date]':
|
|
146
|
+
// Object.is so two Invalid Dates (getTime() = NaN) compare equal.
|
|
147
|
+
return Object.is(a.getTime(), b.getTime());
|
|
148
|
+
case '[object RegExp]':
|
|
149
|
+
return a.source === b.source && a.flags === b.flags;
|
|
150
|
+
case '[object Number]':
|
|
151
|
+
case '[object String]':
|
|
152
|
+
case '[object Boolean]':
|
|
153
|
+
case '[object BigInt]':
|
|
154
|
+
case '[object Symbol]':
|
|
155
|
+
// Boxed primitives: unwrap and compare with Object.is (so
|
|
156
|
+
// new Number(NaN) equals new Number(NaN), new Number(0) vs
|
|
157
|
+
// new Number(-0) is false). Extra expando props are ignored.
|
|
158
|
+
return Object.is(a.valueOf(), b.valueOf());
|
|
159
|
+
case '[object ArrayBuffer]':
|
|
160
|
+
return eqBytes(new Uint8Array(a), new Uint8Array(b));
|
|
161
|
+
default:
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (ArrayBuffer.isView(a)) {
|
|
166
|
+
// Same view type guaranteed by the prototype + tag checks above, so
|
|
167
|
+
// Float64Array vs Uint8Array over identical bytes is already false here.
|
|
168
|
+
// DataView and every TypedArray flavor compare byte-wise.
|
|
169
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
170
|
+
return eqBytes(
|
|
171
|
+
new Uint8Array(a.buffer, a.byteOffset, a.byteLength),
|
|
172
|
+
new Uint8Array(b.buffer, b.byteOffset, b.byteLength)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Containers that can participate in cycles: memo the in-progress pair.
|
|
177
|
+
let pairs = seen.get(a);
|
|
178
|
+
if (pairs !== undefined && pairs.has(b)) return true; // cycle re-entry
|
|
179
|
+
if (pairs === undefined) {
|
|
180
|
+
pairs = new WeakSet();
|
|
181
|
+
seen.set(a, pairs);
|
|
182
|
+
}
|
|
183
|
+
pairs.add(b);
|
|
184
|
+
|
|
185
|
+
let result;
|
|
186
|
+
if (tag === '[object Map]') {
|
|
187
|
+
result = eqMap(a, b, seen);
|
|
188
|
+
} else if (tag === '[object Set]') {
|
|
189
|
+
result = eqSet(a, b, seen);
|
|
190
|
+
} else if (Array.isArray(a)) {
|
|
191
|
+
// length compared explicitly (it is not an enumerable key); sparse holes
|
|
192
|
+
// then fall out of the key comparison: a hole has no own key, an explicit
|
|
193
|
+
// undefined does — so [ ,1 ] never equals [ undefined, 1 ].
|
|
194
|
+
result = a.length === b.length && eqOwnKeys(a, b, seen);
|
|
195
|
+
} else {
|
|
196
|
+
// Plain objects and same-class instances (prototype identity was already
|
|
197
|
+
// required above).
|
|
198
|
+
result = eqOwnKeys(a, b, seen);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pairs.delete(b);
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Structural deep equality.
|
|
207
|
+
* @param {*} a
|
|
208
|
+
* @param {*} b
|
|
209
|
+
* @returns {boolean} true iff a and b are structurally equal under the
|
|
210
|
+
* documented rules.
|
|
211
|
+
*/
|
|
212
|
+
function deepEqual(a, b) {
|
|
213
|
+
return eq(a, b, new WeakMap());
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = { deepEqual };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@verifyhash/deep-equal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "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 cyclic structures — fully typed from birth.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deep-equal",
|
|
7
|
+
"deep-equality",
|
|
8
|
+
"structural-equality",
|
|
9
|
+
"equal",
|
|
10
|
+
"compare",
|
|
11
|
+
"cycles",
|
|
12
|
+
"circular",
|
|
13
|
+
"map",
|
|
14
|
+
"set",
|
|
15
|
+
"typed-array",
|
|
16
|
+
"zero-dependency",
|
|
17
|
+
"typescript"
|
|
18
|
+
],
|
|
19
|
+
"main": "index.js",
|
|
20
|
+
"types": "index.d.ts",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node test/index.test.js"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"files": [
|
|
26
|
+
"index.js",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"index.d.ts"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/verifyhash/libs.git",
|
|
37
|
+
"directory": "deep-equal"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/verifyhash/libs/tree/main/deep-equal#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/verifyhash/libs/issues"
|
|
42
|
+
}
|
|
43
|
+
}
|