@macroforge/mcp-server 0.1.42 → 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 +67 -48
- package/docs/api/expand-sync.md +88 -53
- package/docs/api/native-plugin.md +121 -71
- package/docs/api/position-mapper.md +115 -55
- package/docs/api/transform-sync.md +86 -60
- 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 +2 -32
- 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 +157 -104
- package/docs/concepts/how-macros-work.md +98 -47
- 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 +800 -520
- package/docs/getting-started/first-macro.md +98 -71
- package/docs/getting-started/installation.md +109 -65
- package/docs/integration/cli.md +214 -105
- package/docs/integration/configuration.md +115 -72
- package/docs/integration/integration-overview.md +55 -18
- package/docs/integration/mcp-server.md +84 -43
- package/docs/integration/svelte-preprocessor.md +183 -126
- package/docs/integration/typescript-plugin.md +101 -53
- package/docs/integration/vite-plugin.md +116 -76
- package/docs/language-servers/ls-overview.md +37 -21
- package/docs/language-servers/svelte.md +69 -38
- package/docs/language-servers/zed.md +81 -44
- package/docs/roadmap/roadmap.md +75 -53
- package/docs/sections.json +333 -44
- package/package.json +27 -28
|
@@ -17,34 +17,6 @@ class User {
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
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
20
|
import { DeserializeContext } from 'macroforge/serde';
|
|
49
21
|
import { DeserializeError } from 'macroforge/serde';
|
|
50
22
|
import type { DeserializeOptions } from 'macroforge/serde';
|
|
@@ -81,7 +53,7 @@ class User {
|
|
|
81
53
|
*/
|
|
82
54
|
static deserialize(
|
|
83
55
|
input: unknown,
|
|
84
|
-
opts?:
|
|
56
|
+
opts?: @{DESERIALIZE_OPTIONS}
|
|
85
57
|
): Result<
|
|
86
58
|
User,
|
|
87
59
|
Array<{
|
|
@@ -93,9 +65,9 @@ class User {
|
|
|
93
65
|
// Auto-detect: if string, parse as JSON first
|
|
94
66
|
const data = typeof input === 'string' ? JSON.parse(input) : input;
|
|
95
67
|
|
|
96
|
-
const ctx =
|
|
68
|
+
const ctx = @{DESERIALIZE_CONTEXT}.create();
|
|
97
69
|
const resultOrRef = User.deserializeWithContext(data, ctx);
|
|
98
|
-
if (
|
|
70
|
+
if (@{PENDING_REF}.is(resultOrRef)) {
|
|
99
71
|
return Result.err([
|
|
100
72
|
{
|
|
101
73
|
field: '_root',
|
|
@@ -109,7 +81,7 @@ class User {
|
|
|
109
81
|
}
|
|
110
82
|
return Result.ok(resultOrRef);
|
|
111
83
|
} catch (e) {
|
|
112
|
-
if (e instanceof
|
|
84
|
+
if (e instanceof @{DESERIALIZE_ERROR}) {
|
|
113
85
|
return Result.err(e.errors);
|
|
114
86
|
}
|
|
115
87
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -123,12 +95,12 @@ class User {
|
|
|
123
95
|
}
|
|
124
96
|
|
|
125
97
|
/** @internal */
|
|
126
|
-
static deserializeWithContext(value: any, ctx:
|
|
98
|
+
static deserializeWithContext(value: any, ctx: @{DESERIALIZE_CONTEXT}): User | @{PENDING_REF} {
|
|
127
99
|
if (value?.__ref !== undefined) {
|
|
128
100
|
return ctx.getOrDefer(value.__ref);
|
|
129
101
|
}
|
|
130
102
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
131
|
-
throw new
|
|
103
|
+
throw new @{DESERIALIZE_ERROR}([
|
|
132
104
|
{
|
|
133
105
|
field: '_root',
|
|
134
106
|
message: 'User.deserializeWithContext: expected an object'
|
|
@@ -162,7 +134,7 @@ class User {
|
|
|
162
134
|
});
|
|
163
135
|
}
|
|
164
136
|
if (errors.length > 0) {
|
|
165
|
-
throw new
|
|
137
|
+
throw new @{DESERIALIZE_ERROR}(errors);
|
|
166
138
|
}
|
|
167
139
|
const instance = Object.create(User.prototype) as User;
|
|
168
140
|
if (obj.__id !== undefined) {
|
|
@@ -188,1386 +160,7 @@ class User {
|
|
|
188
160
|
instance.age = __raw_age;
|
|
189
161
|
}
|
|
190
162
|
if (errors.length > 0) {
|
|
191
|
-
throw new
|
|
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);
|
|
163
|
+
throw new @{DESERIALIZE_ERROR}(errors);
|
|
1571
164
|
}
|
|
1572
165
|
return instance;
|
|
1573
166
|
}
|
|
@@ -1621,5 +214,4 @@ if (Result.isOk(result)) {
|
|
|
1621
214
|
## Required Imports
|
|
1622
215
|
|
|
1623
216
|
The generated code automatically imports:
|
|
1624
|
-
- `Result` from `macroforge/utils`
|
|
1625
217
|
- `DeserializeContext`, `DeserializeError`, `PendingRef` from `macroforge/serde`
|