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