@macroforge/mcp-server 0.1.34 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,412 +1,93 @@
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';
22
2
 
23
- class User {
24
- name: string;
25
- age: number;
26
- createdAt: Date;
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.
27
6
 
28
- constructor(name: string, age: number) {
29
- this.name = name;
30
- this.age = age;
31
- this.createdAt = new Date();
32
- }
7
+ ## Generated Methods
33
8
 
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
- ``` ```
63
- const user = new User("Alice", 30);
64
- console.log(JSON.stringify(user));
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:**
80
- ```
81
- /** @derive(Serialize) */
82
- class User {
83
- /** @serde({ rename: "user_id" }) */
84
- id: string;
85
-
86
- /** @serde({ rename: "full_name" }) */
87
- name: string;
88
- }
89
- ```
90
- **After:**
91
- ```
92
- import { SerializeContext } from 'macroforge/serde';
9
+ | Type | Generated Code | Description |
10
+ |------|----------------|-------------|
11
+ | Class | `toStringifiedJSON()`, `toObject()`, `__serialize(ctx)` | Instance methods |
12
+ | Enum | `toStringifiedJSONEnumName(value)`, `__serializeEnumName` | Standalone functions |
13
+ | Interface | `toStringifiedJSONInterfaceName(value)`, etc. | Standalone functions |
14
+ | Type Alias | `toStringifiedJSONTypeName(value)`, etc. | Standalone functions |
93
15
 
94
- class User {
95
- id: string;
16
+ ## Configuration
96
17
 
97
- name: string;
18
+ The `functionNamingStyle` option in `macroforge.json` controls naming:
19
+ - `"suffix"` (default): Suffixes with type name (e.g., `toStringifiedJSONMyType`)
20
+ - `"prefix"`: Prefixes with type name (e.g., `myTypeToStringifiedJSON`)
21
+ - `"generic"`: Uses TypeScript generics (e.g., `toStringifiedJSON<T extends MyType>`)
22
+ - `"namespace"`: Legacy namespace wrapping
98
23
 
99
- toStringifiedJSON(): string {
100
- const ctx = SerializeContext.create();
101
- return JSON.stringify(this.__serialize(ctx));
102
- }
24
+ ## Cycle Detection Protocol
103
25
 
104
- toObject(): Record<string, unknown> {
105
- const ctx = SerializeContext.create();
106
- return this.__serialize(ctx);
107
- }
26
+ The generated code handles circular references using `__id` and `__ref` markers:
108
27
 
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
- }
28
+ ```json
29
+ {
30
+ "__type": "User",
31
+ "__id": 1,
32
+ "name": "Alice",
33
+ "friend": { "__ref": 2 } // Reference to object with __id: 2
125
34
  }
126
- ``` ```
127
- const user = new User();
128
- user.id = "123";
129
- user.name = "Alice";
130
- console.log(JSON.stringify(user));
131
- // {"user_id":"123","full_name":"Alice"}
132
- ``` ### Skipping Fields
133
- **Before:**
134
35
  ```
135
- /** @derive(Serialize) */
136
- class User {
137
- name: string;
138
- email: string;
139
36
 
140
- /** @serde({ skip: true }) */
141
- password: string;
37
+ When an object is serialized:
38
+ 1. Check if it's already been serialized (has an `__id`)
39
+ 2. If so, return `{ "__ref": existingId }` instead
40
+ 3. Otherwise, register the object and serialize its fields
142
41
 
143
- /** @serde({ skip_serializing: true }) */
144
- internalId: string;
145
- }
146
- ```
147
- **After:**
148
- ```
149
- import { SerializeContext } from 'macroforge/serde';
42
+ ## Type-Specific Serialization
150
43
 
151
- class User {
152
- name: string;
153
- email: string;
44
+ | Type | Serialization Strategy |
45
+ |------|------------------------|
46
+ | Primitives | Direct value |
47
+ | `Date` | `toISOString()` |
48
+ | Arrays | For primitive-like element types, pass through; for `Date`/`Date | null`, map to ISO strings; otherwise map and call `__serialize(ctx)` when available |
49
+ | `Map<K,V>` | For primitive-like values, `Object.fromEntries(map.entries())`; for `Date`/`Date | null`, convert to ISO strings; otherwise call `__serialize(ctx)` per value when available |
50
+ | `Set<T>` | Convert to array; element handling matches `Array<T>` |
51
+ | Nullable | Include `null` explicitly; for primitive-like and `Date` unions the generator avoids runtime `__serialize` checks |
52
+ | Objects | Call `__serialize(ctx)` if available (to support user-defined implementations) |
154
53
 
155
- password: string;
54
+ Note: the generator specializes some code paths based on the declared TypeScript type to
55
+ avoid runtime feature detection on primitives and literal unions.
156
56
 
157
- internalId: string;
57
+ ## Field-Level Options
158
58
 
159
- toStringifiedJSON(): string {
160
- const ctx = SerializeContext.create();
161
- return JSON.stringify(this.__serialize(ctx));
162
- }
59
+ The `@serde` decorator supports:
163
60
 
164
- toObject(): Record<string, unknown> {
165
- const ctx = SerializeContext.create();
166
- return this.__serialize(ctx);
167
- }
61
+ - `skip` / `skip_serializing` - Exclude field from serialization
62
+ - `rename = "jsonKey"` - Use different JSON property name
63
+ - `flatten` - Merge nested object's fields into parent
168
64
 
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) */
192
- /** @serde({ rename_all: "camelCase" }) */
193
- class ApiResponse {
194
- user_name: string;
195
- created_at: Date;
196
- is_active: boolean;
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) */
208
- class Address {
209
- city: string;
210
- zip: string;
211
- }
65
+ ## Example
212
66
 
213
- /** @derive(Serialize) */
67
+ ```typescript
68
+ @derive(Serialize)
214
69
  class User {
215
- name: string;
70
+ id: number;
216
71
 
217
- /** @serde({ flatten: true }) */
218
- address: Address;
219
- }
220
- ``` ```
221
- const user = new User();
222
- user.name = "Alice";
223
- user.address = { city: "NYC", zip: "10001" };
224
- console.log(JSON.stringify(user));
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:**
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';
72
+ @serde(rename = "userName")
73
+ name: string;
252
74
 
253
- interface ApiResponse {
254
- status: number;
255
- message: string;
256
- timestamp: Date;
257
- }
75
+ @serde(skip_serializing)
76
+ password: string;
258
77
 
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
- }
78
+ @serde(flatten)
79
+ metadata: UserMetadata;
280
80
  }
281
- ``` ```
282
- const response: ApiResponse = {
283
- status: 200,
284
- message: "OK",
285
- timestamp: new Date()
286
- };
287
81
 
288
- console.log(JSON.stringify(ApiResponse.toJSON(response)));
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:**
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
- }
82
+ // Usage:
83
+ const user = new User();
84
+ const json = user.toStringifiedJSON();
85
+ // => '{"__type":"User","__id":1,"id":1,"userName":"Alice",...}'
308
86
 
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
- ``` ```
318
- console.log(Status.toJSON(Status.Active)); // "active"
319
- console.log(Status.toJSON(Status.Pending)); // "pending"
320
- ``` Works with numeric enums too:
321
- **Source:**
322
- ```
323
- /** @derive(Serialize) */
324
- enum Priority {
325
- Low = 1,
326
- Medium = 2,
327
- High = 3,
328
- }
329
- ``` ```
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:**
87
+ const obj = user.toObject();
88
+ // => { __type: "User", __id: 1, id: 1, userName: "Alice", ... }
334
89
  ```
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';
345
-
346
- type UserProfile = {
347
- id: string;
348
- name: string;
349
- createdAt: Date;
350
- };
351
90
 
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
- ``` ```
378
- const profile: UserProfile = {
379
- id: "123",
380
- name: "Alice",
381
- createdAt: new Date("2024-01-15")
382
- };
383
-
384
- console.log(JSON.stringify(UserProfile.toJSON(profile)));
385
- // {"id":"123","name":"Alice","createdAt":"2024-01-15T00:00:00.000Z"}
386
- ``` For union types, the value is returned directly:
387
- **Source:**
388
- ```
389
- /** @derive(Serialize) */
390
- type ApiStatus = "loading" | "success" | "error";
391
- ``` ```
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:**
396
- ```
397
- /** @derive(Serialize, Deserialize) */
398
- class User {
399
- name: string;
400
- createdAt: Date;
401
- }
402
- ``` ```
403
- // Serialize
404
- const user = new User();
405
- user.name = "Alice";
406
- user.createdAt = new Date();
407
- const json = JSON.stringify(user);
91
+ ## Required Import
408
92
 
409
- // Deserialize
410
- const parsed = User.fromJSON(JSON.parse(json));
411
- console.log(parsed.createdAt instanceof Date); // true
412
- ```
93
+ The generated code automatically imports `SerializeContext` from `macroforge/serde`.
@@ -106,10 +106,7 @@ class User {
106
106
  result['user_id'] = this.id;
107
107
  result['name'] = this.name;
108
108
  {
109
- const __flattened =
110
- typeof (this.metadata as any)?.__serialize === 'function'
111
- ? (this.metadata as any).__serialize(ctx)
112
- : this.metadata;
109
+ const __flattened = __serializeRecord<string, unknown>(this.metadata, ctx);
113
110
  const { __type: _, __id: __, ...rest } = __flattened as any;
114
111
  Object.assign(result, rest);
115
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@macroforge/mcp-server",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "MCP server for Macroforge documentation and code analysis",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "typescript": "^5.7.0"
30
30
  },
31
31
  "peerDependencies": {
32
- "macroforge": "^0.1.34"
32
+ "macroforge": "^0.1.35"
33
33
  },
34
34
  "peerDependenciesMeta": {
35
35
  "macroforge": {