fast-equals 3.0.1 → 4.0.0-beta.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/.prettierrc +4 -0
- package/CHANGELOG.md +9 -0
- package/README.md +172 -41
- package/dist/fast-equals.cjs.js +345 -242
- package/dist/fast-equals.cjs.js.map +1 -1
- package/dist/fast-equals.esm.js +344 -242
- package/dist/fast-equals.esm.js.map +1 -1
- package/dist/fast-equals.js +345 -242
- package/dist/fast-equals.js.map +1 -1
- package/dist/fast-equals.min.js +1 -1
- package/dist/fast-equals.mjs +344 -242
- package/dist/fast-equals.mjs.map +1 -1
- package/index.d.ts +26 -25
- package/package.json +21 -19
- package/src/arrays.ts +36 -0
- package/src/comparator.ts +146 -0
- package/src/dates.ts +12 -0
- package/src/index.ts +135 -0
- package/src/maps.ts +66 -0
- package/src/objects.ts +61 -0
- package/src/regexps.ts +11 -0
- package/src/sets.ts +61 -0
- package/src/utils.ts +130 -0
package/.prettierrc
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# fast-equals CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 3.0.3
|
|
4
|
+
|
|
5
|
+
- Fix [#77](https://github.com/planttheidea/fast-equals/issues/73) - better circular object validation
|
|
6
|
+
|
|
7
|
+
## 3.0.2
|
|
8
|
+
|
|
9
|
+
- Fix [#73](https://github.com/planttheidea/fast-equals/issues/73) - support comparison of primitive wrappers
|
|
10
|
+
- [#76](https://github.com/planttheidea/fast-equals/pull/76) - improve speed and accuracy of `RegExp` comparison in modern environments
|
|
11
|
+
|
|
3
12
|
## 3.0.1
|
|
4
13
|
|
|
5
14
|
- Fix [#71](https://github.com/planttheidea/fast-equals/pull/71) - use generic types for better type flow-through
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<img src="https://img.shields.io/badge/coverage-100%25-brightgreen.svg"/>
|
|
5
5
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg"/>
|
|
6
6
|
|
|
7
|
-
Perform [blazing fast](#benchmarks) equality comparisons (either deep or shallow) on two objects passed. It has no dependencies, and is ~
|
|
7
|
+
Perform [blazing fast](#benchmarks) equality comparisons (either deep or shallow) on two objects passed. It has no dependencies, and is ~1.23kB when minified and gzipped.
|
|
8
8
|
|
|
9
9
|
Unlike most equality validation libraries, the following types are handled out-of-the-box:
|
|
10
10
|
|
|
@@ -36,22 +36,12 @@ Starting with version `1.5.0`, circular objects are supported for both deep and
|
|
|
36
36
|
|
|
37
37
|
## Usage
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
39
|
+
```ts
|
|
42
40
|
import { deepEqual } from 'fast-equals';
|
|
43
41
|
|
|
44
42
|
console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true
|
|
45
43
|
```
|
|
46
44
|
|
|
47
|
-
Or if you want to import all functions under a namespace:
|
|
48
|
-
|
|
49
|
-
```javascript
|
|
50
|
-
import * as fe from 'fast-equals';
|
|
51
|
-
|
|
52
|
-
console.log(fe.deep({ foo: 'bar' }, { foo: 'bar' })); // true
|
|
53
|
-
```
|
|
54
|
-
|
|
55
45
|
### Specific builds
|
|
56
46
|
|
|
57
47
|
There are three builds, an ESM build for modern build systems / runtimes, a CommonJS build for traditional NodeJS environments, and a UMD build for legacy implementations. The ideal one will likely be chosen for you automatically, however if you want to use a specific build you can always import it directly:
|
|
@@ -60,9 +50,6 @@ There are three builds, an ESM build for modern build systems / runtimes, a Comm
|
|
|
60
50
|
- For older `nodejs` versions that do not allow ESM with file extensions other than `.mjs` => `fast-equals/dist/fast-equals.mjs`
|
|
61
51
|
- CommonJS => `fast-equals/dist/fast-equals.cjs.js`
|
|
62
52
|
- UMD => `fast-equals/dist/fast-equals.js`
|
|
63
|
-
|
|
64
|
-
There is also a pre-minified version of the UMD build available:
|
|
65
|
-
|
|
66
53
|
- Minified UMD => `fast-equals/dist/fast-equals.min.js`
|
|
67
54
|
|
|
68
55
|
## Available methods
|
|
@@ -71,7 +58,7 @@ There is also a pre-minified version of the UMD build available:
|
|
|
71
58
|
|
|
72
59
|
Performs a deep equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
|
|
73
60
|
|
|
74
|
-
```
|
|
61
|
+
```ts
|
|
75
62
|
import { deepEqual } from 'fast-equals';
|
|
76
63
|
|
|
77
64
|
const objectA = { foo: { bar: 'baz' } };
|
|
@@ -85,7 +72,7 @@ console.log(deepEqual(objectA, objectB)); // true
|
|
|
85
72
|
|
|
86
73
|
`Map` objects support complex keys (objects, Arrays, etc.), however [the spec for key lookups in `Map` are based on `SameZeroValue`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#key_equality). If the spec were followed for comparison, the following would always be `false`:
|
|
87
74
|
|
|
88
|
-
```
|
|
75
|
+
```ts
|
|
89
76
|
const mapA = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
|
|
90
77
|
const mapB = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
|
|
91
78
|
|
|
@@ -98,7 +85,7 @@ To support true deep equality of all contents, `fast-equals` will perform a deep
|
|
|
98
85
|
|
|
99
86
|
Performs a shallow equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
|
|
100
87
|
|
|
101
|
-
```
|
|
88
|
+
```ts
|
|
102
89
|
import { shallowEqual } from 'fast-equals';
|
|
103
90
|
|
|
104
91
|
const nestedObject = { bar: 'baz' };
|
|
@@ -116,7 +103,7 @@ console.log(shallowEqual(objectA, objectC)); // false
|
|
|
116
103
|
|
|
117
104
|
Performs a [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) comparison on the two objects passed and returns a boolean representing the value equivalency of the objects. In simple terms, this means either strictly equal or both `NaN`.
|
|
118
105
|
|
|
119
|
-
```
|
|
106
|
+
```ts
|
|
120
107
|
import { sameValueZeroEqual } from 'fast-equals';
|
|
121
108
|
|
|
122
109
|
const mainObject = { foo: NaN, bar: 'baz' };
|
|
@@ -134,7 +121,7 @@ console.log(sameValueZeroEqual(mainObject, objectC)); // false
|
|
|
134
121
|
|
|
135
122
|
Performs the same comparison as `deepEqual` but supports circular objects. It is slower than `deepEqual`, so only use if you know circular objects are present.
|
|
136
123
|
|
|
137
|
-
```
|
|
124
|
+
```ts
|
|
138
125
|
function Circular(value) {
|
|
139
126
|
this.me = {
|
|
140
127
|
deeply: {
|
|
@@ -156,7 +143,7 @@ Just as with `deepEqual`, [both keys and values are compared for deep equality](
|
|
|
156
143
|
|
|
157
144
|
Performs the same comparison as `shallowequal` but supports circular objects. It is slower than `shallowEqual`, so only use if you know circular objects are present.
|
|
158
145
|
|
|
159
|
-
```
|
|
146
|
+
```ts
|
|
160
147
|
const array = ['foo'];
|
|
161
148
|
|
|
162
149
|
array.push(array);
|
|
@@ -167,43 +154,187 @@ console.log(circularShallowEqual(array, [array])); // false
|
|
|
167
154
|
|
|
168
155
|
### createCustomEqual
|
|
169
156
|
|
|
170
|
-
Creates a custom equality comparator that will be used on nested values in the object. Unlike `deepEqual` and `shallowEqual`, this is a
|
|
157
|
+
Creates a custom equality comparator that will be used on nested values in the object. Unlike `deepEqual` and `shallowEqual`, this is a factory method that receives the default options used internally, and allows you to override the defaults as needed. This is generally for extreme edge-cases, or supporting legacy environments.
|
|
171
158
|
|
|
172
159
|
The signature is as follows:
|
|
173
160
|
|
|
174
|
-
```
|
|
175
|
-
type
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
161
|
+
```ts
|
|
162
|
+
type InternalEqualityComparator = (
|
|
163
|
+
a: any,
|
|
164
|
+
b: any,
|
|
165
|
+
indexOrKeyA: any,
|
|
166
|
+
indexOrKeyB: any,
|
|
167
|
+
parentA: any,
|
|
168
|
+
parentB: any,
|
|
169
|
+
meta: any,
|
|
170
|
+
) => boolean;
|
|
171
|
+
|
|
172
|
+
type TypeEqualityComparator = <Type, Meta>(
|
|
173
|
+
a: Type,
|
|
174
|
+
b: Type,
|
|
175
|
+
isEqual: InternalEqualityComparator,
|
|
176
|
+
meta: Meta,
|
|
177
|
+
) => boolean;
|
|
178
|
+
|
|
179
|
+
interface CreateComparatorCreatorOptions<Meta> {
|
|
180
|
+
areArraysEqual: TypeEqualityComparator<any[], Meta>;
|
|
181
|
+
areDatesEqual: TypeEqualityComparator<Date, Meta>;
|
|
182
|
+
areMapsEqual: TypeEqualityComparator<Map<any, any>, Meta>;
|
|
183
|
+
areObjectsEqual: TypeEqualityComparator<Record<string, any>, Meta>;
|
|
184
|
+
areRegExpsEqual: TypeEqualityComparator<RegExp, Meta>;
|
|
185
|
+
areSetsEqual: TypeEqualityComparator<Set<any>, Meta>;
|
|
186
|
+
createIsNestedEqual?: (
|
|
187
|
+
comparator: <A, B>(a: A, b: B, meta: Meta) => boolean,
|
|
188
|
+
) => InternalEqualityComparator;
|
|
189
|
+
}
|
|
180
190
|
|
|
181
191
|
function createCustomEqual(
|
|
182
|
-
|
|
192
|
+
getComparatorOptions: (
|
|
193
|
+
defaultOptions: CreateComparatorOptions,
|
|
194
|
+
) => Partial<CreateComparatorOptions>,
|
|
183
195
|
): EqualityComparator;
|
|
184
196
|
```
|
|
185
197
|
|
|
186
|
-
The `meta` parameter
|
|
198
|
+
The `meta` parameter above is whatever you want it to be. It will be passed through to all equality checks, and is meant specifically for use with custom equality methods. For example, with the `circularDeepEqual` and `circularShallowEqual` methods, it is used to pass through a cache of processed objects. You also only need to return the override handlers; absent being provided, the defaults are used.
|
|
187
199
|
|
|
188
200
|
_**NOTE**: `Map` implementations compare equality for both keys and value. When using a custom comparator and comparing equality of the keys, the iteration index is provided as both `indexOrKeyA` and `indexOrKeyB` to help use-cases where ordering of keys matters to equality._
|
|
189
201
|
|
|
190
|
-
|
|
202
|
+
#### Legacy environment support
|
|
203
|
+
|
|
204
|
+
Starting in `4.x.x`, `RegExp.prototype.flags` is expected to be available in the environment. All modern browsers support this feature, however there may be situations where a legacy environmental support is required (example: IE11). If you need to support such an environment, creating a custom comparator that uses a more verbose comparison of all possible flags is a simple solution.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { createCustomEqual, sameValueZeroEqual } from 'deep-Equals';
|
|
208
|
+
|
|
209
|
+
function areRegExpsEqual(a: RegExp, b: RegExp): Boolean {
|
|
210
|
+
return (
|
|
211
|
+
a.source === b.source &&
|
|
212
|
+
a.global === b.global &&
|
|
213
|
+
a.ignoreCase === b.ignoreCase &&
|
|
214
|
+
a.multiline === b.multiline &&
|
|
215
|
+
a.unicode === b.unicode &&
|
|
216
|
+
a.sticky === b.sticky &&
|
|
217
|
+
a.lastIndex === b.lastIndex
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const deepEqual = createCustomEqual(() => ({ areRegExpEqual }));
|
|
222
|
+
const shallowEqual = createCustomEqual(() => ({
|
|
223
|
+
areRegExpsEqual,
|
|
224
|
+
createIsNestedEqual: () => sameValueZeroEqual,
|
|
225
|
+
}));
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### Custom targeted comparisons
|
|
229
|
+
|
|
230
|
+
Sometimes it is necessary to squeeze every once of performance out of your runtime code, and deep equality checks can be a bottleneck. When this is occurs, it can be advantageous to build a custom comparison that allows for highly specific equality checks.
|
|
231
|
+
|
|
232
|
+
An example where you know the shape of the objects being passed in, where the `foo` property is a simple primitive and the `bar` property is a nested object:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { createCustomEqual } from 'fast-equals';
|
|
236
|
+
|
|
237
|
+
const isCollectionEqual = createCustomEqual<Meta>({
|
|
238
|
+
areObjectsEqual(a, b, isEqual, meta) {
|
|
239
|
+
return a.foo === b.foo && isEqual(a.bar, b.bar, meta);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
191
243
|
|
|
192
|
-
|
|
244
|
+
This avoids ambiguous iteration and type-checking, which can boost performance in extreme hot-path scenarios.
|
|
245
|
+
|
|
246
|
+
Here is another example, with a custom equality comparison that also checks against values in the meta object:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
193
249
|
import { createCustomEqual } from 'fast-equals';
|
|
194
250
|
|
|
195
|
-
const isDeepEqualOrFooMatchesMeta = createCustomEqual(
|
|
196
|
-
(deepEqual)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
251
|
+
const isDeepEqualOrFooMatchesMeta = createCustomEqual<Meta>(() => ({
|
|
252
|
+
createIsNestedEqual(deepEqual) {
|
|
253
|
+
return (a, b, keyA, keyB, parentA, parentB, meta) =>
|
|
254
|
+
a === meta || b === meta || deepEqual(a, b, meta);
|
|
255
|
+
},
|
|
256
|
+
}));
|
|
257
|
+
|
|
258
|
+
console.log(
|
|
259
|
+
'shallow',
|
|
260
|
+
isDeepEqualOrFooMatchesMeta({ foo: 'bar' }, { foo: 'baz' }, 'bar'),
|
|
200
261
|
);
|
|
262
|
+
console.log(
|
|
263
|
+
'deep',
|
|
264
|
+
isDeepEqualOrFooMatchesMeta(
|
|
265
|
+
{ nested: { foo: 'bar' } },
|
|
266
|
+
{ nested: { foo: 'baz' } },
|
|
267
|
+
'bar',
|
|
268
|
+
),
|
|
269
|
+
); // true
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### `createCustomCircularEqual`
|
|
273
|
+
|
|
274
|
+
Operates nearly identically to [`createCustomEqual`](#createcustomequal), with the difference being that the `meta` property expected by the comparator is expected to have a `WeakMap` contract. This is because it is used for caching accessed objects to avoid maximum stack exceeded errors. The most common use for this method is a simple way to support circular checks for references that do not have `WeakMap` natively.
|
|
275
|
+
|
|
276
|
+
#### Legacy environment support
|
|
277
|
+
|
|
278
|
+
Starting in `4.x.x`, `WeakMap` is expected to be available in the environment. All modern browsers support this global object, however there may be situations where a legacy environmental support is required (example: IE11). If you need to support such an environment, creating a custom comparator that uses a custom cache implementation with the same contract is a simple solution.
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import { createCustomEqual, sameValueZeroEqual } from 'deep-Equals';
|
|
282
|
+
|
|
283
|
+
interface Cache {
|
|
284
|
+
delete(key: object): boolean;
|
|
285
|
+
get(key: object): any;
|
|
286
|
+
set(key: object, value: any);
|
|
287
|
+
|
|
288
|
+
customMethod(): void;
|
|
289
|
+
customValue: string;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function getCache(): Cache {
|
|
293
|
+
const entries = [];
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
delete(key) {
|
|
297
|
+
for (let index = 0; index < entries.length; ++index) {
|
|
298
|
+
if (entries[index][0] === key) {
|
|
299
|
+
entries.splice(index, 1);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return false;
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
get(key) {
|
|
308
|
+
for (let index = 0; index < entries.length; ++index) {
|
|
309
|
+
if (entries[index][0] === key) {
|
|
310
|
+
return entries[index][1];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
set(key, value) {
|
|
316
|
+
for (let index = 0; index < entries.length; ++index) {
|
|
317
|
+
if (entries[index][0] === key) {
|
|
318
|
+
entries[index][1] = value;
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
entries.push([key, value]);
|
|
324
|
+
|
|
325
|
+
return this;
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
customMethod() {
|
|
329
|
+
console.log('hello!');
|
|
330
|
+
},
|
|
331
|
+
customValue: 'goodbye',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
201
334
|
|
|
202
|
-
const
|
|
203
|
-
const objectB = { foo: 'baz' };
|
|
204
|
-
const meta = 'bar';
|
|
335
|
+
const customCircularHandler = createCustomCircularEqual(() => ({}));
|
|
205
336
|
|
|
206
|
-
|
|
337
|
+
const circularDeepEqual = (a, b) => customCircularHandler(a, b, getCache());
|
|
207
338
|
```
|
|
208
339
|
|
|
209
340
|
## Benchmarks
|