@oscarpalmer/atoms 0.129.0 → 0.130.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.
@@ -325,6 +325,51 @@ function words(value) {
325
325
  }
326
326
  const EXPRESSION_IGNORED = /(^|\.)(__proto__|constructor|prototype)(\.|$)/i;
327
327
  const EXPRESSION_WORDS = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
328
+ function getCompareHandlers(owner, options) {
329
+ const { get, register, unregister } = getHandlers(owner, options);
330
+ return {
331
+ register,
332
+ unregister,
333
+ handle(first, second, ...parameters) {
334
+ const handler = get(first, second);
335
+ if (handler == null) return options.callback(first, second, ...parameters);
336
+ return typeof handler === "function" ? handler(first, second) : first[handler](second);
337
+ }
338
+ };
339
+ }
340
+ function getHandlers(owner, options) {
341
+ const handlers = /* @__PURE__ */ new WeakMap();
342
+ return {
343
+ get(first, second) {
344
+ if (isConstructable(first) && isConstructable(second) && first.constructor === second.constructor) return handlers.get(first.constructor);
345
+ },
346
+ register(constructor, handler) {
347
+ if (!isConstructor(constructor) || handler === owner) return;
348
+ let actual = handler ?? options.method;
349
+ if (typeof actual !== "function" && typeof actual !== "string") return;
350
+ if (typeof actual === "string") actual = typeof constructor.prototype[actual] === "function" ? actual : void 0;
351
+ if (actual != null) handlers.set(constructor, actual);
352
+ },
353
+ unregister(constructor) {
354
+ handlers.delete(constructor);
355
+ }
356
+ };
357
+ }
358
+ function getSelfHandlers(owner, options) {
359
+ const { get, register, unregister } = getHandlers(owner, options);
360
+ return {
361
+ register,
362
+ unregister,
363
+ handle(value, ...parameters) {
364
+ const handler = get(value, value);
365
+ if (handler == null) return options.callback(value, ...parameters);
366
+ return typeof handler === "function" ? handler(value) : value[handler]();
367
+ }
368
+ };
369
+ }
370
+ function isConstructable(value) {
371
+ return typeof value === "object" && value !== null;
372
+ }
328
373
  function compare(first, second) {
329
374
  if (Object.is(first, second)) return 0;
330
375
  if (first == null) return -1;
@@ -347,6 +392,16 @@ function compare(first, second) {
347
392
  }
348
393
  return 0;
349
394
  }
395
+ compare.handlers = getCompareHandlers(compare, {
396
+ callback: (first, second, compareStrings) => {
397
+ if (compareStrings) return getString(first).localeCompare(getString(second));
398
+ },
399
+ method: "compare"
400
+ });
401
+ compare.register = function(constructor, handler) {
402
+ compare.handlers.register(constructor, handler);
403
+ };
404
+ compare.unregister = compare.handlers.unregister;
350
405
  function compareNumbers(first, second) {
351
406
  const firstNumber = Number(first);
352
407
  const secondNumber = Number(second);
@@ -360,7 +415,7 @@ function compareValue(first, second, compareStrings) {
360
415
  const firstType = typeof first;
361
416
  if (firstType === typeof second && firstType in comparators) return comparators[firstType](first, second);
362
417
  if (first instanceof Date && second instanceof Date) return compareNumbers(first.getTime(), second.getTime());
363
- if (compareStrings) return getString(first).localeCompare(getString(second));
418
+ return compare.handlers.handle(first, second, compareStrings);
364
419
  }
365
420
  function getComparisonParts(value) {
366
421
  if (Array.isArray(value)) return value;
@@ -1626,12 +1681,12 @@ function titleCase(value) {
1626
1681
  return memoizedTitleCase.run(value);
1627
1682
  }
1628
1683
  function toCase(type, value, capitalizeAny, capitalizeFirst) {
1629
- memoizers$1[type] ??= memoize(toCaseCallback.bind({
1684
+ caseMemoizers[type] ??= memoize(toCaseCallback.bind({
1630
1685
  type,
1631
1686
  capitalizeAny,
1632
1687
  capitalizeFirst
1633
1688
  }));
1634
- return memoizers$1[type].run(value);
1689
+ return caseMemoizers[type].run(value);
1635
1690
  }
1636
1691
  function toCaseCallback(value) {
1637
1692
  if (typeof value !== "string") return "";
@@ -1667,7 +1722,7 @@ const delimiters = {
1667
1722
  };
1668
1723
  const EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
1669
1724
  const EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
1670
- const memoizers$1 = {};
1725
+ const caseMemoizers = {};
1671
1726
  const REPLACEMENT_CAMEL_CASE = "$1-$2";
1672
1727
  let memoizedCapitalize;
1673
1728
  let memoizedTitleCase;
@@ -1696,8 +1751,8 @@ function includes(haystack, needle, ignoreCase) {
1696
1751
  }
1697
1752
  function match(type, haystack, needle, ignoreCase) {
1698
1753
  if (typeof haystack !== "string" || typeof needle !== "string") return false;
1699
- memoizers[type] ??= memoize(matchCallback.bind(type));
1700
- return memoizers[type].run(haystack, needle, ignoreCase);
1754
+ matchMemoizers[type] ??= memoize(matchCallback.bind(type));
1755
+ return matchMemoizers[type].run(haystack, needle, ignoreCase);
1701
1756
  }
1702
1757
  function matchCallback(haystack, needle, ignoreCase) {
1703
1758
  return (ignoreCase ? haystack.toLocaleLowerCase() : haystack)[this](ignoreCase ? needle.toLocaleLowerCase() : needle);
@@ -1724,7 +1779,7 @@ function truncate(value, length, suffix) {
1724
1779
  const truncatedLength = length - actualSuffixLength;
1725
1780
  return `${value.slice(0, truncatedLength)}${actualSuffix}`;
1726
1781
  }
1727
- const memoizers = {};
1782
+ const matchMemoizers = {};
1728
1783
  function getValue(data, path, ignoreCase) {
1729
1784
  if (typeof data !== "object" || data === null || typeof path !== "string" || path.trim().length === 0) return;
1730
1785
  const shouldIgnoreCase = ignoreCase === true;
@@ -1807,11 +1862,6 @@ function equalMap(first, second, options) {
1807
1862
  }
1808
1863
  return true;
1809
1864
  }
1810
- function equalObject(first, second) {
1811
- const comparison = comparisons.get(first.constructor);
1812
- if (comparison == null) return Object.is(first, second);
1813
- return second instanceof first.constructor && comparison(first, second);
1814
- }
1815
1865
  function equalPlainObject(first, second, options) {
1816
1866
  let firstKeys = [...Object.keys(first), ...Object.getOwnPropertySymbols(first)];
1817
1867
  let secondKeys = [...Object.keys(second), ...Object.getOwnPropertySymbols(second)];
@@ -1870,10 +1920,10 @@ function equalValue(first, second, options) {
1870
1920
  case Array.isArray(first) && Array.isArray(second): return equalArray(first, second, options);
1871
1921
  case isPlainObject(first) && isPlainObject(second): return equalPlainObject(first, second, options);
1872
1922
  case isTypedArray(first) && isTypedArray(second): return equalTypedArray(first, second);
1873
- case typeof first === "object": return equalObject(first, second);
1874
- default: return Object.is(first, second);
1923
+ default: return equal.handlers.handle(first, second, options);
1875
1924
  }
1876
1925
  }
1926
+ equal.handlers = getCompareHandlers(equal, { callback: Object.is });
1877
1927
  equal.initialize = function(options) {
1878
1928
  const actual = getEqualOptions(options);
1879
1929
  const equalizer = (first, second) => equalValue(first, second, actual);
@@ -1881,12 +1931,10 @@ equal.initialize = function(options) {
1881
1931
  equalizer.unregister = equal.unregister;
1882
1932
  return equalizer;
1883
1933
  };
1884
- equal.register = function(constructor, comparison) {
1885
- if (isConstructor(constructor) && typeof comparison === "function") comparisons.set(constructor, comparison);
1886
- };
1887
- equal.unregister = function(constructor) {
1888
- if (isConstructor(constructor)) comparisons.delete(constructor);
1934
+ equal.register = function(constructor, fn) {
1935
+ equal.handlers.register(constructor, fn);
1889
1936
  };
1937
+ equal.unregister = equal.handlers.unregister;
1890
1938
  function filterKey(key, options) {
1891
1939
  if (typeof key !== "string") return true;
1892
1940
  if (options.ignoreExpressions.enabled && options.ignoreExpressions.values.some((expression) => expression.test(key))) return false;
@@ -1921,18 +1969,17 @@ function getEqualOptions(input) {
1921
1969
  }
1922
1970
  const ARRAY_PEEK_PERCENTAGE = 10;
1923
1971
  const ARRAY_THRESHOLD = 100;
1924
- const comparisons = /* @__PURE__ */ new WeakMap();
1925
1972
  function clone(value) {
1926
1973
  return cloneValue(value, 0, /* @__PURE__ */ new WeakMap());
1927
1974
  }
1928
- clone.register = function(constructor, cloner) {
1929
- if (!isConstructor(constructor) || cloner === clone) return;
1930
- const actual = cloner ?? "clone";
1931
- if (typeof actual === "function" || typeof constructor.prototype[actual] === "function") cloners.set(constructor, actual);
1932
- };
1933
- clone.unregister = function(constructor) {
1934
- if (isConstructor(constructor)) cloners.delete(constructor);
1975
+ clone.handlers = getSelfHandlers(clone, {
1976
+ callback: tryStructuredClone,
1977
+ method: "clone"
1978
+ });
1979
+ clone.register = function(constructor, handler) {
1980
+ clone.handlers.register(constructor, handler);
1935
1981
  };
1982
+ clone.unregister = clone.handlers.unregister;
1936
1983
  function cloneArrayBuffer(value, depth, references) {
1937
1984
  if (typeof depth === "number" && depth >= MAX_CLONE_DEPTH) return value;
1938
1985
  const cloned = new ArrayBuffer(value.byteLength);
@@ -1967,12 +2014,6 @@ function cloneNode(node, depth, references) {
1967
2014
  references.set(node, cloned);
1968
2015
  return cloned;
1969
2016
  }
1970
- function cloneObject(value, depth, references) {
1971
- const cloner = cloners.get(value.constructor);
1972
- if (typeof cloner === "function") return cloner(value);
1973
- if (cloner == null) return tryStructuredClone(value, depth, references);
1974
- return value[cloner]();
1975
- }
1976
2017
  function clonePlainObject(value, depth, references) {
1977
2018
  if (depth >= MAX_CLONE_DEPTH) return Array.isArray(value) ? [...value] : { ...value };
1978
2019
  const cloned = Array.isArray(value) ? [] : {};
@@ -2010,13 +2051,14 @@ function cloneValue(value, depth, references) {
2010
2051
  case references.has(value): return references.get(value);
2011
2052
  case value instanceof ArrayBuffer: return cloneArrayBuffer(value, depth, references);
2012
2053
  case value instanceof DataView: return cloneDataView(value, depth, references);
2054
+ case value instanceof Date: return new Date(value.getTime());
2013
2055
  case value instanceof RegExp: return cloneRegularExpression(value, depth, references);
2014
2056
  case value instanceof Map:
2015
2057
  case value instanceof Set: return cloneMapOrSet(value, depth, references);
2016
2058
  case value instanceof Node: return cloneNode(value, depth, references);
2017
2059
  case isArrayOrPlainObject(value): return clonePlainObject(value, depth, references);
2018
2060
  case isTypedArray(value): return cloneTypedArray(value, depth, references);
2019
- default: return cloneObject(value, depth, references);
2061
+ default: return clone.handlers.handle(value, depth, references);
2020
2062
  }
2021
2063
  }
2022
2064
  function tryStructuredClone(value, depth, references) {
@@ -2030,7 +2072,6 @@ function tryStructuredClone(value, depth, references) {
2030
2072
  return value;
2031
2073
  }
2032
2074
  }
2033
- const cloners = /* @__PURE__ */ new WeakMap();
2034
2075
  const MAX_CLONE_DEPTH = 100;
2035
2076
  function diff(first, second, options) {
2036
2077
  const relaxedNullish = typeof options === "object" && options?.relaxedNullish === true;
@@ -1,5 +1,6 @@
1
1
  import { max } from "../math/aggregate.js";
2
2
  import { getString, words } from "../string.js";
3
+ import { getCompareHandlers } from "./handlers.js";
3
4
  function compare(first, second) {
4
5
  if (Object.is(first, second)) return 0;
5
6
  if (first == null) return -1;
@@ -22,6 +23,16 @@ function compare(first, second) {
22
23
  }
23
24
  return 0;
24
25
  }
26
+ compare.handlers = getCompareHandlers(compare, {
27
+ callback: (first, second, compareStrings) => {
28
+ if (compareStrings) return getString(first).localeCompare(getString(second));
29
+ },
30
+ method: "compare"
31
+ });
32
+ compare.register = function(constructor, handler) {
33
+ compare.handlers.register(constructor, handler);
34
+ };
35
+ compare.unregister = compare.handlers.unregister;
25
36
  function compareNumbers(first, second) {
26
37
  const firstNumber = Number(first);
27
38
  const secondNumber = Number(second);
@@ -35,7 +46,7 @@ function compareValue(first, second, compareStrings) {
35
46
  const firstType = typeof first;
36
47
  if (firstType === typeof second && firstType in comparators) return comparators[firstType](first, second);
37
48
  if (first instanceof Date && second instanceof Date) return compareNumbers(first.getTime(), second.getTime());
38
- if (compareStrings) return getString(first).localeCompare(getString(second));
49
+ return compare.handlers.handle(first, second, compareStrings);
39
50
  }
40
51
  function getComparisonParts(value) {
41
52
  if (Array.isArray(value)) return value;
@@ -1,5 +1,6 @@
1
- import { isConstructor, isPlainObject, isTypedArray } from "../is.js";
1
+ import { isPlainObject, isTypedArray } from "../is.js";
2
2
  import { chunk } from "../array/chunk.js";
3
+ import { getCompareHandlers } from "./handlers.js";
3
4
  function equal(first, second, options) {
4
5
  return equalValue(first, second, getEqualOptions(options));
5
6
  }
@@ -41,11 +42,6 @@ function equalMap(first, second, options) {
41
42
  }
42
43
  return true;
43
44
  }
44
- function equalObject(first, second) {
45
- const comparison = comparisons.get(first.constructor);
46
- if (comparison == null) return Object.is(first, second);
47
- return second instanceof first.constructor && comparison(first, second);
48
- }
49
45
  function equalPlainObject(first, second, options) {
50
46
  let firstKeys = [...Object.keys(first), ...Object.getOwnPropertySymbols(first)];
51
47
  let secondKeys = [...Object.keys(second), ...Object.getOwnPropertySymbols(second)];
@@ -104,10 +100,10 @@ function equalValue(first, second, options) {
104
100
  case Array.isArray(first) && Array.isArray(second): return equalArray(first, second, options);
105
101
  case isPlainObject(first) && isPlainObject(second): return equalPlainObject(first, second, options);
106
102
  case isTypedArray(first) && isTypedArray(second): return equalTypedArray(first, second);
107
- case typeof first === "object": return equalObject(first, second);
108
- default: return Object.is(first, second);
103
+ default: return equal.handlers.handle(first, second, options);
109
104
  }
110
105
  }
106
+ equal.handlers = getCompareHandlers(equal, { callback: Object.is });
111
107
  equal.initialize = function(options) {
112
108
  const actual = getEqualOptions(options);
113
109
  const equalizer = (first, second) => equalValue(first, second, actual);
@@ -115,12 +111,10 @@ equal.initialize = function(options) {
115
111
  equalizer.unregister = equal.unregister;
116
112
  return equalizer;
117
113
  };
118
- equal.register = function(constructor, comparison) {
119
- if (isConstructor(constructor) && typeof comparison === "function") comparisons.set(constructor, comparison);
120
- };
121
- equal.unregister = function(constructor) {
122
- if (isConstructor(constructor)) comparisons.delete(constructor);
114
+ equal.register = function(constructor, fn) {
115
+ equal.handlers.register(constructor, fn);
123
116
  };
117
+ equal.unregister = equal.handlers.unregister;
124
118
  function filterKey(key, options) {
125
119
  if (typeof key !== "string") return true;
126
120
  if (options.ignoreExpressions.enabled && options.ignoreExpressions.values.some((expression) => expression.test(key))) return false;
@@ -155,5 +149,4 @@ function getEqualOptions(input) {
155
149
  }
156
150
  var ARRAY_PEEK_PERCENTAGE = 10;
157
151
  var ARRAY_THRESHOLD = 100;
158
- var comparisons = /* @__PURE__ */ new WeakMap();
159
152
  export { equal };
@@ -0,0 +1,47 @@
1
+ import { isConstructor } from "../is.js";
2
+ function getCompareHandlers(owner, options) {
3
+ const { get, register, unregister } = getHandlers(owner, options);
4
+ return {
5
+ register,
6
+ unregister,
7
+ handle(first, second, ...parameters) {
8
+ const handler = get(first, second);
9
+ if (handler == null) return options.callback(first, second, ...parameters);
10
+ return typeof handler === "function" ? handler(first, second) : first[handler](second);
11
+ }
12
+ };
13
+ }
14
+ function getHandlers(owner, options) {
15
+ const handlers = /* @__PURE__ */ new WeakMap();
16
+ return {
17
+ get(first, second) {
18
+ if (isConstructable(first) && isConstructable(second) && first.constructor === second.constructor) return handlers.get(first.constructor);
19
+ },
20
+ register(constructor, handler) {
21
+ if (!isConstructor(constructor) || handler === owner) return;
22
+ let actual = handler ?? options.method;
23
+ if (typeof actual !== "function" && typeof actual !== "string") return;
24
+ if (typeof actual === "string") actual = typeof constructor.prototype[actual] === "function" ? actual : void 0;
25
+ if (actual != null) handlers.set(constructor, actual);
26
+ },
27
+ unregister(constructor) {
28
+ handlers.delete(constructor);
29
+ }
30
+ };
31
+ }
32
+ function getSelfHandlers(owner, options) {
33
+ const { get, register, unregister } = getHandlers(owner, options);
34
+ return {
35
+ register,
36
+ unregister,
37
+ handle(value, ...parameters) {
38
+ const handler = get(value, value);
39
+ if (handler == null) return options.callback(value, ...parameters);
40
+ return typeof handler === "function" ? handler(value) : value[handler]();
41
+ }
42
+ };
43
+ }
44
+ function isConstructable(value) {
45
+ return typeof value === "object" && value !== null;
46
+ }
47
+ export { getCompareHandlers, getSelfHandlers };
@@ -26,12 +26,12 @@ function titleCase(value) {
26
26
  return memoizedTitleCase.run(value);
27
27
  }
28
28
  function toCase(type, value, capitalizeAny, capitalizeFirst) {
29
- memoizers[type] ??= memoize(toCaseCallback.bind({
29
+ caseMemoizers[type] ??= memoize(toCaseCallback.bind({
30
30
  type,
31
31
  capitalizeAny,
32
32
  capitalizeFirst
33
33
  }));
34
- return memoizers[type].run(value);
34
+ return caseMemoizers[type].run(value);
35
35
  }
36
36
  function toCaseCallback(value) {
37
37
  if (typeof value !== "string") return "";
@@ -67,7 +67,7 @@ var delimiters = {
67
67
  };
68
68
  var EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
69
69
  var EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
70
- var memoizers = {};
70
+ var caseMemoizers = {};
71
71
  var REPLACEMENT_CAMEL_CASE = "$1-$2";
72
72
  var memoizedCapitalize;
73
73
  var memoizedTitleCase;
@@ -24,8 +24,8 @@ function includes(haystack, needle, ignoreCase) {
24
24
  }
25
25
  function match(type, haystack, needle, ignoreCase) {
26
26
  if (typeof haystack !== "string" || typeof needle !== "string") return false;
27
- memoizers[type] ??= memoize(matchCallback.bind(type));
28
- return memoizers[type].run(haystack, needle, ignoreCase);
27
+ matchMemoizers[type] ??= memoize(matchCallback.bind(type));
28
+ return matchMemoizers[type].run(haystack, needle, ignoreCase);
29
29
  }
30
30
  function matchCallback(haystack, needle, ignoreCase) {
31
31
  return (ignoreCase ? haystack.toLocaleLowerCase() : haystack)[this](ignoreCase ? needle.toLocaleLowerCase() : needle);
@@ -52,5 +52,5 @@ function truncate(value, length, suffix) {
52
52
  const truncatedLength = length - actualSuffixLength;
53
53
  return `${value.slice(0, truncatedLength)}${actualSuffix}`;
54
54
  }
55
- var memoizers = {};
55
+ var matchMemoizers = {};
56
56
  export { createUuid, endsWith, getUuid, includes, parse, startsWith, trim, truncate };
@@ -1,15 +1,16 @@
1
- import { isArrayOrPlainObject, isConstructor, isTypedArray } from "../internal/is.js";
1
+ import { isArrayOrPlainObject, isTypedArray } from "../internal/is.js";
2
+ import { getSelfHandlers } from "../internal/value/handlers.js";
2
3
  function clone(value) {
3
4
  return cloneValue(value, 0, /* @__PURE__ */ new WeakMap());
4
5
  }
5
- clone.register = function(constructor, cloner) {
6
- if (!isConstructor(constructor) || cloner === clone) return;
7
- const actual = cloner ?? "clone";
8
- if (typeof actual === "function" || typeof constructor.prototype[actual] === "function") cloners.set(constructor, actual);
9
- };
10
- clone.unregister = function(constructor) {
11
- if (isConstructor(constructor)) cloners.delete(constructor);
6
+ clone.handlers = getSelfHandlers(clone, {
7
+ callback: tryStructuredClone,
8
+ method: "clone"
9
+ });
10
+ clone.register = function(constructor, handler) {
11
+ clone.handlers.register(constructor, handler);
12
12
  };
13
+ clone.unregister = clone.handlers.unregister;
13
14
  function cloneArrayBuffer(value, depth, references) {
14
15
  if (typeof depth === "number" && depth >= MAX_CLONE_DEPTH) return value;
15
16
  const cloned = new ArrayBuffer(value.byteLength);
@@ -44,12 +45,6 @@ function cloneNode(node, depth, references) {
44
45
  references.set(node, cloned);
45
46
  return cloned;
46
47
  }
47
- function cloneObject(value, depth, references) {
48
- const cloner = cloners.get(value.constructor);
49
- if (typeof cloner === "function") return cloner(value);
50
- if (cloner == null) return tryStructuredClone(value, depth, references);
51
- return value[cloner]();
52
- }
53
48
  function clonePlainObject(value, depth, references) {
54
49
  if (depth >= MAX_CLONE_DEPTH) return Array.isArray(value) ? [...value] : { ...value };
55
50
  const cloned = Array.isArray(value) ? [] : {};
@@ -87,13 +82,14 @@ function cloneValue(value, depth, references) {
87
82
  case references.has(value): return references.get(value);
88
83
  case value instanceof ArrayBuffer: return cloneArrayBuffer(value, depth, references);
89
84
  case value instanceof DataView: return cloneDataView(value, depth, references);
85
+ case value instanceof Date: return new Date(value.getTime());
90
86
  case value instanceof RegExp: return cloneRegularExpression(value, depth, references);
91
87
  case value instanceof Map:
92
88
  case value instanceof Set: return cloneMapOrSet(value, depth, references);
93
89
  case value instanceof Node: return cloneNode(value, depth, references);
94
90
  case isArrayOrPlainObject(value): return clonePlainObject(value, depth, references);
95
91
  case isTypedArray(value): return cloneTypedArray(value, depth, references);
96
- default: return cloneObject(value, depth, references);
92
+ default: return clone.handlers.handle(value, depth, references);
97
93
  }
98
94
  }
99
95
  function tryStructuredClone(value, depth, references) {
@@ -107,6 +103,5 @@ function tryStructuredClone(value, depth, references) {
107
103
  return value;
108
104
  }
109
105
  }
110
- var cloners = /* @__PURE__ */ new WeakMap();
111
106
  var MAX_CLONE_DEPTH = 100;
112
107
  export { clone };
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "rolldown": "1.0.0-rc.1",
14
14
  "tslib": "^2.8",
15
15
  "typescript": "^5.9",
16
- "vite": "8.0.0-beta.9",
16
+ "vite": "8.0.0-beta.10",
17
17
  "vitest": "^4"
18
18
  },
19
19
  "exports": {
@@ -105,5 +105,5 @@
105
105
  },
106
106
  "type": "module",
107
107
  "types": "./types/index.d.ts",
108
- "version": "0.129.0"
108
+ "version": "0.130.0"
109
109
  }
@@ -1,5 +1,13 @@
1
+ import type {Constructor} from '../../models';
1
2
  import {max} from '../math/aggregate';
2
3
  import {getString, words} from '../string';
4
+ import {getCompareHandlers} from './handlers';
5
+
6
+ // #region Types
7
+
8
+ type Comparator<Value = any> = (first: Value, second: Value) => number;
9
+
10
+ // #endregion
3
11
 
4
12
  // #region Functions
5
13
 
@@ -53,6 +61,33 @@ export function compare(first: unknown, second: unknown): number {
53
61
  return 0;
54
62
  }
55
63
 
64
+ compare.handlers = getCompareHandlers<number>(compare, {
65
+ callback: (first, second, compareStrings) => {
66
+ if (compareStrings) {
67
+ return getString(first).localeCompare(getString(second));
68
+ }
69
+ },
70
+ method: 'compare',
71
+ });
72
+
73
+ /**
74
+ * Register a custom comparison handler for a class
75
+ * @param constructor Class constructor
76
+ * @param handler Method name or comparison function _(defaults to `compare`)_
77
+ */
78
+ compare.register = function <Instance>(
79
+ constructor: Constructor<Instance>,
80
+ handler?: string | ((first: Instance, second: Instance) => number),
81
+ ): void {
82
+ compare.handlers.register(constructor, handler);
83
+ };
84
+
85
+ /**
86
+ * Unregister a custom comparison handler for a class
87
+ * @param constructor Class constructor
88
+ */
89
+ compare.unregister = compare.handlers.unregister;
90
+
56
91
  function compareNumbers(
57
92
  first: bigint | boolean | number,
58
93
  second: bigint | boolean | number,
@@ -89,9 +124,7 @@ function compareValue(
89
124
  return compareNumbers(first.getTime(), second.getTime());
90
125
  }
91
126
 
92
- if (compareStrings) {
93
- return getString(first).localeCompare(getString(second));
94
- }
127
+ return compare.handlers.handle(first, second, compareStrings);
95
128
  }
96
129
 
97
130
  function getComparisonParts(value: unknown): unknown[] {
@@ -106,7 +139,7 @@ function getComparisonParts(value: unknown): unknown[] {
106
139
 
107
140
  // #region Constants
108
141
 
109
- const comparators: Record<string, (first: never, second: never) => number | undefined> = {
142
+ const comparators: Record<string, Comparator> = {
110
143
  bigint: compareNumbers,
111
144
  boolean: compareNumbers,
112
145
  number: compareNumbers,
@@ -1,11 +1,10 @@
1
1
  import type {ArrayOrPlainObject, Constructor, PlainObject, TypedArray} from '../../models';
2
2
  import {chunk} from '../array/chunk';
3
- import {isConstructor, isPlainObject, isTypedArray} from '../is';
3
+ import {isPlainObject, isTypedArray} from '../is';
4
+ import {getCompareHandlers} from './handlers';
4
5
 
5
6
  // #region Types
6
7
 
7
- type Comparison<Instance> = (first: Instance, second: Instance) => boolean;
8
-
9
8
  /**
10
9
  * Options for value equality comparison
11
10
  */
@@ -34,14 +33,20 @@ type Equalizer = {
34
33
  (first: unknown, second: unknown): boolean;
35
34
 
36
35
  /**
37
- * @inheritdoc equal.register
36
+ * Register a equality comparison handler for a specific class
37
+ * @param constructor Class constructor
38
+ * @param handler Equality comparison handler
38
39
  */
39
- register: typeof equal.register;
40
+ register: <Instance>(
41
+ constructor: Constructor<Instance>,
42
+ handler?: string | ((first: Instance, second: Instance) => boolean),
43
+ ) => void;
40
44
 
41
45
  /**
42
- * @inheritdoc equal.unregister
46
+ * Unregister a equality comparison handler for a specific class
47
+ * @param constructor Class constructor
43
48
  */
44
- unregister: typeof equal.unregister;
49
+ unregister: (constructor: Constructor) => void;
45
50
  };
46
51
 
47
52
  type Options = {
@@ -169,16 +174,6 @@ function equalMap(
169
174
  return true;
170
175
  }
171
176
 
172
- function equalObject(first: object, second: object): boolean {
173
- const comparison = comparisons.get(first.constructor as Constructor);
174
-
175
- if (comparison == null) {
176
- return Object.is(first, second);
177
- }
178
-
179
- return second instanceof first.constructor && comparison(first, second);
180
- }
181
-
182
177
  function equalPlainObject(
183
178
  first: ArrayOrPlainObject,
184
179
  second: ArrayOrPlainObject,
@@ -317,14 +312,15 @@ function equalValue(first: unknown, second: unknown, options: Options): boolean
317
312
  case isTypedArray(first) && isTypedArray(second):
318
313
  return equalTypedArray(first as TypedArray, second as TypedArray);
319
314
 
320
- case typeof first === 'object':
321
- return equalObject(first, second);
322
-
323
315
  default:
324
- return Object.is(first, second);
316
+ return equal.handlers.handle(first, second, options);
325
317
  }
326
318
  }
327
319
 
320
+ equal.handlers = getCompareHandlers<boolean>(equal, {
321
+ callback: Object.is,
322
+ });
323
+
328
324
  /**
329
325
  * Create an equalizer with predefined options
330
326
  * @param options Comparison options
@@ -338,32 +334,26 @@ equal.initialize = function (options?: EqualOptions): Equalizer {
338
334
  equalizer.register = equal.register;
339
335
  equalizer.unregister = equal.unregister;
340
336
 
341
- return equalizer;
337
+ return equalizer as Equalizer;
342
338
  };
343
339
 
344
340
  /**
345
- * Register a custom comparison for a specific class
341
+ * Register a equality comparison function for a specific class
346
342
  * @param constructor Class constructor
347
- * @param comparison Comparison function
343
+ * @param fn Comparison function
348
344
  */
349
345
  equal.register = function <Instance>(
350
346
  constructor: Constructor<Instance>,
351
- comparison: Comparison<Instance>,
347
+ fn: (first: Instance, second: Instance) => boolean,
352
348
  ): void {
353
- if (isConstructor(constructor) && typeof comparison === 'function') {
354
- comparisons.set(constructor, comparison as Comparison<unknown>);
355
- }
349
+ equal.handlers.register(constructor, fn);
356
350
  };
357
351
 
358
352
  /**
359
- * Unregister a custom comparison for a specific class
353
+ * Unregister a equality comparison handler for a specific class
360
354
  * @param constructor Class constructor
361
355
  */
362
- equal.unregister = function <Instance>(constructor: Constructor<Instance>): void {
363
- if (isConstructor(constructor)) {
364
- comparisons.delete(constructor);
365
- }
366
- };
356
+ equal.unregister = equal.handlers.unregister;
367
357
 
368
358
  function filterKey(key: string | symbol, options: Options): boolean {
369
359
  if (typeof key !== 'string') {
@@ -437,6 +427,4 @@ const ARRAY_PEEK_PERCENTAGE = 10;
437
427
 
438
428
  const ARRAY_THRESHOLD = 100;
439
429
 
440
- const comparisons = new WeakMap<Constructor, Comparison<unknown>>();
441
-
442
430
  // #endregion
@@ -0,0 +1,87 @@
1
+ import type {Constructor, GenericCallback} from '../../models';
2
+ import {isConstructor} from '../is';
3
+
4
+ type Options = {
5
+ callback: GenericCallback;
6
+ method?: string;
7
+ };
8
+
9
+ export function getCompareHandlers<Value>(owner: GenericCallback, options: Options) {
10
+ const {get, register, unregister} = getHandlers(owner, options);
11
+
12
+ return {
13
+ register,
14
+ unregister,
15
+ handle(first: unknown, second: unknown, ...parameters: unknown[]): Value {
16
+ const handler = get(first, second);
17
+
18
+ if (handler == null) {
19
+ return options.callback(first, second, ...parameters);
20
+ }
21
+
22
+ return typeof handler === 'function'
23
+ ? handler(first, second)
24
+ : (first as any)[handler](second);
25
+ },
26
+ };
27
+ }
28
+
29
+ function getHandlers(owner: GenericCallback, options: Options) {
30
+ const handlers = new WeakMap<Constructor, string | GenericCallback>();
31
+
32
+ return {
33
+ get(first: unknown, second: unknown): string | GenericCallback | undefined {
34
+ if (
35
+ isConstructable(first) &&
36
+ isConstructable(second) &&
37
+ (first as object).constructor === (second as object).constructor
38
+ ) {
39
+ return handlers.get((first as object).constructor as Constructor);
40
+ }
41
+ },
42
+ register(constructor: Constructor, handler?: string | GenericCallback) {
43
+ if (!isConstructor(constructor) || handler === owner) {
44
+ return;
45
+ }
46
+
47
+ let actual: string | GenericCallback | undefined = handler ?? options.method;
48
+
49
+ if (typeof actual !== 'function' && typeof actual !== 'string') {
50
+ return;
51
+ }
52
+
53
+ if (typeof actual === 'string') {
54
+ actual = typeof constructor.prototype[actual] === 'function' ? actual : undefined;
55
+ }
56
+
57
+ if (actual != null) {
58
+ handlers.set(constructor, actual);
59
+ }
60
+ },
61
+ unregister(constructor: Constructor) {
62
+ handlers.delete(constructor);
63
+ },
64
+ };
65
+ }
66
+
67
+ export function getSelfHandlers(owner: GenericCallback, options: Options) {
68
+ const {get, register, unregister} = getHandlers(owner, options);
69
+
70
+ return {
71
+ register,
72
+ unregister,
73
+ handle(value: unknown, ...parameters: unknown[]) {
74
+ const handler = get(value, value);
75
+
76
+ if (handler == null) {
77
+ return options.callback(value, ...parameters);
78
+ }
79
+
80
+ return typeof handler === 'function' ? handler(value) : (value as any)[handler]();
81
+ },
82
+ };
83
+ }
84
+
85
+ function isConstructable(value: unknown): boolean {
86
+ return typeof value === 'object' && value !== null;
87
+ }
@@ -102,9 +102,9 @@ function toCase(
102
102
  capitalizeAny: boolean,
103
103
  capitalizeFirst: boolean,
104
104
  ): string {
105
- memoizers[type] ??= memoize(toCaseCallback.bind({type, capitalizeAny, capitalizeFirst}));
105
+ caseMemoizers[type] ??= memoize(toCaseCallback.bind({type, capitalizeAny, capitalizeFirst}));
106
106
 
107
- return memoizers[type].run(value);
107
+ return caseMemoizers[type].run(value);
108
108
  }
109
109
 
110
110
  function toCaseCallback(this: Options, value: string): string {
@@ -185,7 +185,7 @@ const EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
185
185
 
186
186
  const EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
187
187
 
188
- const memoizers: Partial<Record<string, Memoized<typeof toCaseCallback>>> = {};
188
+ const caseMemoizers: Partial<Record<string, Memoized<typeof toCaseCallback>>> = {};
189
189
 
190
190
  const REPLACEMENT_CAMEL_CASE = '$1-$2';
191
191
 
@@ -68,9 +68,9 @@ function match(type: Match, haystack: string, needle: string, ignoreCase: boolea
68
68
  return false;
69
69
  }
70
70
 
71
- memoizers[type] ??= memoize(matchCallback.bind(type));
71
+ matchMemoizers[type] ??= memoize(matchCallback.bind(type));
72
72
 
73
- return memoizers[type].run(haystack, needle, ignoreCase);
73
+ return matchMemoizers[type].run(haystack, needle, ignoreCase);
74
74
  }
75
75
 
76
76
  function matchCallback(
@@ -153,6 +153,6 @@ export function truncate(value: string, length: number, suffix?: string): string
153
153
 
154
154
  // #region Constants
155
155
 
156
- const memoizers: Partial<Record<Match, Memoized<typeof matchCallback>>> = {};
156
+ const matchMemoizers: Partial<Record<Match, Memoized<typeof matchCallback>>> = {};
157
157
 
158
158
  // #endregion
@@ -1,12 +1,7 @@
1
- import {isArrayOrPlainObject, isConstructor, isTypedArray} from '../internal/is';
1
+ import {isArrayOrPlainObject, isTypedArray} from '../internal/is';
2
+ import {getSelfHandlers} from '../internal/value/handlers';
2
3
  import type {ArrayOrPlainObject, Constructor, PlainObject, TypedArray} from '../models';
3
4
 
4
- // #region Types
5
-
6
- type Cloner<Instance> = (original: Instance) => Instance;
7
-
8
- // #endregion
9
-
10
5
  // #region Functions
11
6
 
12
7
  /**
@@ -20,35 +15,28 @@ export function clone(value: unknown): unknown {
20
15
  return cloneValue(value, 0, new WeakMap());
21
16
  }
22
17
 
18
+ clone.handlers = getSelfHandlers(clone, {
19
+ callback: tryStructuredClone,
20
+ method: 'clone',
21
+ });
22
+
23
23
  /**
24
- * Register a custom cloner for a specific class
24
+ * Register a clone handler for a specific class
25
25
  * @param constructor Class constructor
26
- * @param cloner Method name or cloning function _(defaults to `clone`)_
26
+ * @param handler Method name or clone function _(defaults to `clone`)_
27
27
  */
28
28
  clone.register = function <Instance>(
29
29
  constructor: Constructor<Instance>,
30
- cloner?: string | Cloner<Instance>,
30
+ handler?: string | ((value: Instance) => Instance),
31
31
  ): void {
32
- if (!isConstructor(constructor) || cloner === clone) {
33
- return;
34
- }
35
-
36
- const actual = cloner ?? 'clone';
37
-
38
- if (typeof actual === 'function' || typeof constructor.prototype[actual] === 'function') {
39
- cloners.set(constructor, actual as string | Cloner<unknown>);
40
- }
32
+ clone.handlers.register(constructor, handler);
41
33
  };
42
34
 
43
35
  /**
44
- * Unregister a custom cloner for a specific class
36
+ * Unregister a clone handler for a specific class
45
37
  * @param constructor Class constructor
46
38
  */
47
- clone.unregister = function (constructor: Constructor): void {
48
- if (isConstructor(constructor)) {
49
- cloners.delete(constructor);
50
- }
51
- };
39
+ clone.unregister = clone.handlers.unregister;
52
40
 
53
41
  function cloneArrayBuffer(
54
42
  value: ArrayBuffer,
@@ -130,20 +118,6 @@ function cloneNode(node: Node, depth: number, references: WeakMap<WeakKey, unkno
130
118
  return cloned;
131
119
  }
132
120
 
133
- function cloneObject(value: object, depth: number, references: WeakMap<WeakKey, unknown>): unknown {
134
- const cloner = cloners.get(value.constructor as Constructor);
135
-
136
- if (typeof cloner === 'function') {
137
- return cloner(value);
138
- }
139
-
140
- if (cloner == null) {
141
- return tryStructuredClone(value, depth, references);
142
- }
143
-
144
- return ((value as unknown as PlainObject)[cloner] as Function)();
145
- }
146
-
147
121
  function clonePlainObject(
148
122
  value: ArrayOrPlainObject,
149
123
  depth: number,
@@ -234,6 +208,9 @@ function cloneValue(value: unknown, depth: number, references: WeakMap<WeakKey,
234
208
  case value instanceof DataView:
235
209
  return cloneDataView(value, depth, references);
236
210
 
211
+ case value instanceof Date:
212
+ return new Date(value.getTime());
213
+
237
214
  case value instanceof RegExp:
238
215
  return cloneRegularExpression(value, depth, references);
239
216
 
@@ -251,7 +228,7 @@ function cloneValue(value: unknown, depth: number, references: WeakMap<WeakKey,
251
228
  return cloneTypedArray(value, depth, references);
252
229
 
253
230
  default:
254
- return cloneObject(value, depth, references);
231
+ return clone.handlers.handle(value, depth, references);
255
232
  }
256
233
  }
257
234
 
@@ -281,8 +258,6 @@ function tryStructuredClone(
281
258
 
282
259
  // #region Constants
283
260
 
284
- const cloners = new WeakMap<Constructor, string | Cloner<unknown>>();
285
-
286
261
  const MAX_CLONE_DEPTH = 100;
287
262
 
288
263
  // #endregion
@@ -1,3 +1,4 @@
1
+ import type { Constructor } from '../../models';
1
2
  /**
2
3
  * Compare two values _(for sorting purposes)_
3
4
  * @param first First value
@@ -5,3 +6,12 @@
5
6
  * @returns `0` if equal; `-1` first comes before second; `1` first comes after second
6
7
  */
7
8
  export declare function compare(first: unknown, second: unknown): number;
9
+ export declare namespace compare {
10
+ var handlers: {
11
+ register: (constructor: Constructor, handler?: string | import("../..").GenericCallback) => void;
12
+ unregister: (constructor: Constructor) => void;
13
+ handle(first: unknown, second: unknown, ...parameters: unknown[]): number;
14
+ };
15
+ var register: <Instance>(constructor: Constructor<Instance>, handler?: string | ((first: Instance, second: Instance) => number)) => void;
16
+ var unregister: (constructor: Constructor) => void;
17
+ }
@@ -1,5 +1,4 @@
1
1
  import type { Constructor } from '../../models';
2
- type Comparison<Instance> = (first: Instance, second: Instance) => boolean;
3
2
  /**
4
3
  * Options for value equality comparison
5
4
  */
@@ -26,13 +25,16 @@ type Equalizer = {
26
25
  */
27
26
  (first: unknown, second: unknown): boolean;
28
27
  /**
29
- * @inheritdoc equal.register
28
+ * Register a equality comparison handler for a specific class
29
+ * @param constructor Class constructor
30
+ * @param handler Equality comparison handler
30
31
  */
31
- register: typeof equal.register;
32
+ register: <Instance>(constructor: Constructor<Instance>, handler?: string | ((first: Instance, second: Instance) => boolean)) => void;
32
33
  /**
33
- * @inheritdoc equal.unregister
34
+ * Unregister a equality comparison handler for a specific class
35
+ * @param constructor Class constructor
34
36
  */
35
- unregister: typeof equal.unregister;
37
+ unregister: (constructor: Constructor) => void;
36
38
  };
37
39
  /**
38
40
  * Are two strings equal?
@@ -51,8 +53,13 @@ export declare function equal(first: string, second: string, ignoreCase?: boolea
51
53
  */
52
54
  export declare function equal(first: unknown, second: unknown, options?: EqualOptions): boolean;
53
55
  export declare namespace equal {
56
+ var handlers: {
57
+ register: (constructor: Constructor, handler?: string | import("../..").GenericCallback) => void;
58
+ unregister: (constructor: Constructor) => void;
59
+ handle(first: unknown, second: unknown, ...parameters: unknown[]): boolean;
60
+ };
54
61
  var initialize: (options?: EqualOptions) => Equalizer;
55
- var register: <Instance>(constructor: Constructor<Instance>, comparison: Comparison<Instance>) => void;
56
- var unregister: <Instance>(constructor: Constructor<Instance>) => void;
62
+ var register: <Instance>(constructor: Constructor<Instance>, fn: (first: Instance, second: Instance) => boolean) => void;
63
+ var unregister: (constructor: Constructor) => void;
57
64
  }
58
65
  export {};
@@ -0,0 +1,16 @@
1
+ import type { Constructor, GenericCallback } from '../../models';
2
+ type Options = {
3
+ callback: GenericCallback;
4
+ method?: string;
5
+ };
6
+ export declare function getCompareHandlers<Value>(owner: GenericCallback, options: Options): {
7
+ register: (constructor: Constructor, handler?: string | GenericCallback) => void;
8
+ unregister: (constructor: Constructor) => void;
9
+ handle(first: unknown, second: unknown, ...parameters: unknown[]): Value;
10
+ };
11
+ export declare function getSelfHandlers(owner: GenericCallback, options: Options): {
12
+ register: (constructor: Constructor, handler?: string | GenericCallback) => void;
13
+ unregister: (constructor: Constructor) => void;
14
+ handle(value: unknown, ...parameters: unknown[]): any;
15
+ };
16
+ export {};
@@ -1,5 +1,4 @@
1
1
  import type { Constructor } from '../models';
2
- type Cloner<Instance> = (original: Instance) => Instance;
3
2
  /**
4
3
  * Clone any kind of value _(deeply, if needed)_
5
4
  * @param value Value to clone
@@ -7,7 +6,11 @@ type Cloner<Instance> = (original: Instance) => Instance;
7
6
  */
8
7
  export declare function clone<Value>(value: Value): Value;
9
8
  export declare namespace clone {
10
- var register: <Instance>(constructor: Constructor<Instance>, cloner?: string | Cloner<Instance>) => void;
9
+ var handlers: {
10
+ register: (constructor: Constructor, handler?: string | import("..").GenericCallback) => void;
11
+ unregister: (constructor: Constructor) => void;
12
+ handle(value: unknown, ...parameters: unknown[]): any;
13
+ };
14
+ var register: <Instance>(constructor: Constructor<Instance>, handler?: string | ((value: Instance) => Instance)) => void;
11
15
  var unregister: (constructor: Constructor) => void;
12
16
  }
13
- export {};