@kurotako/gen-angular 0.1.0 → 0.2.0
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/CHANGELOG.md +36 -0
- package/dist/index.cjs +302 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +297 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @kurotako/gen-angular
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- da7dd32: Union-type control support. A `{ kind: 'ref' }` field now types its control as
|
|
8
|
+
the Zod-emitted DTO / alias name (`FormControl<AddressDto>`); a non-discriminated
|
|
9
|
+
`union` types as the variant types joined (`FormControl<string | number>`), a branch whose ref is
|
|
10
|
+
in `GenerateContext.cycles` widening the control to `unknown`. Both fallback
|
|
11
|
+
controls carry a `// union: validated by zodValidator(schema)` note and a
|
|
12
|
+
`logger.warn`.
|
|
13
|
+
|
|
14
|
+
A **discriminated** union field (`discriminator.mapping` set, every target an
|
|
15
|
+
entity in the same source) becomes a nested `FormGroup` holding a discriminator
|
|
16
|
+
`FormControl` plus one `FormGroup<<Variant>FormControls>` per discriminator
|
|
17
|
+
value, built by delegating to the target entity's injected `FormFactory`. A new
|
|
18
|
+
runtime helper `switchDiscriminatedGroup` toggles the active sub-group on the
|
|
19
|
+
discriminator control's `valueChanges` and rewrites the group's `getRawValue()`
|
|
20
|
+
to the flat active-variant shape a root `z.discriminatedUnion` schema can parse.
|
|
21
|
+
|
|
22
|
+
A `{ kind: 'ref' }` field whose target is an alias resolves its type through the
|
|
23
|
+
Zod artifact's alias entry; `gen-angular` itself adds no alias entry to its own
|
|
24
|
+
artifact (aliases produce no Angular form and `gen-zod`'s `<ns>/zod/aliases.ts`
|
|
25
|
+
is the single exporter — re-declaring them would make the root-barrel ambiguity
|
|
26
|
+
check flag a phantom conflict).
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [489a50d]
|
|
31
|
+
- Updated dependencies [c575bc1]
|
|
32
|
+
- Updated dependencies [6307397]
|
|
33
|
+
- Updated dependencies [7a50439]
|
|
34
|
+
- @kurotako/core@0.1.1
|
|
35
|
+
- @kurotako/gen-zod@0.2.0
|
|
36
|
+
- @kurotako/ir@0.2.0
|
|
37
|
+
- @kurotako/config@0.1.1
|
|
38
|
+
|
|
3
39
|
## 0.1.0
|
|
4
40
|
|
|
5
41
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -139,6 +139,18 @@ function zodSymbol(zod, namespace, entity, role) {
|
|
|
139
139
|
function zodModule(zod, namespace, entity) {
|
|
140
140
|
return zodEntity(zod, namespace, entity).module;
|
|
141
141
|
}
|
|
142
|
+
function zodRefType(zod, namespace, ref) {
|
|
143
|
+
const key = entityKey(namespace, ref);
|
|
144
|
+
const entry = zod.entities[key];
|
|
145
|
+
if (entry === void 0) {
|
|
146
|
+
throw new MissingZodSymbolError(key, "type");
|
|
147
|
+
}
|
|
148
|
+
const id = entry.symbols.type;
|
|
149
|
+
if (id === void 0) {
|
|
150
|
+
throw new MissingZodSymbolError(key, "type");
|
|
151
|
+
}
|
|
152
|
+
return { typeName: id, module: entry.module };
|
|
153
|
+
}
|
|
142
154
|
function zodExtra(zod) {
|
|
143
155
|
return zod.extra;
|
|
144
156
|
}
|
|
@@ -264,7 +276,37 @@ function importStmt(spec, names, typeOnly) {
|
|
|
264
276
|
}
|
|
265
277
|
|
|
266
278
|
// src/render/controls.ts
|
|
279
|
+
var import_ir3 = require("@kurotako/ir");
|
|
280
|
+
|
|
281
|
+
// src/render/unions.ts
|
|
267
282
|
var import_ir2 = require("@kurotako/ir");
|
|
283
|
+
function discriminatedUnion(field, source) {
|
|
284
|
+
const type = field.type;
|
|
285
|
+
if (type.kind !== "union" || type.discriminator?.mapping === void 0) {
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
288
|
+
if (field.list || field.nullable) {
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
const variants = [];
|
|
292
|
+
for (const [value, ref] of Object.entries(type.discriminator.mapping)) {
|
|
293
|
+
if (source.entities[ref] === void 0) {
|
|
294
|
+
return void 0;
|
|
295
|
+
}
|
|
296
|
+
variants.push({ value, entity: ref });
|
|
297
|
+
}
|
|
298
|
+
return variants.length >= 2 ? { discriminator: type.discriminator.propertyName, variants } : void 0;
|
|
299
|
+
}
|
|
300
|
+
function refNames(type, into = /* @__PURE__ */ new Set()) {
|
|
301
|
+
if (type.kind === "ref") {
|
|
302
|
+
into.add(type.ref);
|
|
303
|
+
} else if (type.kind === "union") {
|
|
304
|
+
for (const variant of type.variants) {
|
|
305
|
+
refNames(variant, into);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return into;
|
|
309
|
+
}
|
|
268
310
|
var SCALAR_BASE = {
|
|
269
311
|
string: "string",
|
|
270
312
|
uuid: "string",
|
|
@@ -278,18 +320,71 @@ var SCALAR_BASE = {
|
|
|
278
320
|
datetime: "Date",
|
|
279
321
|
json: "unknown"
|
|
280
322
|
};
|
|
281
|
-
function
|
|
323
|
+
function variantType(type, refTypeName, enumTypeName, cyclicRefs) {
|
|
324
|
+
switch (type.kind) {
|
|
325
|
+
case "scalar":
|
|
326
|
+
return SCALAR_BASE[type.scalar] ?? "unknown";
|
|
327
|
+
case "enum":
|
|
328
|
+
return enumTypeName(type.ref);
|
|
329
|
+
case "ref":
|
|
330
|
+
return refTypeName(type.ref);
|
|
331
|
+
case "unknown":
|
|
332
|
+
return "unknown";
|
|
333
|
+
case "union":
|
|
334
|
+
return unionType(type, refTypeName, enumTypeName, cyclicRefs).text;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function unionType(type, refTypeName, enumTypeName, cyclicRefs = /* @__PURE__ */ new Set()) {
|
|
338
|
+
const variants = (0, import_ir2.flattenUnion)(type);
|
|
339
|
+
if (variants.length === 0) {
|
|
340
|
+
return { text: "unknown", recursive: false };
|
|
341
|
+
}
|
|
342
|
+
const recursive = [...refNames(type)].some((ref) => cyclicRefs.has(ref));
|
|
343
|
+
if (recursive) {
|
|
344
|
+
return { text: "unknown", recursive: true };
|
|
345
|
+
}
|
|
346
|
+
const text = variants.map((variant) => {
|
|
347
|
+
const inner = variantType(variant, refTypeName, enumTypeName, cyclicRefs);
|
|
348
|
+
return variant.kind === "union" ? `(${inner})` : inner;
|
|
349
|
+
}).join(" | ");
|
|
350
|
+
return { text, recursive: false };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/render/controls.ts
|
|
354
|
+
var SCALAR_BASE2 = {
|
|
355
|
+
string: "string",
|
|
356
|
+
uuid: "string",
|
|
357
|
+
decimal: "string",
|
|
358
|
+
bytes: "string",
|
|
359
|
+
int: "number",
|
|
360
|
+
float: "number",
|
|
361
|
+
bigint: "bigint",
|
|
362
|
+
boolean: "boolean",
|
|
363
|
+
date: "Date",
|
|
364
|
+
datetime: "Date",
|
|
365
|
+
json: "unknown"
|
|
366
|
+
};
|
|
367
|
+
function baseType(field, resolvers) {
|
|
282
368
|
switch (field.type.kind) {
|
|
283
369
|
case "scalar":
|
|
284
|
-
return
|
|
370
|
+
return SCALAR_BASE2[field.type.scalar];
|
|
285
371
|
case "enum":
|
|
286
|
-
return
|
|
372
|
+
return resolvers.enumTypeName(field.type.ref);
|
|
287
373
|
case "unknown":
|
|
288
374
|
return "unknown";
|
|
375
|
+
case "ref":
|
|
376
|
+
return resolvers.refTypeName(field.type.ref);
|
|
377
|
+
case "union":
|
|
378
|
+
return unionType(
|
|
379
|
+
field.type,
|
|
380
|
+
resolvers.refTypeName,
|
|
381
|
+
resolvers.enumTypeName,
|
|
382
|
+
resolvers.cyclicRefs
|
|
383
|
+
).text;
|
|
289
384
|
}
|
|
290
385
|
}
|
|
291
|
-
function controlType(field,
|
|
292
|
-
let t = baseType(field,
|
|
386
|
+
function controlType(field, resolvers) {
|
|
387
|
+
let t = baseType(field, resolvers);
|
|
293
388
|
if (field.list) {
|
|
294
389
|
t = `${t}[]`;
|
|
295
390
|
}
|
|
@@ -299,7 +394,7 @@ function controlType(field, zodEnumTypeName) {
|
|
|
299
394
|
return t;
|
|
300
395
|
}
|
|
301
396
|
function enumZeroFromSource(source, entity) {
|
|
302
|
-
return (ref) => (0,
|
|
397
|
+
return (ref) => (0, import_ir3.resolveEnum)(source, entity, ref)?.values[0]?.name;
|
|
303
398
|
}
|
|
304
399
|
function zeroValue(field, enumZero) {
|
|
305
400
|
if (field.type.kind === "scalar") {
|
|
@@ -345,12 +440,18 @@ function controlExpr(field, typeArg, sourceExpr) {
|
|
|
345
440
|
if (field.nullable) {
|
|
346
441
|
return `new FormControl<${typeArg}>(${sourceExpr})`;
|
|
347
442
|
}
|
|
443
|
+
if (field.type.kind === "union") {
|
|
444
|
+
return `new FormControl<${typeArg}>((${sourceExpr}) as ${typeArg}) /* union: validated by zodValidator(schema) */`;
|
|
445
|
+
}
|
|
446
|
+
if (field.type.kind === "ref") {
|
|
447
|
+
return `new FormControl<${typeArg}>((${sourceExpr}) as ${typeArg})`;
|
|
448
|
+
}
|
|
348
449
|
return `new FormControl(${sourceExpr}, { nonNullable: true })`;
|
|
349
450
|
}
|
|
350
|
-
function fieldControlEntry(field,
|
|
451
|
+
function fieldControlEntry(field, resolvers) {
|
|
351
452
|
return {
|
|
352
453
|
name: field.name,
|
|
353
|
-
fullType: `FormControl<${controlType(field,
|
|
454
|
+
fullType: `FormControl<${controlType(field, resolvers)}>`
|
|
354
455
|
};
|
|
355
456
|
}
|
|
356
457
|
function controlsInterface(interfaceName, entries) {
|
|
@@ -364,11 +465,11 @@ ${body}
|
|
|
364
465
|
}
|
|
365
466
|
|
|
366
467
|
// src/render/relations.ts
|
|
367
|
-
var
|
|
468
|
+
var import_ir4 = require("@kurotako/ir");
|
|
368
469
|
function deepRelations(entity, variant, namespace, logger) {
|
|
369
470
|
const out = [];
|
|
370
471
|
for (const relation of entity.relations) {
|
|
371
|
-
if ((0,
|
|
472
|
+
if ((0, import_ir4.isCrossSource)(namespace, relation)) {
|
|
372
473
|
logger?.debug(
|
|
373
474
|
`gen-angular: relation '${relation.name}' targets another source ('${relation.target.namespace}.${relation.target.entity}'); degrading to the flat FK scalar in deep mode`
|
|
374
475
|
);
|
|
@@ -399,16 +500,48 @@ function deepRelations(entity, variant, namespace, logger) {
|
|
|
399
500
|
}
|
|
400
501
|
|
|
401
502
|
// src/render/variants.ts
|
|
402
|
-
var
|
|
503
|
+
var import_ir5 = require("@kurotako/ir");
|
|
403
504
|
function variantFields(entity, variant) {
|
|
404
|
-
return variant === "Create" ? (0,
|
|
505
|
+
return variant === "Create" ? (0, import_ir5.createFields)(entity) : (0, import_ir5.updateFields)(entity);
|
|
405
506
|
}
|
|
406
507
|
|
|
407
508
|
// src/render/reactive.ts
|
|
509
|
+
function discriminatedControlEntry(union, fieldName, variant, family) {
|
|
510
|
+
const lit = union.variants.map((v2) => JSON.stringify(v2.value)).join(" | ");
|
|
511
|
+
const subs = union.variants.map(
|
|
512
|
+
(v2) => `${JSON.stringify(v2.value)}: FormGroup<${controlsTypeName(v2.entity, variant, family)}>`
|
|
513
|
+
).join("; ");
|
|
514
|
+
return {
|
|
515
|
+
name: fieldName,
|
|
516
|
+
fullType: `FormGroup<{ ${JSON.stringify(union.discriminator)}: FormControl<${lit}>; ${subs} }>`
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function discriminatedFactoryExpr(union, fieldName, variant, deep, zod, namespace, injectedFactories) {
|
|
520
|
+
const lit = union.variants.map((v2) => JSON.stringify(v2.value)).join(" | ");
|
|
521
|
+
const first = JSON.stringify(union.variants[0]?.value ?? "");
|
|
522
|
+
const method = factoryMethod(variant);
|
|
523
|
+
const subLines = union.variants.map((v2) => {
|
|
524
|
+
const param = injectedFactories.get(v2.entity) ?? `${lowerFirst2(v2.entity)}FormFactory`;
|
|
525
|
+
if (variant === "Create") {
|
|
526
|
+
return ` ${JSON.stringify(v2.value)}: this.${param}.${method}(),`;
|
|
527
|
+
}
|
|
528
|
+
const role = deep ? "updateDeepType" : "updateType";
|
|
529
|
+
const dto = zodSymbol(zod, namespace, v2.entity, role);
|
|
530
|
+
return ` ${JSON.stringify(v2.value)}: this.${param}.${method}(value.${fieldName} as unknown as ${dto}),`;
|
|
531
|
+
}).join("\n");
|
|
532
|
+
return `(() => {
|
|
533
|
+
const g = new FormGroup({
|
|
534
|
+
${JSON.stringify(union.discriminator)}: new FormControl<${lit}>(${first}, { nonNullable: true }),
|
|
535
|
+
${subLines}
|
|
536
|
+
});
|
|
537
|
+
switchDiscriminatedGroup(g, ${JSON.stringify(union.discriminator)});
|
|
538
|
+
return g;
|
|
539
|
+
})()`;
|
|
540
|
+
}
|
|
408
541
|
function lowerFirst2(s) {
|
|
409
542
|
return s.length === 0 ? s : s.charAt(0).toLowerCase() + s.slice(1);
|
|
410
543
|
}
|
|
411
|
-
function reactiveEntity(entity, namespace, source, options, zod, imports, logger) {
|
|
544
|
+
function reactiveEntity(entity, namespace, source, options, zod, imports, cyclicRefs, logger) {
|
|
412
545
|
const deep = options.relations === "deep";
|
|
413
546
|
const enumZero = enumZeroFromSource(source, entity);
|
|
414
547
|
imports.value("@angular/core", "Injectable");
|
|
@@ -421,6 +554,7 @@ function reactiveEntity(entity, namespace, source, options, zod, imports, logger
|
|
|
421
554
|
};
|
|
422
555
|
const blocks = [];
|
|
423
556
|
const injectedFactories = /* @__PURE__ */ new Map();
|
|
557
|
+
const warnedUnionFields = /* @__PURE__ */ new Set();
|
|
424
558
|
for (const variant of ["Create", "Update"]) {
|
|
425
559
|
const family = deep ? "Deep" : "";
|
|
426
560
|
const schemaRole = deep ? variant === "Create" ? "createDeepSchema" : "updateDeepSchema" : variant === "Create" ? "createSchema" : "updateSchema";
|
|
@@ -433,8 +567,61 @@ function reactiveEntity(entity, namespace, source, options, zod, imports, logger
|
|
|
433
567
|
imports.value(`${namespace}/angular/zod-forms.runtime`, "zodValidator");
|
|
434
568
|
const interfaceName = controlsTypeName(entity.name, variant, family);
|
|
435
569
|
const formType = formTypeName(entity.name, variant, family);
|
|
570
|
+
const resolvers = {
|
|
571
|
+
enumTypeName: zodEnumTypeName,
|
|
572
|
+
refTypeName: (ref) => {
|
|
573
|
+
const r = zodRefType(zod, namespace, ref);
|
|
574
|
+
imports.type(r.module, r.typeName);
|
|
575
|
+
return r.typeName;
|
|
576
|
+
},
|
|
577
|
+
cyclicRefs
|
|
578
|
+
};
|
|
436
579
|
const fieldEntries = variantFields(entity, variant).map(
|
|
437
|
-
(field) =>
|
|
580
|
+
(field) => {
|
|
581
|
+
const union = field.type.kind === "union" ? discriminatedUnion(field, source) : void 0;
|
|
582
|
+
if (union !== void 0) {
|
|
583
|
+
imports.value(
|
|
584
|
+
`${namespace}/angular/zod-forms.runtime`,
|
|
585
|
+
"switchDiscriminatedGroup"
|
|
586
|
+
);
|
|
587
|
+
for (const v2 of union.variants) {
|
|
588
|
+
const targetModule = `${namespace}/angular/${v2.entity}.form`;
|
|
589
|
+
imports.type(
|
|
590
|
+
targetModule,
|
|
591
|
+
controlsTypeName(v2.entity, variant, family)
|
|
592
|
+
);
|
|
593
|
+
if (!injectedFactories.has(v2.entity)) {
|
|
594
|
+
injectedFactories.set(
|
|
595
|
+
v2.entity,
|
|
596
|
+
`${lowerFirst2(v2.entity)}FormFactory`
|
|
597
|
+
);
|
|
598
|
+
imports.value(targetModule, factoryName(v2.entity));
|
|
599
|
+
}
|
|
600
|
+
if (variant === "Update") {
|
|
601
|
+
const role = deep ? "updateDeepType" : "updateType";
|
|
602
|
+
imports.type(
|
|
603
|
+
zodModule(zod, namespace, v2.entity),
|
|
604
|
+
zodSymbol(zod, namespace, v2.entity, role)
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return discriminatedControlEntry(union, field.name, variant, family);
|
|
609
|
+
}
|
|
610
|
+
if (field.type.kind === "union" && !warnedUnionFields.has(field.name)) {
|
|
611
|
+
warnedUnionFields.add(field.name);
|
|
612
|
+
const recursive = unionType(
|
|
613
|
+
field.type,
|
|
614
|
+
resolvers.refTypeName,
|
|
615
|
+
resolvers.enumTypeName,
|
|
616
|
+
cyclicRefs
|
|
617
|
+
).recursive;
|
|
618
|
+
const cause = recursive ? "a recursive ref branch (chains into a cycle)" : "no discriminator mapping, an alias target, or a list/nullable union";
|
|
619
|
+
logger?.warn(
|
|
620
|
+
`gen-angular: union field '${entity.name}.${field.name}' has no usable discriminated sub-form (${cause}); emitting a free FormControl${recursive ? "<unknown>" : ""} validated by zodValidator`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
return fieldControlEntry(field, resolvers);
|
|
624
|
+
}
|
|
438
625
|
);
|
|
439
626
|
const relations = deep ? deepRelations(entity, variant, namespace, logger) : [];
|
|
440
627
|
const manyRelations = relations.filter((r) => r.many);
|
|
@@ -481,15 +668,17 @@ function reactiveEntity(entity, namespace, source, options, zod, imports, logger
|
|
|
481
668
|
renderFactoryClass(
|
|
482
669
|
entity,
|
|
483
670
|
namespace,
|
|
671
|
+
source,
|
|
484
672
|
options,
|
|
485
673
|
zod,
|
|
486
674
|
injectedFactories,
|
|
487
|
-
enumZero
|
|
675
|
+
enumZero,
|
|
676
|
+
cyclicRefs
|
|
488
677
|
)
|
|
489
678
|
);
|
|
490
679
|
return blocks.join("\n\n");
|
|
491
680
|
}
|
|
492
|
-
function renderFactoryClass(entity, namespace, options, zod, injectedFactories, enumZero) {
|
|
681
|
+
function renderFactoryClass(entity, namespace, source, options, zod, injectedFactories, enumZero, cyclicRefs) {
|
|
493
682
|
const deep = options.relations === "deep";
|
|
494
683
|
const className = factoryName(entity.name);
|
|
495
684
|
const ctorParams = [...injectedFactories.entries()].map(
|
|
@@ -504,11 +693,13 @@ function renderFactoryClass(entity, namespace, options, zod, injectedFactories,
|
|
|
504
693
|
renderFactoryMethod(
|
|
505
694
|
entity,
|
|
506
695
|
namespace,
|
|
696
|
+
source,
|
|
507
697
|
variant,
|
|
508
698
|
options,
|
|
509
699
|
zod,
|
|
510
700
|
injectedFactories,
|
|
511
|
-
enumZero
|
|
701
|
+
enumZero,
|
|
702
|
+
cyclicRefs
|
|
512
703
|
)
|
|
513
704
|
);
|
|
514
705
|
}
|
|
@@ -546,7 +737,7 @@ export class ${className} {
|
|
|
546
737
|
${body}
|
|
547
738
|
}`;
|
|
548
739
|
}
|
|
549
|
-
function renderFactoryMethod(entity, namespace, variant, options, zod, injectedFactories, enumZero) {
|
|
740
|
+
function renderFactoryMethod(entity, namespace, source, variant, options, zod, injectedFactories, enumZero, cyclicRefs) {
|
|
550
741
|
const deep = options.relations === "deep";
|
|
551
742
|
const family = deep ? "Deep" : "";
|
|
552
743
|
const typeRole = deep && variant === "Create" ? "createDeepType" : deep && variant === "Update" ? "updateDeepType" : variant === "Create" ? "createType" : "updateType";
|
|
@@ -556,13 +747,29 @@ function renderFactoryMethod(entity, namespace, variant, options, zod, injectedF
|
|
|
556
747
|
const interfaceName = controlsTypeName(entity.name, variant, family);
|
|
557
748
|
const formType = formTypeName(entity.name, variant, family);
|
|
558
749
|
const methodName = factoryMethod(variant);
|
|
559
|
-
const
|
|
750
|
+
const resolvers = {
|
|
751
|
+
enumTypeName: (ref) => ref,
|
|
752
|
+
refTypeName: (ref) => zodRefType(zod, namespace, ref).typeName,
|
|
753
|
+
cyclicRefs
|
|
754
|
+
};
|
|
560
755
|
const fields = variantFields(entity, variant);
|
|
561
756
|
const lines = fields.map((field) => {
|
|
562
|
-
const
|
|
757
|
+
const union = field.type.kind === "union" ? discriminatedUnion(field, source) : void 0;
|
|
758
|
+
if (union !== void 0) {
|
|
759
|
+
return ` ${field.name}: ${discriminatedFactoryExpr(
|
|
760
|
+
union,
|
|
761
|
+
field.name,
|
|
762
|
+
variant,
|
|
763
|
+
deep,
|
|
764
|
+
zod,
|
|
765
|
+
namespace,
|
|
766
|
+
injectedFactories
|
|
767
|
+
)},`;
|
|
768
|
+
}
|
|
769
|
+
const typeArg = controlType(field, resolvers);
|
|
563
770
|
const accessor = variant === "Create" ? `init?.${field.name}` : `value.${field.name}`;
|
|
564
|
-
const
|
|
565
|
-
return ` ${field.name}: ${controlExpr(field, typeArg,
|
|
771
|
+
const seed = `${accessor} ?? ${initExpr(field, enumZero)}`;
|
|
772
|
+
return ` ${field.name}: ${controlExpr(field, typeArg, seed)},`;
|
|
566
773
|
});
|
|
567
774
|
const relations = deep ? deepRelations(entity, variant, namespace, void 0) : [];
|
|
568
775
|
const relationLines = relations.map((r) => {
|
|
@@ -623,9 +830,12 @@ function signalEntity(entity, namespace, source, options, zod, imports, logger)
|
|
|
623
830
|
imports.value(module2, schemaId);
|
|
624
831
|
imports.value(`${namespace}/angular/zod-forms.runtime`, "zodTreeValidate");
|
|
625
832
|
const fields = variantFields(entity, variant);
|
|
626
|
-
const fieldLines = fields.map(
|
|
627
|
-
(field
|
|
628
|
-
)
|
|
833
|
+
const fieldLines = fields.map((field) => {
|
|
834
|
+
if (field.type.kind === "ref" || field.type.kind === "union") {
|
|
835
|
+
return ` ${field.name}: (init?.${field.name} ?? undefined) as ${typeId}[${JSON.stringify(field.name)}],`;
|
|
836
|
+
}
|
|
837
|
+
return ` ${field.name}: init?.${field.name} ?? ${initExpr(field, enumZero)},`;
|
|
838
|
+
});
|
|
629
839
|
const relations = deep ? deepRelations(entity, variant, namespace, logger) : [];
|
|
630
840
|
const relationLines = relations.map((rel) => {
|
|
631
841
|
if (rel.many) {
|
|
@@ -665,12 +875,21 @@ ${modelBody}
|
|
|
665
875
|
}
|
|
666
876
|
|
|
667
877
|
// src/emit/entity.ts
|
|
668
|
-
function emitEntity(entity, namespace, source, options, zod, logger) {
|
|
878
|
+
function emitEntity(entity, namespace, source, options, zod, cyclicRefs, logger) {
|
|
669
879
|
const imports = new ImportsRecorder();
|
|
670
880
|
const blocks = [];
|
|
671
881
|
if (options.forms.includes("reactive")) {
|
|
672
882
|
blocks.push(
|
|
673
|
-
reactiveEntity(
|
|
883
|
+
reactiveEntity(
|
|
884
|
+
entity,
|
|
885
|
+
namespace,
|
|
886
|
+
source,
|
|
887
|
+
options,
|
|
888
|
+
zod,
|
|
889
|
+
imports,
|
|
890
|
+
cyclicRefs,
|
|
891
|
+
logger
|
|
892
|
+
)
|
|
674
893
|
);
|
|
675
894
|
}
|
|
676
895
|
if (options.forms.includes("signal")) {
|
|
@@ -764,6 +983,53 @@ function collectControls(control: AbstractControl): AbstractControl[] {
|
|
|
764
983
|
}
|
|
765
984
|
return out;
|
|
766
985
|
}`;
|
|
986
|
+
var SWITCH_DISCRIMINATED_GROUP = `/**
|
|
987
|
+
* Wire a discriminated-union sub-FormGroup: enable only the sub-group named by
|
|
988
|
+
* the discriminator control's value (disable the rest), keep it in sync on
|
|
989
|
+
* valueChanges, and rewrite the group's getRawValue() to the flat
|
|
990
|
+
* active-variant shape a z.discriminatedUnion schema can parse.
|
|
991
|
+
*/
|
|
992
|
+
export function switchDiscriminatedGroup(
|
|
993
|
+
group: AbstractControl,
|
|
994
|
+
discriminator: string,
|
|
995
|
+
): void {
|
|
996
|
+
const controls = (group as unknown as {
|
|
997
|
+
controls: Record<string, AbstractControl>;
|
|
998
|
+
}).controls;
|
|
999
|
+
const selector = controls[discriminator];
|
|
1000
|
+
if (selector === undefined) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const variantKeys = Object.keys(controls).filter(
|
|
1004
|
+
(key) => key !== discriminator,
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
const apply = (active: unknown): void => {
|
|
1008
|
+
for (const key of variantKeys) {
|
|
1009
|
+
const sub = controls[key];
|
|
1010
|
+
if (sub === undefined) {
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (key === active) {
|
|
1014
|
+
sub.enable({ emitEvent: false });
|
|
1015
|
+
} else {
|
|
1016
|
+
sub.disable({ emitEvent: false });
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
apply(selector.value);
|
|
1022
|
+
selector.valueChanges.subscribe(apply);
|
|
1023
|
+
|
|
1024
|
+
(group as { getRawValue: () => unknown }).getRawValue = () => {
|
|
1025
|
+
const active = controls[String(selector.value)];
|
|
1026
|
+
const variant =
|
|
1027
|
+
active === undefined || active === selector
|
|
1028
|
+
? {}
|
|
1029
|
+
: (active.getRawValue() as Record<string, unknown>);
|
|
1030
|
+
return { ...variant, [discriminator]: selector.value };
|
|
1031
|
+
};
|
|
1032
|
+
}`;
|
|
767
1033
|
var ZOD_TREE_VALIDATE = `export function zodTreeValidate<T>(
|
|
768
1034
|
path: SchemaPath<T>,
|
|
769
1035
|
schema: ZodType<T>,
|
|
@@ -813,7 +1079,7 @@ function emitRuntime(_source, options) {
|
|
|
813
1079
|
imports.push("import type { ZodType } from 'zod';");
|
|
814
1080
|
const blocks = [];
|
|
815
1081
|
if (reactive) {
|
|
816
|
-
blocks.push(ZOD_VALIDATOR);
|
|
1082
|
+
blocks.push(ZOD_VALIDATOR, SWITCH_DISCRIMINATED_GROUP);
|
|
817
1083
|
}
|
|
818
1084
|
if (signal) {
|
|
819
1085
|
blocks.push(ZOD_TREE_VALIDATE);
|
|
@@ -850,6 +1116,13 @@ var angularGenerator = (0, import_config.defineGenerator)({
|
|
|
850
1116
|
for (const [namespace, source] of Object.entries(ctx.ir.sources)) {
|
|
851
1117
|
const prefix = `${namespace}/angular`;
|
|
852
1118
|
const entities = Object.values(source.entities);
|
|
1119
|
+
const cyclicRefs = /* @__PURE__ */ new Set();
|
|
1120
|
+
for (const key of ctx.cycles) {
|
|
1121
|
+
const dot = key.indexOf(".");
|
|
1122
|
+
if (dot > 0 && key.slice(0, dot) === namespace) {
|
|
1123
|
+
cyclicRefs.add(key.slice(dot + 1));
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
853
1126
|
if (entities.length > 0 && options.forms.length > 0) {
|
|
854
1127
|
files.push({
|
|
855
1128
|
path: `${prefix}/zod-forms.runtime.ts`,
|
|
@@ -865,6 +1138,7 @@ var angularGenerator = (0, import_config.defineGenerator)({
|
|
|
865
1138
|
source,
|
|
866
1139
|
options,
|
|
867
1140
|
zod,
|
|
1141
|
+
cyclicRefs,
|
|
868
1142
|
ctx.logger
|
|
869
1143
|
)
|
|
870
1144
|
});
|