@art-suite/art-core-ts-compare 0.0.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.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # @art-suite/art-core-ts-compare
2
+
3
+ Deep equality, inequality, less-than and greater-than comparison - complete with custom object support.
4
+
5
+ ## Quick Start - Equality Functions
6
+
7
+ The easiest way to use this library is with the standard equality functions:
8
+
9
+ ```ts
10
+ import { eq, neq, lt, gt, lte, gte } from "@art-suite/art-core-ts-compare";
11
+
12
+ // Basic equality
13
+ eq(5, 5); // true
14
+ eq([1, 2], [1, 2]); // true
15
+ eq({ a: 1 }, { a: 1 }); // true
16
+ eq(null, null); // true
17
+
18
+ // Inequality
19
+ neq(5, 10); // true
20
+ neq([1, 2], [1, 3]); // true
21
+ neq({ a: 1 }, { a: 2 }); // true
22
+
23
+ // Less than / Greater than
24
+ lt(5, 10); // true
25
+ lt("a", "z"); // true
26
+ lt([1, 2], [1, 2, 3]); // true
27
+ lt({ a: 1 }, { a: 1, b: 2 }); // true (missing key)
28
+
29
+ gt(10, 5); // true
30
+ gt("z", "a"); // true
31
+ gt([1, 2, 3], [1, 2]); // true
32
+
33
+ // Less than or equal / Greater than or equal
34
+ lte(5, 5); // true
35
+ lte(5, 10); // true
36
+ gte(10, 10); // true
37
+ gte(10, 5); // true
38
+
39
+ // Custom objects with comparison methods
40
+ class Box {
41
+ constructor(public value: number) {}
42
+ compare(other: any) {
43
+ if (!(other instanceof Box)) return -1;
44
+ return this.value - other.value;
45
+ }
46
+ }
47
+
48
+ eq(new Box(5), new Box(5)); // true
49
+ lt(new Box(2), new Box(5)); // true
50
+ ```
51
+
52
+ ## Power User - Deep Compare Function
53
+
54
+ For advanced use cases, the `compare` function provides fine-grained control:
55
+
56
+ ## Deep Compare Specification
57
+
58
+ This function performs a **deep comparison** between two JavaScript values and returns:
59
+
60
+ - A **negative number** if the left value is less than the right.
61
+ - A **positive number** if the left value is greater than the right.
62
+ - `0` if the values are considered equal.
63
+ - `NaN` if the values are not comparable (different types or incompatible comparisons).
64
+
65
+ ### Scope
66
+
67
+ Primarily designed for comparing **JSON-style values**:
68
+
69
+ - `null`, booleans, numbers, strings
70
+ - Arrays
71
+ - Plain objects
72
+
73
+ It also supports **custom objects** that implement comparison interfaces.
74
+
75
+ ### Custom Comparison Methods
76
+
77
+ If the **left operand** defines one of the following methods, it will be used in this priority order:
78
+
79
+ 1. **`.compare(right: unknown): number`** (highest priority)
80
+ Must return a negative number, zero, or a positive number. Used for sorting.
81
+
82
+ 2. **`.lt(right: unknown): boolean` and `.gt(right: unknown): boolean`**
83
+ Used only if `.compare` is not present. Returns `-1` if `a.lt(b)` is true, `1` if `a.gt(b)` is true, or `0` if neither is true.
84
+
85
+ 3. **`.eq(right: unknown): boolean`** (lowest priority)
86
+ Used only if `.compare` and inequality methods are not present. If `true`, returns `0`. If `false`, falls back to type-based comparison.
87
+
88
+ These methods may throw. The caller is responsible for handling errors.
89
+
90
+ ### Comparison Rules
91
+
92
+ #### Primitive Types
93
+
94
+ - **Numbers**: Compared via subtraction (`a - b`) for efficiency.
95
+ - **Strings**: Compared via `String.prototype.localeCompare()` for proper locale-aware comparison.
96
+ - **Booleans**: Converted to numbers (`Number(a) - Number(b)`) for comparison.
97
+ - **`null`** is treated as less than any non-`null` value.
98
+ - **`undefined`** is treated as greater than `null` but less than any number or string.
99
+ - **`NaN`** is not equal to anything, including itself.
100
+
101
+ #### Arrays
102
+
103
+ Compared element by element:
104
+
105
+ - Compare each index recursively.
106
+ - First non-zero result is returned.
107
+ - If all elements are equal, shorter arrays sort first.
108
+
109
+ #### Plain Objects
110
+
111
+ - Keys are merged from both objects and sorted lexicographically.
112
+ - For each key in sorted order:
113
+ - If a key is **missing in one object and present in the other**, the object missing the key is always considered less (comes first), regardless of the present value or key order.
114
+ - If both objects have the key, their values are compared recursively.
115
+ - If all keys and values are equal, objects are equal.
116
+
117
+ **Examples:**
118
+
119
+ - `{a: 1}` vs `{a: 1, b: 2}` → first is less (missing `b`)
120
+ - `{a: 1, b: 0}` vs `{a: 1}` → second is less (missing `b`)
121
+ - `{}` vs `{a: undefined}` → first is less (missing `a`)
122
+ - `{a: null}` vs `{}` → second is less (missing `a`)
123
+ - `{x: 1}` vs `{y: 1}` → `{y: 1}` is less (missing `x`), so `compare({x: 1}, {y: 1})` returns `1`, `compare({y: 1}, {x: 1})` returns `-1`
124
+
125
+ #### Incompatible Comparisons
126
+
127
+ The function returns `NaN` when:
128
+
129
+ - **Different types**: Comparing values of different primitive types (e.g., number vs string, boolean vs number)
130
+ - **Incompatible objects**: Objects that are not `==` and don't implement custom comparison methods
131
+ - **Unsupported types**: Functions, symbols, classes, or other non-JSON-like values
132
+
133
+ **Note**: Arrays and plain objects are always comparable (element-by-element and key-by-key respectively), even if they contain incompatible values internally.
134
+
135
+ ### Error Handling
136
+
137
+ This function **may throw** if:
138
+
139
+ - Custom `.compare` or `.eq` methods throw.
140
+ - Incompatible or uncoercible types are compared.
141
+
142
+ Consumers (e.g., equality wrappers) are expected to catch errors and treat them as inequality or failure to compare.
143
+
144
+ ### TypeScript Usage Examples
145
+
146
+ ```ts
147
+ deepCompare(5, 10); // -5 (using subtraction)
148
+ deepCompare("z", "a"); // 1 (using localeCompare)
149
+ deepCompare(null, 0); // -1
150
+ deepCompare([1, 2], [1, 2, 3]); // -1
151
+ deepCompare({ a: 1 }, { a: 1 }); // 0
152
+ deepCompare({ a: 1 }, { a: 2 }); // -1
153
+
154
+ deepCompare({ a: 1 }, { a: 1, b: 2 }); // -1 (missing b)
155
+ deepCompare({ a: 1, b: 0 }, { a: 1 }); // 1 (other is missing b)
156
+ deepCompare({}, { a: undefined }); // -1 (missing a)
157
+ deepCompare({ a: null }, {}); // 1 (other is missing a)
158
+ deepCompare({ x: 1 }, { y: 1 }); // 1 ({y: 1} is missing x)
159
+ deepCompare({ y: 1 }, { x: 1 }); // -1 ({y: 1} is missing x)
160
+
161
+ // Incompatible comparisons return NaN
162
+ deepCompare(5, "hello"); // NaN
163
+ deepCompare(true, 42); // NaN
164
+ deepCompare(() => {}, {}); // NaN
165
+
166
+ class Box {
167
+ constructor(public value: number) {}
168
+ compare(other: any) {
169
+ if (!(other instanceof Box)) return -1;
170
+ return this.value - other.value;
171
+ }
172
+ }
173
+
174
+ deepCompare(new Box(2), new Box(5)); // -3
175
+
176
+ class Tag {
177
+ constructor(public name: string) {}
178
+ eq(other: any) {
179
+ return other instanceof Tag && other.name === this.name;
180
+ }
181
+ }
182
+
183
+ deepCompare(new Tag("a"), new Tag("a")); // 0
184
+ deepCompare(new Tag("a"), new Tag("b")); // fallback result (non-zero)
185
+
186
+ class Range {
187
+ constructor(public start: number, public end: number) {}
188
+ lt(other: any): boolean {
189
+ return other instanceof Range && this.end < other.start;
190
+ }
191
+ gt(other: any): boolean {
192
+ return other instanceof Range && this.start > other.end;
193
+ }
194
+ }
195
+
196
+ deepCompare(new Range(1, 3), new Range(4, 6)); // -1 (1-3 < 4-6)
197
+ deepCompare(new Range(4, 6), new Range(1, 3)); // 1 (4-6 > 1-3)
198
+ deepCompare(new Range(1, 3), new Range(2, 4)); // 0 (overlapping ranges)
199
+ ```
200
+
201
+ ### Guarantees
202
+
203
+ - Returns a stable comparison result for all JSON-like structures.
204
+ - Respects `.compare()` and `.eq()` if defined on the **left operand**.
205
+ - Returns `NaN` for incompatible type comparisons.
206
+ - Never swallows internal errors — follows `localeCompare`-style strictness.
package/dist/index.cjs ADDED
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ compare: () => compare,
24
+ eq: () => eq,
25
+ gt: () => gt,
26
+ gte: () => gte,
27
+ lt: () => lt,
28
+ lte: () => lte,
29
+ neq: () => neq
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/compare.ts
34
+ var import_art_core_ts_types = require("@art-suite/art-core-ts-types");
35
+ var compareArrays = (a, b) => {
36
+ const minLength = Math.min(a.length, b.length);
37
+ for (let i = 0; i < minLength; i++) {
38
+ const result = compare(a[i], b[i]);
39
+ if (result !== 0) return result;
40
+ }
41
+ return a.length - b.length;
42
+ };
43
+ var comparePlainObjects = (a, b) => {
44
+ const allKeys = [.../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])].sort();
45
+ for (const key of allKeys) {
46
+ const aHas = key in a;
47
+ const bHas = key in b;
48
+ if (!aHas && bHas) return -1;
49
+ if (aHas && !bHas) return 1;
50
+ const result = compare(a[key], b[key]);
51
+ if (result !== 0) return result;
52
+ }
53
+ return 0;
54
+ };
55
+ var compareCustomComparableHelper = (a, b) => {
56
+ if (a !== null && a !== void 0 && typeof a === "object") {
57
+ if ((0, import_art_core_ts_types.isFunction)(a.compare)) return a.compare(b);
58
+ if ((0, import_art_core_ts_types.isFunction)(a.eq) && a?.eq(b)) return 0;
59
+ if ((0, import_art_core_ts_types.isFunction)(a.lt) && a?.lt(b)) return -1;
60
+ if ((0, import_art_core_ts_types.isFunction)(a.gt) && a?.gt(b)) return 1;
61
+ if ((0, import_art_core_ts_types.isFunction)(a.lte) && a?.lte(b)) return -1;
62
+ if ((0, import_art_core_ts_types.isFunction)(a.gte) && a?.gte(b)) return 1;
63
+ }
64
+ return NaN;
65
+ };
66
+ var compareCustomComparable = (a, b) => {
67
+ const customResult = compareCustomComparableHelper(a, b);
68
+ if (!Number.isNaN(customResult)) return customResult;
69
+ return -compareCustomComparableHelper(b, a);
70
+ };
71
+ var comparePrimitives = (a, b) => {
72
+ if (a === null && b === null) return 0;
73
+ if (a === null) return -1;
74
+ if (b === null) return 1;
75
+ if (a === void 0 && b === void 0) return 0;
76
+ if (a === void 0) return 1;
77
+ if (b === void 0) return -1;
78
+ if (Number.isNaN(a) && Number.isNaN(b)) return 0;
79
+ if (Number.isNaN(a)) return -1;
80
+ if (Number.isNaN(b)) return 1;
81
+ if (typeof a === "number" && typeof b === "number") {
82
+ return a - b;
83
+ }
84
+ if (typeof a === "string" && typeof b === "string") {
85
+ return a.localeCompare(b);
86
+ }
87
+ if (typeof a === "boolean" && typeof b === "boolean") {
88
+ return Number(a) - Number(b);
89
+ }
90
+ if (typeof a !== typeof b) return NaN;
91
+ return NaN;
92
+ };
93
+ var compare = (a, b) => {
94
+ const customResult = compareCustomComparable(a, b);
95
+ if (!Number.isNaN(customResult)) return customResult;
96
+ if ((0, import_art_core_ts_types.isArray)(a) && (0, import_art_core_ts_types.isArray)(b)) {
97
+ return compareArrays(a, b);
98
+ }
99
+ if ((0, import_art_core_ts_types.isPlainObject)(a) && (0, import_art_core_ts_types.isPlainObject)(b)) {
100
+ return comparePlainObjects(a, b);
101
+ }
102
+ return comparePrimitives(a, b);
103
+ };
104
+
105
+ // src/equality.ts
106
+ var eq = (a, b) => compare(a, b) === 0;
107
+ var neq = (a, b) => compare(a, b) !== 0;
108
+ var lt = (a, b) => compare(a, b) < 0;
109
+ var gt = (a, b) => compare(a, b) > 0;
110
+ var lte = (a, b) => compare(a, b) <= 0;
111
+ var gte = (a, b) => compare(a, b) >= 0;
112
+ // Annotate the CommonJS export names for ESM import in node:
113
+ 0 && (module.exports = {
114
+ compare,
115
+ eq,
116
+ gt,
117
+ gte,
118
+ lt,
119
+ lte,
120
+ neq
121
+ });
122
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/compare.ts","../src/equality.ts"],"sourcesContent":["export * from './compare'\nexport * from './equality'","import { isArray, isPlainObject, isFunction } from '@art-suite/art-core-ts-types'\n\nexport interface CustomComparableInterface {\n\n /**\n * Compares `this` with `b`\n * @param b - The value to compare `this` with\n * @returns A number greater than zero if `this` is greater than `b`, less than zero if `this` is less than `b`, and zero if `this` is equal to `b`\n */\n compare(b: any): number\n}\n\nexport interface CustomEqualityInterface {\n /**\n * Checks if `this` equals `b`\n * @param b - The value to compare `this` with\n * @returns true if equal, false otherwise\n */\n eq(b: any): boolean\n}\n\nexport interface CustomInequalityInterface extends CustomEqualityInterface {\n /**\n * Checks if `this` is not equal to `b`\n * @param b - The value to compare `this` with\n * @returns true if not equal, false otherwise\n */\n lt(b: any): boolean\n gt(b: any): boolean\n lte(b: any): boolean\n gte(b: any): boolean\n}\n\nconst compareArrays = (a: any[], b: any[]): number => {\n const minLength = Math.min(a.length, b.length)\n\n for (let i = 0; i < minLength; i++) {\n const result = compare(a[i], b[i])\n if (result !== 0) return result\n }\n\n return a.length - b.length\n}\n\nconst comparePlainObjects = (a: Record<string, any>, b: Record<string, any>): number => {\n // Create a merged list of all unique keys and sort it\n const allKeys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort()\n\n // Iterate through the sorted keys to find the first non-zero comparison result\n for (const key of allKeys) {\n const aHas = key in a\n const bHas = key in b\n if (!aHas && bHas) return -1 // a is missing the key, so a is less\n if (aHas && !bHas) return 1 // b is missing the key, so b is less\n // Both have the key, compare their values\n const result = compare(a[key], b[key])\n if (result !== 0) return result\n }\n\n return 0 // All keys and values are equal\n}\n\nconst compareCustomComparableHelper = (a: any, b: any): number => {\n // Only check for custom methods if a is not null/undefined and is an object\n if (a !== null && a !== undefined && typeof a === 'object') {\n // Check if left operand supports any of the custom methods (highest priority)\n if (isFunction(a.compare)) return a.compare(b);\n if (isFunction(a.eq) && a?.eq(b)) return 0;\n if (isFunction(a.lt) && a?.lt(b)) return -1;\n if (isFunction(a.gt) && a?.gt(b)) return 1;\n if (isFunction(a.lte) && a?.lte(b)) return -1;\n if (isFunction(a.gte) && a?.gte(b)) return 1;\n }\n return NaN\n}\n\nconst compareCustomComparable = (a: any, b: any): number => {\n const customResult = compareCustomComparableHelper(a, b)\n if (!Number.isNaN(customResult)) return customResult\n return -compareCustomComparableHelper(b, a)\n}\n\nconst comparePrimitives = (a: any, b: any): number => {\n // Handle null\n if (a === null && b === null) return 0\n if (a === null) return -1\n if (b === null) return 1\n\n // Handle undefined\n if (a === undefined && b === undefined) return 0\n if (a === undefined) return 1\n if (b === undefined) return -1\n\n // Handle NaN\n if (Number.isNaN(a) && Number.isNaN(b)) return 0\n if (Number.isNaN(a)) return -1\n if (Number.isNaN(b)) return 1\n\n // Handle numbers - use subtraction for efficiency\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b\n }\n\n // Handle strings - use localeCompare for proper string comparison\n if (typeof a === 'string' && typeof b === 'string') {\n return a.localeCompare(b)\n }\n\n // Handle booleans - convert to numbers for comparison\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return Number(a) - Number(b)\n }\n\n // Return NaN for different types or incompatible comparisons\n if (typeof a !== typeof b) return NaN\n\n // For same types that aren't numbers, strings, or booleans, return NaN\n return NaN\n}\n\nexport const compare = (a: any, b: any): number => {\n // Handle custom comparables first\n const customResult = compareCustomComparable(a, b)\n if (!Number.isNaN(customResult)) return customResult\n\n // Handle arrays\n if (isArray(a) && isArray(b)) {\n return compareArrays(a, b)\n }\n\n // Handle plain objects\n if (isPlainObject(a) && isPlainObject(b)) {\n return comparePlainObjects(a, b)\n }\n\n // Handle primitives\n return comparePrimitives(a, b)\n}","import { compare } from './compare'\n\nexport const eq = (a: any, b: any): boolean => compare(a, b) === 0;\nexport const neq = (a: any, b: any): boolean => compare(a, b) !== 0;\nexport const lt = (a: any, b: any): boolean => compare(a, b) < 0;\nexport const gt = (a: any, b: any): boolean => compare(a, b) > 0;\nexport const lte = (a: any, b: any): boolean => compare(a, b) <= 0;\nexport const gte = (a: any, b: any): boolean => compare(a, b) >= 0;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,37 @@
1
+ interface CustomComparableInterface {
2
+ /**
3
+ * Compares `this` with `b`
4
+ * @param b - The value to compare `this` with
5
+ * @returns A number greater than zero if `this` is greater than `b`, less than zero if `this` is less than `b`, and zero if `this` is equal to `b`
6
+ */
7
+ compare(b: any): number;
8
+ }
9
+ interface CustomEqualityInterface {
10
+ /**
11
+ * Checks if `this` equals `b`
12
+ * @param b - The value to compare `this` with
13
+ * @returns true if equal, false otherwise
14
+ */
15
+ eq(b: any): boolean;
16
+ }
17
+ interface CustomInequalityInterface extends CustomEqualityInterface {
18
+ /**
19
+ * Checks if `this` is not equal to `b`
20
+ * @param b - The value to compare `this` with
21
+ * @returns true if not equal, false otherwise
22
+ */
23
+ lt(b: any): boolean;
24
+ gt(b: any): boolean;
25
+ lte(b: any): boolean;
26
+ gte(b: any): boolean;
27
+ }
28
+ declare const compare: (a: any, b: any) => number;
29
+
30
+ declare const eq: (a: any, b: any) => boolean;
31
+ declare const neq: (a: any, b: any) => boolean;
32
+ declare const lt: (a: any, b: any) => boolean;
33
+ declare const gt: (a: any, b: any) => boolean;
34
+ declare const lte: (a: any, b: any) => boolean;
35
+ declare const gte: (a: any, b: any) => boolean;
36
+
37
+ export { type CustomComparableInterface, type CustomEqualityInterface, type CustomInequalityInterface, compare, eq, gt, gte, lt, lte, neq };
@@ -0,0 +1,37 @@
1
+ interface CustomComparableInterface {
2
+ /**
3
+ * Compares `this` with `b`
4
+ * @param b - The value to compare `this` with
5
+ * @returns A number greater than zero if `this` is greater than `b`, less than zero if `this` is less than `b`, and zero if `this` is equal to `b`
6
+ */
7
+ compare(b: any): number;
8
+ }
9
+ interface CustomEqualityInterface {
10
+ /**
11
+ * Checks if `this` equals `b`
12
+ * @param b - The value to compare `this` with
13
+ * @returns true if equal, false otherwise
14
+ */
15
+ eq(b: any): boolean;
16
+ }
17
+ interface CustomInequalityInterface extends CustomEqualityInterface {
18
+ /**
19
+ * Checks if `this` is not equal to `b`
20
+ * @param b - The value to compare `this` with
21
+ * @returns true if not equal, false otherwise
22
+ */
23
+ lt(b: any): boolean;
24
+ gt(b: any): boolean;
25
+ lte(b: any): boolean;
26
+ gte(b: any): boolean;
27
+ }
28
+ declare const compare: (a: any, b: any) => number;
29
+
30
+ declare const eq: (a: any, b: any) => boolean;
31
+ declare const neq: (a: any, b: any) => boolean;
32
+ declare const lt: (a: any, b: any) => boolean;
33
+ declare const gt: (a: any, b: any) => boolean;
34
+ declare const lte: (a: any, b: any) => boolean;
35
+ declare const gte: (a: any, b: any) => boolean;
36
+
37
+ export { type CustomComparableInterface, type CustomEqualityInterface, type CustomInequalityInterface, compare, eq, gt, gte, lt, lte, neq };
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
1
+ // src/compare.ts
2
+ import { isArray, isPlainObject, isFunction } from "@art-suite/art-core-ts-types";
3
+ var compareArrays = (a, b) => {
4
+ const minLength = Math.min(a.length, b.length);
5
+ for (let i = 0; i < minLength; i++) {
6
+ const result = compare(a[i], b[i]);
7
+ if (result !== 0) return result;
8
+ }
9
+ return a.length - b.length;
10
+ };
11
+ var comparePlainObjects = (a, b) => {
12
+ const allKeys = [.../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])].sort();
13
+ for (const key of allKeys) {
14
+ const aHas = key in a;
15
+ const bHas = key in b;
16
+ if (!aHas && bHas) return -1;
17
+ if (aHas && !bHas) return 1;
18
+ const result = compare(a[key], b[key]);
19
+ if (result !== 0) return result;
20
+ }
21
+ return 0;
22
+ };
23
+ var compareCustomComparableHelper = (a, b) => {
24
+ if (a !== null && a !== void 0 && typeof a === "object") {
25
+ if (isFunction(a.compare)) return a.compare(b);
26
+ if (isFunction(a.eq) && a?.eq(b)) return 0;
27
+ if (isFunction(a.lt) && a?.lt(b)) return -1;
28
+ if (isFunction(a.gt) && a?.gt(b)) return 1;
29
+ if (isFunction(a.lte) && a?.lte(b)) return -1;
30
+ if (isFunction(a.gte) && a?.gte(b)) return 1;
31
+ }
32
+ return NaN;
33
+ };
34
+ var compareCustomComparable = (a, b) => {
35
+ const customResult = compareCustomComparableHelper(a, b);
36
+ if (!Number.isNaN(customResult)) return customResult;
37
+ return -compareCustomComparableHelper(b, a);
38
+ };
39
+ var comparePrimitives = (a, b) => {
40
+ if (a === null && b === null) return 0;
41
+ if (a === null) return -1;
42
+ if (b === null) return 1;
43
+ if (a === void 0 && b === void 0) return 0;
44
+ if (a === void 0) return 1;
45
+ if (b === void 0) return -1;
46
+ if (Number.isNaN(a) && Number.isNaN(b)) return 0;
47
+ if (Number.isNaN(a)) return -1;
48
+ if (Number.isNaN(b)) return 1;
49
+ if (typeof a === "number" && typeof b === "number") {
50
+ return a - b;
51
+ }
52
+ if (typeof a === "string" && typeof b === "string") {
53
+ return a.localeCompare(b);
54
+ }
55
+ if (typeof a === "boolean" && typeof b === "boolean") {
56
+ return Number(a) - Number(b);
57
+ }
58
+ if (typeof a !== typeof b) return NaN;
59
+ return NaN;
60
+ };
61
+ var compare = (a, b) => {
62
+ const customResult = compareCustomComparable(a, b);
63
+ if (!Number.isNaN(customResult)) return customResult;
64
+ if (isArray(a) && isArray(b)) {
65
+ return compareArrays(a, b);
66
+ }
67
+ if (isPlainObject(a) && isPlainObject(b)) {
68
+ return comparePlainObjects(a, b);
69
+ }
70
+ return comparePrimitives(a, b);
71
+ };
72
+
73
+ // src/equality.ts
74
+ var eq = (a, b) => compare(a, b) === 0;
75
+ var neq = (a, b) => compare(a, b) !== 0;
76
+ var lt = (a, b) => compare(a, b) < 0;
77
+ var gt = (a, b) => compare(a, b) > 0;
78
+ var lte = (a, b) => compare(a, b) <= 0;
79
+ var gte = (a, b) => compare(a, b) >= 0;
80
+ export {
81
+ compare,
82
+ eq,
83
+ gt,
84
+ gte,
85
+ lt,
86
+ lte,
87
+ neq
88
+ };
89
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compare.ts","../src/equality.ts"],"sourcesContent":["import { isArray, isPlainObject, isFunction } from '@art-suite/art-core-ts-types'\n\nexport interface CustomComparableInterface {\n\n /**\n * Compares `this` with `b`\n * @param b - The value to compare `this` with\n * @returns A number greater than zero if `this` is greater than `b`, less than zero if `this` is less than `b`, and zero if `this` is equal to `b`\n */\n compare(b: any): number\n}\n\nexport interface CustomEqualityInterface {\n /**\n * Checks if `this` equals `b`\n * @param b - The value to compare `this` with\n * @returns true if equal, false otherwise\n */\n eq(b: any): boolean\n}\n\nexport interface CustomInequalityInterface extends CustomEqualityInterface {\n /**\n * Checks if `this` is not equal to `b`\n * @param b - The value to compare `this` with\n * @returns true if not equal, false otherwise\n */\n lt(b: any): boolean\n gt(b: any): boolean\n lte(b: any): boolean\n gte(b: any): boolean\n}\n\nconst compareArrays = (a: any[], b: any[]): number => {\n const minLength = Math.min(a.length, b.length)\n\n for (let i = 0; i < minLength; i++) {\n const result = compare(a[i], b[i])\n if (result !== 0) return result\n }\n\n return a.length - b.length\n}\n\nconst comparePlainObjects = (a: Record<string, any>, b: Record<string, any>): number => {\n // Create a merged list of all unique keys and sort it\n const allKeys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort()\n\n // Iterate through the sorted keys to find the first non-zero comparison result\n for (const key of allKeys) {\n const aHas = key in a\n const bHas = key in b\n if (!aHas && bHas) return -1 // a is missing the key, so a is less\n if (aHas && !bHas) return 1 // b is missing the key, so b is less\n // Both have the key, compare their values\n const result = compare(a[key], b[key])\n if (result !== 0) return result\n }\n\n return 0 // All keys and values are equal\n}\n\nconst compareCustomComparableHelper = (a: any, b: any): number => {\n // Only check for custom methods if a is not null/undefined and is an object\n if (a !== null && a !== undefined && typeof a === 'object') {\n // Check if left operand supports any of the custom methods (highest priority)\n if (isFunction(a.compare)) return a.compare(b);\n if (isFunction(a.eq) && a?.eq(b)) return 0;\n if (isFunction(a.lt) && a?.lt(b)) return -1;\n if (isFunction(a.gt) && a?.gt(b)) return 1;\n if (isFunction(a.lte) && a?.lte(b)) return -1;\n if (isFunction(a.gte) && a?.gte(b)) return 1;\n }\n return NaN\n}\n\nconst compareCustomComparable = (a: any, b: any): number => {\n const customResult = compareCustomComparableHelper(a, b)\n if (!Number.isNaN(customResult)) return customResult\n return -compareCustomComparableHelper(b, a)\n}\n\nconst comparePrimitives = (a: any, b: any): number => {\n // Handle null\n if (a === null && b === null) return 0\n if (a === null) return -1\n if (b === null) return 1\n\n // Handle undefined\n if (a === undefined && b === undefined) return 0\n if (a === undefined) return 1\n if (b === undefined) return -1\n\n // Handle NaN\n if (Number.isNaN(a) && Number.isNaN(b)) return 0\n if (Number.isNaN(a)) return -1\n if (Number.isNaN(b)) return 1\n\n // Handle numbers - use subtraction for efficiency\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b\n }\n\n // Handle strings - use localeCompare for proper string comparison\n if (typeof a === 'string' && typeof b === 'string') {\n return a.localeCompare(b)\n }\n\n // Handle booleans - convert to numbers for comparison\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return Number(a) - Number(b)\n }\n\n // Return NaN for different types or incompatible comparisons\n if (typeof a !== typeof b) return NaN\n\n // For same types that aren't numbers, strings, or booleans, return NaN\n return NaN\n}\n\nexport const compare = (a: any, b: any): number => {\n // Handle custom comparables first\n const customResult = compareCustomComparable(a, b)\n if (!Number.isNaN(customResult)) return customResult\n\n // Handle arrays\n if (isArray(a) && isArray(b)) {\n return compareArrays(a, b)\n }\n\n // Handle plain objects\n if (isPlainObject(a) && isPlainObject(b)) {\n return comparePlainObjects(a, b)\n }\n\n // Handle primitives\n return comparePrimitives(a, b)\n}","import { compare } from './compare'\n\nexport const eq = (a: any, b: any): boolean => compare(a, b) === 0;\nexport const neq = (a: any, b: any): boolean => compare(a, b) !== 0;\nexport const lt = (a: any, b: any): boolean => compare(a, b) < 0;\nexport const gt = (a: any, b: any): boolean => compare(a, b) > 0;\nexport const lte = (a: any, b: any): boolean => compare(a, b) <= 0;\nexport const gte = (a: any, b: any): boolean => compare(a, b) >= 0;\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@art-suite/art-core-ts-compare",
3
+ "version": "0.0.1",
4
+ "description": "A TypeScript comparison utility library",
5
+ "keywords": [],
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/art-suite/art-suite-ts"
9
+ },
10
+ "license": "MIT",
11
+ "author": "Shane Delamore",
12
+ "type": "module",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "prebuild": "npm run clean",
27
+ "build": "npx sort-package-json;tsup src/index.ts --format esm,cjs --dts --sourcemap",
28
+ "build:clean": "npm run clean && npm run build",
29
+ "clean": "rm -rf dist",
30
+ "prepublishOnly": "npm run build",
31
+ "test": "vitest run",
32
+ "test:coverage": "vitest run --coverage",
33
+ "test:watch": "vitest"
34
+ },
35
+ "dependencies": {
36
+ "@art-suite/art-core-ts-types": "^0.3.2"
37
+ },
38
+ "devDependencies": {
39
+ "@vitest/coverage-v8": "^3.1.4"
40
+ }
41
+ }