@longlast/equals 0.5.7 → 0.5.9
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/README.md +1 -1
- package/dist/index.d.ts +37 -5
- package/dist/index.js +89 -65
- package/package.json +1 -1
package/README.md
CHANGED
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
|
|
@@ -21,9 +53,9 @@ import { type Curried2 } from "@longlast/curry";
|
|
|
21
53
|
* - Maps are equal iff they have the same set of keys, and their
|
|
22
54
|
* corresponding values are deeply equal. Note that map keys are _not_
|
|
23
55
|
* deeply compared.
|
|
24
|
-
* - Partially applied
|
|
25
|
-
* the same
|
|
26
|
-
* according to `equals`.
|
|
56
|
+
* - Partially applied functions (via {@link curry} or {@link partial-apply})
|
|
57
|
+
* are equal iff they originate from the same function and their bound
|
|
58
|
+
* arguments are equal according to `equals`.
|
|
27
59
|
* - Other objects are equal iff they have the same prototype (e.g. the same
|
|
28
60
|
* class) and the same set of enumerable string-keyed properties, and the
|
|
29
61
|
* values of their corresponding properties are equal (according to
|
|
@@ -34,7 +66,7 @@ import { type Curried2 } from "@longlast/curry";
|
|
|
34
66
|
* class. For example:
|
|
35
67
|
*
|
|
36
68
|
* ```ts
|
|
37
|
-
* import {$equals} from "@longlast/symbols"
|
|
69
|
+
* import {$equals} from "@longlast/symbols";
|
|
38
70
|
*
|
|
39
71
|
* class HttpError extends Error {
|
|
40
72
|
* private statusCode: number;
|
package/dist/index.js
CHANGED
|
@@ -3,74 +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
|
-
*
|
|
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}.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
* - Primitives `a` and `b` are equal iff `Object.is(a, b)`. This is similar
|
|
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
|
-
* - Maps are equal iff they have the same set of keys, and their
|
|
25
|
-
* corresponding values are deeply equal. Note that map keys are _not_
|
|
26
|
-
* deeply compared.
|
|
27
|
-
* - Partially applied curried functions are equal iff they originate from
|
|
28
|
-
* the same curried function and their bound arguments are equal
|
|
29
|
-
* according to `equals`. See {@link curry}.
|
|
30
|
-
* - Other objects are equal iff they have the same prototype (e.g. the same
|
|
31
|
-
* class) and the same set of enumerable string-keyed properties, and the
|
|
32
|
-
* values of their corresponding properties are equal (according to
|
|
33
|
-
* `equals`).
|
|
34
|
-
*
|
|
35
|
-
* You can customize how `equals()` compares values of a specific class by
|
|
36
|
-
* using the {@link symbols.$equals $equals} symbol to define a method on that
|
|
37
|
-
* class. For example:
|
|
14
|
+
* `equalsWith` is curried. See {@link curry}.
|
|
38
15
|
*
|
|
16
|
+
* @example
|
|
39
17
|
* ```ts
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* class HttpError extends Error {
|
|
43
|
-
* private statusCode: number;
|
|
44
|
-
* constructor(message: string, statusCode: number) {
|
|
45
|
-
* super(message);
|
|
46
|
-
* this.statusCode = statusCode;
|
|
47
|
-
* }
|
|
18
|
+
* // `mathEquals` treats -0 as equal to 0.
|
|
19
|
+
* const mathEquals = equalsWith({override: trueIfBothZero});
|
|
48
20
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* other.message === this.message;
|
|
21
|
+
* function trueIfBothZero(a: unknown, b: unknown) {
|
|
22
|
+
* if (a === 0 && b === 0) {
|
|
23
|
+
* return true;
|
|
53
24
|
* }
|
|
54
25
|
* }
|
|
55
|
-
* ```
|
|
56
26
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* called on the *first* argument to `equals()`.
|
|
60
|
-
*
|
|
61
|
-
* `equals()` is curried. See {@link curry}.
|
|
62
|
-
*
|
|
63
|
-
* ## Limitations
|
|
64
|
-
*
|
|
65
|
-
* `equals()` can throw a `RangeError` if one of its arguments contains a
|
|
66
|
-
* reference cycle. Avoid passing mutable objects to `equals()` unless you know
|
|
67
|
-
* that they do not contain cycles.
|
|
27
|
+
* mathEquals({x: 0}, {x: -0}); // => true
|
|
28
|
+
* ```
|
|
68
29
|
*/
|
|
69
|
-
export const
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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) {
|
|
74
37
|
return a === b;
|
|
75
38
|
}
|
|
76
39
|
// TODO: (pre-1.0.0) decide if we should pass `equals` as the second
|
|
@@ -88,7 +51,7 @@ function _equals(a, b) {
|
|
|
88
51
|
const bUnapplied = b[$unapplied];
|
|
89
52
|
return (aUnapplied != null &&
|
|
90
53
|
aUnapplied === bUnapplied &&
|
|
91
|
-
|
|
54
|
+
_equalsWith(options, getBoundArguments(a), getBoundArguments(b)));
|
|
92
55
|
}
|
|
93
56
|
// If `a` is a primitive at this point, return false, since we already know
|
|
94
57
|
// it is not identical to `b`.
|
|
@@ -116,7 +79,8 @@ function _equals(a, b) {
|
|
|
116
79
|
}
|
|
117
80
|
if (Array.isArray(a)) {
|
|
118
81
|
unsafeNarrow(b);
|
|
119
|
-
return a.length === b.length &&
|
|
82
|
+
return (a.length === b.length &&
|
|
83
|
+
a.every((_, i) => _equalsWith(options, a[i], b[i])));
|
|
120
84
|
}
|
|
121
85
|
if (setConstructorString === aConstructorString) {
|
|
122
86
|
unsafeNarrow(a);
|
|
@@ -131,7 +95,7 @@ function _equals(a, b) {
|
|
|
131
95
|
return false;
|
|
132
96
|
}
|
|
133
97
|
for (const key of a.keys()) {
|
|
134
|
-
if (!b.has(key) || !
|
|
98
|
+
if (!b.has(key) || !_equalsWith(options, a.get(key), b.get(key))) {
|
|
135
99
|
return false;
|
|
136
100
|
}
|
|
137
101
|
}
|
|
@@ -151,7 +115,7 @@ function _equals(a, b) {
|
|
|
151
115
|
if (!bKeySet.has(key)) {
|
|
152
116
|
return false;
|
|
153
117
|
}
|
|
154
|
-
if (!
|
|
118
|
+
if (!_equalsWith(options, a[key], b[key])) {
|
|
155
119
|
return false;
|
|
156
120
|
}
|
|
157
121
|
}
|
|
@@ -159,6 +123,69 @@ function _equals(a, b) {
|
|
|
159
123
|
}
|
|
160
124
|
return false;
|
|
161
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 functions (via {@link curry} or {@link partial-apply})
|
|
146
|
+
* are equal iff they originate from the same function and their bound
|
|
147
|
+
* arguments are equal according to `equals`.
|
|
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({});
|
|
162
189
|
function getBoundArguments(f) {
|
|
163
190
|
// TODO: (pre-1.0.0) remove `f[$boundArguments]` fallback.
|
|
164
191
|
return f[$getBoundArguments]?.() ?? f[$boundArguments];
|
|
@@ -170,9 +197,6 @@ function functionString(f) {
|
|
|
170
197
|
return Function.prototype.toString.call(f);
|
|
171
198
|
}
|
|
172
199
|
function constructorOf(value) {
|
|
173
|
-
if (value == null) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
200
|
return protoOf(value)?.constructor;
|
|
177
201
|
}
|
|
178
202
|
function unsafeNarrow(value) {
|