@macroforge/mcp-server 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/docs/api/api-overview.md +13 -13
  2. package/docs/api/expand-sync.md +8 -8
  3. package/docs/api/native-plugin.md +15 -15
  4. package/docs/api/position-mapper.md +6 -6
  5. package/docs/api/transform-sync.md +11 -11
  6. package/docs/builtin-macros/clone.md +43 -23
  7. package/docs/builtin-macros/debug.md +50 -18
  8. package/docs/builtin-macros/default.md +79 -28
  9. package/docs/builtin-macros/deserialize/cycleforward-reference-support.md +11 -0
  10. package/docs/builtin-macros/deserialize/example.md +1625 -0
  11. package/docs/builtin-macros/deserialize/overview.md +15 -10
  12. package/docs/builtin-macros/deserialize/union-type-deserialization.md +27 -0
  13. package/docs/builtin-macros/deserialize/validation.md +34 -0
  14. package/docs/builtin-macros/deserialize.md +1608 -23
  15. package/docs/builtin-macros/hash.md +87 -20
  16. package/docs/builtin-macros/macros-overview.md +40 -40
  17. package/docs/builtin-macros/ord.md +56 -31
  18. package/docs/builtin-macros/partial-eq/example.md +526 -0
  19. package/docs/builtin-macros/partial-eq/overview.md +39 -0
  20. package/docs/builtin-macros/partial-eq.md +184 -26
  21. package/docs/builtin-macros/partial-ord.md +68 -30
  22. package/docs/builtin-macros/serialize/example.md +139 -0
  23. package/docs/builtin-macros/serialize/overview.md +32 -0
  24. package/docs/builtin-macros/serialize/type-specific-serialization.md +22 -0
  25. package/docs/builtin-macros/serialize.md +130 -28
  26. package/docs/concepts/architecture.md +2 -2
  27. package/docs/concepts/derive-system.md +25 -39
  28. package/docs/concepts/how-macros-work.md +8 -4
  29. package/docs/custom-macros/custom-overview.md +23 -23
  30. package/docs/custom-macros/rust-setup.md +31 -31
  31. package/docs/custom-macros/ts-macro-derive.md +107 -107
  32. package/docs/custom-macros/ts-quote.md +226 -226
  33. package/docs/getting-started/first-macro.md +38 -28
  34. package/docs/getting-started/installation.md +15 -15
  35. package/docs/integration/cli.md +9 -9
  36. package/docs/integration/configuration.md +16 -16
  37. package/docs/integration/mcp-server.md +6 -6
  38. package/docs/integration/svelte-preprocessor.md +40 -41
  39. package/docs/integration/typescript-plugin.md +13 -12
  40. package/docs/integration/vite-plugin.md +12 -12
  41. package/docs/language-servers/zed.md +1 -1
  42. package/docs/sections.json +88 -2
  43. package/package.json +2 -2
@@ -0,0 +1,1625 @@
1
+ ## Example
2
+
3
+ ```typescript before
4
+ /** @derive(Deserialize) @serde({ denyUnknownFields: true }) */
5
+ class User {
6
+ id: number;
7
+
8
+ /** @serde({ validate: { email: true, maxLength: 255 } }) */
9
+ email: string;
10
+
11
+ /** @serde({ default: "guest" }) */
12
+ name: string;
13
+
14
+ /** @serde({ validate: { positive: true } }) */
15
+ age?: number;
16
+ }
17
+ ```
18
+
19
+ ```typescript after
20
+ class User {
21
+ id: number;
22
+
23
+ email: string;
24
+
25
+ name: string;
26
+
27
+ age?: number;
28
+ }
29
+ ```
30
+
31
+ Generated output:
32
+
33
+ ```typescript
34
+ class User {
35
+ id: number;
36
+
37
+ email: string;
38
+
39
+ name: string;
40
+
41
+ age?: number;
42
+ }
43
+ ```
44
+
45
+ Generated output:
46
+
47
+ ```typescript
48
+ import { DeserializeContext } from 'macroforge/serde';
49
+ import { DeserializeError } from 'macroforge/serde';
50
+ import type { DeserializeOptions } from 'macroforge/serde';
51
+ import { PendingRef } from 'macroforge/serde';
52
+
53
+ /** @serde({ denyUnknownFields: true }) */
54
+ class User {
55
+ id: number;
56
+
57
+ email: string;
58
+
59
+ name: string;
60
+
61
+ age?: number;
62
+
63
+ constructor(props: {
64
+ id: number;
65
+ email: string;
66
+ name?: string;
67
+ age?: number;
68
+ }) {
69
+ this.id = props.id;
70
+ this.email = props.email;
71
+ this.name = props.name as string;
72
+ this.age = props.age as number;
73
+ }
74
+
75
+ /**
76
+ * Deserializes input to an instance of this class.
77
+ * Automatically detects whether input is a JSON string or object.
78
+ * @param input - JSON string or object to deserialize
79
+ * @param opts - Optional deserialization options
80
+ * @returns Result containing the deserialized instance or validation errors
81
+ */
82
+ static deserialize(
83
+ input: unknown,
84
+ opts?: DeserializeOptions
85
+ ): Result<
86
+ User,
87
+ Array<{
88
+ field: string;
89
+ message: string;
90
+ }>
91
+ > {
92
+ try {
93
+ // Auto-detect: if string, parse as JSON first
94
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
95
+
96
+ const ctx = DeserializeContext.create();
97
+ const resultOrRef = User.deserializeWithContext(data, ctx);
98
+ if (PendingRef.is(resultOrRef)) {
99
+ return Result.err([
100
+ {
101
+ field: '_root',
102
+ message: 'User.deserialize: root cannot be a forward reference'
103
+ }
104
+ ]);
105
+ }
106
+ ctx.applyPatches();
107
+ if (opts?.freeze) {
108
+ ctx.freezeAll();
109
+ }
110
+ return Result.ok(resultOrRef);
111
+ } catch (e) {
112
+ if (e instanceof DeserializeError) {
113
+ return Result.err(e.errors);
114
+ }
115
+ const message = e instanceof Error ? e.message : String(e);
116
+ return Result.err([
117
+ {
118
+ field: '_root',
119
+ message
120
+ }
121
+ ]);
122
+ }
123
+ }
124
+
125
+ /** @internal */
126
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
127
+ if (value?.__ref !== undefined) {
128
+ return ctx.getOrDefer(value.__ref);
129
+ }
130
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
131
+ throw new DeserializeError([
132
+ {
133
+ field: '_root',
134
+ message: 'User.deserializeWithContext: expected an object'
135
+ }
136
+ ]);
137
+ }
138
+ const obj = value as Record<string, unknown>;
139
+ const errors: Array<{
140
+ field: string;
141
+ message: string;
142
+ }> = [];
143
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
144
+ for (const key of Object.keys(obj)) {
145
+ if (!knownKeys.has(key)) {
146
+ errors.push({
147
+ field: key,
148
+ message: 'unknown field'
149
+ });
150
+ }
151
+ }
152
+ if (!('id' in obj)) {
153
+ errors.push({
154
+ field: 'id',
155
+ message: 'missing required field'
156
+ });
157
+ }
158
+ if (!('email' in obj)) {
159
+ errors.push({
160
+ field: 'email',
161
+ message: 'missing required field'
162
+ });
163
+ }
164
+ if (errors.length > 0) {
165
+ throw new DeserializeError(errors);
166
+ }
167
+ const instance = Object.create(User.prototype) as User;
168
+ if (obj.__id !== undefined) {
169
+ ctx.register(obj.__id as number, instance);
170
+ }
171
+ ctx.trackForFreeze(instance);
172
+ {
173
+ const __raw_id = obj['id'] as number;
174
+ instance.id = __raw_id;
175
+ }
176
+ {
177
+ const __raw_email = obj['email'] as string;
178
+ instance.email = __raw_email;
179
+ }
180
+ if ('name' in obj && obj['name'] !== undefined) {
181
+ const __raw_name = obj['name'] as string;
182
+ instance.name = __raw_name;
183
+ } else {
184
+ instance.name = "guest";
185
+ }
186
+ if ('age' in obj && obj['age'] !== undefined) {
187
+ const __raw_age = obj['age'] as number;
188
+ instance.age = __raw_age;
189
+ }
190
+ if (errors.length > 0) {
191
+ throw new DeserializeError(errors);
192
+ }
193
+ return instance;
194
+ }
195
+
196
+ static validateField<K extends keyof User>(
197
+ field: K,
198
+ value: User[K]
199
+ ): Array<{
200
+ field: string;
201
+ message: string;
202
+ }> {
203
+ return [];
204
+ }
205
+
206
+ static validateFields(partial: Partial<User>): Array<{
207
+ field: string;
208
+ message: string;
209
+ }> {
210
+ return [];
211
+ }
212
+
213
+ static hasShape(obj: unknown): boolean {
214
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
215
+ return false;
216
+ }
217
+ const o = obj as Record<string, unknown>;
218
+ return 'id' in o && 'email' in o;
219
+ }
220
+
221
+ static is(obj: unknown): obj is User {
222
+ if (obj instanceof User) {
223
+ return true;
224
+ }
225
+ if (!User.hasShape(obj)) {
226
+ return false;
227
+ }
228
+ const result = User.deserialize(obj);
229
+ return Result.isOk(result);
230
+ }
231
+ }
232
+
233
+ // Usage:
234
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
235
+ if (Result.isOk(result)) {
236
+ const user = result.value;
237
+ } else {
238
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
239
+ }
240
+ ```
241
+
242
+ Generated output:
243
+
244
+ ```typescript
245
+ import { DeserializeContext } from 'macroforge/serde';
246
+ import { DeserializeError } from 'macroforge/serde';
247
+ import type { DeserializeOptions } from 'macroforge/serde';
248
+ import { PendingRef } from 'macroforge/serde';
249
+
250
+ /** @serde({ denyUnknownFields: true }) */
251
+ class User {
252
+ id: number;
253
+
254
+ email: string;
255
+
256
+ name: string;
257
+
258
+ age?: number;
259
+
260
+ constructor(props: {
261
+ id: number;
262
+ email: string;
263
+ name?: string;
264
+ age?: number;
265
+ }) {
266
+ this.id = props.id;
267
+ this.email = props.email;
268
+ this.name = props.name as string;
269
+ this.age = props.age as number;
270
+ }
271
+
272
+ /**
273
+ * Deserializes input to an instance of this class.
274
+ * Automatically detects whether input is a JSON string or object.
275
+ * @param input - JSON string or object to deserialize
276
+ * @param opts - Optional deserialization options
277
+ * @returns Result containing the deserialized instance or validation errors
278
+ */
279
+ static deserialize(
280
+ input: unknown,
281
+ opts?: DeserializeOptions
282
+ ): Result<
283
+ User,
284
+ Array<{
285
+ field: string;
286
+ message: string;
287
+ }>
288
+ > {
289
+ try {
290
+ // Auto-detect: if string, parse as JSON first
291
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
292
+
293
+ const ctx = DeserializeContext.create();
294
+ const resultOrRef = User.deserializeWithContext(data, ctx);
295
+ if (PendingRef.is(resultOrRef)) {
296
+ return Result.err([
297
+ {
298
+ field: '_root',
299
+ message: 'User.deserialize: root cannot be a forward reference'
300
+ }
301
+ ]);
302
+ }
303
+ ctx.applyPatches();
304
+ if (opts?.freeze) {
305
+ ctx.freezeAll();
306
+ }
307
+ return Result.ok(resultOrRef);
308
+ } catch (e) {
309
+ if (e instanceof DeserializeError) {
310
+ return Result.err(e.errors);
311
+ }
312
+ const message = e instanceof Error ? e.message : String(e);
313
+ return Result.err([
314
+ {
315
+ field: '_root',
316
+ message
317
+ }
318
+ ]);
319
+ }
320
+ }
321
+
322
+ /** @internal */
323
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
324
+ if (value?.__ref !== undefined) {
325
+ return ctx.getOrDefer(value.__ref);
326
+ }
327
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
328
+ throw new DeserializeError([
329
+ {
330
+ field: '_root',
331
+ message: 'User.deserializeWithContext: expected an object'
332
+ }
333
+ ]);
334
+ }
335
+ const obj = value as Record<string, unknown>;
336
+ const errors: Array<{
337
+ field: string;
338
+ message: string;
339
+ }> = [];
340
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
341
+ for (const key of Object.keys(obj)) {
342
+ if (!knownKeys.has(key)) {
343
+ errors.push({
344
+ field: key,
345
+ message: 'unknown field'
346
+ });
347
+ }
348
+ }
349
+ if (!('id' in obj)) {
350
+ errors.push({
351
+ field: 'id',
352
+ message: 'missing required field'
353
+ });
354
+ }
355
+ if (!('email' in obj)) {
356
+ errors.push({
357
+ field: 'email',
358
+ message: 'missing required field'
359
+ });
360
+ }
361
+ if (errors.length > 0) {
362
+ throw new DeserializeError(errors);
363
+ }
364
+ const instance = Object.create(User.prototype) as User;
365
+ if (obj.__id !== undefined) {
366
+ ctx.register(obj.__id as number, instance);
367
+ }
368
+ ctx.trackForFreeze(instance);
369
+ {
370
+ const __raw_id = obj['id'] as number;
371
+ instance.id = __raw_id;
372
+ }
373
+ {
374
+ const __raw_email = obj['email'] as string;
375
+ instance.email = __raw_email;
376
+ }
377
+ if ('name' in obj && obj['name'] !== undefined) {
378
+ const __raw_name = obj['name'] as string;
379
+ instance.name = __raw_name;
380
+ } else {
381
+ instance.name = 'guest';
382
+ }
383
+ if ('age' in obj && obj['age'] !== undefined) {
384
+ const __raw_age = obj['age'] as number;
385
+ instance.age = __raw_age;
386
+ }
387
+ if (errors.length > 0) {
388
+ throw new DeserializeError(errors);
389
+ }
390
+ return instance;
391
+ }
392
+
393
+ static validateField<K extends keyof User>(
394
+ field: K,
395
+ value: User[K]
396
+ ): Array<{
397
+ field: string;
398
+ message: string;
399
+ }> {
400
+ return [];
401
+ }
402
+
403
+ static validateFields(partial: Partial<User>): Array<{
404
+ field: string;
405
+ message: string;
406
+ }> {
407
+ return [];
408
+ }
409
+
410
+ static hasShape(obj: unknown): boolean {
411
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
412
+ return false;
413
+ }
414
+ const o = obj as Record<string, unknown>;
415
+ return 'id' in o && 'email' in o;
416
+ }
417
+
418
+ static is(obj: unknown): obj is User {
419
+ if (obj instanceof User) {
420
+ return true;
421
+ }
422
+ if (!User.hasShape(obj)) {
423
+ return false;
424
+ }
425
+ const result = User.deserialize(obj);
426
+ return Result.isOk(result);
427
+ }
428
+ }
429
+
430
+ // Usage:
431
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
432
+ if (Result.isOk(result)) {
433
+ const user = result.value;
434
+ } else {
435
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
436
+ }
437
+ ```
438
+
439
+ Generated output:
440
+
441
+ ```typescript
442
+ import { DeserializeContext } from 'macroforge/serde';
443
+ import { DeserializeError } from 'macroforge/serde';
444
+ import type { DeserializeOptions } from 'macroforge/serde';
445
+ import { PendingRef } from 'macroforge/serde';
446
+
447
+ /** @serde({ denyUnknownFields: true }) */
448
+ class User {
449
+ id: number;
450
+
451
+ email: string;
452
+
453
+ name: string;
454
+
455
+ age?: number;
456
+
457
+ constructor(props: {
458
+ id: number;
459
+ email: string;
460
+ name?: string;
461
+ age?: number;
462
+ }) {
463
+ this.id = props.id;
464
+ this.email = props.email;
465
+ this.name = props.name as string;
466
+ this.age = props.age as number;
467
+ }
468
+
469
+ /**
470
+ * Deserializes input to an instance of this class.
471
+ * Automatically detects whether input is a JSON string or object.
472
+ * @param input - JSON string or object to deserialize
473
+ * @param opts - Optional deserialization options
474
+ * @returns Result containing the deserialized instance or validation errors
475
+ */
476
+ static deserialize(
477
+ input: unknown,
478
+ opts?: DeserializeOptions
479
+ ): Result<
480
+ User,
481
+ Array<{
482
+ field: string;
483
+ message: string;
484
+ }>
485
+ > {
486
+ try {
487
+ // Auto-detect: if string, parse as JSON first
488
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
489
+
490
+ const ctx = DeserializeContext.create();
491
+ const resultOrRef = User.deserializeWithContext(data, ctx);
492
+ if (PendingRef.is(resultOrRef)) {
493
+ return Result.err([
494
+ {
495
+ field: '_root',
496
+ message: 'User.deserialize: root cannot be a forward reference'
497
+ }
498
+ ]);
499
+ }
500
+ ctx.applyPatches();
501
+ if (opts?.freeze) {
502
+ ctx.freezeAll();
503
+ }
504
+ return Result.ok(resultOrRef);
505
+ } catch (e) {
506
+ if (e instanceof DeserializeError) {
507
+ return Result.err(e.errors);
508
+ }
509
+ const message = e instanceof Error ? e.message : String(e);
510
+ return Result.err([
511
+ {
512
+ field: '_root',
513
+ message
514
+ }
515
+ ]);
516
+ }
517
+ }
518
+
519
+ /** @internal */
520
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
521
+ if (value?.__ref !== undefined) {
522
+ return ctx.getOrDefer(value.__ref);
523
+ }
524
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
525
+ throw new DeserializeError([
526
+ {
527
+ field: '_root',
528
+ message: 'User.deserializeWithContext: expected an object'
529
+ }
530
+ ]);
531
+ }
532
+ const obj = value as Record<string, unknown>;
533
+ const errors: Array<{
534
+ field: string;
535
+ message: string;
536
+ }> = [];
537
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
538
+ for (const key of Object.keys(obj)) {
539
+ if (!knownKeys.has(key)) {
540
+ errors.push({
541
+ field: key,
542
+ message: 'unknown field'
543
+ });
544
+ }
545
+ }
546
+ if (!('id' in obj)) {
547
+ errors.push({
548
+ field: 'id',
549
+ message: 'missing required field'
550
+ });
551
+ }
552
+ if (!('email' in obj)) {
553
+ errors.push({
554
+ field: 'email',
555
+ message: 'missing required field'
556
+ });
557
+ }
558
+ if (errors.length > 0) {
559
+ throw new DeserializeError(errors);
560
+ }
561
+ const instance = Object.create(User.prototype) as User;
562
+ if (obj.__id !== undefined) {
563
+ ctx.register(obj.__id as number, instance);
564
+ }
565
+ ctx.trackForFreeze(instance);
566
+ {
567
+ const __raw_id = obj['id'] as number;
568
+ instance.id = __raw_id;
569
+ }
570
+ {
571
+ const __raw_email = obj['email'] as string;
572
+ instance.email = __raw_email;
573
+ }
574
+ if ('name' in obj && obj['name'] !== undefined) {
575
+ const __raw_name = obj['name'] as string;
576
+ instance.name = __raw_name;
577
+ } else {
578
+ instance.name = "guest";
579
+ }
580
+ if ('age' in obj && obj['age'] !== undefined) {
581
+ const __raw_age = obj['age'] as number;
582
+ instance.age = __raw_age;
583
+ }
584
+ if (errors.length > 0) {
585
+ throw new DeserializeError(errors);
586
+ }
587
+ return instance;
588
+ }
589
+
590
+ static validateField<K extends keyof User>(
591
+ field: K,
592
+ value: User[K]
593
+ ): Array<{
594
+ field: string;
595
+ message: string;
596
+ }> {
597
+ return [];
598
+ }
599
+
600
+ static validateFields(partial: Partial<User>): Array<{
601
+ field: string;
602
+ message: string;
603
+ }> {
604
+ return [];
605
+ }
606
+
607
+ static hasShape(obj: unknown): boolean {
608
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
609
+ return false;
610
+ }
611
+ const o = obj as Record<string, unknown>;
612
+ return 'id' in o && 'email' in o;
613
+ }
614
+
615
+ static is(obj: unknown): obj is User {
616
+ if (obj instanceof User) {
617
+ return true;
618
+ }
619
+ if (!User.hasShape(obj)) {
620
+ return false;
621
+ }
622
+ const result = User.deserialize(obj);
623
+ return Result.isOk(result);
624
+ }
625
+ }
626
+
627
+ // Usage:
628
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
629
+ if (Result.isOk(result)) {
630
+ const user = result.value;
631
+ } else {
632
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
633
+ }
634
+ ```
635
+
636
+ Generated output:
637
+
638
+ ```typescript
639
+ import { DeserializeContext } from 'macroforge/serde';
640
+ import { DeserializeError } from 'macroforge/serde';
641
+ import type { DeserializeOptions } from 'macroforge/serde';
642
+ import { PendingRef } from 'macroforge/serde';
643
+
644
+ /** @serde({ denyUnknownFields: true }) */
645
+ class User {
646
+ id: number;
647
+
648
+ email: string;
649
+
650
+ name: string;
651
+
652
+ age?: number;
653
+
654
+ constructor(props: {
655
+ id: number;
656
+ email: string;
657
+ name?: string;
658
+ age?: number;
659
+ }) {
660
+ this.id = props.id;
661
+ this.email = props.email;
662
+ this.name = props.name as string;
663
+ this.age = props.age as number;
664
+ }
665
+
666
+ /**
667
+ * Deserializes input to an instance of this class.
668
+ * Automatically detects whether input is a JSON string or object.
669
+ * @param input - JSON string or object to deserialize
670
+ * @param opts - Optional deserialization options
671
+ * @returns Result containing the deserialized instance or validation errors
672
+ */
673
+ static deserialize(
674
+ input: unknown,
675
+ opts?: DeserializeOptions
676
+ ): Result<
677
+ User,
678
+ Array<{
679
+ field: string;
680
+ message: string;
681
+ }>
682
+ > {
683
+ try {
684
+ // Auto-detect: if string, parse as JSON first
685
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
686
+
687
+ const ctx = DeserializeContext.create();
688
+ const resultOrRef = User.deserializeWithContext(data, ctx);
689
+ if (PendingRef.is(resultOrRef)) {
690
+ return Result.err([
691
+ {
692
+ field: '_root',
693
+ message: 'User.deserialize: root cannot be a forward reference'
694
+ }
695
+ ]);
696
+ }
697
+ ctx.applyPatches();
698
+ if (opts?.freeze) {
699
+ ctx.freezeAll();
700
+ }
701
+ return Result.ok(resultOrRef);
702
+ } catch (e) {
703
+ if (e instanceof DeserializeError) {
704
+ return Result.err(e.errors);
705
+ }
706
+ const message = e instanceof Error ? e.message : String(e);
707
+ return Result.err([
708
+ {
709
+ field: '_root',
710
+ message
711
+ }
712
+ ]);
713
+ }
714
+ }
715
+
716
+ /** @internal */
717
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
718
+ if (value?.__ref !== undefined) {
719
+ return ctx.getOrDefer(value.__ref);
720
+ }
721
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
722
+ throw new DeserializeError([
723
+ {
724
+ field: '_root',
725
+ message: 'User.deserializeWithContext: expected an object'
726
+ }
727
+ ]);
728
+ }
729
+ const obj = value as Record<string, unknown>;
730
+ const errors: Array<{
731
+ field: string;
732
+ message: string;
733
+ }> = [];
734
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
735
+ for (const key of Object.keys(obj)) {
736
+ if (!knownKeys.has(key)) {
737
+ errors.push({
738
+ field: key,
739
+ message: 'unknown field'
740
+ });
741
+ }
742
+ }
743
+ if (!('id' in obj)) {
744
+ errors.push({
745
+ field: 'id',
746
+ message: 'missing required field'
747
+ });
748
+ }
749
+ if (!('email' in obj)) {
750
+ errors.push({
751
+ field: 'email',
752
+ message: 'missing required field'
753
+ });
754
+ }
755
+ if (errors.length > 0) {
756
+ throw new DeserializeError(errors);
757
+ }
758
+ const instance = Object.create(User.prototype) as User;
759
+ if (obj.__id !== undefined) {
760
+ ctx.register(obj.__id as number, instance);
761
+ }
762
+ ctx.trackForFreeze(instance);
763
+ {
764
+ const __raw_id = obj['id'] as number;
765
+ instance.id = __raw_id;
766
+ }
767
+ {
768
+ const __raw_email = obj['email'] as string;
769
+ instance.email = __raw_email;
770
+ }
771
+ if ('name' in obj && obj['name'] !== undefined) {
772
+ const __raw_name = obj['name'] as string;
773
+ instance.name = __raw_name;
774
+ } else {
775
+ instance.name = 'guest';
776
+ }
777
+ if ('age' in obj && obj['age'] !== undefined) {
778
+ const __raw_age = obj['age'] as number;
779
+ instance.age = __raw_age;
780
+ }
781
+ if (errors.length > 0) {
782
+ throw new DeserializeError(errors);
783
+ }
784
+ return instance;
785
+ }
786
+
787
+ static validateField<K extends keyof User>(
788
+ field: K,
789
+ value: User[K]
790
+ ): Array<{
791
+ field: string;
792
+ message: string;
793
+ }> {
794
+ return [];
795
+ }
796
+
797
+ static validateFields(partial: Partial<User>): Array<{
798
+ field: string;
799
+ message: string;
800
+ }> {
801
+ return [];
802
+ }
803
+
804
+ static hasShape(obj: unknown): boolean {
805
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
806
+ return false;
807
+ }
808
+ const o = obj as Record<string, unknown>;
809
+ return 'id' in o && 'email' in o;
810
+ }
811
+
812
+ static is(obj: unknown): obj is User {
813
+ if (obj instanceof User) {
814
+ return true;
815
+ }
816
+ if (!User.hasShape(obj)) {
817
+ return false;
818
+ }
819
+ const result = User.deserialize(obj);
820
+ return Result.isOk(result);
821
+ }
822
+ }
823
+
824
+ // Usage:
825
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
826
+ if (Result.isOk(result)) {
827
+ const user = result.value;
828
+ } else {
829
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
830
+ }
831
+ ```
832
+
833
+ Generated output:
834
+
835
+ ```typescript
836
+ import { DeserializeContext } from 'macroforge/serde';
837
+ import { DeserializeError } from 'macroforge/serde';
838
+ import type { DeserializeOptions } from 'macroforge/serde';
839
+ import { PendingRef } from 'macroforge/serde';
840
+
841
+ /** @serde({ denyUnknownFields: true }) */
842
+ class User {
843
+ id: number;
844
+
845
+ email: string;
846
+
847
+ name: string;
848
+
849
+ age?: number;
850
+
851
+ constructor(props: {
852
+ id: number;
853
+ email: string;
854
+ name?: string;
855
+ age?: number;
856
+ }) {
857
+ this.id = props.id;
858
+ this.email = props.email;
859
+ this.name = props.name as string;
860
+ this.age = props.age as number;
861
+ }
862
+
863
+ /**
864
+ * Deserializes input to an instance of this class.
865
+ * Automatically detects whether input is a JSON string or object.
866
+ * @param input - JSON string or object to deserialize
867
+ * @param opts - Optional deserialization options
868
+ * @returns Result containing the deserialized instance or validation errors
869
+ */
870
+ static deserialize(
871
+ input: unknown,
872
+ opts?: DeserializeOptions
873
+ ): Result<
874
+ User,
875
+ Array<{
876
+ field: string;
877
+ message: string;
878
+ }>
879
+ > {
880
+ try {
881
+ // Auto-detect: if string, parse as JSON first
882
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
883
+
884
+ const ctx = DeserializeContext.create();
885
+ const resultOrRef = User.deserializeWithContext(data, ctx);
886
+ if (PendingRef.is(resultOrRef)) {
887
+ return Result.err([
888
+ {
889
+ field: '_root',
890
+ message: 'User.deserialize: root cannot be a forward reference'
891
+ }
892
+ ]);
893
+ }
894
+ ctx.applyPatches();
895
+ if (opts?.freeze) {
896
+ ctx.freezeAll();
897
+ }
898
+ return Result.ok(resultOrRef);
899
+ } catch (e) {
900
+ if (e instanceof DeserializeError) {
901
+ return Result.err(e.errors);
902
+ }
903
+ const message = e instanceof Error ? e.message : String(e);
904
+ return Result.err([
905
+ {
906
+ field: '_root',
907
+ message
908
+ }
909
+ ]);
910
+ }
911
+ }
912
+
913
+ /** @internal */
914
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
915
+ if (value?.__ref !== undefined) {
916
+ return ctx.getOrDefer(value.__ref);
917
+ }
918
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
919
+ throw new DeserializeError([
920
+ {
921
+ field: '_root',
922
+ message: 'User.deserializeWithContext: expected an object'
923
+ }
924
+ ]);
925
+ }
926
+ const obj = value as Record<string, unknown>;
927
+ const errors: Array<{
928
+ field: string;
929
+ message: string;
930
+ }> = [];
931
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
932
+ for (const key of Object.keys(obj)) {
933
+ if (!knownKeys.has(key)) {
934
+ errors.push({
935
+ field: key,
936
+ message: 'unknown field'
937
+ });
938
+ }
939
+ }
940
+ if (!('id' in obj)) {
941
+ errors.push({
942
+ field: 'id',
943
+ message: 'missing required field'
944
+ });
945
+ }
946
+ if (!('email' in obj)) {
947
+ errors.push({
948
+ field: 'email',
949
+ message: 'missing required field'
950
+ });
951
+ }
952
+ if (errors.length > 0) {
953
+ throw new DeserializeError(errors);
954
+ }
955
+ const instance = Object.create(User.prototype) as User;
956
+ if (obj.__id !== undefined) {
957
+ ctx.register(obj.__id as number, instance);
958
+ }
959
+ ctx.trackForFreeze(instance);
960
+ {
961
+ const __raw_id = obj['id'] as number;
962
+ instance.id = __raw_id;
963
+ }
964
+ {
965
+ const __raw_email = obj['email'] as string;
966
+ instance.email = __raw_email;
967
+ }
968
+ if ('name' in obj && obj['name'] !== undefined) {
969
+ const __raw_name = obj['name'] as string;
970
+ instance.name = __raw_name;
971
+ } else {
972
+ instance.name = "guest";
973
+ }
974
+ if ('age' in obj && obj['age'] !== undefined) {
975
+ const __raw_age = obj['age'] as number;
976
+ instance.age = __raw_age;
977
+ }
978
+ if (errors.length > 0) {
979
+ throw new DeserializeError(errors);
980
+ }
981
+ return instance;
982
+ }
983
+
984
+ static validateField<K extends keyof User>(
985
+ field: K,
986
+ value: User[K]
987
+ ): Array<{
988
+ field: string;
989
+ message: string;
990
+ }> {
991
+ return [];
992
+ }
993
+
994
+ static validateFields(partial: Partial<User>): Array<{
995
+ field: string;
996
+ message: string;
997
+ }> {
998
+ return [];
999
+ }
1000
+
1001
+ static hasShape(obj: unknown): boolean {
1002
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
1003
+ return false;
1004
+ }
1005
+ const o = obj as Record<string, unknown>;
1006
+ return 'id' in o && 'email' in o;
1007
+ }
1008
+
1009
+ static is(obj: unknown): obj is User {
1010
+ if (obj instanceof User) {
1011
+ return true;
1012
+ }
1013
+ if (!User.hasShape(obj)) {
1014
+ return false;
1015
+ }
1016
+ const result = User.deserialize(obj);
1017
+ return Result.isOk(result);
1018
+ }
1019
+ }
1020
+
1021
+ // Usage:
1022
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
1023
+ if (Result.isOk(result)) {
1024
+ const user = result.value;
1025
+ } else {
1026
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
1027
+ }
1028
+ ```
1029
+
1030
+ Generated output:
1031
+
1032
+ ```typescript
1033
+ import { DeserializeContext } from 'macroforge/serde';
1034
+ import { DeserializeError } from 'macroforge/serde';
1035
+ import type { DeserializeOptions } from 'macroforge/serde';
1036
+ import { PendingRef } from 'macroforge/serde';
1037
+
1038
+ /** @serde({ denyUnknownFields: true }) */
1039
+ class User {
1040
+ id: number;
1041
+
1042
+ email: string;
1043
+
1044
+ name: string;
1045
+
1046
+ age?: number;
1047
+
1048
+ constructor(props: {
1049
+ id: number;
1050
+ email: string;
1051
+ name?: string;
1052
+ age?: number;
1053
+ }) {
1054
+ this.id = props.id;
1055
+ this.email = props.email;
1056
+ this.name = props.name as string;
1057
+ this.age = props.age as number;
1058
+ }
1059
+
1060
+ /**
1061
+ * Deserializes input to an instance of this class.
1062
+ * Automatically detects whether input is a JSON string or object.
1063
+ * @param input - JSON string or object to deserialize
1064
+ * @param opts - Optional deserialization options
1065
+ * @returns Result containing the deserialized instance or validation errors
1066
+ */
1067
+ static deserialize(
1068
+ input: unknown,
1069
+ opts?: DeserializeOptions
1070
+ ): Result<
1071
+ User,
1072
+ Array<{
1073
+ field: string;
1074
+ message: string;
1075
+ }>
1076
+ > {
1077
+ try {
1078
+ // Auto-detect: if string, parse as JSON first
1079
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
1080
+
1081
+ const ctx = DeserializeContext.create();
1082
+ const resultOrRef = User.deserializeWithContext(data, ctx);
1083
+ if (PendingRef.is(resultOrRef)) {
1084
+ return Result.err([
1085
+ {
1086
+ field: '_root',
1087
+ message: 'User.deserialize: root cannot be a forward reference'
1088
+ }
1089
+ ]);
1090
+ }
1091
+ ctx.applyPatches();
1092
+ if (opts?.freeze) {
1093
+ ctx.freezeAll();
1094
+ }
1095
+ return Result.ok(resultOrRef);
1096
+ } catch (e) {
1097
+ if (e instanceof DeserializeError) {
1098
+ return Result.err(e.errors);
1099
+ }
1100
+ const message = e instanceof Error ? e.message : String(e);
1101
+ return Result.err([
1102
+ {
1103
+ field: '_root',
1104
+ message
1105
+ }
1106
+ ]);
1107
+ }
1108
+ }
1109
+
1110
+ /** @internal */
1111
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
1112
+ if (value?.__ref !== undefined) {
1113
+ return ctx.getOrDefer(value.__ref);
1114
+ }
1115
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1116
+ throw new DeserializeError([
1117
+ {
1118
+ field: '_root',
1119
+ message: 'User.deserializeWithContext: expected an object'
1120
+ }
1121
+ ]);
1122
+ }
1123
+ const obj = value as Record<string, unknown>;
1124
+ const errors: Array<{
1125
+ field: string;
1126
+ message: string;
1127
+ }> = [];
1128
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
1129
+ for (const key of Object.keys(obj)) {
1130
+ if (!knownKeys.has(key)) {
1131
+ errors.push({
1132
+ field: key,
1133
+ message: 'unknown field'
1134
+ });
1135
+ }
1136
+ }
1137
+ if (!('id' in obj)) {
1138
+ errors.push({
1139
+ field: 'id',
1140
+ message: 'missing required field'
1141
+ });
1142
+ }
1143
+ if (!('email' in obj)) {
1144
+ errors.push({
1145
+ field: 'email',
1146
+ message: 'missing required field'
1147
+ });
1148
+ }
1149
+ if (errors.length > 0) {
1150
+ throw new DeserializeError(errors);
1151
+ }
1152
+ const instance = Object.create(User.prototype) as User;
1153
+ if (obj.__id !== undefined) {
1154
+ ctx.register(obj.__id as number, instance);
1155
+ }
1156
+ ctx.trackForFreeze(instance);
1157
+ {
1158
+ const __raw_id = obj['id'] as number;
1159
+ instance.id = __raw_id;
1160
+ }
1161
+ {
1162
+ const __raw_email = obj['email'] as string;
1163
+ instance.email = __raw_email;
1164
+ }
1165
+ if ('name' in obj && obj['name'] !== undefined) {
1166
+ const __raw_name = obj['name'] as string;
1167
+ instance.name = __raw_name;
1168
+ } else {
1169
+ instance.name = 'guest';
1170
+ }
1171
+ if ('age' in obj && obj['age'] !== undefined) {
1172
+ const __raw_age = obj['age'] as number;
1173
+ instance.age = __raw_age;
1174
+ }
1175
+ if (errors.length > 0) {
1176
+ throw new DeserializeError(errors);
1177
+ }
1178
+ return instance;
1179
+ }
1180
+
1181
+ static validateField<K extends keyof User>(
1182
+ field: K,
1183
+ value: User[K]
1184
+ ): Array<{
1185
+ field: string;
1186
+ message: string;
1187
+ }> {
1188
+ return [];
1189
+ }
1190
+
1191
+ static validateFields(partial: Partial<User>): Array<{
1192
+ field: string;
1193
+ message: string;
1194
+ }> {
1195
+ return [];
1196
+ }
1197
+
1198
+ static hasShape(obj: unknown): boolean {
1199
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
1200
+ return false;
1201
+ }
1202
+ const o = obj as Record<string, unknown>;
1203
+ return 'id' in o && 'email' in o;
1204
+ }
1205
+
1206
+ static is(obj: unknown): obj is User {
1207
+ if (obj instanceof User) {
1208
+ return true;
1209
+ }
1210
+ if (!User.hasShape(obj)) {
1211
+ return false;
1212
+ }
1213
+ const result = User.deserialize(obj);
1214
+ return Result.isOk(result);
1215
+ }
1216
+ }
1217
+
1218
+ // Usage:
1219
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
1220
+ if (Result.isOk(result)) {
1221
+ const user = result.value;
1222
+ } else {
1223
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
1224
+ }
1225
+ ```
1226
+
1227
+ Generated output:
1228
+
1229
+ ```typescript
1230
+ import { DeserializeContext } from 'macroforge/serde';
1231
+ import { DeserializeError } from 'macroforge/serde';
1232
+ import type { DeserializeOptions } from 'macroforge/serde';
1233
+ import { PendingRef } from 'macroforge/serde';
1234
+
1235
+ /** @serde({ denyUnknownFields: true }) */
1236
+ class User {
1237
+ id: number;
1238
+
1239
+ email: string;
1240
+
1241
+ name: string;
1242
+
1243
+ age?: number;
1244
+
1245
+ constructor(props: {
1246
+ id: number;
1247
+ email: string;
1248
+ name?: string;
1249
+ age?: number;
1250
+ }) {
1251
+ this.id = props.id;
1252
+ this.email = props.email;
1253
+ this.name = props.name as string;
1254
+ this.age = props.age as number;
1255
+ }
1256
+
1257
+ /**
1258
+ * Deserializes input to an instance of this class.
1259
+ * Automatically detects whether input is a JSON string or object.
1260
+ * @param input - JSON string or object to deserialize
1261
+ * @param opts - Optional deserialization options
1262
+ * @returns Result containing the deserialized instance or validation errors
1263
+ */
1264
+ static deserialize(
1265
+ input: unknown,
1266
+ opts?: DeserializeOptions
1267
+ ): Result<
1268
+ User,
1269
+ Array<{
1270
+ field: string;
1271
+ message: string;
1272
+ }>
1273
+ > {
1274
+ try {
1275
+ // Auto-detect: if string, parse as JSON first
1276
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
1277
+
1278
+ const ctx = DeserializeContext.create();
1279
+ const resultOrRef = User.deserializeWithContext(data, ctx);
1280
+ if (PendingRef.is(resultOrRef)) {
1281
+ return Result.err([
1282
+ {
1283
+ field: '_root',
1284
+ message: 'User.deserialize: root cannot be a forward reference'
1285
+ }
1286
+ ]);
1287
+ }
1288
+ ctx.applyPatches();
1289
+ if (opts?.freeze) {
1290
+ ctx.freezeAll();
1291
+ }
1292
+ return Result.ok(resultOrRef);
1293
+ } catch (e) {
1294
+ if (e instanceof DeserializeError) {
1295
+ return Result.err(e.errors);
1296
+ }
1297
+ const message = e instanceof Error ? e.message : String(e);
1298
+ return Result.err([
1299
+ {
1300
+ field: '_root',
1301
+ message
1302
+ }
1303
+ ]);
1304
+ }
1305
+ }
1306
+
1307
+ /** @internal */
1308
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
1309
+ if (value?.__ref !== undefined) {
1310
+ return ctx.getOrDefer(value.__ref);
1311
+ }
1312
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1313
+ throw new DeserializeError([
1314
+ {
1315
+ field: '_root',
1316
+ message: 'User.deserializeWithContext: expected an object'
1317
+ }
1318
+ ]);
1319
+ }
1320
+ const obj = value as Record<string, unknown>;
1321
+ const errors: Array<{
1322
+ field: string;
1323
+ message: string;
1324
+ }> = [];
1325
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
1326
+ for (const key of Object.keys(obj)) {
1327
+ if (!knownKeys.has(key)) {
1328
+ errors.push({
1329
+ field: key,
1330
+ message: 'unknown field'
1331
+ });
1332
+ }
1333
+ }
1334
+ if (!('id' in obj)) {
1335
+ errors.push({
1336
+ field: 'id',
1337
+ message: 'missing required field'
1338
+ });
1339
+ }
1340
+ if (!('email' in obj)) {
1341
+ errors.push({
1342
+ field: 'email',
1343
+ message: 'missing required field'
1344
+ });
1345
+ }
1346
+ if (errors.length > 0) {
1347
+ throw new DeserializeError(errors);
1348
+ }
1349
+ const instance = Object.create(User.prototype) as User;
1350
+ if (obj.__id !== undefined) {
1351
+ ctx.register(obj.__id as number, instance);
1352
+ }
1353
+ ctx.trackForFreeze(instance);
1354
+ {
1355
+ const __raw_id = obj['id'] as number;
1356
+ instance.id = __raw_id;
1357
+ }
1358
+ {
1359
+ const __raw_email = obj['email'] as string;
1360
+ instance.email = __raw_email;
1361
+ }
1362
+ if ('name' in obj && obj['name'] !== undefined) {
1363
+ const __raw_name = obj['name'] as string;
1364
+ instance.name = __raw_name;
1365
+ } else {
1366
+ instance.name = 'guest';
1367
+ }
1368
+ if ('age' in obj && obj['age'] !== undefined) {
1369
+ const __raw_age = obj['age'] as number;
1370
+ instance.age = __raw_age;
1371
+ }
1372
+ if (errors.length > 0) {
1373
+ throw new DeserializeError(errors);
1374
+ }
1375
+ return instance;
1376
+ }
1377
+
1378
+ static validateField<K extends keyof User>(
1379
+ field: K,
1380
+ value: User[K]
1381
+ ): Array<{
1382
+ field: string;
1383
+ message: string;
1384
+ }> {
1385
+ return [];
1386
+ }
1387
+
1388
+ static validateFields(partial: Partial<User>): Array<{
1389
+ field: string;
1390
+ message: string;
1391
+ }> {
1392
+ return [];
1393
+ }
1394
+
1395
+ static hasShape(obj: unknown): boolean {
1396
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
1397
+ return false;
1398
+ }
1399
+ const o = obj as Record<string, unknown>;
1400
+ return 'id' in o && 'email' in o;
1401
+ }
1402
+
1403
+ static is(obj: unknown): obj is User {
1404
+ if (obj instanceof User) {
1405
+ return true;
1406
+ }
1407
+ if (!User.hasShape(obj)) {
1408
+ return false;
1409
+ }
1410
+ const result = User.deserialize(obj);
1411
+ return Result.isOk(result);
1412
+ }
1413
+ }
1414
+
1415
+ // Usage:
1416
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
1417
+ if (Result.isOk(result)) {
1418
+ const user = result.value;
1419
+ } else {
1420
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
1421
+ }
1422
+ ```
1423
+
1424
+ Generated output:
1425
+
1426
+ ```typescript
1427
+ import { DeserializeContext } from 'macroforge/serde';
1428
+ import { DeserializeError } from 'macroforge/serde';
1429
+ import type { DeserializeOptions } from 'macroforge/serde';
1430
+ import { PendingRef } from 'macroforge/serde';
1431
+
1432
+ /** @serde({ denyUnknownFields: true }) */
1433
+ class User {
1434
+ id: number;
1435
+
1436
+ email: string;
1437
+
1438
+ name: string;
1439
+
1440
+ age?: number;
1441
+
1442
+ constructor(props: {
1443
+ id: number;
1444
+ email: string;
1445
+ name?: string;
1446
+ age?: number;
1447
+ }) {
1448
+ this.id = props.id;
1449
+ this.email = props.email;
1450
+ this.name = props.name as string;
1451
+ this.age = props.age as number;
1452
+ }
1453
+
1454
+ /**
1455
+ * Deserializes input to an instance of this class.
1456
+ * Automatically detects whether input is a JSON string or object.
1457
+ * @param input - JSON string or object to deserialize
1458
+ * @param opts - Optional deserialization options
1459
+ * @returns Result containing the deserialized instance or validation errors
1460
+ */
1461
+ static deserialize(
1462
+ input: unknown,
1463
+ opts?: DeserializeOptions
1464
+ ): Result<
1465
+ User,
1466
+ Array<{
1467
+ field: string;
1468
+ message: string;
1469
+ }>
1470
+ > {
1471
+ try {
1472
+ // Auto-detect: if string, parse as JSON first
1473
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
1474
+
1475
+ const ctx = DeserializeContext.create();
1476
+ const resultOrRef = User.deserializeWithContext(data, ctx);
1477
+ if (PendingRef.is(resultOrRef)) {
1478
+ return Result.err([
1479
+ {
1480
+ field: '_root',
1481
+ message: 'User.deserialize: root cannot be a forward reference'
1482
+ }
1483
+ ]);
1484
+ }
1485
+ ctx.applyPatches();
1486
+ if (opts?.freeze) {
1487
+ ctx.freezeAll();
1488
+ }
1489
+ return Result.ok(resultOrRef);
1490
+ } catch (e) {
1491
+ if (e instanceof DeserializeError) {
1492
+ return Result.err(e.errors);
1493
+ }
1494
+ const message = e instanceof Error ? e.message : String(e);
1495
+ return Result.err([
1496
+ {
1497
+ field: '_root',
1498
+ message
1499
+ }
1500
+ ]);
1501
+ }
1502
+ }
1503
+
1504
+ /** @internal */
1505
+ static deserializeWithContext(value: any, ctx: DeserializeContext): User | PendingRef {
1506
+ if (value?.__ref !== undefined) {
1507
+ return ctx.getOrDefer(value.__ref);
1508
+ }
1509
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
1510
+ throw new DeserializeError([
1511
+ {
1512
+ field: '_root',
1513
+ message: 'User.deserializeWithContext: expected an object'
1514
+ }
1515
+ ]);
1516
+ }
1517
+ const obj = value as Record<string, unknown>;
1518
+ const errors: Array<{
1519
+ field: string;
1520
+ message: string;
1521
+ }> = [];
1522
+ const knownKeys = new Set(['__type', '__id', '__ref', 'id', 'email', 'name', 'age']);
1523
+ for (const key of Object.keys(obj)) {
1524
+ if (!knownKeys.has(key)) {
1525
+ errors.push({
1526
+ field: key,
1527
+ message: 'unknown field'
1528
+ });
1529
+ }
1530
+ }
1531
+ if (!('id' in obj)) {
1532
+ errors.push({
1533
+ field: 'id',
1534
+ message: 'missing required field'
1535
+ });
1536
+ }
1537
+ if (!('email' in obj)) {
1538
+ errors.push({
1539
+ field: 'email',
1540
+ message: 'missing required field'
1541
+ });
1542
+ }
1543
+ if (errors.length > 0) {
1544
+ throw new DeserializeError(errors);
1545
+ }
1546
+ const instance = Object.create(User.prototype) as User;
1547
+ if (obj.__id !== undefined) {
1548
+ ctx.register(obj.__id as number, instance);
1549
+ }
1550
+ ctx.trackForFreeze(instance);
1551
+ {
1552
+ const __raw_id = obj['id'] as number;
1553
+ instance.id = __raw_id;
1554
+ }
1555
+ {
1556
+ const __raw_email = obj['email'] as string;
1557
+ instance.email = __raw_email;
1558
+ }
1559
+ if ('name' in obj && obj['name'] !== undefined) {
1560
+ const __raw_name = obj['name'] as string;
1561
+ instance.name = __raw_name;
1562
+ } else {
1563
+ instance.name = 'guest';
1564
+ }
1565
+ if ('age' in obj && obj['age'] !== undefined) {
1566
+ const __raw_age = obj['age'] as number;
1567
+ instance.age = __raw_age;
1568
+ }
1569
+ if (errors.length > 0) {
1570
+ throw new DeserializeError(errors);
1571
+ }
1572
+ return instance;
1573
+ }
1574
+
1575
+ static validateField<K extends keyof User>(
1576
+ field: K,
1577
+ value: User[K]
1578
+ ): Array<{
1579
+ field: string;
1580
+ message: string;
1581
+ }> {
1582
+ return [];
1583
+ }
1584
+
1585
+ static validateFields(partial: Partial<User>): Array<{
1586
+ field: string;
1587
+ message: string;
1588
+ }> {
1589
+ return [];
1590
+ }
1591
+
1592
+ static hasShape(obj: unknown): boolean {
1593
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
1594
+ return false;
1595
+ }
1596
+ const o = obj as Record<string, unknown>;
1597
+ return 'id' in o && 'email' in o;
1598
+ }
1599
+
1600
+ static is(obj: unknown): obj is User {
1601
+ if (obj instanceof User) {
1602
+ return true;
1603
+ }
1604
+ if (!User.hasShape(obj)) {
1605
+ return false;
1606
+ }
1607
+ const result = User.deserialize(obj);
1608
+ return Result.isOk(result);
1609
+ }
1610
+ }
1611
+
1612
+ // Usage:
1613
+ const result = User.deserialize('{"id":1,"email":"test@example.com"}');
1614
+ if (Result.isOk(result)) {
1615
+ const user = result.value;
1616
+ } else {
1617
+ console.error(result.error); // [{ field: "email", message: "must be a valid email" }]
1618
+ }
1619
+ ```
1620
+
1621
+ ## Required Imports
1622
+
1623
+ The generated code automatically imports:
1624
+ - `Result` from `macroforge/utils`
1625
+ - `DeserializeContext`, `DeserializeError`, `PendingRef` from `macroforge/serde`