@macroforge/mcp-server 0.1.17
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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/docs-loader.d.ts +30 -0
- package/dist/tools/docs-loader.d.ts.map +1 -0
- package/dist/tools/docs-loader.js +112 -0
- package/dist/tools/docs-loader.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +348 -0
- package/dist/tools/index.js.map +1 -0
- package/docs/api/api-overview.md +75 -0
- package/docs/api/expand-sync.md +121 -0
- package/docs/api/native-plugin.md +106 -0
- package/docs/api/position-mapper.md +127 -0
- package/docs/api/transform-sync.md +98 -0
- package/docs/builtin-macros/clone.md +180 -0
- package/docs/builtin-macros/debug.md +222 -0
- package/docs/builtin-macros/default.md +192 -0
- package/docs/builtin-macros/deserialize.md +662 -0
- package/docs/builtin-macros/hash.md +205 -0
- package/docs/builtin-macros/macros-overview.md +169 -0
- package/docs/builtin-macros/ord.md +258 -0
- package/docs/builtin-macros/partial-eq.md +306 -0
- package/docs/builtin-macros/partial-ord.md +268 -0
- package/docs/builtin-macros/serialize.md +321 -0
- package/docs/concepts/architecture.md +139 -0
- package/docs/concepts/derive-system.md +173 -0
- package/docs/concepts/how-macros-work.md +124 -0
- package/docs/custom-macros/custom-overview.md +84 -0
- package/docs/custom-macros/rust-setup.md +146 -0
- package/docs/custom-macros/ts-macro-derive.md +307 -0
- package/docs/custom-macros/ts-quote.md +696 -0
- package/docs/getting-started/first-macro.md +120 -0
- package/docs/getting-started/installation.md +110 -0
- package/docs/integration/cli.md +207 -0
- package/docs/integration/configuration.md +116 -0
- package/docs/integration/integration-overview.md +51 -0
- package/docs/integration/typescript-plugin.md +96 -0
- package/docs/integration/vite-plugin.md +126 -0
- package/docs/language-servers/ls-overview.md +47 -0
- package/docs/language-servers/svelte-ls.md +80 -0
- package/docs/language-servers/zed-extensions.md +84 -0
- package/docs/sections.json +258 -0
- package/package.json +48 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Hash
|
|
2
|
+
|
|
3
|
+
*The `Hash` macro generates a `hashCode()` method that computes a numeric hash value based on field values.*
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
/** @derive(Hash) */
|
|
9
|
+
class Point {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
|
|
13
|
+
constructor(x: number, y: number) {
|
|
14
|
+
this.x = x;
|
|
15
|
+
this.y = y;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const p1 = new Point(10, 20);
|
|
20
|
+
const p2 = new Point(10, 20);
|
|
21
|
+
const p3 = new Point(5, 5);
|
|
22
|
+
|
|
23
|
+
console.log(p1.hashCode()); // Same hash
|
|
24
|
+
console.log(p2.hashCode()); // Same hash (equal values = equal hash)
|
|
25
|
+
console.log(p3.hashCode()); // Different hash
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Generated Code
|
|
29
|
+
|
|
30
|
+
The Hash macro generates a method that combines field hashes using the FNV-1a style algorithm:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
hashCode(): number {
|
|
34
|
+
let hash = 17;
|
|
35
|
+
hash = (hash * 31 + (Number.isInteger(this.x) ? this.x | 0 : this.x.toString().split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) | 0;
|
|
36
|
+
hash = (hash * 31 + (Number.isInteger(this.y) ? this.y | 0 : this.y.toString().split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) | 0;
|
|
37
|
+
return hash;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Hash Algorithm
|
|
42
|
+
|
|
43
|
+
The generated hash function uses the following algorithm for different types:
|
|
44
|
+
|
|
45
|
+
- `number` → Integers use bitwise OR (`| 0`), floats are stringified and hashed
|
|
46
|
+
|
|
47
|
+
- `string` → Character-by-character hash: `(h * 31 + charCode) | 0`
|
|
48
|
+
|
|
49
|
+
- `boolean` → `1231` for true, `1237` for false (Java convention)
|
|
50
|
+
|
|
51
|
+
- `bigint` → Converted to string and hashed character-by-character
|
|
52
|
+
|
|
53
|
+
- `Date` → Uses `getTime() | 0` for timestamp hash
|
|
54
|
+
|
|
55
|
+
- `Array` → Combines element hashes with `h * 31 + elementHash`
|
|
56
|
+
|
|
57
|
+
- `Map/Set` → Combines all entry hashes
|
|
58
|
+
|
|
59
|
+
- `Object` → Calls `hashCode()` if available, otherwise JSON stringifies and hashes
|
|
60
|
+
|
|
61
|
+
- `null` → Returns 0
|
|
62
|
+
|
|
63
|
+
- `undefined` → Returns 1
|
|
64
|
+
|
|
65
|
+
## Field Options
|
|
66
|
+
|
|
67
|
+
### @hash(skip)
|
|
68
|
+
|
|
69
|
+
Use `@hash(skip)` to exclude a field from hash computation:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
/** @derive(Hash) */
|
|
73
|
+
class User {
|
|
74
|
+
id: number;
|
|
75
|
+
name: string;
|
|
76
|
+
|
|
77
|
+
/** @hash(skip) */
|
|
78
|
+
lastLogin: Date; // Not included in hash
|
|
79
|
+
|
|
80
|
+
constructor(id: number, name: string, lastLogin: Date) {
|
|
81
|
+
this.id = id;
|
|
82
|
+
this.name = name;
|
|
83
|
+
this.lastLogin = lastLogin;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const user1 = new User(1, "Alice", new Date("2024-01-01"));
|
|
88
|
+
const user2 = new User(1, "Alice", new Date("2024-12-01"));
|
|
89
|
+
|
|
90
|
+
console.log(user1.hashCode() === user2.hashCode()); // true (lastLogin is skipped)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Use with PartialEq
|
|
94
|
+
|
|
95
|
+
Hash is often used together with PartialEq. Objects that are equal should have the same hash code:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
/** @derive(Hash, PartialEq) */
|
|
99
|
+
class Product {
|
|
100
|
+
sku: string;
|
|
101
|
+
name: string;
|
|
102
|
+
|
|
103
|
+
constructor(sku: string, name: string) {
|
|
104
|
+
this.sku = sku;
|
|
105
|
+
this.name = name;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const p1 = new Product("ABC123", "Widget");
|
|
110
|
+
const p2 = new Product("ABC123", "Widget");
|
|
111
|
+
|
|
112
|
+
// Equal objects have equal hash codes
|
|
113
|
+
console.log(p1.equals(p2)); // true
|
|
114
|
+
console.log(p1.hashCode() === p2.hashCode()); // true
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Interface Support
|
|
118
|
+
|
|
119
|
+
Hash also works with interfaces. For interfaces, a namespace is generated with a `hashCode` function:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
/** @derive(Hash) */
|
|
123
|
+
interface Point {
|
|
124
|
+
x: number;
|
|
125
|
+
y: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Generated:
|
|
129
|
+
// export namespace Point {
|
|
130
|
+
// export function hashCode(self: Point): number {
|
|
131
|
+
// let hash = 17;
|
|
132
|
+
// hash = (hash * 31 + (self.x | 0)) | 0;
|
|
133
|
+
// hash = (hash * 31 + (self.y | 0)) | 0;
|
|
134
|
+
// return hash;
|
|
135
|
+
// }
|
|
136
|
+
// }
|
|
137
|
+
|
|
138
|
+
const p: Point = { x: 10, y: 20 };
|
|
139
|
+
console.log(Point.hashCode(p)); // numeric hash value
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Enum Support
|
|
143
|
+
|
|
144
|
+
Hash works with enums. For string enums, it hashes the string value; for numeric enums, it uses the numeric value directly:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
/** @derive(Hash) */
|
|
148
|
+
enum Status {
|
|
149
|
+
Active = "active",
|
|
150
|
+
Inactive = "inactive",
|
|
151
|
+
Pending = "pending",
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Generated:
|
|
155
|
+
// export namespace Status {
|
|
156
|
+
// export function hashCode(value: Status): number {
|
|
157
|
+
// if (typeof value === "string") {
|
|
158
|
+
// let hash = 0;
|
|
159
|
+
// for (let i = 0; i < value.length; i++) {
|
|
160
|
+
// hash = (hash * 31 + value.charCodeAt(i)) | 0;
|
|
161
|
+
// }
|
|
162
|
+
// return hash;
|
|
163
|
+
// }
|
|
164
|
+
// return value | 0;
|
|
165
|
+
// }
|
|
166
|
+
// }
|
|
167
|
+
|
|
168
|
+
console.log(Status.hashCode(Status.Active)); // consistent hash
|
|
169
|
+
console.log(Status.hashCode(Status.Inactive)); // different hash
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Type Alias Support
|
|
173
|
+
|
|
174
|
+
Hash works with type aliases. For object types, it hashes each field:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
/** @derive(Hash) */
|
|
178
|
+
type Coordinates = {
|
|
179
|
+
lat: number;
|
|
180
|
+
lng: number;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Generated:
|
|
184
|
+
// export namespace Coordinates {
|
|
185
|
+
// export function hashCode(value: Coordinates): number {
|
|
186
|
+
// let hash = 17;
|
|
187
|
+
// hash = (hash * 31 + (value.lat | 0)) | 0;
|
|
188
|
+
// hash = (hash * 31 + (value.lng | 0)) | 0;
|
|
189
|
+
// return hash;
|
|
190
|
+
// }
|
|
191
|
+
// }
|
|
192
|
+
|
|
193
|
+
const loc: Coordinates = { lat: 40.7128, lng: -74.0060 };
|
|
194
|
+
console.log(Coordinates.hashCode(loc));
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
For union types, it uses JSON stringification as a fallback:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
/** @derive(Hash) */
|
|
201
|
+
type Result = "success" | "error" | "pending";
|
|
202
|
+
|
|
203
|
+
console.log(Result.hashCode("success")); // hash of "success" string
|
|
204
|
+
console.log(Result.hashCode("error")); // hash of "error" string
|
|
205
|
+
```
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Built-in Macros
|
|
2
|
+
|
|
3
|
+
*Macroforge comes with five built-in derive macros that cover the most common code generation needs. All macros work with classes, interfaces, enums, and type aliases.*
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
| `Debug`
|
|
8
|
+
| `toString(): string`
|
|
9
|
+
| Human-readable string representation
|
|
10
|
+
|
|
11
|
+
| `Clone`
|
|
12
|
+
| `clone(): T`
|
|
13
|
+
| Creates a copy of the object
|
|
14
|
+
|
|
15
|
+
| `PartialEq`
|
|
16
|
+
| `equals(other: T): boolean`
|
|
17
|
+
| Value equality comparison
|
|
18
|
+
|
|
19
|
+
| `Serialize`
|
|
20
|
+
| `toJSON(): Record<string, unknown>`
|
|
21
|
+
| JSON serialization with type handling
|
|
22
|
+
|
|
23
|
+
| `Deserialize`
|
|
24
|
+
| `fromJSON(data: unknown): T`
|
|
25
|
+
| JSON deserialization with validation
|
|
26
|
+
|
|
27
|
+
## Using Built-in Macros
|
|
28
|
+
|
|
29
|
+
Built-in macros don't require imports. Just use them with `@derive`:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
/** @derive(Debug, Clone, PartialEq) */
|
|
33
|
+
class User {
|
|
34
|
+
name: string;
|
|
35
|
+
age: number;
|
|
36
|
+
|
|
37
|
+
constructor(name: string, age: number) {
|
|
38
|
+
this.name = name;
|
|
39
|
+
this.age = age;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Interface Support
|
|
45
|
+
|
|
46
|
+
All built-in macros work with interfaces. For interfaces, methods are generated as functions in a namespace with the same name, using `self` as the first parameter:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
/** @derive(Debug, Clone, PartialEq) */
|
|
50
|
+
interface Point {
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Generated namespace:
|
|
56
|
+
// namespace Point {
|
|
57
|
+
// export function toString(self: Point): string { ... }
|
|
58
|
+
// export function clone(self: Point): Point { ... }
|
|
59
|
+
// export function equals(self: Point, other: Point): boolean { ... }
|
|
60
|
+
// export function hashCode(self: Point): number { ... }
|
|
61
|
+
// }
|
|
62
|
+
|
|
63
|
+
const point: Point = { x: 10, y: 20 };
|
|
64
|
+
|
|
65
|
+
// Use the namespace functions
|
|
66
|
+
console.log(Point.toString(point)); // "Point { x: 10, y: 20 }"
|
|
67
|
+
const copy = Point.clone(point); // { x: 10, y: 20 }
|
|
68
|
+
console.log(Point.equals(point, copy)); // true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Enum Support
|
|
72
|
+
|
|
73
|
+
All built-in macros work with enums. For enums, methods are generated as functions in a namespace with the same name:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
/** @derive(Debug, Clone, PartialEq, Serialize, Deserialize) */
|
|
77
|
+
enum Status {
|
|
78
|
+
Active = "active",
|
|
79
|
+
Inactive = "inactive",
|
|
80
|
+
Pending = "pending",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Generated namespace:
|
|
84
|
+
// namespace Status {
|
|
85
|
+
// export function toString(value: Status): string { ... }
|
|
86
|
+
// export function clone(value: Status): Status { ... }
|
|
87
|
+
// export function equals(a: Status, b: Status): boolean { ... }
|
|
88
|
+
// export function hashCode(value: Status): number { ... }
|
|
89
|
+
// export function toJSON(value: Status): string | number { ... }
|
|
90
|
+
// export function fromJSON(data: unknown): Status { ... }
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
// Use the namespace functions
|
|
94
|
+
console.log(Status.toString(Status.Active)); // "Status.Active"
|
|
95
|
+
console.log(Status.equals(Status.Active, Status.Active)); // true
|
|
96
|
+
const json = Status.toJSON(Status.Pending); // "pending"
|
|
97
|
+
const parsed = Status.fromJSON("active"); // Status.Active
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Type Alias Support
|
|
101
|
+
|
|
102
|
+
All built-in macros work with type aliases. For object type aliases, field-aware methods are generated in a namespace:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
/** @derive(Debug, Clone, PartialEq, Serialize, Deserialize) */
|
|
106
|
+
type Point = {
|
|
107
|
+
x: number;
|
|
108
|
+
y: number;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Generated namespace:
|
|
112
|
+
// namespace Point {
|
|
113
|
+
// export function toString(value: Point): string { ... }
|
|
114
|
+
// export function clone(value: Point): Point { ... }
|
|
115
|
+
// export function equals(a: Point, b: Point): boolean { ... }
|
|
116
|
+
// export function hashCode(value: Point): number { ... }
|
|
117
|
+
// export function toJSON(value: Point): Record<string, unknown> { ... }
|
|
118
|
+
// export function fromJSON(data: unknown): Point { ... }
|
|
119
|
+
// }
|
|
120
|
+
|
|
121
|
+
const point: Point = { x: 10, y: 20 };
|
|
122
|
+
console.log(Point.toString(point)); // "Point { x: 10, y: 20 }"
|
|
123
|
+
const copy = Point.clone(point); // { x: 10, y: 20 }
|
|
124
|
+
console.log(Point.equals(point, copy)); // true
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Union type aliases also work, using JSON-based implementations:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
/** @derive(Debug, PartialEq) */
|
|
131
|
+
type ApiStatus = "loading" | "success" | "error";
|
|
132
|
+
|
|
133
|
+
const status: ApiStatus = "success";
|
|
134
|
+
console.log(ApiStatus.toString(status)); // "ApiStatus(\\"success\\")"
|
|
135
|
+
console.log(ApiStatus.equals("success", "success")); // true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Combining Macros
|
|
139
|
+
|
|
140
|
+
All macros can be used together. They don't conflict and each generates independent methods:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const user = new User("Alice", 30);
|
|
144
|
+
|
|
145
|
+
// Debug
|
|
146
|
+
console.log(user.toString());
|
|
147
|
+
// "User { name: Alice, age: 30 }"
|
|
148
|
+
|
|
149
|
+
// Clone
|
|
150
|
+
const copy = user.clone();
|
|
151
|
+
console.log(copy.name); // "Alice"
|
|
152
|
+
|
|
153
|
+
// Eq
|
|
154
|
+
console.log(user.equals(copy)); // true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Detailed Documentation
|
|
158
|
+
|
|
159
|
+
Each macro has its own options and behaviors:
|
|
160
|
+
|
|
161
|
+
- [**Debug**]({base}/docs/builtin-macros/debug) - Customizable field renaming and skipping
|
|
162
|
+
|
|
163
|
+
- [**Clone**]({base}/docs/builtin-macros/clone) - Shallow copying for all field types
|
|
164
|
+
|
|
165
|
+
- [**PartialEq**]({base}/docs/builtin-macros/partial-eq) - Value-based equality comparison
|
|
166
|
+
|
|
167
|
+
- [**Serialize**]({base}/docs/builtin-macros/serialize) - JSON serialization with serde-style options
|
|
168
|
+
|
|
169
|
+
- [**Deserialize**]({base}/docs/builtin-macros/deserialize) - JSON deserialization with validation
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Ord
|
|
2
|
+
|
|
3
|
+
*The `Ord` macro generates a `compareTo()` method that implements total ordering, always returning `-1`, `0`, or `1`.*
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
/** @derive(Ord) */
|
|
9
|
+
class Version {
|
|
10
|
+
major: number;
|
|
11
|
+
minor: number;
|
|
12
|
+
patch: number;
|
|
13
|
+
|
|
14
|
+
constructor(major: number, minor: number, patch: number) {
|
|
15
|
+
this.major = major;
|
|
16
|
+
this.minor = minor;
|
|
17
|
+
this.patch = patch;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const v1 = new Version(1, 0, 0);
|
|
22
|
+
const v2 = new Version(1, 2, 0);
|
|
23
|
+
const v3 = new Version(1, 2, 0);
|
|
24
|
+
|
|
25
|
+
console.log(v1.compareTo(v2)); // -1 (v1 < v2)
|
|
26
|
+
console.log(v2.compareTo(v1)); // 1 (v2 > v1)
|
|
27
|
+
console.log(v2.compareTo(v3)); // 0 (v2 == v3)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Generated Code
|
|
31
|
+
|
|
32
|
+
The Ord macro generates a compareTo method using lexicographic field comparison:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
compareTo(other: Version): number {
|
|
36
|
+
if (this === other) return 0;
|
|
37
|
+
const typedOther = other;
|
|
38
|
+
const cmp0 = (this.major < typedOther.major ? -1 : this.major > typedOther.major ? 1 : 0);
|
|
39
|
+
if (cmp0 !== 0) return cmp0;
|
|
40
|
+
const cmp1 = (this.minor < typedOther.minor ? -1 : this.minor > typedOther.minor ? 1 : 0);
|
|
41
|
+
if (cmp1 !== 0) return cmp1;
|
|
42
|
+
const cmp2 = (this.patch < typedOther.patch ? -1 : this.patch > typedOther.patch ? 1 : 0);
|
|
43
|
+
if (cmp2 !== 0) return cmp2;
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Comparison Logic
|
|
49
|
+
|
|
50
|
+
The Ord macro compares fields in declaration order (lexicographic ordering). For each type:
|
|
51
|
+
|
|
52
|
+
- `number` / `bigint` → Direct numeric comparison
|
|
53
|
+
|
|
54
|
+
- `string` → Uses `localeCompare()` clamped to -1/0/1
|
|
55
|
+
|
|
56
|
+
- `boolean` → `false < true`
|
|
57
|
+
|
|
58
|
+
- `Date` → Compares timestamps via `getTime()`
|
|
59
|
+
|
|
60
|
+
- `Array` → Lexicographic: compares element-by-element, then length
|
|
61
|
+
|
|
62
|
+
- `Map/Set` → Size and content comparison
|
|
63
|
+
|
|
64
|
+
- `Object` → Calls `compareTo()` if available, otherwise 0
|
|
65
|
+
|
|
66
|
+
- `null/undefined` → Treated as equal (returns 0)
|
|
67
|
+
|
|
68
|
+
## Return Values
|
|
69
|
+
|
|
70
|
+
The `compareTo()` method always returns:
|
|
71
|
+
|
|
72
|
+
- `-1` → `this` is less than `other`
|
|
73
|
+
|
|
74
|
+
- `0` → `this` equals `other`
|
|
75
|
+
|
|
76
|
+
- `1` → `this` is greater than `other`
|
|
77
|
+
|
|
78
|
+
Unlike `PartialOrd`, the `Ord` macro never returns `null` - it provides total ordering.
|
|
79
|
+
|
|
80
|
+
## Field Options
|
|
81
|
+
|
|
82
|
+
### @ord(skip)
|
|
83
|
+
|
|
84
|
+
Use `@ord(skip)` to exclude a field from ordering comparison:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
/** @derive(Ord) */
|
|
88
|
+
class Task {
|
|
89
|
+
priority: number;
|
|
90
|
+
name: string;
|
|
91
|
+
|
|
92
|
+
/** @ord(skip) */
|
|
93
|
+
createdAt: Date; // Not used for ordering
|
|
94
|
+
|
|
95
|
+
constructor(priority: number, name: string, createdAt: Date) {
|
|
96
|
+
this.priority = priority;
|
|
97
|
+
this.name = name;
|
|
98
|
+
this.createdAt = createdAt;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const t1 = new Task(1, "Bug fix", new Date("2024-01-01"));
|
|
103
|
+
const t2 = new Task(1, "Bug fix", new Date("2024-12-01"));
|
|
104
|
+
|
|
105
|
+
console.log(t1.compareTo(t2)); // 0 (createdAt is skipped)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Sorting Arrays
|
|
109
|
+
|
|
110
|
+
The generated `compareTo()` method works directly with `Array.sort()`:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
/** @derive(Ord) */
|
|
114
|
+
class Score {
|
|
115
|
+
points: number;
|
|
116
|
+
name: string;
|
|
117
|
+
|
|
118
|
+
constructor(points: number, name: string) {
|
|
119
|
+
this.points = points;
|
|
120
|
+
this.name = name;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const scores = [
|
|
125
|
+
new Score(100, "Alice"),
|
|
126
|
+
new Score(50, "Bob"),
|
|
127
|
+
new Score(150, "Charlie"),
|
|
128
|
+
new Score(50, "Alice") // Same points, different name
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
// Sort ascending
|
|
132
|
+
scores.sort((a, b) => a.compareTo(b));
|
|
133
|
+
// Result: [Bob(50), Alice(50), Alice(100), Charlie(150)]
|
|
134
|
+
|
|
135
|
+
// Sort descending
|
|
136
|
+
scores.sort((a, b) => b.compareTo(a));
|
|
137
|
+
// Result: [Charlie(150), Alice(100), Alice(50), Bob(50)]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Interface Support
|
|
141
|
+
|
|
142
|
+
Ord works with interfaces. For interfaces, a namespace is generated with a `compareTo` function:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
/** @derive(Ord) */
|
|
146
|
+
interface Point {
|
|
147
|
+
x: number;
|
|
148
|
+
y: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Generated:
|
|
152
|
+
// export namespace Point {
|
|
153
|
+
// export function compareTo(self: Point, other: Point): number {
|
|
154
|
+
// if (self === other) return 0;
|
|
155
|
+
// const cmp0 = (self.x < other.x ? -1 : self.x > other.x ? 1 : 0);
|
|
156
|
+
// if (cmp0 !== 0) return cmp0;
|
|
157
|
+
// const cmp1 = (self.y < other.y ? -1 : self.y > other.y ? 1 : 0);
|
|
158
|
+
// if (cmp1 !== 0) return cmp1;
|
|
159
|
+
// return 0;
|
|
160
|
+
// }
|
|
161
|
+
// }
|
|
162
|
+
|
|
163
|
+
const points: Point[] = [
|
|
164
|
+
{ x: 5, y: 10 },
|
|
165
|
+
{ x: 1, y: 20 },
|
|
166
|
+
{ x: 5, y: 5 }
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
points.sort((a, b) => Point.compareTo(a, b));
|
|
170
|
+
// Result: [{ x: 1, y: 20 }, { x: 5, y: 5 }, { x: 5, y: 10 }]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Enum Support
|
|
174
|
+
|
|
175
|
+
Ord works with enums. For numeric enums, it compares the numeric values; for string enums, it uses string comparison:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
/** @derive(Ord) */
|
|
179
|
+
enum Priority {
|
|
180
|
+
Low = 0,
|
|
181
|
+
Medium = 1,
|
|
182
|
+
High = 2,
|
|
183
|
+
Critical = 3
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Generated:
|
|
187
|
+
// export namespace Priority {
|
|
188
|
+
// export function compareTo(a: Priority, b: Priority): number {
|
|
189
|
+
// return a < b ? -1 : a > b ? 1 : 0;
|
|
190
|
+
// }
|
|
191
|
+
// }
|
|
192
|
+
|
|
193
|
+
console.log(Priority.compareTo(Priority.Low, Priority.High)); // -1
|
|
194
|
+
console.log(Priority.compareTo(Priority.Critical, Priority.Low)); // 1
|
|
195
|
+
console.log(Priority.compareTo(Priority.Medium, Priority.Medium)); // 0
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Type Alias Support
|
|
199
|
+
|
|
200
|
+
Ord works with type aliases. For object types, it uses lexicographic field comparison:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
/** @derive(Ord) */
|
|
204
|
+
type Coordinate = {
|
|
205
|
+
x: number;
|
|
206
|
+
y: number;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Generated:
|
|
210
|
+
// export namespace Coordinate {
|
|
211
|
+
// export function compareTo(a: Coordinate, b: Coordinate): number {
|
|
212
|
+
// if (a === b) return 0;
|
|
213
|
+
// const cmp0 = (a.x < b.x ? -1 : a.x > b.x ? 1 : 0);
|
|
214
|
+
// if (cmp0 !== 0) return cmp0;
|
|
215
|
+
// const cmp1 = (a.y < b.y ? -1 : a.y > b.y ? 1 : 0);
|
|
216
|
+
// if (cmp1 !== 0) return cmp1;
|
|
217
|
+
// return 0;
|
|
218
|
+
// }
|
|
219
|
+
// }
|
|
220
|
+
|
|
221
|
+
const c1: Coordinate = { x: 10, y: 20 };
|
|
222
|
+
const c2: Coordinate = { x: 10, y: 30 };
|
|
223
|
+
|
|
224
|
+
console.log(Coordinate.compareTo(c1, c2)); // -1 (c1 < c2)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Ord vs PartialOrd
|
|
228
|
+
|
|
229
|
+
Use `Ord` when all values of a type are comparable. Use `PartialOrd` when some values might be incomparable (e.g., different types at runtime).
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Ord: Total ordering - never returns null
|
|
233
|
+
/** @derive(Ord) */
|
|
234
|
+
class Version {
|
|
235
|
+
major: number;
|
|
236
|
+
minor: number;
|
|
237
|
+
constructor(major: number, minor: number) {
|
|
238
|
+
this.major = major;
|
|
239
|
+
this.minor = minor;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const v1 = new Version(1, 0);
|
|
244
|
+
const v2 = new Version(2, 0);
|
|
245
|
+
console.log(v1.compareTo(v2)); // Always -1, 0, or 1
|
|
246
|
+
|
|
247
|
+
// PartialOrd: Partial ordering - can return null
|
|
248
|
+
/** @derive(PartialOrd) */
|
|
249
|
+
class Value {
|
|
250
|
+
data: number;
|
|
251
|
+
constructor(data: number) {
|
|
252
|
+
this.data = data;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const val = new Value(10);
|
|
257
|
+
console.log(val.compareTo("not a Value")); // null (incomparable)
|
|
258
|
+
```
|