@macroforge/mcp-server 0.1.34 → 0.1.35
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/docs/builtin-macros/clone.md +42 -183
- package/docs/builtin-macros/debug.md +33 -239
- package/docs/builtin-macros/default.md +78 -257
- package/docs/builtin-macros/deserialize.md +94 -999
- package/docs/builtin-macros/hash.md +62 -260
- package/docs/builtin-macros/ord.md +70 -251
- package/docs/builtin-macros/partial-eq.md +55 -262
- package/docs/builtin-macros/partial-ord.md +69 -272
- package/docs/builtin-macros/serialize.md +63 -382
- package/docs/concepts/derive-system.md +1 -4
- package/package.json +2 -2
|
@@ -1,279 +1,81 @@
|
|
|
1
1
|
# Hash
|
|
2
|
-
*The `Hash` macro generates a `hashCode()` method for computing numeric hash codes. This is analogous to Rust's `Hash` trait and Java's `hashCode()` method, enabling objects to be used as keys in hash-based collections.*
|
|
3
|
-
## Basic Usage
|
|
4
|
-
**Before:**
|
|
5
|
-
```
|
|
6
|
-
/** @derive(Hash) */
|
|
7
|
-
class Point {
|
|
8
|
-
x: number;
|
|
9
|
-
y: number;
|
|
10
2
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
```
|
|
17
|
-
**After:**
|
|
18
|
-
```
|
|
19
|
-
class Point {
|
|
20
|
-
x: number;
|
|
21
|
-
y: number;
|
|
3
|
+
The `Hash` macro generates a `hashCode()` method for computing numeric hash codes.
|
|
4
|
+
This is analogous to Rust's `Hash` trait and Java's `hashCode()` method, enabling
|
|
5
|
+
objects to be used as keys in hash-based collections.
|
|
22
6
|
|
|
23
|
-
|
|
24
|
-
this.x = x;
|
|
25
|
-
this.y = y;
|
|
26
|
-
}
|
|
7
|
+
## Generated Output
|
|
27
8
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
: this.x
|
|
35
|
-
.toString()
|
|
36
|
-
.split('')
|
|
37
|
-
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
38
|
-
0;
|
|
39
|
-
hash =
|
|
40
|
-
(hash * 31 +
|
|
41
|
-
(Number.isInteger(this.y)
|
|
42
|
-
? this.y | 0
|
|
43
|
-
: this.y
|
|
44
|
-
.toString()
|
|
45
|
-
.split('')
|
|
46
|
-
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
47
|
-
0;
|
|
48
|
-
return hash;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
``` ```
|
|
52
|
-
const p1 = new Point(10, 20);
|
|
53
|
-
const p2 = new Point(10, 20);
|
|
54
|
-
const p3 = new Point(5, 5);
|
|
9
|
+
| Type | Generated Code | Description |
|
|
10
|
+
|------|----------------|-------------|
|
|
11
|
+
| Class | `hashCode(): number` | Instance method computing hash from all fields |
|
|
12
|
+
| Enum | `hashCodeEnumName(value: EnumName): number` | Standalone function hashing by enum value |
|
|
13
|
+
| Interface | `hashCodeInterfaceName(value: InterfaceName): number` | Standalone function computing hash |
|
|
14
|
+
| Type Alias | `hashCodeTypeName(value: TypeName): number` | Standalone function computing hash |
|
|
55
15
|
|
|
56
|
-
|
|
57
|
-
console.log(p2.hashCode()); // Same hash (equal values = equal hash)
|
|
58
|
-
console.log(p3.hashCode()); // Different hash
|
|
59
|
-
``` ## Hash Algorithm
|
|
60
|
-
The generated hash function uses the following algorithm for different types:
|
|
61
|
-
- `number` → Integers use bitwise OR (`| 0`), floats are stringified and hashed
|
|
62
|
-
- `string` → Character-by-character hash: `(h * 31 + charCode) | 0`
|
|
63
|
-
- `boolean` → `1231` for true, `1237` for false (Java convention)
|
|
64
|
-
- `bigint` → Converted to string and hashed character-by-character
|
|
65
|
-
- `Date` → Uses `getTime() | 0` for timestamp hash
|
|
66
|
-
- `Array` → Combines element hashes with `h * 31 + elementHash`
|
|
67
|
-
- `Map/Set` → Combines all entry hashes
|
|
68
|
-
- `Object` → Calls `hashCode()` if available, otherwise JSON stringifies and hashes
|
|
69
|
-
- `null` → Returns 0
|
|
70
|
-
- `undefined` → Returns 1
|
|
71
|
-
## Field Options
|
|
72
|
-
### @hash(skip)
|
|
73
|
-
Use `@hash(skip)` to exclude a field from hash computation:
|
|
74
|
-
**Before:**
|
|
75
|
-
```
|
|
76
|
-
/** @derive(Hash) */
|
|
77
|
-
class User {
|
|
78
|
-
id: number;
|
|
79
|
-
name: string;
|
|
16
|
+
## Configuration
|
|
80
17
|
|
|
81
|
-
|
|
82
|
-
|
|
18
|
+
The `functionNamingStyle` option in `macroforge.json` controls naming:
|
|
19
|
+
- `"suffix"` (default): Suffixes with type name (e.g., `hashCodeMyType`)
|
|
20
|
+
- `"prefix"`: Prefixes with type name (e.g., `myTypeHashCode`)
|
|
21
|
+
- `"generic"`: Uses TypeScript generics (e.g., `hashCode<T extends MyType>`)
|
|
22
|
+
- `"namespace"`: Legacy namespace wrapping
|
|
83
23
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
24
|
+
## Hash Algorithm
|
|
25
|
+
|
|
26
|
+
Uses the standard polynomial rolling hash algorithm:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
hash = 17 // Initial seed
|
|
30
|
+
for each field:
|
|
31
|
+
hash = (hash * 31 + fieldHash) | 0 // Bitwise OR keeps it 32-bit integer
|
|
92
32
|
```
|
|
93
|
-
class User {
|
|
94
|
-
id: number;
|
|
95
|
-
name: string;
|
|
96
33
|
|
|
97
|
-
|
|
34
|
+
This algorithm is consistent with Java's `Objects.hash()` implementation.
|
|
98
35
|
|
|
99
|
-
|
|
100
|
-
this.id = id;
|
|
101
|
-
this.name = name;
|
|
102
|
-
this.lastLogin = lastLogin;
|
|
103
|
-
}
|
|
36
|
+
## Type-Specific Hashing
|
|
104
37
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
hash =
|
|
117
|
-
(hash * 31 +
|
|
118
|
-
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
119
|
-
0;
|
|
120
|
-
return hash;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
``` ```
|
|
124
|
-
const user1 = new User(1, "Alice", new Date("2024-01-01"));
|
|
125
|
-
const user2 = new User(1, "Alice", new Date("2024-12-01"));
|
|
38
|
+
| Type | Hash Strategy |
|
|
39
|
+
|------|---------------|
|
|
40
|
+
| `number` | Integer: direct value; Float: string hash of decimal |
|
|
41
|
+
| `bigint` | String hash of decimal representation |
|
|
42
|
+
| `string` | Character-by-character polynomial hash |
|
|
43
|
+
| `boolean` | 1231 for true, 1237 for false (Java convention) |
|
|
44
|
+
| `Date` | `getTime()` timestamp |
|
|
45
|
+
| Arrays | Element-by-element hash combination |
|
|
46
|
+
| `Map` | Entry-by-entry key+value hash |
|
|
47
|
+
| `Set` | Element-by-element hash |
|
|
48
|
+
| Objects | Calls `hashCode()` if available, else JSON string hash |
|
|
126
49
|
|
|
127
|
-
|
|
128
|
-
``` ## Use with PartialEq
|
|
129
|
-
Hash is often used together with PartialEq. Objects that are equal should have the same hash code:
|
|
130
|
-
**Source:**
|
|
131
|
-
```
|
|
132
|
-
/** @derive(Hash, PartialEq) */
|
|
133
|
-
class Product {
|
|
134
|
-
sku: string;
|
|
135
|
-
name: string;
|
|
50
|
+
## Field-Level Options
|
|
136
51
|
|
|
137
|
-
|
|
138
|
-
this.sku = sku;
|
|
139
|
-
this.name = name;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
``` ```
|
|
143
|
-
const p1 = new Product("ABC123", "Widget");
|
|
144
|
-
const p2 = new Product("ABC123", "Widget");
|
|
52
|
+
The `@hash` decorator supports:
|
|
145
53
|
|
|
146
|
-
|
|
147
|
-
console.log(p1.equals(p2)); // true
|
|
148
|
-
console.log(p1.hashCode() === p2.hashCode()); // true
|
|
149
|
-
``` ## Interface Support
|
|
150
|
-
Hash also works with interfaces. For interfaces, a namespace is generated with a `hashCode` function:
|
|
151
|
-
**Before:**
|
|
152
|
-
```
|
|
153
|
-
/** @derive(Hash) */
|
|
154
|
-
interface Point {
|
|
155
|
-
x: number;
|
|
156
|
-
y: number;
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
**After:**
|
|
160
|
-
```
|
|
161
|
-
interface Point {
|
|
162
|
-
x: number;
|
|
163
|
-
y: number;
|
|
164
|
-
}
|
|
54
|
+
- `skip` - Exclude the field from hash calculation
|
|
165
55
|
|
|
166
|
-
|
|
167
|
-
export function hashCode(self: Point): number {
|
|
168
|
-
let hash = 17;
|
|
169
|
-
hash =
|
|
170
|
-
(hash * 31 +
|
|
171
|
-
(Number.isInteger(self.x)
|
|
172
|
-
? self.x | 0
|
|
173
|
-
: self.x
|
|
174
|
-
.toString()
|
|
175
|
-
.split('')
|
|
176
|
-
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
177
|
-
0;
|
|
178
|
-
hash =
|
|
179
|
-
(hash * 31 +
|
|
180
|
-
(Number.isInteger(self.y)
|
|
181
|
-
? self.y | 0
|
|
182
|
-
: self.y
|
|
183
|
-
.toString()
|
|
184
|
-
.split('')
|
|
185
|
-
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
186
|
-
0;
|
|
187
|
-
return hash;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
``` ```
|
|
191
|
-
const p: Point = { x: 10, y: 20 };
|
|
192
|
-
console.log(Point.hashCode(p)); // numeric hash value
|
|
193
|
-
``` ## Enum Support
|
|
194
|
-
Hash works with enums. For string enums, it hashes the string value; for numeric enums, it uses the numeric value directly:
|
|
195
|
-
**Before:**
|
|
196
|
-
```
|
|
197
|
-
/** @derive(Hash) */
|
|
198
|
-
enum Status {
|
|
199
|
-
Active = 'active',
|
|
200
|
-
Inactive = 'inactive',
|
|
201
|
-
Pending = 'pending'
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
**After:**
|
|
205
|
-
```
|
|
206
|
-
enum Status {
|
|
207
|
-
Active = 'active',
|
|
208
|
-
Inactive = 'inactive',
|
|
209
|
-
Pending = 'pending'
|
|
210
|
-
}
|
|
56
|
+
## Example
|
|
211
57
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
hash = (hash * 31 + value.charCodeAt(i)) | 0;
|
|
218
|
-
}
|
|
219
|
-
return hash;
|
|
220
|
-
}
|
|
221
|
-
return value as number;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
``` ```
|
|
225
|
-
console.log(Status.hashCode(Status.Active)); // consistent hash
|
|
226
|
-
console.log(Status.hashCode(Status.Inactive)); // different hash
|
|
227
|
-
``` ## Type Alias Support
|
|
228
|
-
Hash works with type aliases. For object types, it hashes each field:
|
|
229
|
-
**Before:**
|
|
230
|
-
```
|
|
231
|
-
/** @derive(Hash) */
|
|
232
|
-
type Coordinates = {
|
|
233
|
-
lat: number;
|
|
234
|
-
lng: number;
|
|
235
|
-
};
|
|
236
|
-
```
|
|
237
|
-
**After:**
|
|
238
|
-
```
|
|
239
|
-
type Coordinates = {
|
|
240
|
-
lat: number;
|
|
241
|
-
lng: number;
|
|
242
|
-
};
|
|
58
|
+
```typescript
|
|
59
|
+
@derive(Hash, PartialEq)
|
|
60
|
+
class User {
|
|
61
|
+
id: number;
|
|
62
|
+
name: string;
|
|
243
63
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
let hash = 17;
|
|
247
|
-
hash =
|
|
248
|
-
(hash * 31 +
|
|
249
|
-
(Number.isInteger(value.lat)
|
|
250
|
-
? value.lat | 0
|
|
251
|
-
: value.lat
|
|
252
|
-
.toString()
|
|
253
|
-
.split('')
|
|
254
|
-
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
255
|
-
0;
|
|
256
|
-
hash =
|
|
257
|
-
(hash * 31 +
|
|
258
|
-
(Number.isInteger(value.lng)
|
|
259
|
-
? value.lng | 0
|
|
260
|
-
: value.lng
|
|
261
|
-
.toString()
|
|
262
|
-
.split('')
|
|
263
|
-
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
264
|
-
0;
|
|
265
|
-
return hash;
|
|
266
|
-
}
|
|
64
|
+
@hash(skip) // Cached value shouldn't affect hash
|
|
65
|
+
cachedScore: number;
|
|
267
66
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
67
|
+
|
|
68
|
+
// Generated:
|
|
69
|
+
// hashCode(): number {
|
|
70
|
+
// let hash = 17;
|
|
71
|
+
// hash = (hash * 31 + (Number.isInteger(this.id) ? this.id | 0 : ...)) | 0;
|
|
72
|
+
// hash = (hash * 31 + (this.name ?? '').split('').reduce(...)) | 0;
|
|
73
|
+
// return hash;
|
|
74
|
+
// }
|
|
273
75
|
```
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
76
|
+
|
|
77
|
+
## Hash Contract
|
|
78
|
+
|
|
79
|
+
Objects that are equal (`PartialEq`) should produce the same hash code.
|
|
80
|
+
When using `@hash(skip)`, ensure the same fields are skipped in both
|
|
81
|
+
`Hash` and `PartialEq` to maintain this contract.
|
|
@@ -1,272 +1,91 @@
|
|
|
1
1
|
# Ord
|
|
2
|
-
*The `Ord` macro generates a `compareTo()` method for **total ordering** comparison. This is analogous to Rust's `Ord` trait, enabling objects to be sorted and compared with a guaranteed ordering relationship.*
|
|
3
|
-
## Basic Usage
|
|
4
|
-
**Before:**
|
|
5
|
-
```
|
|
6
|
-
/** @derive(Ord) */
|
|
7
|
-
class Version {
|
|
8
|
-
major: number;
|
|
9
|
-
minor: number;
|
|
10
|
-
patch: number;
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.patch = patch;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
**After:**
|
|
20
|
-
```
|
|
21
|
-
class Version {
|
|
22
|
-
major: number;
|
|
23
|
-
minor: number;
|
|
24
|
-
patch: number;
|
|
3
|
+
The `Ord` macro generates a `compareTo()` method for **total ordering** comparison.
|
|
4
|
+
This is analogous to Rust's `Ord` trait, enabling objects to be sorted and
|
|
5
|
+
compared with a guaranteed ordering relationship.
|
|
25
6
|
|
|
26
|
-
|
|
27
|
-
this.major = major;
|
|
28
|
-
this.minor = minor;
|
|
29
|
-
this.patch = patch;
|
|
30
|
-
}
|
|
7
|
+
## Generated Output
|
|
31
8
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (cmp1 !== 0) return cmp1;
|
|
39
|
-
const cmp2 = this.patch < typedOther.patch ? -1 : this.patch > typedOther.patch ? 1 : 0;
|
|
40
|
-
if (cmp2 !== 0) return cmp2;
|
|
41
|
-
return 0;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
``` ```
|
|
45
|
-
const v1 = new Version(1, 0, 0);
|
|
46
|
-
const v2 = new Version(1, 2, 0);
|
|
47
|
-
const v3 = new Version(1, 2, 0);
|
|
9
|
+
| Type | Generated Code | Description |
|
|
10
|
+
|------|----------------|-------------|
|
|
11
|
+
| Class | `compareTo(other): number` | Instance method returning -1, 0, or 1 |
|
|
12
|
+
| Enum | `compareEnumName(a: EnumName, b: EnumName): number` | Standalone function comparing enum values |
|
|
13
|
+
| Interface | `compareInterfaceName(a: InterfaceName, b: InterfaceName): number` | Standalone function comparing fields |
|
|
14
|
+
| Type Alias | `compareTypeName(a: TypeName, b: TypeName): number` | Standalone function with type-appropriate comparison |
|
|
48
15
|
|
|
49
|
-
|
|
50
|
-
console.log(v2.compareTo(v1)); // 1 (v2 > v1)
|
|
51
|
-
console.log(v2.compareTo(v3)); // 0 (v2 == v3)
|
|
52
|
-
``` ## Comparison Logic
|
|
53
|
-
The Ord macro compares fields in declaration order (lexicographic ordering). For each type:
|
|
54
|
-
- `number` / `bigint` → Direct numeric comparison
|
|
55
|
-
- `string` → Uses `localeCompare()` clamped to -1/0/1
|
|
56
|
-
- `boolean` → `false < true`
|
|
57
|
-
- `Date` → Compares timestamps via `getTime()`
|
|
58
|
-
- `Array` → Lexicographic: compares element-by-element, then length
|
|
59
|
-
- `Map/Set` → Size and content comparison
|
|
60
|
-
- `Object` → Calls `compareTo()` if available, otherwise 0
|
|
61
|
-
- `null/undefined` → Treated as equal (returns 0)
|
|
62
|
-
## Return Values
|
|
63
|
-
The `compareTo()` method always returns:
|
|
64
|
-
- `-1` → `this` is less than `other`
|
|
65
|
-
- `0` → `this` equals `other`
|
|
66
|
-
- `1` → `this` is greater than `other`
|
|
67
|
-
Unlike `PartialOrd`, the `Ord` macro never returns `null` - it provides total ordering.
|
|
68
|
-
## Field Options
|
|
69
|
-
### @ord(skip)
|
|
70
|
-
Use `@ord(skip)` to exclude a field from ordering comparison:
|
|
71
|
-
**Before:**
|
|
72
|
-
```
|
|
73
|
-
/** @derive(Ord) */
|
|
74
|
-
class Task {
|
|
75
|
-
priority: number;
|
|
76
|
-
name: string;
|
|
16
|
+
## Configuration
|
|
77
17
|
|
|
78
|
-
|
|
79
|
-
|
|
18
|
+
The `functionNamingStyle` option in `macroforge.json` controls naming:
|
|
19
|
+
- `"suffix"` (default): Suffixes with type name (e.g., `compareMyType`)
|
|
20
|
+
- `"prefix"`: Prefixes with type name (e.g., `myTypeCompare`)
|
|
21
|
+
- `"generic"`: Uses TypeScript generics (e.g., `compare<T extends MyType>`)
|
|
22
|
+
- `"namespace"`: Legacy namespace wrapping
|
|
80
23
|
|
|
81
|
-
|
|
82
|
-
this.priority = priority;
|
|
83
|
-
this.name = name;
|
|
84
|
-
this.createdAt = createdAt;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
**After:**
|
|
89
|
-
```
|
|
90
|
-
class Task {
|
|
91
|
-
priority: number;
|
|
92
|
-
name: string;
|
|
24
|
+
## Return Values
|
|
93
25
|
|
|
94
|
-
|
|
26
|
+
Unlike `PartialOrd`, `Ord` provides **total ordering** - every pair of values
|
|
27
|
+
can be compared:
|
|
95
28
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.createdAt = createdAt;
|
|
100
|
-
}
|
|
29
|
+
- **-1**: `this` is less than `other`
|
|
30
|
+
- **0**: `this` is equal to `other`
|
|
31
|
+
- **1**: `this` is greater than `other`
|
|
101
32
|
|
|
102
|
-
|
|
103
|
-
if (this === other) return 0;
|
|
104
|
-
const typedOther = other;
|
|
105
|
-
const cmp0 =
|
|
106
|
-
this.priority < typedOther.priority ? -1 : this.priority > typedOther.priority ? 1 : 0;
|
|
107
|
-
if (cmp0 !== 0) return cmp0;
|
|
108
|
-
const cmp1 = ((cmp) => (cmp < 0 ? -1 : cmp > 0 ? 1 : 0))(
|
|
109
|
-
this.name.localeCompare(typedOther.name)
|
|
110
|
-
);
|
|
111
|
-
if (cmp1 !== 0) return cmp1;
|
|
112
|
-
return 0;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
``` ```
|
|
116
|
-
const t1 = new Task(1, "Bug fix", new Date("2024-01-01"));
|
|
117
|
-
const t2 = new Task(1, "Bug fix", new Date("2024-12-01"));
|
|
33
|
+
The method **never returns null** - all values must be comparable.
|
|
118
34
|
|
|
119
|
-
|
|
120
|
-
``` ## Sorting Arrays
|
|
121
|
-
The generated `compareTo()` method works directly with `Array.sort()`:
|
|
122
|
-
**Source:**
|
|
123
|
-
```
|
|
124
|
-
/** @derive(Ord) */
|
|
125
|
-
class Score {
|
|
126
|
-
points: number;
|
|
127
|
-
name: string;
|
|
35
|
+
## Comparison Strategy
|
|
128
36
|
|
|
129
|
-
|
|
130
|
-
this.points = points;
|
|
131
|
-
this.name = name;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
``` ```
|
|
135
|
-
const scores = [
|
|
136
|
-
new Score(100, "Alice"),
|
|
137
|
-
new Score(50, "Bob"),
|
|
138
|
-
new Score(150, "Charlie"),
|
|
139
|
-
new Score(50, "Alice") // Same points, different name
|
|
140
|
-
];
|
|
37
|
+
Fields are compared **lexicographically** in declaration order:
|
|
141
38
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
39
|
+
1. Compare first field
|
|
40
|
+
2. If not equal, return that result
|
|
41
|
+
3. Otherwise, compare next field
|
|
42
|
+
4. Continue until a difference is found or all fields are equal
|
|
145
43
|
|
|
146
|
-
|
|
147
|
-
scores.sort((a, b) => b.compareTo(a));
|
|
148
|
-
// Result: [Charlie(150), Alice(100), Alice(50), Bob(50)]
|
|
149
|
-
``` ## Interface Support
|
|
150
|
-
Ord works with interfaces. For interfaces, a namespace is generated with a `compareTo` function:
|
|
151
|
-
**Before:**
|
|
152
|
-
```
|
|
153
|
-
/** @derive(Ord) */
|
|
154
|
-
interface Point {
|
|
155
|
-
x: number;
|
|
156
|
-
y: number;
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
**After:**
|
|
160
|
-
```
|
|
161
|
-
interface Point {
|
|
162
|
-
x: number;
|
|
163
|
-
y: number;
|
|
164
|
-
}
|
|
44
|
+
## Type-Specific Comparisons
|
|
165
45
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
``` ```
|
|
177
|
-
const points: Point[] = [
|
|
178
|
-
{ x: 5, y: 10 },
|
|
179
|
-
{ x: 1, y: 20 },
|
|
180
|
-
{ x: 5, y: 5 }
|
|
181
|
-
];
|
|
46
|
+
| Type | Comparison Method |
|
|
47
|
+
|------|-------------------|
|
|
48
|
+
| `number`/`bigint` | Direct `<` and `>` comparison |
|
|
49
|
+
| `string` | `localeCompare()` (clamped to -1, 0, 1) |
|
|
50
|
+
| `boolean` | false < true |
|
|
51
|
+
| Arrays | Lexicographic element-by-element |
|
|
52
|
+
| `Date` | `getTime()` timestamp comparison |
|
|
53
|
+
| Objects | Calls `compareTo()` if available, else 0 |
|
|
182
54
|
|
|
183
|
-
|
|
184
|
-
// Result: [{ x: 1, y: 20 }, { x: 5, y: 5 }, { x: 5, y: 10 }]
|
|
185
|
-
``` ## Enum Support
|
|
186
|
-
Ord works with enums. For numeric enums, it compares the numeric values; for string enums, it uses string comparison:
|
|
187
|
-
**Before:**
|
|
188
|
-
```
|
|
189
|
-
/** @derive(Ord) */
|
|
190
|
-
enum Priority {
|
|
191
|
-
Low = 0,
|
|
192
|
-
Medium = 1,
|
|
193
|
-
High = 2,
|
|
194
|
-
Critical = 3
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
**After:**
|
|
198
|
-
```
|
|
199
|
-
enum Priority {
|
|
200
|
-
Low = 0,
|
|
201
|
-
Medium = 1,
|
|
202
|
-
High = 2,
|
|
203
|
-
Critical = 3
|
|
204
|
-
}
|
|
55
|
+
## Field-Level Options
|
|
205
56
|
|
|
206
|
-
|
|
207
|
-
export function compareTo(a: Priority, b: Priority): number {
|
|
208
|
-
if (typeof a === 'number' && typeof b === 'number') {
|
|
209
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
210
|
-
}
|
|
211
|
-
if (typeof a === 'string' && typeof b === 'string') {
|
|
212
|
-
const cmp = a.localeCompare(b);
|
|
213
|
-
return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
|
|
214
|
-
}
|
|
215
|
-
return 0;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
``` ```
|
|
219
|
-
console.log(Priority.compareTo(Priority.Low, Priority.High)); // -1
|
|
220
|
-
console.log(Priority.compareTo(Priority.Critical, Priority.Low)); // 1
|
|
221
|
-
console.log(Priority.compareTo(Priority.Medium, Priority.Medium)); // 0
|
|
222
|
-
``` ## Type Alias Support
|
|
223
|
-
Ord works with type aliases. For object types, it uses lexicographic field comparison:
|
|
224
|
-
**Before:**
|
|
225
|
-
```
|
|
226
|
-
/** @derive(Ord) */
|
|
227
|
-
type Coordinate = {
|
|
228
|
-
x: number;
|
|
229
|
-
y: number;
|
|
230
|
-
};
|
|
231
|
-
```
|
|
232
|
-
**After:**
|
|
233
|
-
```
|
|
234
|
-
type Coordinate = {
|
|
235
|
-
x: number;
|
|
236
|
-
y: number;
|
|
237
|
-
};
|
|
57
|
+
The `@ord` decorator supports:
|
|
238
58
|
|
|
239
|
-
|
|
240
|
-
export function compareTo(a: Coordinate, b: Coordinate): number {
|
|
241
|
-
if (a === b) return 0;
|
|
242
|
-
const cmp0 = a.x < b.x ? -1 : a.x > b.x ? 1 : 0;
|
|
243
|
-
if (cmp0 !== 0) return cmp0;
|
|
244
|
-
const cmp1 = a.y < b.y ? -1 : a.y > b.y ? 1 : 0;
|
|
245
|
-
if (cmp1 !== 0) return cmp1;
|
|
246
|
-
return 0;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
``` ```
|
|
250
|
-
const c1: Coordinate = { x: 10, y: 20 };
|
|
251
|
-
const c2: Coordinate = { x: 10, y: 30 };
|
|
59
|
+
- `skip` - Exclude the field from ordering comparison
|
|
252
60
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
-
// Ord: Total ordering - never returns null
|
|
259
|
-
/** @derive(Ord) */
|
|
61
|
+
## Example
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
@derive(Ord)
|
|
260
65
|
class Version {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this.major = major;
|
|
265
|
-
this.minor = minor;
|
|
266
|
-
}
|
|
66
|
+
major: number;
|
|
67
|
+
minor: number;
|
|
68
|
+
patch: number;
|
|
267
69
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
70
|
+
|
|
71
|
+
// Generated:
|
|
72
|
+
// compareTo(other: Version): number {
|
|
73
|
+
// if (this === other) return 0;
|
|
74
|
+
// const typedOther = other;
|
|
75
|
+
// const cmp0 = this.major < typedOther.major ? -1 : this.major > typedOther.major ? 1 : 0;
|
|
76
|
+
// if (cmp0 !== 0) return cmp0;
|
|
77
|
+
// const cmp1 = this.minor < typedOther.minor ? -1 : ...;
|
|
78
|
+
// if (cmp1 !== 0) return cmp1;
|
|
79
|
+
// const cmp2 = this.patch < typedOther.patch ? -1 : ...;
|
|
80
|
+
// if (cmp2 !== 0) return cmp2;
|
|
81
|
+
// return 0;
|
|
82
|
+
// }
|
|
83
|
+
|
|
84
|
+
// Usage:
|
|
85
|
+
versions.sort((a, b) => a.compareTo(b));
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Ord vs PartialOrd
|
|
89
|
+
|
|
90
|
+
- Use **Ord** when all values are comparable (total ordering)
|
|
91
|
+
- Use **PartialOrd** when some values may be incomparable (returns `Option<number>`)
|