fast-equals 3.0.3 → 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 ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "all"
4
+ }
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 ~1kB when minified and gzipped.
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
- You can either import the individual functions desired:
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
- ```javascript
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
- ```javascript
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
- ```javascript
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
- ```javascript
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
- ```javascript
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
- ```javascript
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 partial-application function that will receive the internal comparator and should return a function that compares two objects.
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
- ```typescript
175
- type EqualityComparator = (a: any, b: any, meta?: any) => boolean;
176
- type InternalEqualityComparator = (a: any, b: any, indexOrKeyA: any, indexOrKeyB: any, parentA: any, parentB: any, meta: any) => boolean;
177
- type EqualityComparatorCreator = (
178
- deepEqual: EqualityComparator,
179
- ) => InternalEqualityComparator;
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
- createIsEqual?: EqualityComparatorCreator,
192
+ getComparatorOptions: (
193
+ defaultOptions: CreateComparatorOptions,
194
+ ) => Partial<CreateComparatorOptions>,
183
195
  ): EqualityComparator;
184
196
  ```
185
197
 
186
- The `meta` parameter in `EqualityComparator` and `InternalEqualityComparator` 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.
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
- An example for a custom equality comparison that also checks against values in the meta object:
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
- ```javascript
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) => (objectA, objectB, indexOrKeyA, indexOrKeyB, parentA, parentB, meta) =>
197
- objectA.foo === meta ||
198
- objectB.foo === meta ||
199
- deepEqual(objectA, objectB, meta),
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 objectA = { foo: 'bar' };
203
- const objectB = { foo: 'baz' };
204
- const meta = 'bar';
335
+ const customCircularHandler = createCustomCircularEqual(() => ({}));
205
336
 
206
- console.log(isDeepEqualOrFooMatchesMeta(objectA, objectB, meta)); // true
337
+ const circularDeepEqual = (a, b) => customCircularHandler(a, b, getCache());
207
338
  ```
208
339
 
209
340
  ## Benchmarks