@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 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 baseType(field, zodEnumTypeName) {
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 SCALAR_BASE[field.type.scalar];
370
+ return SCALAR_BASE2[field.type.scalar];
285
371
  case "enum":
286
- return zodEnumTypeName(field.type.ref);
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, zodEnumTypeName) {
292
- let t = baseType(field, zodEnumTypeName);
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, import_ir2.resolveEnum)(source, entity, ref)?.values[0]?.name;
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, zodEnumTypeName) {
451
+ function fieldControlEntry(field, resolvers) {
351
452
  return {
352
453
  name: field.name,
353
- fullType: `FormControl<${controlType(field, zodEnumTypeName)}>`
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 import_ir3 = require("@kurotako/ir");
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, import_ir3.isCrossSource)(namespace, relation)) {
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 import_ir4 = require("@kurotako/ir");
503
+ var import_ir5 = require("@kurotako/ir");
403
504
  function variantFields(entity, variant) {
404
- return variant === "Create" ? (0, import_ir4.createFields)(entity) : (0, import_ir4.updateFields)(entity);
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) => fieldControlEntry(field, zodEnumTypeName)
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 zodEnumTypeName = (ref) => ref;
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 typeArg = controlType(field, zodEnumTypeName);
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 source = `${accessor} ?? ${initExpr(field, enumZero)}`;
565
- return ` ${field.name}: ${controlExpr(field, typeArg, source)},`;
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) => ` ${field.name}: init?.${field.name} ?? ${initExpr(field, enumZero)},`
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(entity, namespace, source, options, zod, imports, logger)
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
  });