@longlast/equals 0.4.3 → 0.5.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/README.md CHANGED
@@ -27,3 +27,9 @@ equals(
27
27
  [3, 4, {openThe: "door"}],
28
28
  ); // => false
29
29
  ```
30
+
31
+ ## Documentation
32
+
33
+ [Browse the docs on longlast.js.org][docs].
34
+
35
+ [docs]: https://longlast.js.org/equals/
package/dist/index.d.ts CHANGED
@@ -18,15 +18,46 @@ import { type Curried2 } from "@longlast/curry";
18
18
  * elements are equal (according to `equals`).
19
19
  * - Sets are equal iff they contain the same elements. Note that set
20
20
  * elements are _not_ deeply compared.
21
+ * - Partially applied curried functions are equal iff they originate from
22
+ * the same curried function and their bound arguments are equal
23
+ * according to `equals`. See {@link curry}.
21
24
  * - Other objects are equal iff they have the same prototype (e.g. the same
22
25
  * class) and the same set of enumerable string-keyed properties, and the
23
26
  * values of their corresponding properties are equal (according to
24
27
  * `equals`).
25
28
  *
29
+ * You can customize how `equals()` compares values of a specific class by
30
+ * using the {@link symbols.$equals $equals} symbol to define a method on that class. For
31
+ * example:
32
+ *
33
+ * ```ts
34
+ * import {$equals} from "@longlast/symbols"
35
+ *
36
+ * class HttpError extends Error {
37
+ * private statusCode: number;
38
+ * constructor(message: string, statusCode: number) {
39
+ * super(message);
40
+ * this.statusCode = statusCode;
41
+ * }
42
+ *
43
+ * [$equals](other: unknown) {
44
+ * return other instanceof HttpError &&
45
+ * other.statusCode === this.statusCode &&
46
+ * other.message === this.message;
47
+ * }
48
+ * }
49
+ * ```
50
+ *
51
+ * Note that this makes the comparison asymmetrical: `a` is considered equal to
52
+ * `b` iff `a[$equals](b)` returns truthy. The `$equals` method will always be
53
+ * called on the *first* argument to `equals()`.
54
+ *
55
+ * `equals()` is curried. See {@link curry}.
56
+ *
57
+ * ## Limitations
58
+ *
26
59
  * `equals()` can throw a `RangeError` if one of its arguments contains a
27
60
  * reference cycle. Avoid passing mutable objects to `equals()` unless you know
28
61
  * that they do not contain cycles.
29
- *
30
- * `equals()` is curried.
31
62
  */
32
63
  export declare const equals: Curried2<unknown, unknown, boolean>;
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  * @module equals
3
3
  */
4
4
  import { curry } from "@longlast/curry";
5
+ import { $boundArguments, $equals, $unapplied } from "@longlast/symbols";
5
6
  /**
6
7
  * @function
7
8
  * Deeply compares two values, returning true if they're equal and false
@@ -18,16 +19,47 @@ import { curry } from "@longlast/curry";
18
19
  * elements are equal (according to `equals`).
19
20
  * - Sets are equal iff they contain the same elements. Note that set
20
21
  * elements are _not_ deeply compared.
22
+ * - Partially applied curried functions are equal iff they originate from
23
+ * the same curried function and their bound arguments are equal
24
+ * according to `equals`. See {@link curry}.
21
25
  * - Other objects are equal iff they have the same prototype (e.g. the same
22
26
  * class) and the same set of enumerable string-keyed properties, and the
23
27
  * values of their corresponding properties are equal (according to
24
28
  * `equals`).
25
29
  *
30
+ * You can customize how `equals()` compares values of a specific class by
31
+ * using the {@link symbols.$equals $equals} symbol to define a method on that class. For
32
+ * example:
33
+ *
34
+ * ```ts
35
+ * import {$equals} from "@longlast/symbols"
36
+ *
37
+ * class HttpError extends Error {
38
+ * private statusCode: number;
39
+ * constructor(message: string, statusCode: number) {
40
+ * super(message);
41
+ * this.statusCode = statusCode;
42
+ * }
43
+ *
44
+ * [$equals](other: unknown) {
45
+ * return other instanceof HttpError &&
46
+ * other.statusCode === this.statusCode &&
47
+ * other.message === this.message;
48
+ * }
49
+ * }
50
+ * ```
51
+ *
52
+ * Note that this makes the comparison asymmetrical: `a` is considered equal to
53
+ * `b` iff `a[$equals](b)` returns truthy. The `$equals` method will always be
54
+ * called on the *first* argument to `equals()`.
55
+ *
56
+ * `equals()` is curried. See {@link curry}.
57
+ *
58
+ * ## Limitations
59
+ *
26
60
  * `equals()` can throw a `RangeError` if one of its arguments contains a
27
61
  * reference cycle. Avoid passing mutable objects to `equals()` unless you know
28
62
  * that they do not contain cycles.
29
- *
30
- * `equals()` is curried.
31
63
  */
32
64
  export const equals = curry(_equals);
33
65
  function _equals(a, b) {
@@ -36,6 +68,9 @@ function _equals(a, b) {
36
68
  if (Object.is(a, b)) {
37
69
  return true;
38
70
  }
71
+ if (a != null && typeof a[$equals] === "function") {
72
+ return Boolean(a[$equals](b));
73
+ }
39
74
  if (a instanceof Date && b instanceof Date) {
40
75
  return Object.is(+a, +b);
41
76
  }
@@ -46,11 +81,20 @@ function _equals(a, b) {
46
81
  return (a.message === b.message &&
47
82
  Object.getPrototypeOf(a) === Object.getPrototypeOf(b));
48
83
  }
84
+ if (Array.isArray(a) && Array.isArray(b)) {
85
+ return a.length === b.length && a.every((_, i) => _equals(a[i], b[i]));
86
+ }
49
87
  if (a instanceof Set && b instanceof Set) {
50
88
  return a.size === b.size && [...a].every((v) => b.has(v));
51
89
  }
52
- if (Array.isArray(a) && Array.isArray(b)) {
53
- return a.length === b.length && a.every((_, i) => equals(a[i], b[i]));
90
+ if (typeof a === "function" && typeof b === "function") {
91
+ const aArgs = a[$boundArguments];
92
+ const bArgs = b[$boundArguments];
93
+ const aUnapplied = a[$unapplied];
94
+ const bUnapplied = b[$unapplied];
95
+ return (aUnapplied != null &&
96
+ aUnapplied === bUnapplied &&
97
+ _equals(aArgs, bArgs));
54
98
  }
55
99
  if (a && b && typeof a === "object" && protoOf(a) === protoOf(b)) {
56
100
  const bKeys = new Set(Object.keys(b));
@@ -58,7 +102,7 @@ function _equals(a, b) {
58
102
  if (!bKeys.has(key)) {
59
103
  return false;
60
104
  }
61
- if (!equals(a[key], b[key])) {
105
+ if (!_equals(a[key], b[key])) {
62
106
  return false;
63
107
  }
64
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longlast/equals",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Deeply compares objects",
5
5
  "license": "MIT",
6
6
  "author": "Ben Christel (https://benchristel.com/)",
@@ -16,6 +16,7 @@
16
16
  "#@longlast/equals": "./src/index.ts"
17
17
  },
18
18
  "dependencies": {
19
- "@longlast/curry": "^0.2.1"
19
+ "@longlast/curry": "^0.4.0",
20
+ "@longlast/symbols": "^1.0.0"
20
21
  }
21
22
  }