fast-equals 4.0.3 → 5.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.
Files changed (71) hide show
  1. package/.babelrc +34 -0
  2. package/.prettierrc +4 -0
  3. package/CHANGELOG.md +39 -0
  4. package/README.md +145 -39
  5. package/build/rollup/config.base.js +49 -0
  6. package/build/rollup/config.cjs.js +10 -0
  7. package/build/rollup/config.esm.js +10 -0
  8. package/build/rollup/config.min.js +13 -0
  9. package/build/rollup/config.umd.js +10 -0
  10. package/build/tsconfig/base.json +32 -0
  11. package/build/tsconfig/cjs.json +8 -0
  12. package/build/tsconfig/declarations.json +9 -0
  13. package/build/tsconfig/esm.json +8 -0
  14. package/build/tsconfig/min.json +8 -0
  15. package/build/tsconfig/umd.json +8 -0
  16. package/build/webpack.config.js +57 -0
  17. package/dist/cjs/index.cjs +510 -0
  18. package/dist/cjs/index.cjs.map +1 -0
  19. package/dist/cjs/types/comparator.d.ts +2 -0
  20. package/dist/cjs/types/equals.d.ts +46 -0
  21. package/dist/cjs/types/index.d.ts +56 -0
  22. package/dist/cjs/types/internalTypes.d.ts +36 -0
  23. package/dist/cjs/types/utils.d.ts +19 -0
  24. package/dist/esm/index.mjs +499 -0
  25. package/dist/esm/index.mjs.map +1 -0
  26. package/dist/esm/types/comparator.d.ts +2 -0
  27. package/dist/esm/types/equals.d.ts +46 -0
  28. package/dist/esm/types/index.d.ts +56 -0
  29. package/dist/esm/types/internalTypes.d.ts +36 -0
  30. package/dist/esm/types/utils.d.ts +19 -0
  31. package/dist/min/index.js +1 -0
  32. package/dist/min/types/comparator.d.ts +2 -0
  33. package/dist/min/types/equals.d.ts +46 -0
  34. package/dist/min/types/index.d.ts +56 -0
  35. package/dist/min/types/internalTypes.d.ts +36 -0
  36. package/dist/min/types/utils.d.ts +19 -0
  37. package/dist/umd/index.js +516 -0
  38. package/dist/umd/index.js.map +1 -0
  39. package/dist/umd/types/comparator.d.ts +2 -0
  40. package/dist/umd/types/equals.d.ts +46 -0
  41. package/dist/umd/types/index.d.ts +56 -0
  42. package/dist/umd/types/internalTypes.d.ts +36 -0
  43. package/dist/umd/types/utils.d.ts +19 -0
  44. package/package.json +58 -37
  45. package/recipes/explicit-property-check.md +4 -6
  46. package/recipes/legacy-circular-equal-support.md +28 -20
  47. package/recipes/legacy-regexp-support.md +15 -16
  48. package/recipes/non-standard-properties.md +35 -23
  49. package/recipes/using-meta-in-comparison.md +10 -13
  50. package/src/comparator.ts +43 -40
  51. package/src/equals.ts +263 -0
  52. package/src/index.ts +183 -79
  53. package/src/internalTypes.ts +74 -0
  54. package/src/utils.ts +42 -49
  55. package/dist/fast-equals.cjs.js +0 -432
  56. package/dist/fast-equals.cjs.js.map +0 -1
  57. package/dist/fast-equals.esm.js +0 -422
  58. package/dist/fast-equals.esm.js.map +0 -1
  59. package/dist/fast-equals.js +0 -438
  60. package/dist/fast-equals.js.map +0 -1
  61. package/dist/fast-equals.min.js +0 -1
  62. package/dist/fast-equals.mjs +0 -422
  63. package/dist/fast-equals.mjs.map +0 -1
  64. package/index.d.ts +0 -58
  65. package/recipes/strict-property-descriptor-check.md +0 -42
  66. package/src/arrays.ts +0 -36
  67. package/src/dates.ts +0 -12
  68. package/src/maps.ts +0 -66
  69. package/src/objects.ts +0 -62
  70. package/src/regexps.ts +0 -11
  71. package/src/sets.ts +0 -61
package/src/equals.ts ADDED
@@ -0,0 +1,263 @@
1
+ import { getStrictProperties, hasOwn, sameValueZeroEqual } from './utils';
2
+ import type { Dictionary, State } from './internalTypes';
3
+
4
+ const OWNER = '_owner';
5
+
6
+ const { getOwnPropertyDescriptor, keys } = Object;
7
+
8
+ /**
9
+ * Whether the arrays are equal in value.
10
+ */
11
+ export function areArraysEqual(a: any[], b: any[], state: State<any>) {
12
+ let index = a.length;
13
+
14
+ if (b.length !== index) {
15
+ return false;
16
+ }
17
+
18
+ while (index-- > 0) {
19
+ if (!state.equals(a[index], b[index], index, index, a, b, state)) {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ return true;
25
+ }
26
+
27
+ /**
28
+ * Whether the dates passed are equal in value.
29
+ *
30
+ * @NOTE
31
+ * This is a standalone function instead of done inline in the comparator
32
+ * to allow for overrides.
33
+ */
34
+ export function areDatesEqual(a: Date, b: Date): boolean {
35
+ return sameValueZeroEqual(a.getTime(), b.getTime());
36
+ }
37
+
38
+ /**
39
+ * Whether the `Map`s are equal in value.
40
+ */
41
+ export function areMapsEqual(
42
+ a: Map<any, any>,
43
+ b: Map<any, any>,
44
+ state: State<any>,
45
+ ): boolean {
46
+ let isValueEqual = a.size === b.size;
47
+
48
+ if (isValueEqual && a.size) {
49
+ // The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
50
+ // the inability to control the performance of the resulting code. It also avoids excessive
51
+ // iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
52
+ // we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
53
+ // equality checks themselves.
54
+
55
+ const matchedIndices: Record<number, true> = {};
56
+
57
+ let indexA = 0;
58
+
59
+ a.forEach((aValue, aKey) => {
60
+ if (!isValueEqual) {
61
+ return;
62
+ }
63
+
64
+ let hasMatch = false;
65
+ let matchIndexB = 0;
66
+
67
+ b.forEach((bValue, bKey) => {
68
+ if (
69
+ !hasMatch &&
70
+ !matchedIndices[matchIndexB] &&
71
+ (hasMatch =
72
+ state.equals(aKey, bKey, indexA, matchIndexB, a, b, state) &&
73
+ state.equals(aValue, bValue, aKey, bKey, a, b, state))
74
+ ) {
75
+ matchedIndices[matchIndexB] = true;
76
+ }
77
+
78
+ matchIndexB++;
79
+ });
80
+
81
+ indexA++;
82
+ isValueEqual = hasMatch;
83
+ });
84
+ }
85
+
86
+ return isValueEqual;
87
+ }
88
+
89
+ /**
90
+ * Whether the objects are equal in value.
91
+ */
92
+ export function areObjectsEqual(
93
+ a: Dictionary,
94
+ b: Dictionary,
95
+ state: State<any>,
96
+ ): boolean {
97
+ const properties = keys(a);
98
+
99
+ let index = properties.length;
100
+
101
+ if (keys(b).length !== index) {
102
+ return false;
103
+ }
104
+
105
+ let property: string | symbol;
106
+
107
+ // Decrementing `while` showed faster results than either incrementing or
108
+ // decrementing `for` loop and than an incrementing `while` loop. Declarative
109
+ // methods like `some` / `every` were not used to avoid incurring the garbage
110
+ // cost of anonymous callbacks.
111
+ while (index-- > 0) {
112
+ property = properties[index]!;
113
+
114
+ if (
115
+ property === OWNER &&
116
+ (a.$$typeof || b.$$typeof) &&
117
+ a.$$typeof !== b.$$typeof
118
+ ) {
119
+ return false;
120
+ }
121
+
122
+ if (
123
+ !hasOwn(b, property) ||
124
+ !state.equals(a[property], b[property], property, property, a, b, state)
125
+ ) {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ return true;
131
+ }
132
+
133
+ /**
134
+ * Whether the objects are equal in value with strict property checking.
135
+ */
136
+ export function areObjectsEqualStrict(
137
+ a: Dictionary,
138
+ b: Dictionary,
139
+ state: State<any>,
140
+ ): boolean {
141
+ const properties = getStrictProperties(a);
142
+
143
+ let index = properties.length;
144
+
145
+ if (getStrictProperties(b).length !== index) {
146
+ return false;
147
+ }
148
+
149
+ let property: string | symbol;
150
+ let descriptorA: ReturnType<typeof getOwnPropertyDescriptor>;
151
+ let descriptorB: ReturnType<typeof getOwnPropertyDescriptor>;
152
+
153
+ // Decrementing `while` showed faster results than either incrementing or
154
+ // decrementing `for` loop and than an incrementing `while` loop. Declarative
155
+ // methods like `some` / `every` were not used to avoid incurring the garbage
156
+ // cost of anonymous callbacks.
157
+ while (index-- > 0) {
158
+ property = properties[index]!;
159
+
160
+ if (
161
+ property === OWNER &&
162
+ (a.$$typeof || b.$$typeof) &&
163
+ a.$$typeof !== b.$$typeof
164
+ ) {
165
+ return false;
166
+ }
167
+
168
+ if (!hasOwn(b, property)) {
169
+ return false;
170
+ }
171
+
172
+ if (
173
+ !state.equals(a[property], b[property], property, property, a, b, state)
174
+ ) {
175
+ return false;
176
+ }
177
+
178
+ descriptorA = getOwnPropertyDescriptor(a, property);
179
+ descriptorB = getOwnPropertyDescriptor(b, property);
180
+
181
+ if (
182
+ (descriptorA || descriptorB) &&
183
+ (!descriptorA ||
184
+ !descriptorB ||
185
+ descriptorA.configurable !== descriptorB.configurable ||
186
+ descriptorA.enumerable !== descriptorB.enumerable ||
187
+ descriptorA.writable !== descriptorB.writable)
188
+ ) {
189
+ return false;
190
+ }
191
+ }
192
+
193
+ return true;
194
+ }
195
+
196
+ /**
197
+ * Whether the primitive wrappers passed are equal in value.
198
+ *
199
+ * @NOTE
200
+ * This is a standalone function instead of done inline in the comparator
201
+ * to allow for overrides.
202
+ */
203
+ export function arePrimitiveWrappersEqual(a: Date, b: Date): boolean {
204
+ return sameValueZeroEqual(a.valueOf(), b.valueOf());
205
+ }
206
+
207
+ /**
208
+ * Whether the regexps passed are equal in value.
209
+ *
210
+ * @NOTE
211
+ * This is a standalone function instead of done inline in the comparator
212
+ * to allow for overrides. An example of this would be supporting a
213
+ * pre-ES2015 environment where the `flags` property is not available.
214
+ */
215
+ export function areRegExpsEqual(a: RegExp, b: RegExp): boolean {
216
+ return a.source === b.source && a.flags === b.flags;
217
+ }
218
+
219
+ /**
220
+ * Whether the `Set`s are equal in value.
221
+ */
222
+ export function areSetsEqual(
223
+ a: Set<any>,
224
+ b: Set<any>,
225
+ state: State<any>,
226
+ ): boolean {
227
+ let isValueEqual = a.size === b.size;
228
+
229
+ if (isValueEqual && a.size) {
230
+ // The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
231
+ // the inability to control the performance of the resulting code. It also avoids excessive
232
+ // iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
233
+ // we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
234
+ // equality checks themselves.
235
+
236
+ const matchedIndices: Record<number, true> = {};
237
+
238
+ a.forEach((aValue, aKey) => {
239
+ if (!isValueEqual) {
240
+ return;
241
+ }
242
+
243
+ let hasMatch = false;
244
+ let matchIndex = 0;
245
+
246
+ b.forEach((bValue, bKey) => {
247
+ if (
248
+ !hasMatch &&
249
+ !matchedIndices[matchIndex] &&
250
+ (hasMatch = state.equals(aValue, bValue, aKey, bKey, a, b, state))
251
+ ) {
252
+ matchedIndices[matchIndex] = true;
253
+ }
254
+
255
+ matchIndex++;
256
+ });
257
+
258
+ isValueEqual = hasMatch;
259
+ });
260
+ }
261
+
262
+ return isValueEqual;
263
+ }
package/src/index.ts CHANGED
@@ -1,121 +1,225 @@
1
1
  import { createComparator } from './comparator';
2
- import { areArraysEqual, areArraysEqualCircular } from './arrays';
3
- import { areDatesEqual } from './dates';
4
- import { areMapsEqual, areMapsEqualCircular } from './maps';
5
- import { areObjectsEqual, areObjectsEqualCircular } from './objects';
6
- import { areRegExpsEqual } from './regexps';
7
- import { areSetsEqual, areSetsEqualCircular } from './sets';
8
- import { createDefaultIsNestedEqual, merge, sameValueZeroEqual } from './utils';
9
-
2
+ import {
3
+ areArraysEqual,
4
+ areDatesEqual,
5
+ areMapsEqual,
6
+ areObjectsEqual,
7
+ areObjectsEqualStrict,
8
+ arePrimitiveWrappersEqual,
9
+ areRegExpsEqual,
10
+ areSetsEqual,
11
+ } from './equals';
10
12
  import type {
11
- BaseCircularMeta,
12
- CreateComparatorCreatorOptions,
13
+ CircularState,
14
+ ComparatorConfig,
15
+ CreateCustomComparatorConfig,
16
+ CreateState,
17
+ DefaultState,
13
18
  EqualityComparator,
14
- GetComparatorOptions,
15
- } from '../index.d';
19
+ } from './internalTypes';
20
+ import {
21
+ combineComparators,
22
+ createInternalComparator,
23
+ createIsCircular,
24
+ sameValueZeroEqual,
25
+ } from './utils';
16
26
 
17
27
  export { sameValueZeroEqual };
18
28
 
19
- const DEFAULT_CONFIG: CreateComparatorCreatorOptions<undefined> = Object.freeze(
20
- {
29
+ interface DefaultEqualCreatorOptions<Meta> {
30
+ comparator?: EqualityComparator<Meta>;
31
+ circular?: boolean;
32
+ strict?: boolean;
33
+ }
34
+
35
+ interface CustomEqualCreatorOptions<Meta>
36
+ extends DefaultEqualCreatorOptions<Meta> {
37
+ createCustomConfig?: CreateCustomComparatorConfig<Meta>;
38
+ createInternalComparator?: typeof createInternalComparator;
39
+ createState?: CreateState<Meta>;
40
+ }
41
+
42
+ function createComparatorConfig<Meta>({
43
+ circular,
44
+ strict,
45
+ }: DefaultEqualCreatorOptions<Meta>) {
46
+ const config: ComparatorConfig<Meta> = {
21
47
  areArraysEqual,
22
48
  areDatesEqual,
23
49
  areMapsEqual,
24
50
  areObjectsEqual,
51
+ arePrimitiveWrappersEqual,
25
52
  areRegExpsEqual,
26
53
  areSetsEqual,
27
- createIsNestedEqual: createDefaultIsNestedEqual,
28
- },
29
- );
30
- const DEFAULT_CIRCULAR_CONFIG: CreateComparatorCreatorOptions<BaseCircularMeta> =
31
- Object.freeze({
32
- areArraysEqual: areArraysEqualCircular,
33
- areDatesEqual,
34
- areMapsEqual: areMapsEqualCircular,
35
- areObjectsEqual: areObjectsEqualCircular,
36
- areRegExpsEqual,
37
- areSetsEqual: areSetsEqualCircular,
38
- createIsNestedEqual: createDefaultIsNestedEqual,
54
+ };
55
+
56
+ if (strict) {
57
+ config.areArraysEqual = areObjectsEqual;
58
+ config.areMapsEqual = combineComparators(areMapsEqual, areObjectsEqual);
59
+ config.areObjectsEqual = areObjectsEqualStrict;
60
+ config.areSetsEqual = combineComparators(areSetsEqual, areObjectsEqual);
61
+ }
62
+
63
+ if (circular) {
64
+ config.areArraysEqual = createIsCircular(config.areArraysEqual);
65
+ config.areMapsEqual = createIsCircular(config.areMapsEqual);
66
+ config.areObjectsEqual = createIsCircular(config.areObjectsEqual);
67
+ config.areSetsEqual = createIsCircular(config.areSetsEqual);
68
+ }
69
+
70
+ return config;
71
+ }
72
+
73
+ function createDefaultEqualCreator(
74
+ options: DefaultEqualCreatorOptions<undefined> = {},
75
+ ) {
76
+ const config = createComparatorConfig(options);
77
+ const isEqual = createComparator(config);
78
+ const isEqualComparator =
79
+ options.comparator || createInternalComparator(isEqual);
80
+ const strict = !!options.strict;
81
+
82
+ if (options.circular) {
83
+ return function equals<A, B>(a: A, b: B): boolean {
84
+ return isEqual(a, b, {
85
+ cache: new WeakMap(),
86
+ equals: isEqualComparator,
87
+ meta: undefined,
88
+ strict,
89
+ });
90
+ };
91
+ }
92
+
93
+ const state = Object.freeze({
94
+ cache: undefined,
95
+ equals: isEqualComparator,
96
+ meta: undefined,
97
+ strict,
39
98
  });
40
99
 
41
- const isDeepEqual = createComparator(DEFAULT_CONFIG);
100
+ return function equals<A, B>(a: A, b: B): boolean {
101
+ return isEqual(a, b, state);
102
+ };
103
+ }
42
104
 
43
105
  /**
44
106
  * Whether the items passed are deeply-equal in value.
45
107
  */
46
- export function deepEqual<A, B>(a: A, b: B): boolean {
47
- return isDeepEqual(a, b, undefined);
48
- }
108
+ export const deepEqual = createDefaultEqualCreator();
49
109
 
50
- const isShallowEqual = createComparator(
51
- merge(DEFAULT_CONFIG, { createIsNestedEqual: () => sameValueZeroEqual }),
52
- );
110
+ /**
111
+ * Whether the items passed are deeply-equal in value based on strict comparison.
112
+ */
113
+ export const strictDeepEqual = createDefaultEqualCreator({ strict: true });
53
114
 
54
115
  /**
55
- * Whether the items passed are shallowly-equal in value.
116
+ * Whether the items passed are deeply-equal in value, including circular references.
56
117
  */
57
- export function shallowEqual<A, B>(a: A, b: B): boolean {
58
- return isShallowEqual(a, b, undefined);
59
- }
118
+ export const circularDeepEqual = createDefaultEqualCreator({ circular: true });
60
119
 
61
- const isCircularDeepEqual = createComparator(DEFAULT_CIRCULAR_CONFIG);
120
+ /**
121
+ * Whether the items passed are deeply-equal in value, including circular references,
122
+ * based on strict comparison.
123
+ */
124
+ export const strictCircularDeepEqual = createDefaultEqualCreator({
125
+ circular: true,
126
+ strict: true,
127
+ });
62
128
 
63
129
  /**
64
- * Whether the items passed are deeply-equal in value, including circular references.
130
+ * Whether the items passed are shallowly-equal in value.
65
131
  */
66
- export function circularDeepEqual<A, B>(a: A, b: B): boolean {
67
- return isCircularDeepEqual(a, b, new WeakMap());
68
- }
132
+ export const shallowEqual = createDefaultEqualCreator({
133
+ comparator: sameValueZeroEqual,
134
+ });
69
135
 
70
- const isCircularShallowEqual = createComparator(
71
- merge(DEFAULT_CIRCULAR_CONFIG, {
72
- createIsNestedEqual: () => sameValueZeroEqual,
73
- }),
74
- );
136
+ /**
137
+ * Whether the items passed are shallowly-equal in value based on strict comparison
138
+ */
139
+ export const strictShallowEqual = createDefaultEqualCreator({
140
+ comparator: sameValueZeroEqual,
141
+ strict: true,
142
+ });
75
143
 
76
144
  /**
77
145
  * Whether the items passed are shallowly-equal in value, including circular references.
78
146
  */
79
- export function circularShallowEqual<A, B>(a: A, b: B): boolean {
80
- return isCircularShallowEqual(a, b, new WeakMap());
81
- }
147
+ export const circularShallowEqual = createDefaultEqualCreator({
148
+ comparator: sameValueZeroEqual,
149
+ circular: true,
150
+ });
82
151
 
83
152
  /**
84
- * Create a custom equality comparison method.
85
- *
86
- * This can be done to create very targeted comparisons in extreme hot-path scenarios
87
- * where the standard methods are not performant enough, but can also be used to provide
88
- * support for legacy environments that do not support expected features like
89
- * `RegExp.prototype.flags` out of the box.
153
+ * Whether the items passed are shallowly-equal in value, including circular references,
154
+ * based on strict comparison.
90
155
  */
91
- export function createCustomEqual<Meta = undefined>(
92
- getComparatorOptions: GetComparatorOptions<Meta>,
93
- ): EqualityComparator<Meta> {
94
- return createComparator<Meta>(
95
- merge(DEFAULT_CONFIG, getComparatorOptions(DEFAULT_CONFIG as any)),
96
- );
97
- }
156
+ export const strictCircularShallowEqual = createDefaultEqualCreator({
157
+ comparator: sameValueZeroEqual,
158
+ circular: true,
159
+ strict: true,
160
+ });
98
161
 
99
162
  /**
100
- * Create a custom equality comparison method that handles circular references. This is very
101
- * similar to `createCustomEqual`, with the only difference being that `meta` expects to be
102
- * populated with a `WeakMap`-like contract.
163
+ * Create a custom equality comparison method.
103
164
  *
104
165
  * This can be done to create very targeted comparisons in extreme hot-path scenarios
105
166
  * where the standard methods are not performant enough, but can also be used to provide
106
167
  * support for legacy environments that do not support expected features like
107
- * `WeakMap` out of the box.
168
+ * `RegExp.prototype.flags` out of the box.
108
169
  */
109
- export function createCustomCircularEqual<
110
- Meta extends BaseCircularMeta = WeakMap<any, any>,
111
- >(getComparatorOptions: GetComparatorOptions<Meta>): EqualityComparator<Meta> {
112
- const comparator = createComparator<Meta>(
113
- merge(
114
- DEFAULT_CIRCULAR_CONFIG,
115
- getComparatorOptions(DEFAULT_CIRCULAR_CONFIG as any),
116
- ),
117
- );
118
-
119
- return ((a: any, b: any, meta: any = new WeakMap()) =>
120
- comparator(a, b, meta)) as EqualityComparator<Meta>;
170
+ export function createCustomEqual<Meta>(
171
+ options: CustomEqualCreatorOptions<Meta> = {},
172
+ ) {
173
+ const {
174
+ comparator,
175
+ createInternalComparator: createCustomInternalComparator,
176
+ createCustomConfig,
177
+ createState,
178
+ } = options;
179
+
180
+ const baseConfig = createComparatorConfig(options);
181
+ const config = createCustomConfig
182
+ ? Object.assign({}, baseConfig, createCustomConfig(baseConfig))
183
+ : baseConfig;
184
+ const isEqualCustom = comparator || createComparator(config);
185
+ const isEqualCustomComparator = createCustomInternalComparator
186
+ ? createCustomInternalComparator(isEqualCustom)
187
+ : createInternalComparator(isEqualCustom);
188
+ const strict = !!options.strict;
189
+
190
+ if (createState) {
191
+ return function isEqual<A, B>(a: A, b: B, metaOverride?: Meta): boolean {
192
+ const customState = createState(isEqualCustom);
193
+
194
+ return isEqualCustom(a, b, {
195
+ cache: customState.cache || new WeakMap(),
196
+ equals: customState.equals || isEqualCustomComparator,
197
+ // @ts-expect-error - inferred `Meta` may be undefined, which is okay
198
+ meta: metaOverride !== undefined ? metaOverride : customState.meta,
199
+ strict: customState.strict !== undefined ? customState.strict : strict,
200
+ });
201
+ };
202
+ }
203
+
204
+ if (options.circular) {
205
+ return function equals<A, B>(a: A, b: B): boolean {
206
+ return isEqualCustom(a, b, {
207
+ cache: new WeakMap(),
208
+ equals: isEqualCustomComparator,
209
+ meta: undefined as Meta,
210
+ strict,
211
+ } as CircularState<Meta>);
212
+ };
213
+ }
214
+
215
+ const state = Object.freeze({
216
+ cache: undefined,
217
+ equals: isEqualCustomComparator,
218
+ meta: undefined,
219
+ strict,
220
+ });
221
+
222
+ return function equals<A, B>(a: A, b: B): boolean {
223
+ return isEqualCustom(a, b, state as DefaultState<Meta>);
224
+ };
121
225
  }
@@ -0,0 +1,74 @@
1
+ export interface BaseCircular
2
+ extends Pick<WeakMap<any, any>, 'delete' | 'get'> {
3
+ set(key: object, value: any): any;
4
+ }
5
+
6
+ export type State<Meta> = CircularState<Meta> | DefaultState<Meta>;
7
+
8
+ export interface CircularState<Meta> {
9
+ readonly cache: BaseCircular;
10
+ readonly equals: InternalEqualityComparator<Meta>;
11
+ meta: Meta;
12
+ readonly strict: boolean;
13
+ }
14
+
15
+ export interface DefaultState<Meta> {
16
+ readonly cache: undefined;
17
+ readonly equals: InternalEqualityComparator<Meta>;
18
+ meta: Meta;
19
+ readonly strict: boolean;
20
+ }
21
+
22
+ export interface Dictionary<Value = any> {
23
+ [key: string | symbol]: Value;
24
+ $$typeof?: any;
25
+ }
26
+
27
+ export interface ComparatorConfig<Meta> {
28
+ areArraysEqual: TypeEqualityComparator<any, Meta>;
29
+ areDatesEqual: TypeEqualityComparator<any, Meta>;
30
+ areMapsEqual: TypeEqualityComparator<any, Meta>;
31
+ areObjectsEqual: TypeEqualityComparator<any, Meta>;
32
+ arePrimitiveWrappersEqual: TypeEqualityComparator<any, Meta>;
33
+ areRegExpsEqual: TypeEqualityComparator<any, Meta>;
34
+ areSetsEqual: TypeEqualityComparator<any, Meta>;
35
+ }
36
+
37
+ export type CreateCustomComparatorConfig<Meta> = (
38
+ config: ComparatorConfig<Meta>,
39
+ ) => Partial<ComparatorConfig<Meta>>;
40
+
41
+ export type CreateState<Meta> = (
42
+ comparator: EqualityComparator<Meta>,
43
+ ) => Partial<State<Meta>>;
44
+
45
+ export type EqualityComparator<Meta> = <A, B>(
46
+ a: A,
47
+ b: B,
48
+ state: State<Meta>,
49
+ ) => boolean;
50
+ export type AnyEqualityComparator<Meta> = (
51
+ a: any,
52
+ b: any,
53
+ state: State<Meta>,
54
+ ) => boolean;
55
+
56
+ export type EqualityComparatorCreator<Meta> = (
57
+ fn: EqualityComparator<Meta>,
58
+ ) => InternalEqualityComparator<Meta>;
59
+
60
+ export type InternalEqualityComparator<Meta> = (
61
+ a: any,
62
+ b: any,
63
+ indexOrKeyA: any,
64
+ indexOrKeyB: any,
65
+ parentA: any,
66
+ parentB: any,
67
+ state: State<Meta>,
68
+ ) => boolean;
69
+
70
+ export type TypeEqualityComparator<Type, Meta = undefined> = (
71
+ a: Type,
72
+ b: Type,
73
+ state: State<Meta>,
74
+ ) => boolean;