@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,321 @@
|
|
|
1
|
+
# Serialize
|
|
2
|
+
|
|
3
|
+
*The `Serialize` macro generates a `toJSON()` method that converts your object to a JSON-compatible format with automatic handling of complex types like Date, Map, Set, and nested objects.*
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
/** @derive(Serialize) */
|
|
9
|
+
class User {
|
|
10
|
+
name: string;
|
|
11
|
+
age: number;
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
|
|
14
|
+
constructor(name: string, age: number) {
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.age = age;
|
|
17
|
+
this.createdAt = new Date();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const user = new User("Alice", 30);
|
|
22
|
+
console.log(JSON.stringify(user));
|
|
23
|
+
// {"name":"Alice","age":30,"createdAt":"2024-01-15T10:30:00.000Z"}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Generated Code
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
toJSON(): Record<string, unknown> {
|
|
30
|
+
const result: Record<string, unknown> = {};
|
|
31
|
+
result["name"] = this.name;
|
|
32
|
+
result["age"] = this.age;
|
|
33
|
+
result["createdAt"] = this.createdAt.toISOString();
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Automatic Type Handling
|
|
39
|
+
|
|
40
|
+
Serialize automatically handles various TypeScript types:
|
|
41
|
+
|
|
42
|
+
| `string`, `number`, `boolean`
|
|
43
|
+
| Direct copy
|
|
44
|
+
|
|
45
|
+
| `Date`
|
|
46
|
+
| `.toISOString()`
|
|
47
|
+
|
|
48
|
+
| `T[]`
|
|
49
|
+
| Maps items, calling `toJSON()` if available
|
|
50
|
+
|
|
51
|
+
| `Map<K, V>`
|
|
52
|
+
| `Object.fromEntries()`
|
|
53
|
+
|
|
54
|
+
| `Set<T>`
|
|
55
|
+
| `Array.from()`
|
|
56
|
+
|
|
57
|
+
| Nested objects
|
|
58
|
+
| Calls `toJSON()` if available
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
/** @derive(Serialize) */
|
|
62
|
+
class DataContainer {
|
|
63
|
+
items: string[];
|
|
64
|
+
metadata: Map<string, number>;
|
|
65
|
+
tags: Set<string>;
|
|
66
|
+
nested: User;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Serde Options
|
|
71
|
+
|
|
72
|
+
Use the `@serde` decorator for fine-grained control over serialization:
|
|
73
|
+
|
|
74
|
+
### Renaming Fields
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
/** @derive(Serialize) */
|
|
78
|
+
class User {
|
|
79
|
+
/** @serde({ rename: "user_id" }) */
|
|
80
|
+
id: string;
|
|
81
|
+
|
|
82
|
+
/** @serde({ rename: "full_name" }) */
|
|
83
|
+
name: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const user = new User();
|
|
87
|
+
user.id = "123";
|
|
88
|
+
user.name = "Alice";
|
|
89
|
+
console.log(JSON.stringify(user));
|
|
90
|
+
// {"user_id":"123","full_name":"Alice"}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Skipping Fields
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
/** @derive(Serialize) */
|
|
97
|
+
class User {
|
|
98
|
+
name: string;
|
|
99
|
+
email: string;
|
|
100
|
+
|
|
101
|
+
/** @serde({ skip: true }) */
|
|
102
|
+
password: string;
|
|
103
|
+
|
|
104
|
+
/** @serde({ skip_serializing: true }) */
|
|
105
|
+
internalId: string;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
<Alert type="tip" title="skip vs skip_serializing">
|
|
110
|
+
Use `skip: true` to exclude from both serialization and deserialization.
|
|
111
|
+
Use `skip_serializing: true` to only skip during serialization.
|
|
112
|
+
</Alert>
|
|
113
|
+
|
|
114
|
+
### Rename All Fields
|
|
115
|
+
|
|
116
|
+
Apply a naming convention to all fields at the container level:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
/** @derive(Serialize) */
|
|
120
|
+
/** @serde({ rename_all: "camelCase" }) */
|
|
121
|
+
class ApiResponse {
|
|
122
|
+
user_name: string; // becomes "userName"
|
|
123
|
+
created_at: Date; // becomes "createdAt"
|
|
124
|
+
is_active: boolean; // becomes "isActive"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Supported conventions:
|
|
129
|
+
|
|
130
|
+
- `camelCase`
|
|
131
|
+
|
|
132
|
+
- `snake_case`
|
|
133
|
+
|
|
134
|
+
- `PascalCase`
|
|
135
|
+
|
|
136
|
+
- `SCREAMING_SNAKE_CASE`
|
|
137
|
+
|
|
138
|
+
- `kebab-case`
|
|
139
|
+
|
|
140
|
+
### Flattening Nested Objects
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
/** @derive(Serialize) */
|
|
144
|
+
class Address {
|
|
145
|
+
city: string;
|
|
146
|
+
zip: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @derive(Serialize) */
|
|
150
|
+
class User {
|
|
151
|
+
name: string;
|
|
152
|
+
|
|
153
|
+
/** @serde({ flatten: true }) */
|
|
154
|
+
address: Address;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const user = new User();
|
|
158
|
+
user.name = "Alice";
|
|
159
|
+
user.address = { city: "NYC", zip: "10001" };
|
|
160
|
+
console.log(JSON.stringify(user));
|
|
161
|
+
// {"name":"Alice","city":"NYC","zip":"10001"}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## All Options
|
|
165
|
+
|
|
166
|
+
### Container Options (on class/interface)
|
|
167
|
+
|
|
168
|
+
| `rename_all`
|
|
169
|
+
| `string`
|
|
170
|
+
| Apply naming convention to all fields
|
|
171
|
+
|
|
172
|
+
### Field Options (on properties)
|
|
173
|
+
|
|
174
|
+
| `rename`
|
|
175
|
+
| `string`
|
|
176
|
+
| Use a different JSON key
|
|
177
|
+
|
|
178
|
+
| `skip`
|
|
179
|
+
| `boolean`
|
|
180
|
+
| Exclude from serialization and deserialization
|
|
181
|
+
|
|
182
|
+
| `skip_serializing`
|
|
183
|
+
| `boolean`
|
|
184
|
+
| Exclude from serialization only
|
|
185
|
+
|
|
186
|
+
| `flatten`
|
|
187
|
+
| `boolean`
|
|
188
|
+
| Merge nested object fields into parent
|
|
189
|
+
|
|
190
|
+
## Interface Support
|
|
191
|
+
|
|
192
|
+
Serialize also works with interfaces. For interfaces, a namespace is generated with a `toJSON` function:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
/** @derive(Serialize) */
|
|
196
|
+
interface ApiResponse {
|
|
197
|
+
status: number;
|
|
198
|
+
message: string;
|
|
199
|
+
timestamp: Date;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Generated:
|
|
203
|
+
// export namespace ApiResponse {
|
|
204
|
+
// export function toJSON(self: ApiResponse): Record<string, unknown> {
|
|
205
|
+
// const result: Record<string, unknown> = {};
|
|
206
|
+
// result["status"] = self.status;
|
|
207
|
+
// result["message"] = self.message;
|
|
208
|
+
// result["timestamp"] = self.timestamp.toISOString();
|
|
209
|
+
// return result;
|
|
210
|
+
// }
|
|
211
|
+
// }
|
|
212
|
+
|
|
213
|
+
const response: ApiResponse = {
|
|
214
|
+
status: 200,
|
|
215
|
+
message: "OK",
|
|
216
|
+
timestamp: new Date()
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
console.log(JSON.stringify(ApiResponse.toJSON(response)));
|
|
220
|
+
// {"status":200,"message":"OK","timestamp":"2024-01-15T10:30:00.000Z"}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Enum Support
|
|
224
|
+
|
|
225
|
+
Serialize also works with enums. The `toJSON` function returns the underlying enum value (string or number):
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
/** @derive(Serialize) */
|
|
229
|
+
enum Status {
|
|
230
|
+
Active = "active",
|
|
231
|
+
Inactive = "inactive",
|
|
232
|
+
Pending = "pending",
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Generated:
|
|
236
|
+
// export namespace Status {
|
|
237
|
+
// export function toJSON(value: Status): string | number {
|
|
238
|
+
// return value;
|
|
239
|
+
// }
|
|
240
|
+
// }
|
|
241
|
+
|
|
242
|
+
console.log(Status.toJSON(Status.Active)); // "active"
|
|
243
|
+
console.log(Status.toJSON(Status.Pending)); // "pending"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Works with numeric enums too:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
/** @derive(Serialize) */
|
|
250
|
+
enum Priority {
|
|
251
|
+
Low = 1,
|
|
252
|
+
Medium = 2,
|
|
253
|
+
High = 3,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log(Priority.toJSON(Priority.High)); // 3
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Type Alias Support
|
|
260
|
+
|
|
261
|
+
Serialize works with type aliases. For object types, fields are serialized with full type handling:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
/** @derive(Serialize) */
|
|
265
|
+
type UserProfile = {
|
|
266
|
+
id: string;
|
|
267
|
+
name: string;
|
|
268
|
+
createdAt: Date;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Generated:
|
|
272
|
+
// export namespace UserProfile {
|
|
273
|
+
// export function toJSON(value: UserProfile): Record<string, unknown> {
|
|
274
|
+
// const result: Record<string, unknown> = {};
|
|
275
|
+
// result["id"] = value.id;
|
|
276
|
+
// result["name"] = value.name;
|
|
277
|
+
// result["createdAt"] = value.createdAt.toISOString();
|
|
278
|
+
// return result;
|
|
279
|
+
// }
|
|
280
|
+
// }
|
|
281
|
+
|
|
282
|
+
const profile: UserProfile = {
|
|
283
|
+
id: "123",
|
|
284
|
+
name: "Alice",
|
|
285
|
+
createdAt: new Date("2024-01-15")
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
console.log(JSON.stringify(UserProfile.toJSON(profile)));
|
|
289
|
+
// {"id":"123","name":"Alice","createdAt":"2024-01-15T00:00:00.000Z"}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
For union types, the value is returned directly:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
/** @derive(Serialize) */
|
|
296
|
+
type ApiStatus = "loading" | "success" | "error";
|
|
297
|
+
|
|
298
|
+
console.log(ApiStatus.toJSON("success")); // "success"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Combining with Deserialize
|
|
302
|
+
|
|
303
|
+
Use both Serialize and Deserialize for complete JSON round-trip support:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
/** @derive(Serialize, Deserialize) */
|
|
307
|
+
class User {
|
|
308
|
+
name: string;
|
|
309
|
+
createdAt: Date;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Serialize
|
|
313
|
+
const user = new User();
|
|
314
|
+
user.name = "Alice";
|
|
315
|
+
user.createdAt = new Date();
|
|
316
|
+
const json = JSON.stringify(user);
|
|
317
|
+
|
|
318
|
+
// Deserialize
|
|
319
|
+
const parsed = User.fromJSON(JSON.parse(json));
|
|
320
|
+
console.log(parsed.createdAt instanceof Date); // true
|
|
321
|
+
```
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
*Macroforge is built as a native Node.js module using Rust and NAPI-RS. It leverages SWC for fast TypeScript parsing and code generation.*
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
┌─────────────────────────────────────────────────────────┐
|
|
9
|
+
│ Node.js / Vite │
|
|
10
|
+
├─────────────────────────────────────────────────────────┤
|
|
11
|
+
│ NAPI-RS Bindings │
|
|
12
|
+
├─────────────────────────────────────────────────────────┤
|
|
13
|
+
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
|
|
14
|
+
│ │ ts_syn │ │ ts_quote │ │ts_macro_derive│ │
|
|
15
|
+
│ │ (parsing) │ │ (templating) │ │ (proc-macro) │ │
|
|
16
|
+
│ └─────────────┘ └──────────────┘ └───────────────┘ │
|
|
17
|
+
├─────────────────────────────────────────────────────────┤
|
|
18
|
+
│ SWC Core │
|
|
19
|
+
│ (TypeScript parsing & codegen) │
|
|
20
|
+
└─────────────────────────────────────────────────────────┘
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Core Components
|
|
24
|
+
|
|
25
|
+
### SWC Core
|
|
26
|
+
|
|
27
|
+
The foundation layer provides:
|
|
28
|
+
|
|
29
|
+
- Fast TypeScript/JavaScript parsing
|
|
30
|
+
|
|
31
|
+
- AST representation
|
|
32
|
+
|
|
33
|
+
- Code generation (AST → source code)
|
|
34
|
+
|
|
35
|
+
### ts_syn
|
|
36
|
+
|
|
37
|
+
A Rust crate that provides:
|
|
38
|
+
|
|
39
|
+
- TypeScript-specific AST types
|
|
40
|
+
|
|
41
|
+
- Parsing utilities for macro input
|
|
42
|
+
|
|
43
|
+
- Derive input structures (class fields, decorators, etc.)
|
|
44
|
+
|
|
45
|
+
### ts_quote
|
|
46
|
+
|
|
47
|
+
Template-based code generation similar to Rust's `quote!`:
|
|
48
|
+
|
|
49
|
+
- `ts_template!` - Generate TypeScript code from templates
|
|
50
|
+
|
|
51
|
+
- `body!` - Generate class body members
|
|
52
|
+
|
|
53
|
+
- Control flow: `{"{#for}"}`, `{"{#if}"}`, `{"{$let}"}`
|
|
54
|
+
|
|
55
|
+
### ts_macro_derive
|
|
56
|
+
|
|
57
|
+
The procedural macro attribute for defining derive macros:
|
|
58
|
+
|
|
59
|
+
- `#[ts_macro_derive(Name)]` attribute
|
|
60
|
+
|
|
61
|
+
- Automatic registration with the macro system
|
|
62
|
+
|
|
63
|
+
- Error handling and span tracking
|
|
64
|
+
|
|
65
|
+
### NAPI-RS Bindings
|
|
66
|
+
|
|
67
|
+
Bridges Rust and Node.js:
|
|
68
|
+
|
|
69
|
+
- Exposes `expandSync`, `transformSync`, etc.
|
|
70
|
+
|
|
71
|
+
- Provides the `NativePlugin` class for caching
|
|
72
|
+
|
|
73
|
+
- Handles data marshaling between Rust and JavaScript
|
|
74
|
+
|
|
75
|
+
## Data Flow
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
1. Source Code (TypeScript with @derive)
|
|
79
|
+
│
|
|
80
|
+
▼
|
|
81
|
+
2. NAPI-RS receives JavaScript string
|
|
82
|
+
│
|
|
83
|
+
▼
|
|
84
|
+
3. SWC parses to AST
|
|
85
|
+
│
|
|
86
|
+
▼
|
|
87
|
+
4. Macro expander finds @derive decorators
|
|
88
|
+
│
|
|
89
|
+
▼
|
|
90
|
+
5. For each macro:
|
|
91
|
+
│ a. Extract class/interface data
|
|
92
|
+
│ b. Run macro function
|
|
93
|
+
│ c. Generate new AST nodes
|
|
94
|
+
│
|
|
95
|
+
▼
|
|
96
|
+
6. Merge generated nodes into AST
|
|
97
|
+
│
|
|
98
|
+
▼
|
|
99
|
+
7. SWC generates source code
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
8. Return to JavaScript with source mapping
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Performance Characteristics
|
|
106
|
+
|
|
107
|
+
- **Thread-safe**: Each expansion runs in an isolated thread with a 32MB stack
|
|
108
|
+
|
|
109
|
+
- **Caching**: `NativePlugin` caches results by file version
|
|
110
|
+
|
|
111
|
+
- **Binary search**: Position mapping uses O(log n) lookups
|
|
112
|
+
|
|
113
|
+
- **Zero-copy**: SWC's arena allocator minimizes allocations
|
|
114
|
+
|
|
115
|
+
## Re-exported Crates
|
|
116
|
+
|
|
117
|
+
For custom macro development, `macroforge_ts` re-exports everything you need:
|
|
118
|
+
|
|
119
|
+
```rust
|
|
120
|
+
// All available via macroforge_ts::*
|
|
121
|
+
pub extern crate ts_syn; // AST types, parsing
|
|
122
|
+
pub extern crate ts_quote; // Code generation templates
|
|
123
|
+
pub extern crate ts_macro_derive; // #[ts_macro_derive] attribute
|
|
124
|
+
pub extern crate inventory; // Macro registration
|
|
125
|
+
pub extern crate serde_json; // Serialization
|
|
126
|
+
pub extern crate napi; // Node.js bindings
|
|
127
|
+
pub extern crate napi_derive; // NAPI proc-macros
|
|
128
|
+
|
|
129
|
+
// SWC modules
|
|
130
|
+
pub use ts_syn::swc_core;
|
|
131
|
+
pub use ts_syn::swc_common;
|
|
132
|
+
pub use ts_syn::swc_ecma_ast;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Next Steps
|
|
136
|
+
|
|
137
|
+
- [Write custom macros]({base}/docs/custom-macros)
|
|
138
|
+
|
|
139
|
+
- [Explore the API reference]({base}/docs/api)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# The Derive System
|
|
2
|
+
|
|
3
|
+
*The derive system is inspired by Rust's derive macros. It allows you to automatically implement common patterns by annotating your classes with `@derive`.*
|
|
4
|
+
|
|
5
|
+
## Syntax Reference
|
|
6
|
+
|
|
7
|
+
Macroforge uses JSDoc comments for all macro annotations. This ensures compatibility with standard TypeScript tooling.
|
|
8
|
+
|
|
9
|
+
### The @derive Statement
|
|
10
|
+
|
|
11
|
+
The `@derive` decorator triggers macro expansion on a class or interface:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
/** @derive(MacroName) */
|
|
15
|
+
class MyClass { }
|
|
16
|
+
|
|
17
|
+
/** @derive(Debug, Clone, PartialEq) */
|
|
18
|
+
class AnotherClass { }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Syntax rules:
|
|
22
|
+
|
|
23
|
+
- Must be inside a JSDoc comment (`/** */`)
|
|
24
|
+
|
|
25
|
+
- Must appear immediately before the class/interface declaration
|
|
26
|
+
|
|
27
|
+
- Multiple macros can be comma-separated: `@derive(A, B, C)`
|
|
28
|
+
|
|
29
|
+
- Multiple `@derive` statements can be stacked
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// Single derive with multiple macros
|
|
33
|
+
/** @derive(Debug, Clone) */
|
|
34
|
+
class User { }
|
|
35
|
+
|
|
36
|
+
// Multiple derive statements (equivalent)
|
|
37
|
+
/** @derive(Debug) */
|
|
38
|
+
/** @derive(Clone) */
|
|
39
|
+
class User { }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### The import macro Statement
|
|
43
|
+
|
|
44
|
+
To use macros from external packages, you must declare them with `import macro`:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
/** import macro { MacroName } from "package-name"; */
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Syntax rules:
|
|
51
|
+
|
|
52
|
+
- Must be inside a JSDoc comment (`/** */`)
|
|
53
|
+
|
|
54
|
+
- Can appear anywhere in the file (typically at the top)
|
|
55
|
+
|
|
56
|
+
- Multiple macros can be imported: `import macro { A, B } from "pkg";`
|
|
57
|
+
|
|
58
|
+
- Multiple import statements can be used for different packages
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
/** import macro { JSON, Validate } from "@my/macros"; */
|
|
62
|
+
/** import macro { Builder } from "@other/macros"; */
|
|
63
|
+
|
|
64
|
+
/** @derive(JSON, Validate, Builder) */
|
|
65
|
+
class User {
|
|
66
|
+
name: string;
|
|
67
|
+
email: string;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
>
|
|
72
|
+
> Built-in macros (Debug, Clone, Default, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize) do not require an import statement.
|
|
73
|
+
|
|
74
|
+
### Field Attributes
|
|
75
|
+
|
|
76
|
+
Macros can define field-level attributes to customize behavior per field:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
/** @attributeName(options) */
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The attribute name and available options depend on the macro. Common patterns:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
/** @derive(Debug, Serialize) */
|
|
86
|
+
class User {
|
|
87
|
+
/** @debug({ rename: "userId" }) */
|
|
88
|
+
/** @serde({ rename: "user_id" }) */
|
|
89
|
+
id: number;
|
|
90
|
+
|
|
91
|
+
name: string;
|
|
92
|
+
|
|
93
|
+
/** @debug({ skip: true }) */
|
|
94
|
+
/** @serde({ skip: true }) */
|
|
95
|
+
password: string;
|
|
96
|
+
|
|
97
|
+
/** @serde({ flatten: true }) */
|
|
98
|
+
metadata: Record<string, unknown>;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Syntax rules:
|
|
103
|
+
|
|
104
|
+
- Must be inside a JSDoc comment immediately before the field
|
|
105
|
+
|
|
106
|
+
- Options use object literal syntax: `@attr({ key: value })`
|
|
107
|
+
|
|
108
|
+
- Boolean options: `@attr({ skip: true })`
|
|
109
|
+
|
|
110
|
+
- String options: `@attr({ rename: "newName" })`
|
|
111
|
+
|
|
112
|
+
- Multiple attributes can be on separate lines or combined
|
|
113
|
+
|
|
114
|
+
Common field attributes by macro:
|
|
115
|
+
|
|
116
|
+
| Debug
|
|
117
|
+
| `@debug`
|
|
118
|
+
| `skip`, `rename`
|
|
119
|
+
|
|
120
|
+
| Clone
|
|
121
|
+
| `@clone`
|
|
122
|
+
| `skip`, `clone_with`
|
|
123
|
+
|
|
124
|
+
| Serialize/Deserialize
|
|
125
|
+
| `@serde`
|
|
126
|
+
| `skip`, `rename`, `flatten`, `default`
|
|
127
|
+
|
|
128
|
+
| Hash
|
|
129
|
+
| `@hash`
|
|
130
|
+
| `skip`
|
|
131
|
+
|
|
132
|
+
| PartialEq/Ord
|
|
133
|
+
| `@eq`, `@ord`
|
|
134
|
+
| `skip`
|
|
135
|
+
|
|
136
|
+
## How It Works
|
|
137
|
+
|
|
138
|
+
1. **Declaration**: You write `@derive(MacroName)` before a class
|
|
139
|
+
|
|
140
|
+
2. **Discovery**: Macroforge finds all derive decorators in your code
|
|
141
|
+
|
|
142
|
+
3. **Expansion**: Each named macro receives the class AST and generates code
|
|
143
|
+
|
|
144
|
+
4. **Injection**: Generated methods/properties are added to the class
|
|
145
|
+
|
|
146
|
+
## What Can Be Derived
|
|
147
|
+
|
|
148
|
+
The derive system works on:
|
|
149
|
+
|
|
150
|
+
- **Classes**: The primary target for derive macros
|
|
151
|
+
|
|
152
|
+
- **Interfaces**: Some macros can generate companion functions
|
|
153
|
+
|
|
154
|
+
> **Warning:**
|
|
155
|
+
> Enums are not currently supported by the derive system.
|
|
156
|
+
|
|
157
|
+
## Built-in vs Custom Macros
|
|
158
|
+
|
|
159
|
+
Macroforge comes with built-in macros that work out of the box. You can also create custom macros in Rust and use them via the `import macro` statement.
|
|
160
|
+
|
|
161
|
+
| Built-in
|
|
162
|
+
| No
|
|
163
|
+
| Debug, Clone, Default, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize
|
|
164
|
+
|
|
165
|
+
| Custom
|
|
166
|
+
| Yes
|
|
167
|
+
| Any macro from an external package
|
|
168
|
+
|
|
169
|
+
## Next Steps
|
|
170
|
+
|
|
171
|
+
- [Explore built-in macros]({base}/docs/builtin-macros)
|
|
172
|
+
|
|
173
|
+
- [Create custom macros]({base}/docs/custom-macros)
|