@macroforge/mcp-server 0.1.32 → 0.1.34
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 +68 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/docs-loader.d.ts +133 -5
- package/dist/tools/docs-loader.d.ts.map +1 -1
- package/dist/tools/docs-loader.js +131 -15
- package/dist/tools/docs-loader.js.map +1 -1
- package/dist/tools/index.d.ts +48 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +163 -14
- package/dist/tools/index.js.map +1 -1
- package/docs/api/api-overview.md +24 -46
- package/docs/api/expand-sync.md +24 -51
- package/docs/api/native-plugin.md +24 -56
- package/docs/api/position-mapper.md +34 -76
- package/docs/api/transform-sync.md +27 -59
- package/docs/builtin-macros/clone.md +150 -68
- package/docs/builtin-macros/debug.md +216 -81
- package/docs/builtin-macros/default.md +234 -91
- package/docs/builtin-macros/deserialize.md +891 -166
- package/docs/builtin-macros/hash.md +238 -82
- package/docs/builtin-macros/macros-overview.md +42 -103
- package/docs/builtin-macros/ord.md +205 -92
- package/docs/builtin-macros/partial-eq.md +178 -97
- package/docs/builtin-macros/partial-ord.md +209 -98
- package/docs/builtin-macros/serialize.md +326 -137
- package/docs/concepts/architecture.md +40 -99
- package/docs/concepts/derive-system.md +132 -125
- package/docs/concepts/how-macros-work.md +52 -84
- package/docs/custom-macros/custom-overview.md +17 -39
- package/docs/custom-macros/rust-setup.md +22 -55
- package/docs/custom-macros/ts-macro-derive.md +43 -107
- package/docs/custom-macros/ts-quote.md +177 -507
- package/docs/getting-started/first-macro.md +108 -33
- package/docs/getting-started/installation.md +32 -73
- package/docs/integration/cli.md +70 -156
- package/docs/integration/configuration.md +32 -75
- package/docs/integration/integration-overview.md +16 -55
- package/docs/integration/mcp-server.md +30 -69
- package/docs/integration/svelte-preprocessor.md +60 -83
- package/docs/integration/typescript-plugin.md +32 -74
- package/docs/integration/vite-plugin.md +30 -79
- package/docs/language-servers/ls-overview.md +22 -46
- package/docs/language-servers/svelte.md +30 -69
- package/docs/language-servers/zed.md +34 -72
- package/docs/roadmap/roadmap.md +54 -130
- package/docs/sections.json +3 -262
- package/package.json +2 -2
|
@@ -1,91 +1,210 @@
|
|
|
1
1
|
# Serialize
|
|
2
|
+
*The `Serialize` macro generates JSON serialization methods with **cycle detection** and object identity tracking. This enables serialization of complex object graphs including circular references.*
|
|
3
|
+
## Basic Usage
|
|
4
|
+
**Before:**
|
|
5
|
+
```
|
|
6
|
+
/** @derive(Serialize) */
|
|
7
|
+
class User {
|
|
8
|
+
name: string;
|
|
9
|
+
age: number;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
|
|
12
|
+
constructor(name: string, age: number) {
|
|
13
|
+
this.name = name;
|
|
14
|
+
this.age = age;
|
|
15
|
+
this.createdAt = new Date();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
**After:**
|
|
20
|
+
```
|
|
21
|
+
import { SerializeContext } from 'macroforge/serde';
|
|
2
22
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
23
|
+
class User {
|
|
24
|
+
name: string;
|
|
25
|
+
age: number;
|
|
26
|
+
createdAt: Date;
|
|
27
|
+
|
|
28
|
+
constructor(name: string, age: number) {
|
|
29
|
+
this.name = name;
|
|
30
|
+
this.age = age;
|
|
31
|
+
this.createdAt = new Date();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
toStringifiedJSON(): string {
|
|
35
|
+
const ctx = SerializeContext.create();
|
|
36
|
+
return JSON.stringify(this.__serialize(ctx));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toObject(): Record<string, unknown> {
|
|
40
|
+
const ctx = SerializeContext.create();
|
|
41
|
+
return this.__serialize(ctx);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
__serialize(ctx: SerializeContext): Record<string, unknown> {
|
|
45
|
+
const existingId = ctx.getId(this);
|
|
46
|
+
if (existingId !== undefined) {
|
|
47
|
+
return {
|
|
48
|
+
__ref: existingId
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const __id = ctx.register(this);
|
|
52
|
+
const result: Record<string, unknown> = {
|
|
53
|
+
__type: 'User',
|
|
54
|
+
__id
|
|
55
|
+
};
|
|
56
|
+
result['name'] = this.name;
|
|
57
|
+
result['age'] = this.age;
|
|
58
|
+
result['createdAt'] = this.createdAt.toISOString();
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
``` ```
|
|
10
63
|
const user = new User("Alice", 30);
|
|
11
64
|
console.log(JSON.stringify(user));
|
|
12
65
|
// {"name":"Alice","age":30,"createdAt":"2024-01-15T10:30:00.000Z"}
|
|
66
|
+
``` ## Automatic Type Handling
|
|
67
|
+
Serialize automatically handles various TypeScript types:
|
|
68
|
+
| Type | Serialization |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `string`, `number`, `boolean` | Direct copy |
|
|
71
|
+
| `Date` | `.toISOString()` |
|
|
72
|
+
| `T[]` | Maps items, calling `toJSON()` if available |
|
|
73
|
+
| `Map<K, V>` | `Object.fromEntries()` |
|
|
74
|
+
| `Set<T>` | `Array.from()` |
|
|
75
|
+
| Nested objects | Calls `toJSON()` if available |
|
|
76
|
+
## Serde Options
|
|
77
|
+
Use the `@serde` decorator for fine-grained control over serialization:
|
|
78
|
+
### Renaming Fields
|
|
79
|
+
**Before:**
|
|
13
80
|
```
|
|
81
|
+
/** @derive(Serialize) */
|
|
82
|
+
class User {
|
|
83
|
+
/** @serde({ rename: "user_id" }) */
|
|
84
|
+
id: string;
|
|
14
85
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| `Date`
|
|
23
|
-
| `.toISOString()`
|
|
24
|
-
|
|
25
|
-
| `T[]`
|
|
26
|
-
| Maps items, calling `toJSON()` if available
|
|
27
|
-
|
|
28
|
-
| `Map<K, V>`
|
|
29
|
-
| `Object.fromEntries()`
|
|
30
|
-
|
|
31
|
-
| `Set<T>`
|
|
32
|
-
| `Array.from()`
|
|
33
|
-
|
|
34
|
-
| Nested objects
|
|
35
|
-
| Calls `toJSON()` if available
|
|
36
|
-
|
|
37
|
-
## Serde Options
|
|
38
|
-
|
|
39
|
-
Use the `@serde` decorator for fine-grained control over serialization:
|
|
40
|
-
|
|
41
|
-
### Renaming Fields
|
|
42
|
-
|
|
43
|
-
<MacroExample before={data.examples.rename.before} after={data.examples.rename.after} />
|
|
86
|
+
/** @serde({ rename: "full_name" }) */
|
|
87
|
+
name: string;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
**After:**
|
|
91
|
+
```
|
|
92
|
+
import { SerializeContext } from 'macroforge/serde';
|
|
44
93
|
|
|
45
|
-
|
|
94
|
+
class User {
|
|
95
|
+
id: string;
|
|
96
|
+
|
|
97
|
+
name: string;
|
|
98
|
+
|
|
99
|
+
toStringifiedJSON(): string {
|
|
100
|
+
const ctx = SerializeContext.create();
|
|
101
|
+
return JSON.stringify(this.__serialize(ctx));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
toObject(): Record<string, unknown> {
|
|
105
|
+
const ctx = SerializeContext.create();
|
|
106
|
+
return this.__serialize(ctx);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
__serialize(ctx: SerializeContext): Record<string, unknown> {
|
|
110
|
+
const existingId = ctx.getId(this);
|
|
111
|
+
if (existingId !== undefined) {
|
|
112
|
+
return {
|
|
113
|
+
__ref: existingId
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const __id = ctx.register(this);
|
|
117
|
+
const result: Record<string, unknown> = {
|
|
118
|
+
__type: 'User',
|
|
119
|
+
__id
|
|
120
|
+
};
|
|
121
|
+
result['user_id'] = this.id;
|
|
122
|
+
result['full_name'] = this.name;
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
``` ```
|
|
46
127
|
const user = new User();
|
|
47
128
|
user.id = "123";
|
|
48
129
|
user.name = "Alice";
|
|
49
130
|
console.log(JSON.stringify(user));
|
|
50
131
|
// {"user_id":"123","full_name":"Alice"}
|
|
132
|
+
``` ### Skipping Fields
|
|
133
|
+
**Before:**
|
|
51
134
|
```
|
|
135
|
+
/** @derive(Serialize) */
|
|
136
|
+
class User {
|
|
137
|
+
name: string;
|
|
138
|
+
email: string;
|
|
52
139
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<MacroExample before={data.examples.skip.before} after={data.examples.skip.after} />
|
|
56
|
-
|
|
57
|
-
<Alert type="tip" title="skip vs skip_serializing">
|
|
58
|
-
Use `skip: true` to exclude from both serialization and deserialization.
|
|
59
|
-
Use `skip_serializing: true` to only skip during serialization.
|
|
60
|
-
</Alert>
|
|
61
|
-
|
|
62
|
-
### Rename All Fields
|
|
140
|
+
/** @serde({ skip: true }) */
|
|
141
|
+
password: string;
|
|
63
142
|
|
|
64
|
-
|
|
143
|
+
/** @serde({ skip_serializing: true }) */
|
|
144
|
+
internalId: string;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
**After:**
|
|
148
|
+
```
|
|
149
|
+
import { SerializeContext } from 'macroforge/serde';
|
|
65
150
|
|
|
66
|
-
|
|
151
|
+
class User {
|
|
152
|
+
name: string;
|
|
153
|
+
email: string;
|
|
154
|
+
|
|
155
|
+
password: string;
|
|
156
|
+
|
|
157
|
+
internalId: string;
|
|
158
|
+
|
|
159
|
+
toStringifiedJSON(): string {
|
|
160
|
+
const ctx = SerializeContext.create();
|
|
161
|
+
return JSON.stringify(this.__serialize(ctx));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
toObject(): Record<string, unknown> {
|
|
165
|
+
const ctx = SerializeContext.create();
|
|
166
|
+
return this.__serialize(ctx);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
__serialize(ctx: SerializeContext): Record<string, unknown> {
|
|
170
|
+
const existingId = ctx.getId(this);
|
|
171
|
+
if (existingId !== undefined) {
|
|
172
|
+
return {
|
|
173
|
+
__ref: existingId
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const __id = ctx.register(this);
|
|
177
|
+
const result: Record<string, unknown> = {
|
|
178
|
+
__type: 'User',
|
|
179
|
+
__id
|
|
180
|
+
};
|
|
181
|
+
result['name'] = this.name;
|
|
182
|
+
result['email'] = this.email;
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
``` **skip vs skip_serializing Use `skip: true` to exclude from both serialization and deserialization.
|
|
187
|
+
Use `skip_serializing: true` to only skip during serialization. ### Rename All Fields
|
|
188
|
+
Apply a naming convention to all fields at the container level:
|
|
189
|
+
****Source:**
|
|
190
|
+
```
|
|
191
|
+
/** @derive(Serialize) */
|
|
67
192
|
/** @serde({ rename_all: "camelCase" }) */
|
|
68
193
|
class ApiResponse {
|
|
69
194
|
user_name: string;
|
|
70
195
|
created_at: Date;
|
|
71
196
|
is_active: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- `
|
|
77
|
-
|
|
78
|
-
- `
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- `kebab-case`
|
|
85
|
-
|
|
86
|
-
### Flattening Nested Objects
|
|
87
|
-
|
|
88
|
-
<InteractiveMacro code={`/** @derive(Serialize) */
|
|
197
|
+
}
|
|
198
|
+
``` Supported conventions:
|
|
199
|
+
- `camelCase`
|
|
200
|
+
- `snake_case`
|
|
201
|
+
- `PascalCase`
|
|
202
|
+
- `SCREAMING_SNAKE_CASE`
|
|
203
|
+
- `kebab-case`
|
|
204
|
+
### Flattening Nested Objects
|
|
205
|
+
**Source:**
|
|
206
|
+
```
|
|
207
|
+
/** @derive(Serialize) */
|
|
89
208
|
class Address {
|
|
90
209
|
city: string;
|
|
91
210
|
zip: string;
|
|
@@ -97,49 +216,69 @@ class User {
|
|
|
97
216
|
|
|
98
217
|
/** @serde({ flatten: true }) */
|
|
99
218
|
address: Address;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
219
|
+
}
|
|
220
|
+
``` ```
|
|
103
221
|
const user = new User();
|
|
104
222
|
user.name = "Alice";
|
|
105
223
|
user.address = { city: "NYC", zip: "10001" };
|
|
106
224
|
console.log(JSON.stringify(user));
|
|
107
225
|
// {"name":"Alice","city":"NYC","zip":"10001"}
|
|
226
|
+
``` ## All Options
|
|
227
|
+
### Container Options (on class/interface)
|
|
228
|
+
| Option | Type | Description |
|
|
229
|
+
| --- | --- | --- |
|
|
230
|
+
| `rename_all` | `string` | Apply naming convention to all fields |
|
|
231
|
+
### Field Options (on properties)
|
|
232
|
+
| Option | Type | Description |
|
|
233
|
+
| --- | --- | --- |
|
|
234
|
+
| `rename` | `string` | Use a different JSON key |
|
|
235
|
+
| `skip` | `boolean` | Exclude from serialization and deserialization |
|
|
236
|
+
| `skip_serializing` | `boolean` | Exclude from serialization only |
|
|
237
|
+
| `flatten` | `boolean` | Merge nested object fields into parent |
|
|
238
|
+
## Interface Support
|
|
239
|
+
Serialize also works with interfaces. For interfaces, a namespace is generated with a `toJSON` function:
|
|
240
|
+
**Before:**
|
|
108
241
|
```
|
|
242
|
+
/** @derive(Serialize) */
|
|
243
|
+
interface ApiResponse {
|
|
244
|
+
status: number;
|
|
245
|
+
message: string;
|
|
246
|
+
timestamp: Date;
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
**After:**
|
|
250
|
+
```
|
|
251
|
+
import { SerializeContext } from 'macroforge/serde';
|
|
109
252
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
| `string`
|
|
116
|
-
| Apply naming convention to all fields
|
|
117
|
-
|
|
118
|
-
### Field Options (on properties)
|
|
119
|
-
|
|
120
|
-
| `rename`
|
|
121
|
-
| `string`
|
|
122
|
-
| Use a different JSON key
|
|
123
|
-
|
|
124
|
-
| `skip`
|
|
125
|
-
| `boolean`
|
|
126
|
-
| Exclude from serialization and deserialization
|
|
127
|
-
|
|
128
|
-
| `skip_serializing`
|
|
129
|
-
| `boolean`
|
|
130
|
-
| Exclude from serialization only
|
|
131
|
-
|
|
132
|
-
| `flatten`
|
|
133
|
-
| `boolean`
|
|
134
|
-
| Merge nested object fields into parent
|
|
135
|
-
|
|
136
|
-
## Interface Support
|
|
137
|
-
|
|
138
|
-
Serialize also works with interfaces. For interfaces, a namespace is generated with a `toJSON` function:
|
|
139
|
-
|
|
140
|
-
<MacroExample before={data.examples.interface.before} after={data.examples.interface.after} />
|
|
253
|
+
interface ApiResponse {
|
|
254
|
+
status: number;
|
|
255
|
+
message: string;
|
|
256
|
+
timestamp: Date;
|
|
257
|
+
}
|
|
141
258
|
|
|
142
|
-
|
|
259
|
+
export namespace ApiResponse {
|
|
260
|
+
export function toStringifiedJSON(self: ApiResponse): string {
|
|
261
|
+
const ctx = SerializeContext.create();
|
|
262
|
+
return JSON.stringify(__serialize(self, ctx));
|
|
263
|
+
}
|
|
264
|
+
export function toObject(self: ApiResponse): Record<string, unknown> {
|
|
265
|
+
const ctx = SerializeContext.create();
|
|
266
|
+
return __serialize(self, ctx);
|
|
267
|
+
}
|
|
268
|
+
export function __serialize(self: ApiResponse, ctx: SerializeContext): Record<string, unknown> {
|
|
269
|
+
const existingId = ctx.getId(self);
|
|
270
|
+
if (existingId !== undefined) {
|
|
271
|
+
return { __ref: existingId };
|
|
272
|
+
}
|
|
273
|
+
const __id = ctx.register(self);
|
|
274
|
+
const result: Record<string, unknown> = { __type: 'ApiResponse', __id };
|
|
275
|
+
result['status'] = self.status;
|
|
276
|
+
result['message'] = self.message;
|
|
277
|
+
result['timestamp'] = self.timestamp.toISOString();
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
``` ```
|
|
143
282
|
const response: ApiResponse = {
|
|
144
283
|
status: 200,
|
|
145
284
|
message: "OK",
|
|
@@ -148,39 +287,94 @@ const response: ApiResponse = {
|
|
|
148
287
|
|
|
149
288
|
console.log(JSON.stringify(ApiResponse.toJSON(response)));
|
|
150
289
|
// {"status":200,"message":"OK","timestamp":"2024-01-15T10:30:00.000Z"}
|
|
290
|
+
``` ## Enum Support
|
|
291
|
+
Serialize also works with enums. The `toJSON` function returns the underlying enum value (string or number):
|
|
292
|
+
**Before:**
|
|
151
293
|
```
|
|
294
|
+
/** @derive(Serialize) */
|
|
295
|
+
enum Status {
|
|
296
|
+
Active = 'active',
|
|
297
|
+
Inactive = 'inactive',
|
|
298
|
+
Pending = 'pending'
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
**After:**
|
|
302
|
+
```
|
|
303
|
+
enum Status {
|
|
304
|
+
Active = 'active',
|
|
305
|
+
Inactive = 'inactive',
|
|
306
|
+
Pending = 'pending'
|
|
307
|
+
}
|
|
152
308
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
309
|
+
export namespace Status {
|
|
310
|
+
export function toStringifiedJSON(value: Status): string {
|
|
311
|
+
return JSON.stringify(value);
|
|
312
|
+
}
|
|
313
|
+
export function __serialize(_ctx: SerializeContext): string | number {
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
``` ```
|
|
160
318
|
console.log(Status.toJSON(Status.Active)); // "active"
|
|
161
319
|
console.log(Status.toJSON(Status.Pending)); // "pending"
|
|
320
|
+
``` Works with numeric enums too:
|
|
321
|
+
**Source:**
|
|
162
322
|
```
|
|
163
|
-
|
|
164
|
-
Works with numeric enums too:
|
|
165
|
-
|
|
166
|
-
<InteractiveMacro code={`/** @derive(Serialize) */
|
|
323
|
+
/** @derive(Serialize) */
|
|
167
324
|
enum Priority {
|
|
168
325
|
Low = 1,
|
|
169
326
|
Medium = 2,
|
|
170
327
|
High = 3,
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
328
|
+
}
|
|
329
|
+
``` ```
|
|
174
330
|
console.log(Priority.toJSON(Priority.High)); // 3
|
|
331
|
+
``` ## Type Alias Support
|
|
332
|
+
Serialize works with type aliases. For object types, fields are serialized with full type handling:
|
|
333
|
+
**Before:**
|
|
175
334
|
```
|
|
335
|
+
/** @derive(Serialize) */
|
|
336
|
+
type UserProfile = {
|
|
337
|
+
id: string;
|
|
338
|
+
name: string;
|
|
339
|
+
createdAt: Date;
|
|
340
|
+
};
|
|
341
|
+
```
|
|
342
|
+
**After:**
|
|
343
|
+
```
|
|
344
|
+
import { SerializeContext } from 'macroforge/serde';
|
|
176
345
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
346
|
+
type UserProfile = {
|
|
347
|
+
id: string;
|
|
348
|
+
name: string;
|
|
349
|
+
createdAt: Date;
|
|
350
|
+
};
|
|
182
351
|
|
|
183
|
-
|
|
352
|
+
export namespace UserProfile {
|
|
353
|
+
export function toStringifiedJSON(value: UserProfile): string {
|
|
354
|
+
const ctx = SerializeContext.create();
|
|
355
|
+
return JSON.stringify(__serialize(value, ctx));
|
|
356
|
+
}
|
|
357
|
+
export function toObject(value: UserProfile): Record<string, unknown> {
|
|
358
|
+
const ctx = SerializeContext.create();
|
|
359
|
+
return __serialize(value, ctx);
|
|
360
|
+
}
|
|
361
|
+
export function __serialize(
|
|
362
|
+
value: UserProfile,
|
|
363
|
+
ctx: SerializeContext
|
|
364
|
+
): Record<string, unknown> {
|
|
365
|
+
const existingId = ctx.getId(value);
|
|
366
|
+
if (existingId !== undefined) {
|
|
367
|
+
return { __ref: existingId };
|
|
368
|
+
}
|
|
369
|
+
const __id = ctx.register(value);
|
|
370
|
+
const result: Record<string, unknown> = { __type: 'UserProfile', __id };
|
|
371
|
+
result['id'] = value.id;
|
|
372
|
+
result['name'] = value.name;
|
|
373
|
+
result['createdAt'] = value.createdAt;
|
|
374
|
+
return result;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
``` ```
|
|
184
378
|
const profile: UserProfile = {
|
|
185
379
|
id: "123",
|
|
186
380
|
name: "Alice",
|
|
@@ -189,28 +383,23 @@ const profile: UserProfile = {
|
|
|
189
383
|
|
|
190
384
|
console.log(JSON.stringify(UserProfile.toJSON(profile)));
|
|
191
385
|
// {"id":"123","name":"Alice","createdAt":"2024-01-15T00:00:00.000Z"}
|
|
386
|
+
``` For union types, the value is returned directly:
|
|
387
|
+
**Source:**
|
|
192
388
|
```
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<InteractiveMacro code={`/** @derive(Serialize) */
|
|
197
|
-
type ApiStatus = "loading" | "success" | "error";`} />
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
389
|
+
/** @derive(Serialize) */
|
|
390
|
+
type ApiStatus = "loading" | "success" | "error";
|
|
391
|
+
``` ```
|
|
200
392
|
console.log(ApiStatus.toJSON("success")); // "success"
|
|
393
|
+
``` ## Combining with Deserialize
|
|
394
|
+
Use both Serialize and Deserialize for complete JSON round-trip support:
|
|
395
|
+
**Source:**
|
|
201
396
|
```
|
|
202
|
-
|
|
203
|
-
## Combining with Deserialize
|
|
204
|
-
|
|
205
|
-
Use both Serialize and Deserialize for complete JSON round-trip support:
|
|
206
|
-
|
|
207
|
-
<InteractiveMacro code={`/** @derive(Serialize, Deserialize) */
|
|
397
|
+
/** @derive(Serialize, Deserialize) */
|
|
208
398
|
class User {
|
|
209
399
|
name: string;
|
|
210
400
|
createdAt: Date;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
401
|
+
}
|
|
402
|
+
``` ```
|
|
214
403
|
// Serialize
|
|
215
404
|
const user = new User();
|
|
216
405
|
user.name = "Alice";
|
|
@@ -1,96 +1,41 @@
|
|
|
1
1
|
# Architecture
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- `ts_template!` - Generate TypeScript code from templates
|
|
41
|
-
|
|
42
|
-
- `body!` - Generate class body members
|
|
43
|
-
|
|
44
|
-
- Control flow: `{"{#for}"}`, `{"{#if}"}`, `{"{$let}"}`
|
|
45
|
-
|
|
46
|
-
### macroforge_ts_macros
|
|
47
|
-
|
|
48
|
-
The procedural macro attribute for defining derive macros:
|
|
49
|
-
|
|
50
|
-
- `#[ts_macro_derive(Name)]` attribute
|
|
51
|
-
|
|
52
|
-
- Automatic registration with the macro system
|
|
53
|
-
|
|
54
|
-
- Error handling and span tracking
|
|
55
|
-
|
|
56
|
-
### NAPI-RS Bindings
|
|
57
|
-
|
|
58
|
-
Bridges Rust and Node.js:
|
|
59
|
-
|
|
60
|
-
- Exposes `expandSync`, `transformSync`, etc.
|
|
61
|
-
|
|
62
|
-
- Provides the `NativePlugin` class for caching
|
|
63
|
-
|
|
64
|
-
- Handles data marshaling between Rust and JavaScript
|
|
65
|
-
|
|
66
|
-
## Data Flow
|
|
67
|
-
|
|
68
|
-
<Flowchart steps={[
|
|
69
|
-
{ title: "1. Source Code", description: "TypeScript with @derive" },
|
|
70
|
-
{ title: "2. NAPI-RS", description: "receives JavaScript string" },
|
|
71
|
-
{ title: "3. SWC Parser", description: "parses to AST" },
|
|
72
|
-
{ title: "4. Macro Expander", description: "finds @derive decorators" },
|
|
73
|
-
{ title: "5. For Each Macro", description: "extract data, run macro, generate AST nodes" },
|
|
74
|
-
{ title: "6. Merge", description: "generated nodes into AST" },
|
|
75
|
-
{ title: "7. SWC Codegen", description: "generates source code" },
|
|
76
|
-
{ title: "8. Return", description: "to JavaScript with source mapping" }
|
|
77
|
-
]} />
|
|
78
|
-
|
|
79
|
-
## Performance Characteristics
|
|
80
|
-
|
|
81
|
-
- **Thread-safe**: Each expansion runs in an isolated thread with a 32MB stack
|
|
82
|
-
|
|
83
|
-
- **Caching**: `NativePlugin` caches results by file version
|
|
84
|
-
|
|
85
|
-
- **Binary search**: Position mapping uses O(log n) lookups
|
|
86
|
-
|
|
87
|
-
- **Zero-copy**: SWC's arena allocator minimizes allocations
|
|
88
|
-
|
|
89
|
-
## Re-exported Crates
|
|
90
|
-
|
|
91
|
-
For custom macro development, `macroforge_ts` re-exports everything you need:
|
|
92
|
-
|
|
93
|
-
```rust
|
|
2
|
+
*Macroforge is built as a native Node.js module using Rust and NAPI-RS. It leverages SWC for fast TypeScript parsing and code generation.*
|
|
3
|
+
## Overview
|
|
4
|
+
<div class="border border-border bg-card p-4 text-center rounded-t-lg "><div class="font-semibold text-foreground">Node.js / Vite <div class="font-semibold text-foreground">NAPI-RS Bindings <div class="font-semibold text-foreground">Macro Crates <div class="px-3 py-1.5 bg-muted rounded text-sm text-muted-foreground font-mono">macroforge_ts_synmacroforge_ts_quotemacroforge_ts_macros<div class="font-semibold text-foreground">SWC Core <div class="px-3 py-1.5 bg-muted rounded text-sm text-muted-foreground font-mono">TypeScript parsing & codegen ## Core Components
|
|
5
|
+
### SWC Core
|
|
6
|
+
The foundation layer provides:
|
|
7
|
+
- Fast TypeScript/JavaScript parsing
|
|
8
|
+
- AST representation
|
|
9
|
+
- Code generation (AST → source code)
|
|
10
|
+
### macroforge_ts_syn
|
|
11
|
+
A Rust crate that provides:
|
|
12
|
+
- TypeScript-specific AST types
|
|
13
|
+
- Parsing utilities for macro input
|
|
14
|
+
- Derive input structures (class fields, decorators, etc.)
|
|
15
|
+
### macroforge_ts_quote
|
|
16
|
+
Template-based code generation similar to Rust's `quote!`:
|
|
17
|
+
- `ts_template!` - Generate TypeScript code from templates
|
|
18
|
+
- `body!` - Generate class body members
|
|
19
|
+
- Control flow: `{#for}`, `{#if}`, `{$let}`
|
|
20
|
+
### macroforge_ts_macros
|
|
21
|
+
The procedural macro attribute for defining derive macros:
|
|
22
|
+
- `#[ts_macro_derive(Name)]` attribute
|
|
23
|
+
- Automatic registration with the macro system
|
|
24
|
+
- Error handling and span tracking
|
|
25
|
+
### NAPI-RS Bindings
|
|
26
|
+
Bridges Rust and Node.js:
|
|
27
|
+
- Exposes `expandSync`, `transformSync`, etc.
|
|
28
|
+
- Provides the `NativePlugin` class for caching
|
|
29
|
+
- Handles data marshaling between Rust and JavaScript
|
|
30
|
+
## Data Flow
|
|
31
|
+
<div class="w-full max-w-md border border-border rounded-lg bg-card p-4 text-center shadow-sm"><div class="font-semibold text-foreground">1. Source Code TypeScript with @derive <div class="font-semibold text-foreground">2. NAPI-RS receives JavaScript string <div class="font-semibold text-foreground">3. SWC Parser parses to AST <div class="font-semibold text-foreground">4. Macro Expander finds @derive decorators <div class="font-semibold text-foreground">5. For Each Macro extract data, run macro, generate AST nodes <div class="font-semibold text-foreground">6. Merge generated nodes into AST <div class="font-semibold text-foreground">7. SWC Codegen generates source code <div class="font-semibold text-foreground">8. Return to JavaScript with source mapping ## Performance Characteristics
|
|
32
|
+
- **Thread-safe**: Each expansion runs in an isolated thread with a 32MB stack
|
|
33
|
+
- **Caching**: `NativePlugin` caches results by file version
|
|
34
|
+
- **Binary search**: Position mapping uses O(log n) lookups
|
|
35
|
+
- **Zero-copy**: SWC's arena allocator minimizes allocations
|
|
36
|
+
## Re-exported Crates
|
|
37
|
+
For custom macro development, `macroforge_ts` re-exports everything you need:
|
|
38
|
+
```
|
|
94
39
|
// Convenient re-exports for macro development
|
|
95
40
|
use macroforge_ts::macros::{ts_macro_derive, body, ts_template, above, below, signature};
|
|
96
41
|
use macroforge_ts::ts_syn::{Data, DeriveInput, MacroforgeError, TsStream, parse_ts_macro_input};
|
|
@@ -99,10 +44,6 @@ use macroforge_ts::ts_syn::{Data, DeriveInput, MacroforgeError, TsStream, parse_
|
|
|
99
44
|
use macroforge_ts::swc_core;
|
|
100
45
|
use macroforge_ts::swc_common;
|
|
101
46
|
use macroforge_ts::swc_ecma_ast;
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- [Write custom macros]({base}/docs/custom-macros)
|
|
107
|
-
|
|
108
|
-
- [Explore the API reference]({base}/docs/api)
|
|
47
|
+
``` ## Next Steps
|
|
48
|
+
- [Write custom macros](../../docs/custom-macros)
|
|
49
|
+
- [Explore the API reference](../../docs/api)
|