@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.
Files changed (50) hide show
  1. package/README.md +68 -0
  2. package/dist/index.d.ts +32 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +46 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/tools/docs-loader.d.ts +133 -5
  7. package/dist/tools/docs-loader.d.ts.map +1 -1
  8. package/dist/tools/docs-loader.js +131 -15
  9. package/dist/tools/docs-loader.js.map +1 -1
  10. package/dist/tools/index.d.ts +48 -1
  11. package/dist/tools/index.d.ts.map +1 -1
  12. package/dist/tools/index.js +163 -14
  13. package/dist/tools/index.js.map +1 -1
  14. package/docs/api/api-overview.md +24 -46
  15. package/docs/api/expand-sync.md +24 -51
  16. package/docs/api/native-plugin.md +24 -56
  17. package/docs/api/position-mapper.md +34 -76
  18. package/docs/api/transform-sync.md +27 -59
  19. package/docs/builtin-macros/clone.md +150 -68
  20. package/docs/builtin-macros/debug.md +216 -81
  21. package/docs/builtin-macros/default.md +234 -91
  22. package/docs/builtin-macros/deserialize.md +891 -166
  23. package/docs/builtin-macros/hash.md +238 -82
  24. package/docs/builtin-macros/macros-overview.md +42 -103
  25. package/docs/builtin-macros/ord.md +205 -92
  26. package/docs/builtin-macros/partial-eq.md +178 -97
  27. package/docs/builtin-macros/partial-ord.md +209 -98
  28. package/docs/builtin-macros/serialize.md +326 -137
  29. package/docs/concepts/architecture.md +40 -99
  30. package/docs/concepts/derive-system.md +132 -125
  31. package/docs/concepts/how-macros-work.md +52 -84
  32. package/docs/custom-macros/custom-overview.md +17 -39
  33. package/docs/custom-macros/rust-setup.md +22 -55
  34. package/docs/custom-macros/ts-macro-derive.md +43 -107
  35. package/docs/custom-macros/ts-quote.md +177 -507
  36. package/docs/getting-started/first-macro.md +108 -33
  37. package/docs/getting-started/installation.md +32 -73
  38. package/docs/integration/cli.md +70 -156
  39. package/docs/integration/configuration.md +32 -75
  40. package/docs/integration/integration-overview.md +16 -55
  41. package/docs/integration/mcp-server.md +30 -69
  42. package/docs/integration/svelte-preprocessor.md +60 -83
  43. package/docs/integration/typescript-plugin.md +32 -74
  44. package/docs/integration/vite-plugin.md +30 -79
  45. package/docs/language-servers/ls-overview.md +22 -46
  46. package/docs/language-servers/svelte.md +30 -69
  47. package/docs/language-servers/zed.md +34 -72
  48. package/docs/roadmap/roadmap.md +54 -130
  49. package/docs/sections.json +3 -262
  50. package/package.json +2 -2
@@ -1,31 +1,206 @@
1
1
  # Deserialize
2
+ *The `Deserialize` macro generates JSON deserialization methods with **cycle and forward-reference support**, plus comprehensive runtime validation. This enables safe parsing of complex JSON structures including circular references.*
3
+ ## Basic Usage
4
+ **Before:**
5
+ ```
6
+ /** @derive(Deserialize) */
7
+ class User {
8
+ name: string;
9
+ age: number;
10
+ createdAt: Date;
11
+ }
12
+ ```
13
+ **After:**
14
+ ```
15
+ import { Result } from 'macroforge/utils';
16
+ import { DeserializeContext } from 'macroforge/serde';
17
+ import { DeserializeError } from 'macroforge/serde';
18
+ import type { DeserializeOptions } from 'macroforge/serde';
19
+ import { PendingRef } from 'macroforge/serde';
2
20
 
3
- *The `Deserialize` macro generates a static `fromJSON()` method that parses JSON data into your class with runtime validation and automatic type conversion.*
4
-
5
- ## Basic Usage
6
-
7
- <MacroExample before={data.examples.basic.before} after={data.examples.basic.after} />
8
-
9
- ```typescript
21
+ class User {
22
+ name: string;
23
+ age: number;
24
+ createdAt: Date;
25
+
26
+ constructor(props: {
27
+ name: string;
28
+ age: number;
29
+ createdAt: Date;
30
+ }) {
31
+ this.name = props.name;
32
+ this.age = props.age;
33
+ this.createdAt = props.createdAt;
34
+ }
35
+
36
+ static fromStringifiedJSON(
37
+ json: string,
38
+ opts?: DeserializeOptions
39
+ ): Result<
40
+ User,
41
+ Array<{
42
+ field: string;
43
+ message: string;
44
+ }>
45
+ > {
46
+ try {
47
+ const raw = JSON.parse(json);
48
+ return User.fromObject(raw, opts);
49
+ } catch (e) {
50
+ if (e instanceof DeserializeError) {
51
+ return Result.err(e.errors);
52
+ }
53
+ const message = e instanceof Error ? e.message : String(e);
54
+ return Result.err([
55
+ {
56
+ field: '_root',
57
+ message
58
+ }
59
+ ]);
60
+ }
61
+ }
62
+
63
+ static fromObject(
64
+ obj: unknown,
65
+ opts?: DeserializeOptions
66
+ ): Result<
67
+ User,
68
+ Array<{
69
+ field: string;
70
+ message: string;
71
+ }>
72
+ > {
73
+ try {
74
+ const ctx = DeserializeContext.create();
75
+ const resultOrRef = User.__deserialize(obj, ctx);
76
+ if (PendingRef.is(resultOrRef)) {
77
+ return Result.err([
78
+ {
79
+ field: '_root',
80
+ message: 'User.fromObject: root cannot be a forward reference'
81
+ }
82
+ ]);
83
+ }
84
+ ctx.applyPatches();
85
+ if (opts?.freeze) {
86
+ ctx.freezeAll();
87
+ }
88
+ return Result.ok(resultOrRef);
89
+ } catch (e) {
90
+ if (e instanceof DeserializeError) {
91
+ return Result.err(e.errors);
92
+ }
93
+ const message = e instanceof Error ? e.message : String(e);
94
+ return Result.err([
95
+ {
96
+ field: '_root',
97
+ message
98
+ }
99
+ ]);
100
+ }
101
+ }
102
+
103
+ static __deserialize(value: any, ctx: DeserializeContext): User | PendingRef {
104
+ if (value?.__ref !== undefined) {
105
+ return ctx.getOrDefer(value.__ref);
106
+ }
107
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
108
+ throw new DeserializeError([
109
+ {
110
+ field: '_root',
111
+ message: 'User.__deserialize: expected an object'
112
+ }
113
+ ]);
114
+ }
115
+ const obj = value as Record<string, unknown>;
116
+ const errors: Array<{
117
+ field: string;
118
+ message: string;
119
+ }> = [];
120
+ if (!('name' in obj)) {
121
+ errors.push({
122
+ field: 'name',
123
+ message: 'missing required field'
124
+ });
125
+ }
126
+ if (!('age' in obj)) {
127
+ errors.push({
128
+ field: 'age',
129
+ message: 'missing required field'
130
+ });
131
+ }
132
+ if (!('createdAt' in obj)) {
133
+ errors.push({
134
+ field: 'createdAt',
135
+ message: 'missing required field'
136
+ });
137
+ }
138
+ if (errors.length > 0) {
139
+ throw new DeserializeError(errors);
140
+ }
141
+ const instance = Object.create(User.prototype) as User;
142
+ if (obj.__id !== undefined) {
143
+ ctx.register(obj.__id as number, instance);
144
+ }
145
+ ctx.trackForFreeze(instance);
146
+ {
147
+ const __raw_name = obj['name'] as string;
148
+ instance.name = __raw_name;
149
+ }
150
+ {
151
+ const __raw_age = obj['age'] as number;
152
+ instance.age = __raw_age;
153
+ }
154
+ {
155
+ const __raw_createdAt = obj['createdAt'] as Date;
156
+ {
157
+ const __dateVal =
158
+ typeof __raw_createdAt === 'string'
159
+ ? new Date(__raw_createdAt)
160
+ : (__raw_createdAt as Date);
161
+ instance.createdAt = __dateVal;
162
+ }
163
+ }
164
+ if (errors.length > 0) {
165
+ throw new DeserializeError(errors);
166
+ }
167
+ return instance;
168
+ }
169
+
170
+ static validateField<K extends keyof User>(
171
+ field: K,
172
+ value: User[K]
173
+ ): Array<{
174
+ field: string;
175
+ message: string;
176
+ }> {
177
+ return [];
178
+ }
179
+
180
+ static validateFields(partial: Partial<User>): Array<{
181
+ field: string;
182
+ message: string;
183
+ }> {
184
+ return [];
185
+ }
186
+ }
187
+ ``` ```
10
188
  const json = '{"name":"Alice","age":30,"createdAt":"2024-01-15T10:30:00.000Z"}';
11
189
  const user = User.fromJSON(JSON.parse(json));
12
190
 
13
191
  console.log(user.name); // "Alice"
14
192
  console.log(user.age); // 30
15
193
  console.log(user.createdAt instanceof Date); // true
194
+ ``` ## Runtime Validation
195
+ Deserialize validates the input data and throws descriptive errors:
196
+ **Source:**
16
197
  ```
17
-
18
- ## Runtime Validation
19
-
20
- Deserialize validates the input data and throws descriptive errors:
21
-
22
- <InteractiveMacro code={`/** @derive(Deserialize) */
198
+ /** @derive(Deserialize) */
23
199
  class User {
24
200
  name: string;
25
201
  email: string;
26
- }`} />
27
-
28
- ```typescript
202
+ }
203
+ ``` ```
29
204
  // Missing required field
30
205
  User.fromJSON({ name: "Alice" });
31
206
  // Error: User.fromJSON: missing required field "email"
@@ -37,63 +212,374 @@ User.fromJSON("not an object");
37
212
  // Array instead of object
38
213
  User.fromJSON([1, 2, 3]);
39
214
  // Error: User.fromJSON: expected an object, got array
215
+ ``` ## Automatic Type Conversion
216
+ Deserialize automatically converts JSON types to their TypeScript equivalents:
217
+ | JSON Type | TypeScript Type | Conversion |
218
+ | --- | --- | --- |
219
+ | string/number/boolean | `string`/`number`/`boolean` | Direct assignment |
220
+ | ISO string | `Date` | `new Date(string)` |
221
+ | array | `T[]` | Maps items with auto-detection |
222
+ | object | `Map<K, V>` | `new Map(Object.entries())` |
223
+ | array | `Set<T>` | `new Set(array)` |
224
+ | object | Nested class | Calls `fromJSON()` if available |
225
+ ## Serde Options
226
+ Use the `@serde` decorator to customize deserialization:
227
+ ### Renaming Fields
228
+ **Before:**
40
229
  ```
230
+ /** @derive(Deserialize) */
231
+ class User {
232
+ /** @serde({ rename: "user_id" }) */
233
+ id: string;
41
234
 
42
- ## Automatic Type Conversion
43
-
44
- Deserialize automatically converts JSON types to their TypeScript equivalents:
45
-
46
- | string/number/boolean
47
- | `string`/`number`/`boolean`
48
- | Direct assignment
49
-
50
- | ISO string
51
- | `Date`
52
- | `new Date(string)`
53
-
54
- | array
55
- | `T[]`
56
- | Maps items with auto-detection
57
-
58
- | object
59
- | `Map<K, V>`
60
- | `new Map(Object.entries())`
61
-
62
- | array
63
- | `Set<T>`
64
- | `new Set(array)`
65
-
66
- | object
67
- | Nested class
68
- | Calls `fromJSON()` if available
69
-
70
- ## Serde Options
71
-
72
- Use the `@serde` decorator to customize deserialization:
73
-
74
- ### Renaming Fields
75
-
76
- <MacroExample before={data.examples.rename.before} after={data.examples.rename.after} />
235
+ /** @serde({ rename: "full_name" }) */
236
+ name: string;
237
+ }
238
+ ```
239
+ **After:**
240
+ ```
241
+ import { Result } from 'macroforge/utils';
242
+ import { DeserializeContext } from 'macroforge/serde';
243
+ import { DeserializeError } from 'macroforge/serde';
244
+ import type { DeserializeOptions } from 'macroforge/serde';
245
+ import { PendingRef } from 'macroforge/serde';
77
246
 
78
- ```typescript
247
+ class User {
248
+ id: string;
249
+
250
+ name: string;
251
+
252
+ constructor(props: {
253
+ id: string;
254
+ name: string;
255
+ }) {
256
+ this.id = props.id;
257
+ this.name = props.name;
258
+ }
259
+
260
+ static fromStringifiedJSON(
261
+ json: string,
262
+ opts?: DeserializeOptions
263
+ ): Result<
264
+ User,
265
+ Array<{
266
+ field: string;
267
+ message: string;
268
+ }>
269
+ > {
270
+ try {
271
+ const raw = JSON.parse(json);
272
+ return User.fromObject(raw, opts);
273
+ } catch (e) {
274
+ if (e instanceof DeserializeError) {
275
+ return Result.err(e.errors);
276
+ }
277
+ const message = e instanceof Error ? e.message : String(e);
278
+ return Result.err([
279
+ {
280
+ field: '_root',
281
+ message
282
+ }
283
+ ]);
284
+ }
285
+ }
286
+
287
+ static fromObject(
288
+ obj: unknown,
289
+ opts?: DeserializeOptions
290
+ ): Result<
291
+ User,
292
+ Array<{
293
+ field: string;
294
+ message: string;
295
+ }>
296
+ > {
297
+ try {
298
+ const ctx = DeserializeContext.create();
299
+ const resultOrRef = User.__deserialize(obj, ctx);
300
+ if (PendingRef.is(resultOrRef)) {
301
+ return Result.err([
302
+ {
303
+ field: '_root',
304
+ message: 'User.fromObject: root cannot be a forward reference'
305
+ }
306
+ ]);
307
+ }
308
+ ctx.applyPatches();
309
+ if (opts?.freeze) {
310
+ ctx.freezeAll();
311
+ }
312
+ return Result.ok(resultOrRef);
313
+ } catch (e) {
314
+ if (e instanceof DeserializeError) {
315
+ return Result.err(e.errors);
316
+ }
317
+ const message = e instanceof Error ? e.message : String(e);
318
+ return Result.err([
319
+ {
320
+ field: '_root',
321
+ message
322
+ }
323
+ ]);
324
+ }
325
+ }
326
+
327
+ static __deserialize(value: any, ctx: DeserializeContext): User | PendingRef {
328
+ if (value?.__ref !== undefined) {
329
+ return ctx.getOrDefer(value.__ref);
330
+ }
331
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
332
+ throw new DeserializeError([
333
+ {
334
+ field: '_root',
335
+ message: 'User.__deserialize: expected an object'
336
+ }
337
+ ]);
338
+ }
339
+ const obj = value as Record<string, unknown>;
340
+ const errors: Array<{
341
+ field: string;
342
+ message: string;
343
+ }> = [];
344
+ if (!('user_id' in obj)) {
345
+ errors.push({
346
+ field: 'user_id',
347
+ message: 'missing required field'
348
+ });
349
+ }
350
+ if (!('full_name' in obj)) {
351
+ errors.push({
352
+ field: 'full_name',
353
+ message: 'missing required field'
354
+ });
355
+ }
356
+ if (errors.length > 0) {
357
+ throw new DeserializeError(errors);
358
+ }
359
+ const instance = Object.create(User.prototype) as User;
360
+ if (obj.__id !== undefined) {
361
+ ctx.register(obj.__id as number, instance);
362
+ }
363
+ ctx.trackForFreeze(instance);
364
+ {
365
+ const __raw_id = obj['user_id'] as string;
366
+ instance.id = __raw_id;
367
+ }
368
+ {
369
+ const __raw_name = obj['full_name'] as string;
370
+ instance.name = __raw_name;
371
+ }
372
+ if (errors.length > 0) {
373
+ throw new DeserializeError(errors);
374
+ }
375
+ return instance;
376
+ }
377
+
378
+ static validateField<K extends keyof User>(
379
+ field: K,
380
+ value: User[K]
381
+ ): Array<{
382
+ field: string;
383
+ message: string;
384
+ }> {
385
+ return [];
386
+ }
387
+
388
+ static validateFields(partial: Partial<User>): Array<{
389
+ field: string;
390
+ message: string;
391
+ }> {
392
+ return [];
393
+ }
394
+ }
395
+ ``` ```
79
396
  const user = User.fromJSON({ user_id: "123", full_name: "Alice" });
80
397
  console.log(user.id); // "123"
81
398
  console.log(user.name); // "Alice"
399
+ ``` ### Default Values
400
+ **Before:**
82
401
  ```
402
+ /** @derive(Deserialize) */
403
+ class Config {
404
+ host: string;
83
405
 
84
- ### Default Values
85
-
86
- <MacroExample before={data.examples.default.before} after={data.examples.default.after} />
406
+ /** @serde({ default: "3000" }) */
407
+ port: string;
87
408
 
88
- ```typescript
409
+ /** @serde({ default: "false" }) */
410
+ debug: boolean;
411
+ }
412
+ ```
413
+ **After:**
414
+ ```
415
+ import { Result } from 'macroforge/utils';
416
+ import { DeserializeContext } from 'macroforge/serde';
417
+ import { DeserializeError } from 'macroforge/serde';
418
+ import type { DeserializeOptions } from 'macroforge/serde';
419
+ import { PendingRef } from 'macroforge/serde';
420
+
421
+ class Config {
422
+ host: string;
423
+
424
+ port: string;
425
+
426
+ debug: boolean;
427
+
428
+ constructor(props: {
429
+ host: string;
430
+ port?: string;
431
+ debug?: boolean;
432
+ }) {
433
+ this.host = props.host;
434
+ this.port = props.port as string;
435
+ this.debug = props.debug as boolean;
436
+ }
437
+
438
+ static fromStringifiedJSON(
439
+ json: string,
440
+ opts?: DeserializeOptions
441
+ ): Result<
442
+ Config,
443
+ Array<{
444
+ field: string;
445
+ message: string;
446
+ }>
447
+ > {
448
+ try {
449
+ const raw = JSON.parse(json);
450
+ return Config.fromObject(raw, opts);
451
+ } catch (e) {
452
+ if (e instanceof DeserializeError) {
453
+ return Result.err(e.errors);
454
+ }
455
+ const message = e instanceof Error ? e.message : String(e);
456
+ return Result.err([
457
+ {
458
+ field: '_root',
459
+ message
460
+ }
461
+ ]);
462
+ }
463
+ }
464
+
465
+ static fromObject(
466
+ obj: unknown,
467
+ opts?: DeserializeOptions
468
+ ): Result<
469
+ Config,
470
+ Array<{
471
+ field: string;
472
+ message: string;
473
+ }>
474
+ > {
475
+ try {
476
+ const ctx = DeserializeContext.create();
477
+ const resultOrRef = Config.__deserialize(obj, ctx);
478
+ if (PendingRef.is(resultOrRef)) {
479
+ return Result.err([
480
+ {
481
+ field: '_root',
482
+ message: 'Config.fromObject: root cannot be a forward reference'
483
+ }
484
+ ]);
485
+ }
486
+ ctx.applyPatches();
487
+ if (opts?.freeze) {
488
+ ctx.freezeAll();
489
+ }
490
+ return Result.ok(resultOrRef);
491
+ } catch (e) {
492
+ if (e instanceof DeserializeError) {
493
+ return Result.err(e.errors);
494
+ }
495
+ const message = e instanceof Error ? e.message : String(e);
496
+ return Result.err([
497
+ {
498
+ field: '_root',
499
+ message
500
+ }
501
+ ]);
502
+ }
503
+ }
504
+
505
+ static __deserialize(value: any, ctx: DeserializeContext): Config | PendingRef {
506
+ if (value?.__ref !== undefined) {
507
+ return ctx.getOrDefer(value.__ref);
508
+ }
509
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
510
+ throw new DeserializeError([
511
+ {
512
+ field: '_root',
513
+ message: 'Config.__deserialize: expected an object'
514
+ }
515
+ ]);
516
+ }
517
+ const obj = value as Record<string, unknown>;
518
+ const errors: Array<{
519
+ field: string;
520
+ message: string;
521
+ }> = [];
522
+ if (!('host' in obj)) {
523
+ errors.push({
524
+ field: 'host',
525
+ message: 'missing required field'
526
+ });
527
+ }
528
+ if (errors.length > 0) {
529
+ throw new DeserializeError(errors);
530
+ }
531
+ const instance = Object.create(Config.prototype) as Config;
532
+ if (obj.__id !== undefined) {
533
+ ctx.register(obj.__id as number, instance);
534
+ }
535
+ ctx.trackForFreeze(instance);
536
+ {
537
+ const __raw_host = obj['host'] as string;
538
+ instance.host = __raw_host;
539
+ }
540
+ if ('port' in obj && obj['port'] !== undefined) {
541
+ const __raw_port = obj['port'] as string;
542
+ instance.port = __raw_port;
543
+ } else {
544
+ instance.port = 3000;
545
+ }
546
+ if ('debug' in obj && obj['debug'] !== undefined) {
547
+ const __raw_debug = obj['debug'] as boolean;
548
+ instance.debug = __raw_debug;
549
+ } else {
550
+ instance.debug = false;
551
+ }
552
+ if (errors.length > 0) {
553
+ throw new DeserializeError(errors);
554
+ }
555
+ return instance;
556
+ }
557
+
558
+ static validateField<K extends keyof Config>(
559
+ field: K,
560
+ value: Config[K]
561
+ ): Array<{
562
+ field: string;
563
+ message: string;
564
+ }> {
565
+ return [];
566
+ }
567
+
568
+ static validateFields(partial: Partial<Config>): Array<{
569
+ field: string;
570
+ message: string;
571
+ }> {
572
+ return [];
573
+ }
574
+ }
575
+ ``` ```
89
576
  const config = Config.fromJSON({ host: "localhost" });
90
577
  console.log(config.port); // "3000"
91
578
  console.log(config.debug); // false
579
+ ``` ### Skipping Fields
580
+ **Source:**
92
581
  ```
93
-
94
- ### Skipping Fields
95
-
96
- <InteractiveMacro code={`/** @derive(Deserialize) */
582
+ /** @derive(Deserialize) */
97
583
  class User {
98
584
  name: string;
99
585
  email: string;
@@ -103,31 +589,25 @@ class User {
103
589
 
104
590
  /** @serde({ skip_deserializing: true }) */
105
591
  computedField: string;
106
- }`} />
107
-
108
- <Alert type="tip" title="skip vs skip_deserializing">
109
- Use `skip: true` to exclude from both serialization and deserialization.
110
- Use `skip_deserializing: true` to only skip during deserialization.
111
- </Alert>
112
-
113
- ### Deny Unknown Fields
114
-
115
- <InteractiveMacro code={`/** @derive(Deserialize) */
592
+ }
593
+ ``` **skip vs skip_deserializing Use `skip: true` to exclude from both serialization and deserialization.
594
+ Use `skip_deserializing: true` to only skip during deserialization. ### Deny Unknown Fields
595
+ ****Source:**
596
+ ```
597
+ /** @derive(Deserialize) */
116
598
  /** @serde({ deny_unknown_fields: true }) */
117
599
  class StrictUser {
118
600
  name: string;
119
601
  email: string;
120
- }`} />
121
-
122
- ```typescript
602
+ }
603
+ ``` ```
123
604
  // This will throw an error
124
605
  StrictUser.fromJSON({ name: "Alice", email: "a@b.com", extra: "field" });
125
606
  // Error: StrictUser.fromJSON: unknown field "extra"
607
+ ``` ### Flatten Nested Objects
608
+ **Source:**
126
609
  ```
127
-
128
- ### Flatten Nested Objects
129
-
130
- <InteractiveMacro code={`/** @derive(Deserialize) */
610
+ /** @derive(Deserialize) */
131
611
  class Address {
132
612
  city: string;
133
613
  zip: string;
@@ -139,9 +619,8 @@ class User {
139
619
 
140
620
  /** @serde({ flatten: true }) */
141
621
  address: Address;
142
- }`} />
143
-
144
- ```typescript
622
+ }
623
+ ``` ```
145
624
  // Flat JSON structure
146
625
  const user = User.fromJSON({
147
626
  name: "Alice",
@@ -149,49 +628,153 @@ const user = User.fromJSON({
149
628
  zip: "10001"
150
629
  });
151
630
  console.log(user.address.city); // "NYC"
631
+ ``` ## All Options
632
+ ### Container Options (on class/interface)
633
+ | Option | Type | Description |
634
+ | --- | --- | --- |
635
+ | `rename_all` | `string` | Apply naming convention to all fields |
636
+ | `deny_unknown_fields` | `boolean` | Throw error if JSON has unknown keys |
637
+ ### Field Options (on properties)
638
+ | Option | Type | Description |
639
+ | --- | --- | --- |
640
+ | `rename` | `string` | Use a different JSON key |
641
+ | `skip` | `boolean` | Exclude from serialization and deserialization |
642
+ | `skip_deserializing` | `boolean` | Exclude from deserialization only |
643
+ | `default` | `boolean | string` | Use TypeScript default or custom expression if missing |
644
+ | `flatten` | `boolean` | Merge nested object fields from parent |
645
+ ## Interface Support
646
+ Deserialize also works with interfaces. For interfaces, a namespace is generated with `is` (type guard) and `fromJSON` functions:
647
+ **Before:**
152
648
  ```
649
+ /** @derive(Deserialize) */
650
+ interface ApiResponse {
651
+ status: number;
652
+ message: string;
653
+ timestamp: Date;
654
+ }
655
+ ```
656
+ **After:**
657
+ ```
658
+ import { Result } from 'macroforge/utils';
659
+ import { DeserializeContext } from 'macroforge/serde';
660
+ import { DeserializeError } from 'macroforge/serde';
661
+ import type { DeserializeOptions } from 'macroforge/serde';
662
+ import { PendingRef } from 'macroforge/serde';
663
+
664
+ interface ApiResponse {
665
+ status: number;
666
+ message: string;
667
+ timestamp: Date;
668
+ }
153
669
 
154
- ## All Options
155
-
156
- ### Container Options (on class/interface)
157
-
158
- | `rename_all`
159
- | `string`
160
- | Apply naming convention to all fields
161
-
162
- | `deny_unknown_fields`
163
- | `boolean`
164
- | Throw error if JSON has unknown keys
165
-
166
- ### Field Options (on properties)
167
-
168
- | `rename`
169
- | `string`
170
- | Use a different JSON key
171
-
172
- | `skip`
173
- | `boolean`
174
- | Exclude from serialization and deserialization
175
-
176
- | `skip_deserializing`
177
- | `boolean`
178
- | Exclude from deserialization only
179
-
180
- | `default`
181
- | `boolean | string`
182
- | Use TypeScript default or custom expression if missing
183
-
184
- | `flatten`
185
- | `boolean`
186
- | Merge nested object fields from parent
187
-
188
- ## Interface Support
189
-
190
- Deserialize also works with interfaces. For interfaces, a namespace is generated with `is` (type guard) and `fromJSON` functions:
191
-
192
- <MacroExample before={data.examples.interface.before} after={data.examples.interface.after} />
193
-
194
- ```typescript
670
+ export namespace ApiResponse {
671
+ export function fromStringifiedJSON(
672
+ json: string,
673
+ opts?: DeserializeOptions
674
+ ): Result<ApiResponse, Array<{ field: string; message: string }>> {
675
+ try {
676
+ const raw = JSON.parse(json);
677
+ return fromObject(raw, opts);
678
+ } catch (e) {
679
+ if (e instanceof DeserializeError) {
680
+ return Result.err(e.errors);
681
+ }
682
+ const message = e instanceof Error ? e.message : String(e);
683
+ return Result.err([{ field: '_root', message }]);
684
+ }
685
+ }
686
+ export function fromObject(
687
+ obj: unknown,
688
+ opts?: DeserializeOptions
689
+ ): Result<ApiResponse, Array<{ field: string; message: string }>> {
690
+ try {
691
+ const ctx = DeserializeContext.create();
692
+ const resultOrRef = __deserialize(obj, ctx);
693
+ if (PendingRef.is(resultOrRef)) {
694
+ return Result.err([
695
+ {
696
+ field: '_root',
697
+ message: 'ApiResponse.fromObject: root cannot be a forward reference'
698
+ }
699
+ ]);
700
+ }
701
+ ctx.applyPatches();
702
+ if (opts?.freeze) {
703
+ ctx.freezeAll();
704
+ }
705
+ return Result.ok(resultOrRef);
706
+ } catch (e) {
707
+ if (e instanceof DeserializeError) {
708
+ return Result.err(e.errors);
709
+ }
710
+ const message = e instanceof Error ? e.message : String(e);
711
+ return Result.err([{ field: '_root', message }]);
712
+ }
713
+ }
714
+ export function __deserialize(value: any, ctx: DeserializeContext): ApiResponse | PendingRef {
715
+ if (value?.__ref !== undefined) {
716
+ return ctx.getOrDefer(value.__ref);
717
+ }
718
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
719
+ throw new DeserializeError([
720
+ { field: '_root', message: 'ApiResponse.__deserialize: expected an object' }
721
+ ]);
722
+ }
723
+ const obj = value as Record<string, unknown>;
724
+ const errors: Array<{ field: string; message: string }> = [];
725
+ if (!('status' in obj)) {
726
+ errors.push({ field: 'status', message: 'missing required field' });
727
+ }
728
+ if (!('message' in obj)) {
729
+ errors.push({ field: 'message', message: 'missing required field' });
730
+ }
731
+ if (!('timestamp' in obj)) {
732
+ errors.push({ field: 'timestamp', message: 'missing required field' });
733
+ }
734
+ if (errors.length > 0) {
735
+ throw new DeserializeError(errors);
736
+ }
737
+ const instance: any = {};
738
+ if (obj.__id !== undefined) {
739
+ ctx.register(obj.__id as number, instance);
740
+ }
741
+ ctx.trackForFreeze(instance);
742
+ {
743
+ const __raw_status = obj['status'] as number;
744
+ instance.status = __raw_status;
745
+ }
746
+ {
747
+ const __raw_message = obj['message'] as string;
748
+ instance.message = __raw_message;
749
+ }
750
+ {
751
+ const __raw_timestamp = obj['timestamp'] as Date;
752
+ {
753
+ const __dateVal =
754
+ typeof __raw_timestamp === 'string'
755
+ ? new Date(__raw_timestamp)
756
+ : (__raw_timestamp as Date);
757
+ instance.timestamp = __dateVal;
758
+ }
759
+ }
760
+ if (errors.length > 0) {
761
+ throw new DeserializeError(errors);
762
+ }
763
+ return instance as ApiResponse;
764
+ }
765
+ export function validateField<K extends keyof ApiResponse>(
766
+ field: K,
767
+ value: ApiResponse[K]
768
+ ): Array<{ field: string; message: string }> {
769
+ return [];
770
+ }
771
+ export function validateFields(
772
+ partial: Partial<ApiResponse>
773
+ ): Array<{ field: string; message: string }> {
774
+ return [];
775
+ }
776
+ }
777
+ ``` ```
195
778
  const json = { status: 200, message: "OK", timestamp: "2024-01-15T10:30:00.000Z" };
196
779
 
197
780
  // Type guard
@@ -202,15 +785,43 @@ if (ApiResponse.is(json)) {
202
785
  // Deserialize with validation
203
786
  const response = ApiResponse.fromJSON(json);
204
787
  console.log(response.timestamp instanceof Date); // true
788
+ ``` ## Enum Support
789
+ Deserialize also works with enums. The `fromJSON` function validates that the input matches one of the enum values:
790
+ **Before:**
205
791
  ```
792
+ /** @derive(Deserialize) */
793
+ enum Status {
794
+ Active = 'active',
795
+ Inactive = 'inactive',
796
+ Pending = 'pending'
797
+ }
798
+ ```
799
+ **After:**
800
+ ```
801
+ import { DeserializeContext } from 'macroforge/serde';
206
802
 
207
- ## Enum Support
208
-
209
- Deserialize also works with enums. The `fromJSON` function validates that the input matches one of the enum values:
210
-
211
- <MacroExample before={data.examples.enum.before} after={data.examples.enum.after} />
803
+ enum Status {
804
+ Active = 'active',
805
+ Inactive = 'inactive',
806
+ Pending = 'pending'
807
+ }
212
808
 
213
- ```typescript
809
+ export namespace Status {
810
+ export function fromStringifiedJSON(json: string): Status {
811
+ const data = JSON.parse(json);
812
+ return __deserialize(data);
813
+ }
814
+ export function __deserialize(data: unknown): Status {
815
+ for (const key of Object.keys(Status)) {
816
+ const enumValue = Status[key as keyof typeof Status];
817
+ if (enumValue === data) {
818
+ return data as Status;
819
+ }
820
+ }
821
+ throw new Error('Invalid Status value: ' + JSON.stringify(data));
822
+ }
823
+ }
824
+ ``` ```
214
825
  const status = Status.fromJSON("active");
215
826
  console.log(status); // Status.Active
216
827
 
@@ -220,29 +831,151 @@ try {
220
831
  } catch (e) {
221
832
  console.log(e.message); // "Invalid Status value: invalid"
222
833
  }
834
+ ``` Works with numeric enums too:
835
+ **Source:**
223
836
  ```
224
-
225
- Works with numeric enums too:
226
-
227
- <InteractiveMacro code={`/** @derive(Deserialize) */
837
+ /** @derive(Deserialize) */
228
838
  enum Priority {
229
839
  Low = 1,
230
840
  Medium = 2,
231
841
  High = 3,
232
- }`} />
233
-
234
- ```typescript
842
+ }
843
+ ``` ```
235
844
  const priority = Priority.fromJSON(3);
236
845
  console.log(priority); // Priority.High
846
+ ``` ## Type Alias Support
847
+ Deserialize works with type aliases. For object types, validation and type conversion is applied:
848
+ **Before:**
237
849
  ```
850
+ /** @derive(Deserialize) */
851
+ type UserProfile = {
852
+ id: string;
853
+ name: string;
854
+ createdAt: Date;
855
+ };
856
+ ```
857
+ **After:**
858
+ ```
859
+ import { Result } from 'macroforge/utils';
860
+ import { DeserializeContext } from 'macroforge/serde';
861
+ import { DeserializeError } from 'macroforge/serde';
862
+ import type { DeserializeOptions } from 'macroforge/serde';
863
+ import { PendingRef } from 'macroforge/serde';
864
+
865
+ type UserProfile = {
866
+ id: string;
867
+ name: string;
868
+ createdAt: Date;
869
+ };
238
870
 
239
- ## Type Alias Support
240
-
241
- Deserialize works with type aliases. For object types, validation and type conversion is applied:
242
-
243
- <MacroExample before={data.examples.typeAlias.before} after={data.examples.typeAlias.after} />
244
-
245
- ```typescript
871
+ export namespace UserProfile {
872
+ export function fromStringifiedJSON(
873
+ json: string,
874
+ opts?: DeserializeOptions
875
+ ): Result<UserProfile, Array<{ field: string; message: string }>> {
876
+ try {
877
+ const raw = JSON.parse(json);
878
+ return fromObject(raw, opts);
879
+ } catch (e) {
880
+ if (e instanceof DeserializeError) {
881
+ return Result.err(e.errors);
882
+ }
883
+ const message = e instanceof Error ? e.message : String(e);
884
+ return Result.err([{ field: '_root', message }]);
885
+ }
886
+ }
887
+ export function fromObject(
888
+ obj: unknown,
889
+ opts?: DeserializeOptions
890
+ ): Result<UserProfile, Array<{ field: string; message: string }>> {
891
+ try {
892
+ const ctx = DeserializeContext.create();
893
+ const resultOrRef = __deserialize(obj, ctx);
894
+ if (PendingRef.is(resultOrRef)) {
895
+ return Result.err([
896
+ {
897
+ field: '_root',
898
+ message: 'UserProfile.fromObject: root cannot be a forward reference'
899
+ }
900
+ ]);
901
+ }
902
+ ctx.applyPatches();
903
+ if (opts?.freeze) {
904
+ ctx.freezeAll();
905
+ }
906
+ return Result.ok(resultOrRef);
907
+ } catch (e) {
908
+ if (e instanceof DeserializeError) {
909
+ return Result.err(e.errors);
910
+ }
911
+ const message = e instanceof Error ? e.message : String(e);
912
+ return Result.err([{ field: '_root', message }]);
913
+ }
914
+ }
915
+ export function __deserialize(value: any, ctx: DeserializeContext): UserProfile | PendingRef {
916
+ if (value?.__ref !== undefined) {
917
+ return ctx.getOrDefer(value.__ref) as UserProfile | PendingRef;
918
+ }
919
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
920
+ throw new DeserializeError([
921
+ { field: '_root', message: 'UserProfile.__deserialize: expected an object' }
922
+ ]);
923
+ }
924
+ const obj = value as Record<string, unknown>;
925
+ const errors: Array<{ field: string; message: string }> = [];
926
+ if (!('id' in obj)) {
927
+ errors.push({ field: 'id', message: 'missing required field' });
928
+ }
929
+ if (!('name' in obj)) {
930
+ errors.push({ field: 'name', message: 'missing required field' });
931
+ }
932
+ if (!('createdAt' in obj)) {
933
+ errors.push({ field: 'createdAt', message: 'missing required field' });
934
+ }
935
+ if (errors.length > 0) {
936
+ throw new DeserializeError(errors);
937
+ }
938
+ const instance: any = {};
939
+ if (obj.__id !== undefined) {
940
+ ctx.register(obj.__id as number, instance);
941
+ }
942
+ ctx.trackForFreeze(instance);
943
+ {
944
+ const __raw_id = obj['id'] as string;
945
+ instance.id = __raw_id;
946
+ }
947
+ {
948
+ const __raw_name = obj['name'] as string;
949
+ instance.name = __raw_name;
950
+ }
951
+ {
952
+ const __raw_createdAt = obj['createdAt'] as Date;
953
+ {
954
+ const __dateVal =
955
+ typeof __raw_createdAt === 'string'
956
+ ? new Date(__raw_createdAt)
957
+ : (__raw_createdAt as Date);
958
+ instance.createdAt = __dateVal;
959
+ }
960
+ }
961
+ if (errors.length > 0) {
962
+ throw new DeserializeError(errors);
963
+ }
964
+ return instance as UserProfile;
965
+ }
966
+ export function validateField<K extends keyof UserProfile>(
967
+ field: K,
968
+ value: UserProfile[K]
969
+ ): Array<{ field: string; message: string }> {
970
+ return [];
971
+ }
972
+ export function validateFields(
973
+ partial: Partial<UserProfile>
974
+ ): Array<{ field: string; message: string }> {
975
+ return [];
976
+ }
977
+ }
978
+ ``` ```
246
979
  const json = {
247
980
  id: "123",
248
981
  name: "Alice",
@@ -251,31 +984,26 @@ const json = {
251
984
 
252
985
  const profile = UserProfile.fromJSON(json);
253
986
  console.log(profile.createdAt instanceof Date); // true
987
+ ``` For union types, basic validation is applied:
988
+ **Source:**
254
989
  ```
255
-
256
- For union types, basic validation is applied:
257
-
258
- <InteractiveMacro code={`/** @derive(Deserialize) */
259
- type ApiStatus = "loading" | "success" | "error";`} />
260
-
261
- ```typescript
990
+ /** @derive(Deserialize) */
991
+ type ApiStatus = "loading" | "success" | "error";
992
+ ``` ```
262
993
  const status = ApiStatus.fromJSON("success");
263
994
  console.log(status); // "success"
995
+ ``` ## Combining with Serialize
996
+ Use both Serialize and Deserialize for complete JSON round-trip support:
997
+ **Source:**
264
998
  ```
265
-
266
- ## Combining with Serialize
267
-
268
- Use both Serialize and Deserialize for complete JSON round-trip support:
269
-
270
- <InteractiveMacro code={`/** @derive(Serialize, Deserialize) */
999
+ /** @derive(Serialize, Deserialize) */
271
1000
  /** @serde({ rename_all: "camelCase" }) */
272
1001
  class UserProfile {
273
1002
  user_name: string;
274
1003
  created_at: Date;
275
1004
  is_active: boolean;
276
- }`} />
277
-
278
- ```typescript
1005
+ }
1006
+ ``` ```
279
1007
  // Create and serialize
280
1008
  const profile = new UserProfile();
281
1009
  profile.user_name = "Alice";
@@ -289,19 +1017,16 @@ const json = JSON.stringify(profile);
289
1017
  const restored = UserProfile.fromJSON(JSON.parse(json));
290
1018
  console.log(restored.user_name); // "Alice"
291
1019
  console.log(restored.created_at instanceof Date); // true
1020
+ ``` ## Error Handling
1021
+ Handle deserialization errors gracefully:
1022
+ **Source:**
292
1023
  ```
293
-
294
- ## Error Handling
295
-
296
- Handle deserialization errors gracefully:
297
-
298
- <InteractiveMacro code={`/** @derive(Deserialize) */
1024
+ /** @derive(Deserialize) */
299
1025
  class User {
300
1026
  name: string;
301
1027
  email: string;
302
- }`} />
303
-
304
- ```typescript
1028
+ }
1029
+ ``` ```
305
1030
  function parseUser(json: unknown): User | null {
306
1031
  try {
307
1032
  return User.fromJSON(json);