fast-equals 4.0.3 → 5.0.0-beta.1

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 +146 -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/index.d.ts +62 -55
  45. package/package.json +59 -38
  46. package/recipes/explicit-property-check.md +4 -6
  47. package/recipes/legacy-circular-equal-support.md +28 -20
  48. package/recipes/legacy-regexp-support.md +15 -16
  49. package/recipes/non-standard-properties.md +35 -23
  50. package/recipes/using-meta-in-comparison.md +10 -13
  51. package/src/comparator.ts +43 -40
  52. package/src/equals.ts +263 -0
  53. package/src/index.ts +183 -79
  54. package/src/internalTypes.ts +74 -0
  55. package/src/utils.ts +42 -49
  56. package/dist/fast-equals.cjs.js +0 -432
  57. package/dist/fast-equals.cjs.js.map +0 -1
  58. package/dist/fast-equals.esm.js +0 -422
  59. package/dist/fast-equals.esm.js.map +0 -1
  60. package/dist/fast-equals.js +0 -438
  61. package/dist/fast-equals.js.map +0 -1
  62. package/dist/fast-equals.min.js +0 -1
  63. package/dist/fast-equals.mjs +0 -422
  64. package/dist/fast-equals.mjs.map +0 -1
  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/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;
package/src/utils.ts CHANGED
@@ -1,31 +1,48 @@
1
1
  import {
2
+ AnyEqualityComparator,
3
+ BaseCircular,
4
+ CircularState,
5
+ Dictionary,
2
6
  EqualityComparator,
3
7
  InternalEqualityComparator,
8
+ State,
4
9
  TypeEqualityComparator,
5
- } from '../index.d';
10
+ } from './internalTypes';
11
+
12
+ const { getOwnPropertyNames, getOwnPropertySymbols } = Object;
13
+ const { hasOwnProperty } = Object.prototype;
14
+
15
+ export function combineComparators<Meta>(
16
+ comparatorA: AnyEqualityComparator<Meta>,
17
+ comparatorB: AnyEqualityComparator<Meta>,
18
+ ) {
19
+ return function isEqual<A, B>(a: A, b: B, state: State<Meta>) {
20
+ return comparatorA(a, b, state) && comparatorB(a, b, state);
21
+ };
22
+ }
6
23
 
7
24
  /**
8
25
  * Default equality comparator pass-through, used as the standard `isEqual` creator for
9
26
  * use inside the built comparator.
10
27
  */
11
- export function createDefaultIsNestedEqual<Meta>(
12
- comparator: EqualityComparator<Meta>,
28
+ export function createInternalComparator<Meta>(
29
+ compare: EqualityComparator<Meta>,
13
30
  ): InternalEqualityComparator<Meta> {
14
- return function isEqual<A, B>(
15
- a: A,
16
- b: B,
31
+ return function (
32
+ a: any,
33
+ b: any,
17
34
  _indexOrKeyA: any,
18
35
  _indexOrKeyB: any,
19
36
  _parentA: any,
20
37
  _parentB: any,
21
- meta: Meta,
38
+ state: State<Meta>,
22
39
  ) {
23
- return comparator(a, b, meta);
40
+ return compare(a, b, state);
24
41
  };
25
42
  }
26
43
 
27
44
  /**
28
- * Wrap the provided `areItemsEqual` method to manage the circular cache, allowing
45
+ * Wrap the provided `areItemsEqual` method to manage the circular state, allowing
29
46
  * for circular references to be safely included in the comparison without creating
30
47
  * stack overflows.
31
48
  */
@@ -35,13 +52,14 @@ export function createIsCircular<
35
52
  return function isCircular(
36
53
  a: any,
37
54
  b: any,
38
- isEqual: InternalEqualityComparator<WeakMap<any, any>>,
39
- cache: WeakMap<any, any>,
55
+ state: CircularState<BaseCircular>,
40
56
  ) {
41
57
  if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {
42
- return areItemsEqual(a, b, isEqual, cache);
58
+ return areItemsEqual(a, b, state);
43
59
  }
44
60
 
61
+ const { cache } = state;
62
+
45
63
  const cachedA = cache.get(a);
46
64
  const cachedB = cache.get(b);
47
65
 
@@ -52,7 +70,7 @@ export function createIsCircular<
52
70
  cache.set(a, b);
53
71
  cache.set(b, a);
54
72
 
55
- const result = areItemsEqual(a, b, isEqual, cache);
73
+ const result = areItemsEqual(a, b, state);
56
74
 
57
75
  cache.delete(a);
58
76
  cache.delete(b);
@@ -61,47 +79,22 @@ export function createIsCircular<
61
79
  } as AreItemsEqual;
62
80
  }
63
81
 
64
- /**
65
- * Targeted shallow merge of two objects.
66
- *
67
- * @NOTE
68
- * This exists as a tinier compiled version of the `__assign` helper that
69
- * `tsc` injects in case of `Object.assign` not being present.
70
- */
71
- export function merge<A extends object, B extends object>(a: A, b: B): A & B {
72
- const merged: Record<string, any> = {};
73
-
74
- for (const key in a) {
75
- merged[key] = a[key];
76
- }
77
-
78
- for (const key in b) {
79
- merged[key] = b[key];
80
- }
81
-
82
- return merged as A & B;
82
+ export function getStrictProperties(
83
+ object: Dictionary,
84
+ ): Array<string | symbol> {
85
+ return (getOwnPropertyNames(object) as Array<string | symbol>).concat(
86
+ getOwnPropertySymbols(object),
87
+ );
83
88
  }
84
89
 
85
- /**
86
- * Whether the value is a plain object.
87
- *
88
- * @NOTE
89
- * This is a same-realm compariosn only.
90
- */
91
- export function isPlainObject(value: any): boolean {
92
- return value.constructor === Object || value.constructor == null;
93
- }
94
-
95
- /**
96
- * When the value is `Promise`-like, aka "then-able".
97
- */
98
- export function isPromiseLike(value: any): boolean {
99
- return typeof value.then === 'function';
100
- }
90
+ export const hasOwn =
91
+ Object.hasOwn ||
92
+ ((object: Dictionary, property: number | string | symbol) =>
93
+ hasOwnProperty.call(object, property));
101
94
 
102
95
  /**
103
96
  * Whether the values passed are strictly equal or both NaN.
104
97
  */
105
98
  export function sameValueZeroEqual(a: any, b: any): boolean {
106
- return a === b || (a !== a && b !== b);
99
+ return a || b ? a === b : a === b || (a !== a && b !== b);
107
100
  }