@longlast/equals 0.5.6 → 0.5.8
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/dist/index.d.ts +36 -1
- package/dist/index.js +102 -62
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module equals
|
|
3
3
|
*/
|
|
4
|
-
import { type Curried2 } from "@longlast/curry";
|
|
4
|
+
import { type Curried2, type Curried3 } from "@longlast/curry";
|
|
5
|
+
export interface EqualsOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Custom logic to use in preference to the default {@link equals}
|
|
8
|
+
* comparison.
|
|
9
|
+
*/
|
|
10
|
+
readonly override?: undefined | ((a: unknown, b: unknown) => boolean | undefined);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* @function
|
|
14
|
+
* A version of {@link equals} that allows callers to override the default
|
|
15
|
+
* comparison algorithm. If the provided override function returns a boolean,
|
|
16
|
+
* it is used as the result of the comparison. If the override returns
|
|
17
|
+
* `undefined`, the behavior of `equalsWith` defaults to that of
|
|
18
|
+
* {@link equals}.
|
|
19
|
+
*
|
|
20
|
+
* `equalsWith` is curried. See {@link curry}.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* // `mathEquals` treats -0 as equal to 0.
|
|
25
|
+
* const mathEquals = equalsWith({override: trueIfBothZero});
|
|
26
|
+
*
|
|
27
|
+
* function trueIfBothZero(a: unknown, b: unknown) {
|
|
28
|
+
* if (a === 0 && b === 0) {
|
|
29
|
+
* return true;
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* mathEquals({x: 0}, {x: -0}); // => true
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const equalsWith: Curried3<EqualsOptions, unknown, unknown, boolean>;
|
|
5
37
|
/**
|
|
6
38
|
* @function
|
|
7
39
|
* Deeply compares two values, returning true if they're equal and false
|
|
@@ -18,6 +50,9 @@ import { type Curried2 } from "@longlast/curry";
|
|
|
18
50
|
* elements are equal (according to `equals`).
|
|
19
51
|
* - Sets are equal iff they contain the same elements. Note that set
|
|
20
52
|
* elements are _not_ deeply compared.
|
|
53
|
+
* - Maps are equal iff they have the same set of keys, and their
|
|
54
|
+
* corresponding values are deeply equal. Note that map keys are _not_
|
|
55
|
+
* deeply compared.
|
|
21
56
|
* - Partially applied curried functions are equal iff they originate from
|
|
22
57
|
* the same curried function and their bound arguments are equal
|
|
23
58
|
* according to `equals`. See {@link curry}.
|
package/dist/index.js
CHANGED
|
@@ -3,71 +3,37 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { curry } from "@longlast/curry";
|
|
5
5
|
import { $boundArguments, $equals, $getBoundArguments, $unapplied, } from "@longlast/symbols";
|
|
6
|
-
// TODO: export an `Equatable` interface that classes with the [$equals] method
|
|
7
|
-
// can implement.
|
|
8
6
|
/**
|
|
9
7
|
* @function
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* to `===` comparison, but treats `NaN` as equal to `NaN` and `0` as
|
|
16
|
-
* different from `-0`.
|
|
17
|
-
* - Dates are equal iff they have the same millisecond-precision timestamp.
|
|
18
|
-
* - RegExps are equal iff they have the same pattern and flags.
|
|
19
|
-
* - Errors are equal iff they have the same class and message.
|
|
20
|
-
* - Arrays are equal iff they have the same length and their corresponding
|
|
21
|
-
* elements are equal (according to `equals`).
|
|
22
|
-
* - Sets are equal iff they contain the same elements. Note that set
|
|
23
|
-
* elements are _not_ deeply compared.
|
|
24
|
-
* - Partially applied curried functions are equal iff they originate from
|
|
25
|
-
* the same curried function and their bound arguments are equal
|
|
26
|
-
* according to `equals`. See {@link curry}.
|
|
27
|
-
* - Other objects are equal iff they have the same prototype (e.g. the same
|
|
28
|
-
* class) and the same set of enumerable string-keyed properties, and the
|
|
29
|
-
* values of their corresponding properties are equal (according to
|
|
30
|
-
* `equals`).
|
|
8
|
+
* A version of {@link equals} that allows callers to override the default
|
|
9
|
+
* comparison algorithm. If the provided override function returns a boolean,
|
|
10
|
+
* it is used as the result of the comparison. If the override returns
|
|
11
|
+
* `undefined`, the behavior of `equalsWith` defaults to that of
|
|
12
|
+
* {@link equals}.
|
|
31
13
|
*
|
|
32
|
-
*
|
|
33
|
-
* using the {@link symbols.$equals $equals} symbol to define a method on that
|
|
34
|
-
* class. For example:
|
|
14
|
+
* `equalsWith` is curried. See {@link curry}.
|
|
35
15
|
*
|
|
16
|
+
* @example
|
|
36
17
|
* ```ts
|
|
37
|
-
*
|
|
18
|
+
* // `mathEquals` treats -0 as equal to 0.
|
|
19
|
+
* const mathEquals = equalsWith({override: trueIfBothZero});
|
|
38
20
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* super(message);
|
|
43
|
-
* this.statusCode = statusCode;
|
|
44
|
-
* }
|
|
45
|
-
*
|
|
46
|
-
* [$equals](other: unknown) {
|
|
47
|
-
* return other instanceof HttpError &&
|
|
48
|
-
* other.statusCode === this.statusCode &&
|
|
49
|
-
* other.message === this.message;
|
|
21
|
+
* function trueIfBothZero(a: unknown, b: unknown) {
|
|
22
|
+
* if (a === 0 && b === 0) {
|
|
23
|
+
* return true;
|
|
50
24
|
* }
|
|
51
25
|
* }
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* Note that this makes the comparison asymmetrical: `a` is considered equal to
|
|
55
|
-
* `b` iff `a[$equals](b)` returns truthy. The `$equals` method will always be
|
|
56
|
-
* called on the *first* argument to `equals()`.
|
|
57
|
-
*
|
|
58
|
-
* `equals()` is curried. See {@link curry}.
|
|
59
|
-
*
|
|
60
|
-
* ## Limitations
|
|
61
26
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* that they do not contain cycles.
|
|
27
|
+
* mathEquals({x: 0}, {x: -0}); // => true
|
|
28
|
+
* ```
|
|
65
29
|
*/
|
|
66
|
-
export const
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
30
|
+
export const equalsWith = curry(_equalsWith);
|
|
31
|
+
function _equalsWith(options, a, b) {
|
|
32
|
+
const override = options.override?.(a, b);
|
|
33
|
+
if (override !== undefined) {
|
|
34
|
+
return override;
|
|
35
|
+
}
|
|
36
|
+
if (a == null || b == null) {
|
|
71
37
|
return a === b;
|
|
72
38
|
}
|
|
73
39
|
// TODO: (pre-1.0.0) decide if we should pass `equals` as the second
|
|
@@ -85,7 +51,7 @@ function _equals(a, b) {
|
|
|
85
51
|
const bUnapplied = b[$unapplied];
|
|
86
52
|
return (aUnapplied != null &&
|
|
87
53
|
aUnapplied === bUnapplied &&
|
|
88
|
-
|
|
54
|
+
_equalsWith(options, getBoundArguments(a), getBoundArguments(b)));
|
|
89
55
|
}
|
|
90
56
|
// If `a` is a primitive at this point, return false, since we already know
|
|
91
57
|
// it is not identical to `b`.
|
|
@@ -113,7 +79,8 @@ function _equals(a, b) {
|
|
|
113
79
|
}
|
|
114
80
|
if (Array.isArray(a)) {
|
|
115
81
|
unsafeNarrow(b);
|
|
116
|
-
return a.length === b.length &&
|
|
82
|
+
return (a.length === b.length &&
|
|
83
|
+
a.every((_, i) => _equalsWith(options, a[i], b[i])));
|
|
117
84
|
}
|
|
118
85
|
if (setConstructorString === aConstructorString) {
|
|
119
86
|
unsafeNarrow(a);
|
|
@@ -121,7 +88,19 @@ function _equals(a, b) {
|
|
|
121
88
|
return a.size === b.size && [...a].every((v) => b.has(v));
|
|
122
89
|
}
|
|
123
90
|
// TODO: typed arrays
|
|
124
|
-
|
|
91
|
+
if (mapConstructorString === aConstructorString) {
|
|
92
|
+
unsafeNarrow(a);
|
|
93
|
+
unsafeNarrow(b);
|
|
94
|
+
if (a.size !== b.size) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
for (const key of a.keys()) {
|
|
98
|
+
if (!b.has(key) || !_equalsWith(options, a.get(key), b.get(key))) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
125
104
|
if (objectConstructorString === aConstructorString ||
|
|
126
105
|
protoOf(a) === protoOf(b)) {
|
|
127
106
|
unsafeNarrow(a);
|
|
@@ -136,7 +115,7 @@ function _equals(a, b) {
|
|
|
136
115
|
if (!bKeySet.has(key)) {
|
|
137
116
|
return false;
|
|
138
117
|
}
|
|
139
|
-
if (!
|
|
118
|
+
if (!_equalsWith(options, a[key], b[key])) {
|
|
140
119
|
return false;
|
|
141
120
|
}
|
|
142
121
|
}
|
|
@@ -144,6 +123,69 @@ function _equals(a, b) {
|
|
|
144
123
|
}
|
|
145
124
|
return false;
|
|
146
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* @function
|
|
128
|
+
* Deeply compares two values, returning true if they're equal and false
|
|
129
|
+
* otherwise. The following criteria are used to determine equality:
|
|
130
|
+
*
|
|
131
|
+
* - All values are equal to themselves.
|
|
132
|
+
* - Primitives `a` and `b` are equal iff `Object.is(a, b)`. This is similar
|
|
133
|
+
* to `===` comparison, but treats `NaN` as equal to `NaN` and `0` as
|
|
134
|
+
* different from `-0`.
|
|
135
|
+
* - Dates are equal iff they have the same millisecond-precision timestamp.
|
|
136
|
+
* - RegExps are equal iff they have the same pattern and flags.
|
|
137
|
+
* - Errors are equal iff they have the same class and message.
|
|
138
|
+
* - Arrays are equal iff they have the same length and their corresponding
|
|
139
|
+
* elements are equal (according to `equals`).
|
|
140
|
+
* - Sets are equal iff they contain the same elements. Note that set
|
|
141
|
+
* elements are _not_ deeply compared.
|
|
142
|
+
* - Maps are equal iff they have the same set of keys, and their
|
|
143
|
+
* corresponding values are deeply equal. Note that map keys are _not_
|
|
144
|
+
* deeply compared.
|
|
145
|
+
* - Partially applied curried functions are equal iff they originate from
|
|
146
|
+
* the same curried function and their bound arguments are equal
|
|
147
|
+
* according to `equals`. See {@link curry}.
|
|
148
|
+
* - Other objects are equal iff they have the same prototype (e.g. the same
|
|
149
|
+
* class) and the same set of enumerable string-keyed properties, and the
|
|
150
|
+
* values of their corresponding properties are equal (according to
|
|
151
|
+
* `equals`).
|
|
152
|
+
*
|
|
153
|
+
* You can customize how `equals()` compares values of a specific class by
|
|
154
|
+
* using the {@link symbols.$equals $equals} symbol to define a method on that
|
|
155
|
+
* class. For example:
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* import {$equals} from "@longlast/symbols"
|
|
159
|
+
*
|
|
160
|
+
* class HttpError extends Error {
|
|
161
|
+
* private statusCode: number;
|
|
162
|
+
* constructor(message: string, statusCode: number) {
|
|
163
|
+
* super(message);
|
|
164
|
+
* this.statusCode = statusCode;
|
|
165
|
+
* }
|
|
166
|
+
*
|
|
167
|
+
* [$equals](other: unknown) {
|
|
168
|
+
* return other instanceof HttpError &&
|
|
169
|
+
* other.statusCode === this.statusCode &&
|
|
170
|
+
* other.message === this.message;
|
|
171
|
+
* }
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* Note that this makes the comparison asymmetrical: `a` is considered equal to
|
|
176
|
+
* `b` iff `a[$equals](b)` returns truthy. The `$equals` method will always be
|
|
177
|
+
* called on the *first* argument to `equals()`.
|
|
178
|
+
*
|
|
179
|
+
* `equals()` is curried. See {@link curry}.
|
|
180
|
+
*
|
|
181
|
+
* ## Limitations
|
|
182
|
+
*
|
|
183
|
+
* `equals()` can throw a `RangeError` if one of its arguments contains a
|
|
184
|
+
* reference cycle. Avoid passing mutable objects to `equals()` unless you know
|
|
185
|
+
* that they do not contain cycles.
|
|
186
|
+
*/
|
|
187
|
+
// TODO: clear function provenance on `equals`.
|
|
188
|
+
export const equals = equalsWith({});
|
|
147
189
|
function getBoundArguments(f) {
|
|
148
190
|
// TODO: (pre-1.0.0) remove `f[$boundArguments]` fallback.
|
|
149
191
|
return f[$getBoundArguments]?.() ?? f[$boundArguments];
|
|
@@ -155,9 +197,6 @@ function functionString(f) {
|
|
|
155
197
|
return Function.prototype.toString.call(f);
|
|
156
198
|
}
|
|
157
199
|
function constructorOf(value) {
|
|
158
|
-
if (value == null) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
200
|
return protoOf(value)?.constructor;
|
|
162
201
|
}
|
|
163
202
|
function unsafeNarrow(value) {
|
|
@@ -168,6 +207,7 @@ const objectConstructorString = functionString(Object);
|
|
|
168
207
|
const dateConstructorString = functionString(Date);
|
|
169
208
|
const regexConstructorString = functionString(RegExp);
|
|
170
209
|
const setConstructorString = functionString(Set);
|
|
210
|
+
const mapConstructorString = functionString(Map);
|
|
171
211
|
const nativeErrorConstructorStrings = [
|
|
172
212
|
functionString(Error),
|
|
173
213
|
// TODO: add DOMException? Be sure to check the `name` property.
|