@macroforge/mcp-server 0.1.37 → 0.1.39

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.
Files changed (43) hide show
  1. package/docs/api/api-overview.md +13 -13
  2. package/docs/api/expand-sync.md +8 -8
  3. package/docs/api/native-plugin.md +15 -15
  4. package/docs/api/position-mapper.md +6 -6
  5. package/docs/api/transform-sync.md +11 -11
  6. package/docs/builtin-macros/clone.md +43 -23
  7. package/docs/builtin-macros/debug.md +50 -18
  8. package/docs/builtin-macros/default.md +79 -28
  9. package/docs/builtin-macros/deserialize/cycleforward-reference-support.md +11 -0
  10. package/docs/builtin-macros/deserialize/example.md +1625 -0
  11. package/docs/builtin-macros/deserialize/overview.md +15 -10
  12. package/docs/builtin-macros/deserialize/union-type-deserialization.md +27 -0
  13. package/docs/builtin-macros/deserialize/validation.md +34 -0
  14. package/docs/builtin-macros/deserialize.md +1608 -23
  15. package/docs/builtin-macros/hash.md +87 -20
  16. package/docs/builtin-macros/macros-overview.md +40 -40
  17. package/docs/builtin-macros/ord.md +56 -31
  18. package/docs/builtin-macros/partial-eq/example.md +526 -0
  19. package/docs/builtin-macros/partial-eq/overview.md +39 -0
  20. package/docs/builtin-macros/partial-eq.md +184 -26
  21. package/docs/builtin-macros/partial-ord.md +68 -30
  22. package/docs/builtin-macros/serialize/example.md +139 -0
  23. package/docs/builtin-macros/serialize/overview.md +32 -0
  24. package/docs/builtin-macros/serialize/type-specific-serialization.md +22 -0
  25. package/docs/builtin-macros/serialize.md +130 -28
  26. package/docs/concepts/architecture.md +2 -2
  27. package/docs/concepts/derive-system.md +25 -39
  28. package/docs/concepts/how-macros-work.md +8 -4
  29. package/docs/custom-macros/custom-overview.md +23 -23
  30. package/docs/custom-macros/rust-setup.md +31 -31
  31. package/docs/custom-macros/ts-macro-derive.md +107 -107
  32. package/docs/custom-macros/ts-quote.md +226 -226
  33. package/docs/getting-started/first-macro.md +38 -28
  34. package/docs/getting-started/installation.md +15 -15
  35. package/docs/integration/cli.md +9 -9
  36. package/docs/integration/configuration.md +16 -16
  37. package/docs/integration/mcp-server.md +6 -6
  38. package/docs/integration/svelte-preprocessor.md +40 -41
  39. package/docs/integration/typescript-plugin.md +13 -12
  40. package/docs/integration/vite-plugin.md +12 -12
  41. package/docs/language-servers/zed.md +1 -1
  42. package/docs/sections.json +88 -2
  43. package/package.json +2 -2
@@ -8,26 +8,17 @@ enabling value-based equality semantics instead of reference equality.
8
8
 
9
9
  | Type | Generated Code | Description |
10
10
  |------|----------------|-------------|
11
- | Class | `equals(other: unknown): boolean` | Instance method with instanceof check |
12
- | Enum | `equalsEnumName(a: EnumName, b: EnumName): boolean` | Standalone function using strict equality |
13
- | Interface | `equalsInterfaceName(a: InterfaceName, b: InterfaceName): boolean` | Standalone function comparing fields |
14
- | Type Alias | `equalsTypeName(a: TypeName, b: TypeName): boolean` | Standalone function with type-appropriate comparison |
15
-
16
- ## Configuration
17
-
18
- The `functionNamingStyle` option in `macroforge.json` controls naming:
19
- - `"prefix"` (default): Prefixes with type name (e.g., `myTypeEquals`)
20
- - `"suffix"`: Suffixes with type name (e.g., `equalsMyType`)
21
- - `"generic"`: Uses TypeScript generics (e.g., `equals<T extends MyType>`)
22
- - `"namespace"`: Legacy namespace wrapping
11
+ | Class | `classNameEquals(a, b)` + `static equals(a, b)` | Standalone function + static wrapper method |
12
+ | Enum | `enumNameEquals(a: EnumName, b: EnumName): boolean` | Standalone function using strict equality |
13
+ | Interface | `interfaceNameEquals(a: InterfaceName, b: InterfaceName): boolean` | Standalone function comparing fields |
14
+ | Type Alias | `typeNameEquals(a: TypeName, b: TypeName): boolean` | Standalone function with type-appropriate comparison |
23
15
 
24
16
  ## Comparison Strategy
25
17
 
26
18
  The generated equality check:
27
19
 
28
- 1. **Identity check**: `this === other` returns true immediately
29
- 2. **Type check**: For classes, uses `instanceof`; returns false if wrong type
30
- 3. **Field comparison**: Compares each non-skipped field
20
+ 1. **Identity check**: `a === b` returns true immediately
21
+ 2. **Field comparison**: Compares each non-skipped field
31
22
 
32
23
  ## Type-Specific Comparisons
33
24
 
@@ -48,25 +39,97 @@ The `@partialEq` decorator supports:
48
39
 
49
40
  ## Example
50
41
 
42
+ ```typescript before
43
+ /** @derive(PartialEq, Hash) */
44
+ class User {
45
+ id: number;
46
+ name: string;
47
+
48
+ /** @partialEq({ skip: true }) @hash({ skip: true }) */
49
+ cachedScore: number;
50
+ }
51
+ ```
52
+
53
+ ```typescript after
54
+ class User {
55
+ id: number;
56
+ name: string;
57
+
58
+ cachedScore: number;
59
+
60
+ static equals(a: User, b: User): boolean {
61
+ return userEquals(a, b);
62
+ }
63
+
64
+ static hashCode(value: User): number {
65
+ return userHashCode(value);
66
+ }
67
+ }
68
+
69
+ export function userEquals(a: User, b: User): boolean {
70
+ if (a === b) return true;
71
+ return a.id === b.id && a.name === b.name;
72
+ }
73
+
74
+ export function userHashCode(value: User): number {
75
+ let hash = 17;
76
+ hash =
77
+ (hash * 31 +
78
+ (Number.isInteger(value.id)
79
+ ? value.id | 0
80
+ : value.id
81
+ .toString()
82
+ .split('')
83
+ .reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
84
+ 0;
85
+ hash =
86
+ (hash * 31 +
87
+ (value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
88
+ 0;
89
+ return hash;
90
+ }
91
+ ```
92
+
93
+ Generated output:
94
+
51
95
  ```typescript
52
- @derive(PartialEq, Hash)
53
96
  class User {
54
97
  id: number;
55
98
  name: string;
56
99
 
57
- @partialEq(skip) // Don't compare cached values
58
- @hash(skip)
59
100
  cachedScore: number;
101
+
102
+ static equals(a: User, b: User): boolean {
103
+ return userEquals(a, b);
104
+ }
105
+
106
+ static hashCode(value: User): number {
107
+ return userHashCode(value);
108
+ }
60
109
  }
61
110
 
62
- // Generated:
63
- // equals(other: unknown): boolean {
64
- // if (this === other) return true;
65
- // if (!(other instanceof User)) return false;
66
- // const typedOther = other as User;
67
- // return this.id === typedOther.id &&
68
- // this.name === typedOther.name;
69
- // }
111
+ export function userEquals(a: User, b: User): boolean {
112
+ if (a === b) return true;
113
+ return a.id === b.id && a.name === b.name;
114
+ }
115
+
116
+ export function userHashCode(value: User): number {
117
+ let hash = 17;
118
+ hash =
119
+ (hash * 31 +
120
+ (Number.isInteger(value.id)
121
+ ? value.id | 0
122
+ : value.id
123
+ .toString()
124
+ .split('')
125
+ .reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
126
+ 0;
127
+ hash =
128
+ (hash * 31 +
129
+ (value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
130
+ 0;
131
+ return hash;
132
+ }
70
133
  ```
71
134
 
72
135
  ## Equality Contract
@@ -76,3 +139,98 @@ When implementing `PartialEq`, consider also implementing `Hash`:
76
139
  - **Reflexivity**: `a.equals(a)` is always true
77
140
  - **Symmetry**: `a.equals(b)` implies `b.equals(a)`
78
141
  - **Hash consistency**: Equal objects must have equal hash codes
142
+
143
+ To maintain the hash contract, skip the same fields in both `PartialEq` and `Hash`:
144
+
145
+ ```typescript before
146
+ /** @derive(PartialEq, Hash) */
147
+ class User {
148
+ id: number;
149
+ name: string;
150
+
151
+ /** @partialEq({ skip: true }) @hash({ skip: true }) */
152
+ cachedScore: number;
153
+ }
154
+ ```
155
+
156
+ ```typescript after
157
+ class User {
158
+ id: number;
159
+ name: string;
160
+
161
+ cachedScore: number;
162
+
163
+ static equals(a: User, b: User): boolean {
164
+ return userEquals(a, b);
165
+ }
166
+
167
+ static hashCode(value: User): number {
168
+ return userHashCode(value);
169
+ }
170
+ }
171
+
172
+ export function userEquals(a: User, b: User): boolean {
173
+ if (a === b) return true;
174
+ return a.id === b.id && a.name === b.name;
175
+ }
176
+
177
+ export function userHashCode(value: User): number {
178
+ let hash = 17;
179
+ hash =
180
+ (hash * 31 +
181
+ (Number.isInteger(value.id)
182
+ ? value.id | 0
183
+ : value.id
184
+ .toString()
185
+ .split('')
186
+ .reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
187
+ 0;
188
+ hash =
189
+ (hash * 31 +
190
+ (value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
191
+ 0;
192
+ return hash;
193
+ }
194
+ ```
195
+
196
+ Generated output:
197
+
198
+ ```typescript
199
+ class User {
200
+ id: number;
201
+ name: string;
202
+
203
+ cachedScore: number;
204
+
205
+ static equals(a: User, b: User): boolean {
206
+ return userEquals(a, b);
207
+ }
208
+
209
+ static hashCode(value: User): number {
210
+ return userHashCode(value);
211
+ }
212
+ }
213
+
214
+ export function userEquals(a: User, b: User): boolean {
215
+ if (a === b) return true;
216
+ return a.id === b.id && a.name === b.name;
217
+ }
218
+
219
+ export function userHashCode(value: User): number {
220
+ let hash = 17;
221
+ hash =
222
+ (hash * 31 +
223
+ (Number.isInteger(value.id)
224
+ ? value.id | 0
225
+ : value.id
226
+ .toString()
227
+ .split('')
228
+ .reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
229
+ 0;
230
+ hash =
231
+ (hash * 31 +
232
+ (value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
233
+ 0;
234
+ return hash;
235
+ }
236
+ ```
@@ -8,26 +8,18 @@ between values where some pairs may be incomparable.
8
8
 
9
9
  | Type | Generated Code | Description |
10
10
  |------|----------------|-------------|
11
- | Class | `compareTo(other): Option<number>` | Instance method with optional result |
12
- | Enum | `partialCompareEnumName(a: EnumName, b: EnumName): Option<number>` | Standalone function returning Option |
13
- | Interface | `partialCompareInterfaceName(a: InterfaceName, b: InterfaceName): Option<number>` | Standalone function with Option |
14
- | Type Alias | `partialCompareTypeName(a: TypeName, b: TypeName): Option<number>` | Standalone function with Option |
15
-
16
- ## Configuration
17
-
18
- The `functionNamingStyle` option in `macroforge.json` controls naming:
19
- - `"prefix"` (default): Prefixes with type name (e.g., `myTypePartialCompare`)
20
- - `"suffix"`: Suffixes with type name (e.g., `partialCompareMyType`)
21
- - `"generic"`: Uses TypeScript generics (e.g., `partialCompare<T extends MyType>`)
22
- - `"namespace"`: Legacy namespace wrapping
11
+ | Class | `classNamePartialCompare(a, b)` + `static compareTo(a, b)` | Standalone function + static wrapper method |
12
+ | Enum | `enumNamePartialCompare(a: EnumName, b: EnumName): Option<number>` | Standalone function returning Option |
13
+ | Interface | `interfaceNamePartialCompare(a: InterfaceName, b: InterfaceName): Option<number>` | Standalone function with Option |
14
+ | Type Alias | `typeNamePartialCompare(a: TypeName, b: TypeName): Option<number>` | Standalone function with Option |
23
15
 
24
16
  ## Return Values
25
17
 
26
18
  Unlike `Ord`, `PartialOrd` returns an `Option<number>` to handle incomparable values:
27
19
 
28
- - **Option.some(-1)**: `this` is less than `other`
29
- - **Option.some(0)**: `this` is equal to `other`
30
- - **Option.some(1)**: `this` is greater than `other`
20
+ - **Option.some(-1)**: `a` is less than `b`
21
+ - **Option.some(0)**: `a` is equal to `b`
22
+ - **Option.some(1)**: `a` is greater than `b`
31
23
  - **Option.none()**: Values are incomparable
32
24
 
33
25
  ## When to Use PartialOrd vs Ord
@@ -69,26 +61,72 @@ The `@ord` decorator supports:
69
61
 
70
62
  ## Example
71
63
 
64
+ ```typescript before
65
+ /** @derive(PartialOrd) */
66
+ class Temperature {
67
+ value: number | null;
68
+ unit: string;
69
+ }
70
+ ```
71
+
72
+ ```typescript after
73
+ import { Option } from 'macroforge/utils';
74
+
75
+ class Temperature {
76
+ value: number | null;
77
+ unit: string;
78
+
79
+ static compareTo(a: Temperature, b: Temperature): Option<number> {
80
+ return temperaturePartialCompare(a, b);
81
+ }
82
+ }
83
+
84
+ export function temperaturePartialCompare(a: Temperature, b: Temperature): Option<number> {
85
+ if (a === b) return Option.some(0);
86
+ const cmp0 = (() => {
87
+ if (typeof (a.value as any)?.compareTo === 'function') {
88
+ const optResult = (a.value as any).compareTo(b.value);
89
+ return Option.isNone(optResult) ? null : optResult.value;
90
+ }
91
+ return a.value === b.value ? 0 : null;
92
+ })();
93
+ if (cmp0 === null) return Option.none();
94
+ if (cmp0 !== 0) return Option.some(cmp0);
95
+ const cmp1 = a.unit.localeCompare(b.unit);
96
+ if (cmp1 === null) return Option.none();
97
+ if (cmp1 !== 0) return Option.some(cmp1);
98
+ return Option.some(0);
99
+ }
100
+ ```
101
+
102
+ Generated output:
103
+
72
104
  ```typescript
73
- @derive(PartialOrd)
74
105
  class Temperature {
75
- value: number | null; // null represents "unknown"
106
+ value: number | null;
76
107
  unit: string;
108
+
109
+ static compareTo(a: Temperature, b: Temperature): Option<number> {
110
+ return temperaturePartialCompare(a, b);
111
+ }
77
112
  }
78
113
 
79
- // Generated:
80
- // compareTo(other: unknown): Option<number> {
81
- // if (this === other) return Option.some(0);
82
- // if (!(other instanceof Temperature)) return Option.none();
83
- // const typedOther = other as Temperature;
84
- // const cmp0 = ...; // Compare value field
85
- // if (cmp0 === null) return Option.none();
86
- // if (cmp0 !== 0) return Option.some(cmp0);
87
- // const cmp1 = ...; // Compare unit field
88
- // if (cmp1 === null) return Option.none();
89
- // if (cmp1 !== 0) return Option.some(cmp1);
90
- // return Option.some(0);
91
- // }
114
+ export function temperaturePartialCompare(a: Temperature, b: Temperature): Option<number> {
115
+ if (a === b) return Option.some(0);
116
+ const cmp0 = (() => {
117
+ if (typeof (a.value as any)?.compareTo === 'function') {
118
+ const optResult = (a.value as any).compareTo(b.value);
119
+ return Option.isNone(optResult) ? null : optResult.value;
120
+ }
121
+ return a.value === b.value ? 0 : null;
122
+ })();
123
+ if (cmp0 === null) return Option.none();
124
+ if (cmp0 !== 0) return Option.some(cmp0);
125
+ const cmp1 = a.unit.localeCompare(b.unit);
126
+ if (cmp1 === null) return Option.none();
127
+ if (cmp1 !== 0) return Option.some(cmp1);
128
+ return Option.some(0);
129
+ }
92
130
  ```
93
131
 
94
132
  ## Required Import
@@ -0,0 +1,139 @@
1
+ ## Example
2
+
3
+ ```typescript before
4
+ /** @derive(Serialize) */
5
+ class User {
6
+ id: number;
7
+
8
+ /** @serde({ rename: "userName" }) */
9
+ name: string;
10
+
11
+ /** @serde({ skipSerializing: true }) */
12
+ password: string;
13
+
14
+ /** @serde({ flatten: true }) */
15
+ metadata: UserMetadata;
16
+ }
17
+ ```
18
+
19
+ ```typescript after
20
+ import { SerializeContext } from 'macroforge/serde';
21
+
22
+ class User {
23
+ id: number;
24
+
25
+ name: string;
26
+
27
+ password: string;
28
+
29
+ metadata: UserMetadata;
30
+ /** Serializes a value to a JSON string.
31
+ @param value - The value to serialize
32
+ @returns JSON string representation with cycle detection metadata */
33
+
34
+ static serialize(value: User): string {
35
+ return userSerialize(value);
36
+ }
37
+ /** @internal Serializes with an existing context for nested/cyclic object graphs.
38
+ @param value - The value to serialize
39
+ @param ctx - The serialization context */
40
+
41
+ static serializeWithContext(value: User, ctx: SerializeContext): Record<string, unknown> {
42
+ return userSerializeWithContext(value, ctx);
43
+ }
44
+ }
45
+
46
+ /** Serializes a value to a JSON string.
47
+ @param value - The value to serialize
48
+ @returns JSON string representation with cycle detection metadata */ export function userSerialize(
49
+ value: User
50
+ ): string {
51
+ const ctx = SerializeContext.create();
52
+ return JSON.stringify(userSerializeWithContext(value, ctx));
53
+ } /** @internal Serializes with an existing context for nested/cyclic object graphs.
54
+ @param value - The value to serialize
55
+ @param ctx - The serialization context */
56
+ export function userSerializeWithContext(
57
+ value: User,
58
+ ctx: SerializeContext
59
+ ): Record<string, unknown> {
60
+ const existingId = ctx.getId(value);
61
+ if (existingId !== undefined) {
62
+ return { __ref: existingId };
63
+ }
64
+ const __id = ctx.register(value);
65
+ const result: Record<string, unknown> = { __type: 'User', __id };
66
+ result['id'] = value.id;
67
+ result['userName'] = value.name;
68
+ {
69
+ const __flattened = userMetadataSerializeWithContext(value.metadata, ctx);
70
+ const { __type: _, __id: __, ...rest } = __flattened as any;
71
+ Object.assign(result, rest);
72
+ }
73
+ return result;
74
+ }
75
+ ```
76
+
77
+ Generated output:
78
+
79
+ ```typescript
80
+ import { SerializeContext } from 'macroforge/serde';
81
+
82
+ class User {
83
+ id: number;
84
+
85
+ name: string;
86
+
87
+ password: string;
88
+
89
+ metadata: UserMetadata;
90
+ /** Serializes a value to a JSON string.
91
+ @param value - The value to serialize
92
+ @returns JSON string representation with cycle detection metadata */
93
+
94
+ static serialize(value: User): string {
95
+ return userSerialize(value);
96
+ }
97
+ /** @internal Serializes with an existing context for nested/cyclic object graphs.
98
+ @param value - The value to serialize
99
+ @param ctx - The serialization context */
100
+
101
+ static serializeWithContext(value: User, ctx: SerializeContext): Record<string, unknown> {
102
+ return userSerializeWithContext(value, ctx);
103
+ }
104
+ }
105
+
106
+ /** Serializes a value to a JSON string.
107
+ @param value - The value to serialize
108
+ @returns JSON string representation with cycle detection metadata */ export function userSerialize(
109
+ value: User
110
+ ): string {
111
+ const ctx = SerializeContext.create();
112
+ return JSON.stringify(userSerializeWithContext(value, ctx));
113
+ } /** @internal Serializes with an existing context for nested/cyclic object graphs.
114
+ @param value - The value to serialize
115
+ @param ctx - The serialization context */
116
+ export function userSerializeWithContext(
117
+ value: User,
118
+ ctx: SerializeContext
119
+ ): Record<string, unknown> {
120
+ const existingId = ctx.getId(value);
121
+ if (existingId !== undefined) {
122
+ return { __ref: existingId };
123
+ }
124
+ const __id = ctx.register(value);
125
+ const result: Record<string, unknown> = { __type: 'User', __id };
126
+ result['id'] = value.id;
127
+ result['userName'] = value.name;
128
+ {
129
+ const __flattened = userMetadataSerializeWithContext(value.metadata, ctx);
130
+ const { __type: _, __id: __, ...rest } = __flattened as any;
131
+ Object.assign(result, rest);
132
+ }
133
+ return result;
134
+ }
135
+ ```
136
+
137
+ ## Required Import
138
+
139
+ The generated code automatically imports `SerializeContext` from `macroforge/serde`.
@@ -0,0 +1,32 @@
1
+ # Serialize
2
+
3
+ The `Serialize` macro generates JSON serialization methods with **cycle detection**
4
+ and object identity tracking. This enables serialization of complex object graphs
5
+ including circular references.
6
+
7
+ ## Generated Methods
8
+
9
+ | Type | Generated Code | Description |
10
+ |------|----------------|-------------|
11
+ | Class | `classNameSerialize(value)` + `static serialize(value)` | Standalone function + static wrapper method |
12
+ | Enum | `enumNameSerialize(value)`, `enumNameSerializeWithContext` | Standalone functions |
13
+ | Interface | `interfaceNameSerialize(value)`, etc. | Standalone functions |
14
+ | Type Alias | `typeNameSerialize(value)`, etc. | Standalone functions |
15
+
16
+ ## Cycle Detection Protocol
17
+
18
+ The generated code handles circular references using `__id` and `__ref` markers:
19
+
20
+ ```json
21
+ {
22
+ "__type": "User",
23
+ "__id": 1,
24
+ "name": "Alice",
25
+ "friend": { "__ref": 2 } // Reference to object with __id: 2
26
+ }
27
+ ```
28
+
29
+ When an object is serialized:
30
+ 1. Check if it's already been serialized (has an `__id`)
31
+ 2. If so, return `{ "__ref": existingId }` instead
32
+ 3. Otherwise, register the object and serialize its fields
@@ -0,0 +1,22 @@
1
+ ## Type-Specific Serialization
2
+
3
+ | Type | Serialization Strategy |
4
+ |------|------------------------|
5
+ | Primitives | Direct value |
6
+ | `Date` | `toISOString()` |
7
+ | Arrays | For primitive-like element types, pass through; for `Date`/`Date | null`, map to ISO strings; otherwise map and call `SerializeWithContext(ctx)` when available |
8
+ | `Map<K,V>` | For primitive-like values, `Object.fromEntries(map.entries())`; for `Date`/`Date | null`, convert to ISO strings; otherwise call `SerializeWithContext(ctx)` per value when available |
9
+ | `Set<T>` | Convert to array; element handling matches `Array<T>` |
10
+ | Nullable | Include `null` explicitly; for primitive-like and `Date` unions the generator avoids runtime `SerializeWithContext` checks |
11
+ | Objects | Call `SerializeWithContext(ctx)` if available (to support user-defined implementations) |
12
+
13
+ Note: the generator specializes some code paths based on the declared TypeScript type to
14
+ avoid runtime feature detection on primitives and literal unions.
15
+
16
+ ## Field-Level Options
17
+
18
+ The `@serde` decorator supports:
19
+
20
+ - `skip` / `skipSerializing` - Exclude field from serialization
21
+ - `rename = "jsonKey"` - Use different JSON property name
22
+ - `flatten` - Merge nested object's fields into parent