@kurotako/gen-angular 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,888 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AngularGenError: () => AngularGenError,
34
+ AngularGeneratorOptions: () => AngularGeneratorOptions,
35
+ MissingZodNamespaceError: () => MissingZodNamespaceError,
36
+ MissingZodSymbolError: () => MissingZodSymbolError,
37
+ angularGenerator: () => angularGenerator
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/errors.ts
42
+ var AngularGenError = class extends Error {
43
+ code;
44
+ constructor(code, message, options) {
45
+ super(message, options);
46
+ this.name = new.target.name;
47
+ this.code = code;
48
+ }
49
+ };
50
+ var MissingZodSymbolError = class extends AngularGenError {
51
+ entityKey;
52
+ role;
53
+ constructor(entityKey2, role) {
54
+ super(
55
+ "angular_missing_zod_symbol",
56
+ `Zod artifact for '${entityKey2}' has no '${role}' symbol; regenerate with a gen-zod version that exposes it`
57
+ );
58
+ this.entityKey = entityKey2;
59
+ this.role = role;
60
+ }
61
+ };
62
+ var MissingZodNamespaceError = class extends AngularGenError {
63
+ namespace;
64
+ constructor(namespace) {
65
+ super(
66
+ "angular_missing_zod_namespace",
67
+ `Zod artifact has no 'extra.perNamespace[${JSON.stringify(namespace)}]' entry`
68
+ );
69
+ this.namespace = namespace;
70
+ }
71
+ };
72
+
73
+ // src/generator.ts
74
+ var import_config = require("@kurotako/config");
75
+
76
+ // src/artifact.ts
77
+ var import_ir = require("@kurotako/ir");
78
+
79
+ // src/names.ts
80
+ function lowerFirst(s) {
81
+ return s.length === 0 ? s : s.charAt(0).toLowerCase() + s.slice(1);
82
+ }
83
+ function controlsTypeName(entity, variant, family = "") {
84
+ return `${entity}${variant}${family}FormControls`;
85
+ }
86
+ function formTypeName(entity, variant, family = "") {
87
+ return `${entity}${variant}${family}Form`;
88
+ }
89
+ function factoryName(entity) {
90
+ return `${entity}FormFactory`;
91
+ }
92
+ function factoryMethod(variant) {
93
+ return `create${variant}Form`;
94
+ }
95
+ function relationBuilderMethod(relationName, variant) {
96
+ const cap = `${relationName.charAt(0).toUpperCase()}${relationName.slice(1)}`;
97
+ return `add${cap}${variant}`;
98
+ }
99
+ function signalSchemaName(entity, variant) {
100
+ return `${lowerFirst(entity)}${variant}FormSchema`;
101
+ }
102
+ function modelFactoryName(entity, variant) {
103
+ return `create${entity}${variant}Model`;
104
+ }
105
+ function signalFormFactoryName(entity, variant) {
106
+ return `create${entity}${variant}Form`;
107
+ }
108
+ function entityModule(namespace, entity) {
109
+ return `${namespace}/angular/${entity}.form`;
110
+ }
111
+ function runtimeModule(namespace) {
112
+ return `${namespace}/angular/zod-forms.runtime`;
113
+ }
114
+ function barrelModule(namespace) {
115
+ return `${namespace}/angular`;
116
+ }
117
+
118
+ // src/zod-artifact.ts
119
+ function entityKey(namespace, entity) {
120
+ return `${namespace}.${entity}`;
121
+ }
122
+ function zodEntity(zod, namespace, entity) {
123
+ const key = entityKey(namespace, entity);
124
+ const entry = zod.entities[key];
125
+ if (entry === void 0) {
126
+ throw new MissingZodSymbolError(key, "<entity>");
127
+ }
128
+ return entry;
129
+ }
130
+ function zodSymbol(zod, namespace, entity, role) {
131
+ const key = entityKey(namespace, entity);
132
+ const entry = zodEntity(zod, namespace, entity);
133
+ const id = entry.symbols[role];
134
+ if (id === void 0) {
135
+ throw new MissingZodSymbolError(key, role);
136
+ }
137
+ return id;
138
+ }
139
+ function zodModule(zod, namespace, entity) {
140
+ return zodEntity(zod, namespace, entity).module;
141
+ }
142
+ function zodExtra(zod) {
143
+ return zod.extra;
144
+ }
145
+ function zodNamespaceExtra(zod, namespace) {
146
+ const per = zodExtra(zod).perNamespace[namespace];
147
+ if (per === void 0) {
148
+ throw new MissingZodNamespaceError(namespace);
149
+ }
150
+ return per;
151
+ }
152
+ function zodEnum(zod, namespace, ref) {
153
+ const per = zodNamespaceExtra(zod, namespace);
154
+ const def = per.enums[ref];
155
+ if (def === void 0) {
156
+ throw new MissingZodSymbolError(`${namespace}.<enum>`, ref);
157
+ }
158
+ return { typeName: def.typeName, module: def.module };
159
+ }
160
+
161
+ // src/artifact.ts
162
+ function entitySymbols(entityName, options) {
163
+ const symbols = {};
164
+ const deep = options.relations === "deep";
165
+ const family = deep ? "Deep" : "";
166
+ if (options.forms.includes("reactive")) {
167
+ const createControls = controlsTypeName(entityName, "Create", family);
168
+ const createForm = formTypeName(entityName, "Create", family);
169
+ const updateControls = controlsTypeName(entityName, "Update", family);
170
+ const updateForm = formTypeName(entityName, "Update", family);
171
+ symbols.createControls = createControls;
172
+ symbols.createForm = createForm;
173
+ symbols.updateControls = updateControls;
174
+ symbols.updateForm = updateForm;
175
+ symbols.factory = factoryName(entityName);
176
+ if (deep) {
177
+ symbols.createDeepControls = createControls;
178
+ symbols.createDeepForm = createForm;
179
+ symbols.updateDeepControls = updateControls;
180
+ symbols.updateDeepForm = updateForm;
181
+ }
182
+ }
183
+ if (options.forms.includes("signal")) {
184
+ symbols.createSchema = signalSchemaName(entityName, "Create");
185
+ symbols.updateSchema = signalSchemaName(entityName, "Update");
186
+ symbols.createModel = modelFactoryName(entityName, "Create");
187
+ symbols.updateModel = modelFactoryName(entityName, "Update");
188
+ symbols.createSignalForm = signalFormFactoryName(entityName, "Create");
189
+ symbols.updateSignalForm = signalFormFactoryName(entityName, "Update");
190
+ }
191
+ return symbols;
192
+ }
193
+ function buildArtifact(ir, zod, options) {
194
+ const entities = {};
195
+ for (const { namespace, entity } of (0, import_ir.iterEntities)(ir)) {
196
+ entities[`${namespace}.${entity.name}`] = {
197
+ module: entityModule(namespace, entity.name),
198
+ symbols: entitySymbols(entity.name, options)
199
+ };
200
+ }
201
+ const perNamespace = {};
202
+ for (const namespace of Object.keys(ir.sources)) {
203
+ perNamespace[namespace] = {
204
+ runtimeModule: runtimeModule(namespace),
205
+ barrelModule: barrelModule(namespace)
206
+ };
207
+ }
208
+ const extra = {
209
+ forms: options.forms,
210
+ relations: options.relations,
211
+ zodVersion: zodExtra(zod).zodVersion,
212
+ perNamespace
213
+ };
214
+ const peerDependencies = options.forms.includes("signal") ? { "@angular/core": ">=22", "@angular/forms": ">=22" } : { "@angular/core": ">=17", "@angular/forms": ">=17" };
215
+ return { entities, peerDependencies, extra };
216
+ }
217
+
218
+ // src/emit/barrel.ts
219
+ function emitBarrel(source, options) {
220
+ const lines = [];
221
+ const entities = Object.values(source.entities);
222
+ if (entities.length > 0 && options.forms.length > 0) {
223
+ lines.push("export * from './zod-forms.runtime';");
224
+ }
225
+ for (const entity of entities) {
226
+ lines.push(`export * from './${entity.name}.form';`);
227
+ }
228
+ return `${lines.join("\n")}
229
+ `;
230
+ }
231
+
232
+ // src/render/imports.ts
233
+ var ImportsRecorder = class {
234
+ values = /* @__PURE__ */ new Map();
235
+ types = /* @__PURE__ */ new Map();
236
+ value(module2, name) {
237
+ add(this.values, module2, name);
238
+ }
239
+ type(module2, name) {
240
+ add(this.types, module2, name);
241
+ }
242
+ /** The full `import ...` block text, one statement per line, no trailing blank line. */
243
+ render() {
244
+ const lines = [];
245
+ for (const [spec, names] of this.values) {
246
+ lines.push({ spec, rank: 0, stmt: importStmt(spec, [...names], false) });
247
+ }
248
+ for (const [spec, names] of this.types) {
249
+ lines.push({ spec, rank: 1, stmt: importStmt(spec, [...names], true) });
250
+ }
251
+ lines.sort((a, b) => a.spec.localeCompare(b.spec) || a.rank - b.rank);
252
+ return lines.map((l) => l.stmt).join("\n");
253
+ }
254
+ };
255
+ function add(map, module2, name) {
256
+ const set = map.get(module2) ?? /* @__PURE__ */ new Set();
257
+ set.add(name);
258
+ map.set(module2, set);
259
+ }
260
+ function importStmt(spec, names, typeOnly) {
261
+ const sorted = [...names].sort((a, b) => a.localeCompare(b)).join(", ");
262
+ const kw = typeOnly ? "import type" : "import";
263
+ return `${kw} { ${sorted} } from '${spec}';`;
264
+ }
265
+
266
+ // src/render/controls.ts
267
+ var import_ir2 = require("@kurotako/ir");
268
+ var SCALAR_BASE = {
269
+ string: "string",
270
+ uuid: "string",
271
+ decimal: "string",
272
+ bytes: "string",
273
+ int: "number",
274
+ float: "number",
275
+ bigint: "bigint",
276
+ boolean: "boolean",
277
+ date: "Date",
278
+ datetime: "Date",
279
+ json: "unknown"
280
+ };
281
+ function baseType(field, zodEnumTypeName) {
282
+ switch (field.type.kind) {
283
+ case "scalar":
284
+ return SCALAR_BASE[field.type.scalar];
285
+ case "enum":
286
+ return zodEnumTypeName(field.type.ref);
287
+ case "unknown":
288
+ return "unknown";
289
+ }
290
+ }
291
+ function controlType(field, zodEnumTypeName) {
292
+ let t = baseType(field, zodEnumTypeName);
293
+ if (field.list) {
294
+ t = `${t}[]`;
295
+ }
296
+ if (field.nullable) {
297
+ t = `${t} | null`;
298
+ }
299
+ return t;
300
+ }
301
+ function enumZeroFromSource(source, entity) {
302
+ return (ref) => (0, import_ir2.resolveEnum)(source, entity, ref)?.values[0]?.name;
303
+ }
304
+ function zeroValue(field, enumZero) {
305
+ if (field.type.kind === "scalar") {
306
+ switch (field.type.scalar) {
307
+ case "string":
308
+ case "uuid":
309
+ case "decimal":
310
+ case "bytes":
311
+ return "''";
312
+ case "int":
313
+ case "float":
314
+ return "0";
315
+ case "bigint":
316
+ return "0n";
317
+ case "boolean":
318
+ return "false";
319
+ case "date":
320
+ case "datetime":
321
+ return "new Date(0)";
322
+ case "json":
323
+ return "undefined";
324
+ }
325
+ }
326
+ if (field.type.kind === "enum") {
327
+ const value = enumZero?.(field.type.ref);
328
+ return value === void 0 ? "undefined" : JSON.stringify(value);
329
+ }
330
+ return "undefined";
331
+ }
332
+ function initExpr(field, enumZero) {
333
+ if (field.list) {
334
+ return field.default?.kind === "value" ? JSON.stringify(field.default.value) : "[]";
335
+ }
336
+ if (field.default?.kind === "value") {
337
+ return JSON.stringify(field.default.value);
338
+ }
339
+ if (field.nullable) {
340
+ return "null";
341
+ }
342
+ return zeroValue(field, enumZero);
343
+ }
344
+ function controlExpr(field, typeArg, sourceExpr) {
345
+ if (field.nullable) {
346
+ return `new FormControl<${typeArg}>(${sourceExpr})`;
347
+ }
348
+ return `new FormControl(${sourceExpr}, { nonNullable: true })`;
349
+ }
350
+ function fieldControlEntry(field, zodEnumTypeName) {
351
+ return {
352
+ name: field.name,
353
+ fullType: `FormControl<${controlType(field, zodEnumTypeName)}>`
354
+ };
355
+ }
356
+ function controlsInterface(interfaceName, entries) {
357
+ if (entries.length === 0) {
358
+ return `export interface ${interfaceName} {}`;
359
+ }
360
+ const body = entries.map((e) => ` ${e.name}: ${e.fullType};`).join("\n");
361
+ return `export interface ${interfaceName} {
362
+ ${body}
363
+ }`;
364
+ }
365
+
366
+ // src/render/relations.ts
367
+ var import_ir3 = require("@kurotako/ir");
368
+ function deepRelations(entity, variant, namespace, logger) {
369
+ const out = [];
370
+ for (const relation of entity.relations) {
371
+ if ((0, import_ir3.isCrossSource)(namespace, relation)) {
372
+ logger?.debug(
373
+ `gen-angular: relation '${relation.name}' targets another source ('${relation.target.namespace}.${relation.target.entity}'); degrading to the flat FK scalar in deep mode`
374
+ );
375
+ continue;
376
+ }
377
+ const many = relation.cardinality === "many";
378
+ const targetControls = controlsTypeName(
379
+ relation.target.entity,
380
+ variant,
381
+ "Deep"
382
+ );
383
+ const targetFormType = formTypeName(
384
+ relation.target.entity,
385
+ variant,
386
+ "Deep"
387
+ );
388
+ const groupType = `FormGroup<${targetControls}>`;
389
+ const fullType = many ? `FormArray<${groupType}>` : groupType;
390
+ out.push({
391
+ relation,
392
+ many,
393
+ entry: { name: relation.name, fullType },
394
+ targetFormType,
395
+ builderMethod: relationBuilderMethod(relation.name, variant)
396
+ });
397
+ }
398
+ return out;
399
+ }
400
+
401
+ // src/render/variants.ts
402
+ var import_ir4 = require("@kurotako/ir");
403
+ function variantFields(entity, variant) {
404
+ return variant === "Create" ? (0, import_ir4.createFields)(entity) : (0, import_ir4.updateFields)(entity);
405
+ }
406
+
407
+ // src/render/reactive.ts
408
+ function lowerFirst2(s) {
409
+ return s.length === 0 ? s : s.charAt(0).toLowerCase() + s.slice(1);
410
+ }
411
+ function reactiveEntity(entity, namespace, source, options, zod, imports, logger) {
412
+ const deep = options.relations === "deep";
413
+ const enumZero = enumZeroFromSource(source, entity);
414
+ imports.value("@angular/core", "Injectable");
415
+ imports.value("@angular/forms", "FormControl");
416
+ imports.value("@angular/forms", "FormGroup");
417
+ const zodEnumTypeName = (ref) => {
418
+ const e = zodEnum(zod, namespace, ref);
419
+ imports.type(e.module, e.typeName);
420
+ return e.typeName;
421
+ };
422
+ const blocks = [];
423
+ const injectedFactories = /* @__PURE__ */ new Map();
424
+ for (const variant of ["Create", "Update"]) {
425
+ const family = deep ? "Deep" : "";
426
+ const schemaRole = deep ? variant === "Create" ? "createDeepSchema" : "updateDeepSchema" : variant === "Create" ? "createSchema" : "updateSchema";
427
+ const typeRole = deep ? variant === "Create" ? "createDeepType" : "updateDeepType" : variant === "Create" ? "createType" : "updateType";
428
+ const module2 = zodModule(zod, namespace, entity.name);
429
+ const schemaId = zodSymbol(zod, namespace, entity.name, schemaRole);
430
+ const typeId = zodSymbol(zod, namespace, entity.name, typeRole);
431
+ imports.value(module2, schemaId);
432
+ imports.type(module2, typeId);
433
+ imports.value(`${namespace}/angular/zod-forms.runtime`, "zodValidator");
434
+ const interfaceName = controlsTypeName(entity.name, variant, family);
435
+ const formType = formTypeName(entity.name, variant, family);
436
+ const fieldEntries = variantFields(entity, variant).map(
437
+ (field) => fieldControlEntry(field, zodEnumTypeName)
438
+ );
439
+ const relations = deep ? deepRelations(entity, variant, namespace, logger) : [];
440
+ const manyRelations = relations.filter((r) => r.many);
441
+ if (manyRelations.length > 0) {
442
+ imports.value("@angular/forms", "FormArray");
443
+ }
444
+ for (const rel of relations) {
445
+ const targetModule = `${namespace}/angular/${rel.relation.target.entity}.form`;
446
+ imports.type(
447
+ targetModule,
448
+ controlsTypeName(rel.relation.target.entity, variant, "Deep")
449
+ );
450
+ imports.type(targetModule, rel.targetFormType);
451
+ if (!injectedFactories.has(rel.relation.target.entity)) {
452
+ const paramName = `${lowerFirst2(rel.relation.target.entity)}FormFactory`;
453
+ injectedFactories.set(rel.relation.target.entity, paramName);
454
+ imports.value(targetModule, factoryName(rel.relation.target.entity));
455
+ }
456
+ if (rel.many) {
457
+ const targetTypeRole = deep && variant === "Create" ? "createDeepType" : deep && variant === "Update" ? "updateDeepType" : variant === "Create" ? "createType" : "updateType";
458
+ const targetZodModule = zodModule(
459
+ zod,
460
+ namespace,
461
+ rel.relation.target.entity
462
+ );
463
+ const targetTypeId = zodSymbol(
464
+ zod,
465
+ namespace,
466
+ rel.relation.target.entity,
467
+ targetTypeRole
468
+ );
469
+ imports.type(targetZodModule, targetTypeId);
470
+ }
471
+ }
472
+ blocks.push(
473
+ controlsInterface(interfaceName, [
474
+ ...fieldEntries,
475
+ ...relations.map((r) => r.entry)
476
+ ])
477
+ );
478
+ blocks.push(`export type ${formType} = FormGroup<${interfaceName}>;`);
479
+ }
480
+ blocks.push(
481
+ renderFactoryClass(
482
+ entity,
483
+ namespace,
484
+ options,
485
+ zod,
486
+ injectedFactories,
487
+ enumZero
488
+ )
489
+ );
490
+ return blocks.join("\n\n");
491
+ }
492
+ function renderFactoryClass(entity, namespace, options, zod, injectedFactories, enumZero) {
493
+ const deep = options.relations === "deep";
494
+ const className = factoryName(entity.name);
495
+ const ctorParams = [...injectedFactories.entries()].map(
496
+ ([target, param]) => `private readonly ${param}: ${factoryName(target)}`
497
+ ).join(", ");
498
+ const ctor = ctorParams.length > 0 ? `
499
+ constructor(${ctorParams}) {}
500
+ ` : "";
501
+ const methods = [];
502
+ for (const variant of ["Create", "Update"]) {
503
+ methods.push(
504
+ renderFactoryMethod(
505
+ entity,
506
+ namespace,
507
+ variant,
508
+ options,
509
+ zod,
510
+ injectedFactories,
511
+ enumZero
512
+ )
513
+ );
514
+ }
515
+ if (deep) {
516
+ for (const variant of ["Create", "Update"]) {
517
+ const relations = deepRelations(
518
+ entity,
519
+ variant,
520
+ namespace,
521
+ void 0
522
+ ).filter((r) => r.many);
523
+ for (const rel of relations) {
524
+ methods.push(
525
+ renderBuilderMethod(
526
+ entity,
527
+ namespace,
528
+ variant,
529
+ options,
530
+ zod,
531
+ rel,
532
+ injectedFactories
533
+ )
534
+ );
535
+ }
536
+ }
537
+ }
538
+ const body = [
539
+ ctor,
540
+ ...methods.map((m) => `
541
+ ${m.split("\n").join("\n ")}
542
+ `)
543
+ ].join("").trimEnd();
544
+ return `@Injectable({ providedIn: 'root' })
545
+ export class ${className} {
546
+ ${body}
547
+ }`;
548
+ }
549
+ function renderFactoryMethod(entity, namespace, variant, options, zod, injectedFactories, enumZero) {
550
+ const deep = options.relations === "deep";
551
+ const family = deep ? "Deep" : "";
552
+ const typeRole = deep && variant === "Create" ? "createDeepType" : deep && variant === "Update" ? "updateDeepType" : variant === "Create" ? "createType" : "updateType";
553
+ const schemaRole = deep && variant === "Create" ? "createDeepSchema" : deep && variant === "Update" ? "updateDeepSchema" : variant === "Create" ? "createSchema" : "updateSchema";
554
+ const typeId = zodSymbol(zod, namespace, entity.name, typeRole);
555
+ const schemaId = zodSymbol(zod, namespace, entity.name, schemaRole);
556
+ const interfaceName = controlsTypeName(entity.name, variant, family);
557
+ const formType = formTypeName(entity.name, variant, family);
558
+ const methodName = factoryMethod(variant);
559
+ const zodEnumTypeName = (ref) => ref;
560
+ const fields = variantFields(entity, variant);
561
+ const lines = fields.map((field) => {
562
+ const typeArg = controlType(field, zodEnumTypeName);
563
+ 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)},`;
566
+ });
567
+ const relations = deep ? deepRelations(entity, variant, namespace, void 0) : [];
568
+ const relationLines = relations.map((r) => {
569
+ if (r.many) {
570
+ const targetControls = controlsTypeName(
571
+ r.relation.target.entity,
572
+ variant,
573
+ "Deep"
574
+ );
575
+ return ` ${r.entry.name}: new FormArray<FormGroup<${targetControls}>>([]),`;
576
+ }
577
+ const factoryParam = injectedFactories.get(r.relation.target.entity) ?? `${lowerFirst2(r.relation.target.entity)}FormFactory`;
578
+ const nestedArg = variant === "Create" ? `init?.${r.relation.name}` : `value.${r.relation.name}!`;
579
+ return ` ${r.entry.name}: this.${factoryParam}.${factoryMethod(variant)}(${nestedArg}),`;
580
+ });
581
+ const groupBody = [...lines, ...relationLines].join("\n");
582
+ const paramList = variant === "Create" ? `init?: Partial<${typeId}>` : `value: ${typeId}`;
583
+ return `${methodName}(${paramList}): ${formType} {
584
+ return new FormGroup<${interfaceName}>({
585
+ ${groupBody}
586
+ }, { validators: [zodValidator(${schemaId})] });
587
+ }`;
588
+ }
589
+ function renderBuilderMethod(entity, namespace, variant, options, zod, rel, injectedFactories) {
590
+ const deep = options.relations === "deep";
591
+ const target = rel.relation.target.entity;
592
+ const factoryParam = injectedFactories.get(target) ?? `${lowerFirst2(target)}FormFactory`;
593
+ const formType = formTypeName(entity.name, variant, "Deep");
594
+ const targetFormType = rel.targetFormType;
595
+ const method = rel.builderMethod;
596
+ const targetTypeRole = deep && variant === "Create" ? "createDeepType" : deep && variant === "Update" ? "updateDeepType" : variant === "Create" ? "createType" : "updateType";
597
+ const targetTypeId = zodSymbol(zod, namespace, target, targetTypeRole);
598
+ const param = variant === "Create" ? `init?: Partial<${targetTypeId}>` : `value: ${targetTypeId}`;
599
+ const createCall = `this.${factoryParam}.${factoryMethod(variant)}(${variant === "Create" ? "init" : "value"})`;
600
+ return `${method}(form: ${formType}, ${param}): ${targetFormType} {
601
+ const group = ${createCall};
602
+ form.controls.${rel.relation.name}.push(group);
603
+ return group;
604
+ }`;
605
+ }
606
+
607
+ // src/render/signal.ts
608
+ function signalEntity(entity, namespace, source, options, zod, imports, logger) {
609
+ const deep = options.relations === "deep";
610
+ const enumZero = enumZeroFromSource(source, entity);
611
+ imports.value("@angular/forms/signals", "schema");
612
+ imports.value("@angular/forms/signals", "form");
613
+ imports.type("@angular/forms/signals", "FieldTree");
614
+ imports.value("@angular/core", "signal");
615
+ const blocks = [];
616
+ for (const variant of ["Create", "Update"]) {
617
+ const typeRole = deep && variant === "Create" ? "createDeepType" : deep && variant === "Update" ? "updateDeepType" : variant === "Create" ? "createType" : "updateType";
618
+ const schemaRole = deep && variant === "Create" ? "createDeepSchema" : deep && variant === "Update" ? "updateDeepSchema" : variant === "Create" ? "createSchema" : "updateSchema";
619
+ const module2 = zodModule(zod, namespace, entity.name);
620
+ const typeId = zodSymbol(zod, namespace, entity.name, typeRole);
621
+ const schemaId = zodSymbol(zod, namespace, entity.name, schemaRole);
622
+ imports.type(module2, typeId);
623
+ imports.value(module2, schemaId);
624
+ imports.value(`${namespace}/angular/zod-forms.runtime`, "zodTreeValidate");
625
+ const fields = variantFields(entity, variant);
626
+ const fieldLines = fields.map(
627
+ (field) => ` ${field.name}: init?.${field.name} ?? ${initExpr(field, enumZero)},`
628
+ );
629
+ const relations = deep ? deepRelations(entity, variant, namespace, logger) : [];
630
+ const relationLines = relations.map((rel) => {
631
+ if (rel.many) {
632
+ return ` ${rel.relation.name}: [],`;
633
+ }
634
+ const targetModule = `${namespace}/angular/${rel.relation.target.entity}.form`;
635
+ const targetModelFactory = modelFactoryName(
636
+ rel.relation.target.entity,
637
+ variant
638
+ );
639
+ imports.value(targetModule, targetModelFactory);
640
+ return ` ${rel.relation.name}: ${targetModelFactory}(init?.${rel.relation.name}),`;
641
+ });
642
+ const modelBody = [...fieldLines, ...relationLines].join("\n");
643
+ const modelName = modelFactoryName(entity.name, variant);
644
+ blocks.push(
645
+ `export function ${modelName}(init?: Partial<${typeId}>): ${typeId} {
646
+ return {
647
+ ${modelBody}
648
+ };
649
+ }`
650
+ );
651
+ const schemaConst = signalSchemaName(entity.name, variant);
652
+ blocks.push(
653
+ `export const ${schemaConst} = schema<${typeId}>((path) => {
654
+ zodTreeValidate(path, ${schemaId});
655
+ });`
656
+ );
657
+ const formFactoryName = signalFormFactoryName(entity.name, variant);
658
+ blocks.push(
659
+ `export function ${formFactoryName}(init?: Partial<${typeId}>): FieldTree<${typeId}> {
660
+ return form(signal(${modelName}(init)), ${schemaConst});
661
+ }`
662
+ );
663
+ }
664
+ return blocks.join("\n\n");
665
+ }
666
+
667
+ // src/emit/entity.ts
668
+ function emitEntity(entity, namespace, source, options, zod, logger) {
669
+ const imports = new ImportsRecorder();
670
+ const blocks = [];
671
+ if (options.forms.includes("reactive")) {
672
+ blocks.push(
673
+ reactiveEntity(entity, namespace, source, options, zod, imports, logger)
674
+ );
675
+ }
676
+ if (options.forms.includes("signal")) {
677
+ blocks.push(
678
+ signalEntity(entity, namespace, source, options, zod, imports, logger)
679
+ );
680
+ }
681
+ const importBlock = imports.render();
682
+ return `${[importBlock, "", ...blocks].join("\n").trimEnd()}
683
+ `;
684
+ }
685
+
686
+ // src/emit/runtime.ts
687
+ var ZOD_VALIDATOR = `export function zodValidator(schema: ZodType): ValidatorFn {
688
+ return (group: AbstractControl) => {
689
+ const result = schema.safeParse(group.getRawValue());
690
+ const touched = new Set<AbstractControl>();
691
+ const rootIssues: { path: (string | number)[]; message: string }[] = [];
692
+
693
+ if (!result.success) {
694
+ for (const issue of result.error.issues) {
695
+ const path = issue.path.map(String).join('.');
696
+ const control = path === '' ? null : group.get(path);
697
+ if (control !== null && control !== undefined) {
698
+ setZodError(control, issue.message);
699
+ touched.add(control);
700
+ } else {
701
+ rootIssues.push({ path: issue.path as (string | number)[], message: issue.message });
702
+ }
703
+ }
704
+ }
705
+
706
+ for (const control of collectControls(group)) {
707
+ if (control !== group && !touched.has(control)) {
708
+ clearZodError(control);
709
+ }
710
+ }
711
+
712
+ if (rootIssues.length === 0) {
713
+ clearZodError(group);
714
+ return null;
715
+ }
716
+
717
+ const formErrors: string[] = [];
718
+ const fieldErrors: Record<string, string[]> = {};
719
+ for (const issue of rootIssues) {
720
+ if (issue.path.length === 0) {
721
+ formErrors.push(issue.message);
722
+ } else {
723
+ const key = String(issue.path[0]);
724
+ (fieldErrors[key] ??= []).push(issue.message);
725
+ }
726
+ }
727
+
728
+ const zodError = { formErrors, fieldErrors };
729
+ setZodError(group, zodError);
730
+ return { zod: zodError };
731
+ };
732
+ }
733
+
734
+ function setZodError(control: AbstractControl, message: unknown): void {
735
+ const current = control.errors;
736
+ if (current !== null && sameZodError(current.zod, message)) {
737
+ return;
738
+ }
739
+ control.setErrors({ ...current, zod: message }, { emitEvent: false });
740
+ }
741
+
742
+ function clearZodError(control: AbstractControl): void {
743
+ const current = control.errors;
744
+ if (current === null || current === undefined || !('zod' in current)) {
745
+ return;
746
+ }
747
+ const { zod: _discard, ...rest } = current;
748
+ control.setErrors(Object.keys(rest).length > 0 ? rest : null, {
749
+ emitEvent: false,
750
+ });
751
+ }
752
+
753
+ function sameZodError(a: unknown, b: unknown): boolean {
754
+ return JSON.stringify(a) === JSON.stringify(b);
755
+ }
756
+
757
+ function collectControls(control: AbstractControl): AbstractControl[] {
758
+ const out: AbstractControl[] = [control];
759
+ const children = (control as { controls?: unknown }).controls;
760
+ if (children !== null && typeof children === 'object') {
761
+ for (const child of Object.values(children as Record<string, AbstractControl>)) {
762
+ out.push(...collectControls(child));
763
+ }
764
+ }
765
+ return out;
766
+ }`;
767
+ var ZOD_TREE_VALIDATE = `export function zodTreeValidate<T>(
768
+ path: SchemaPath<T>,
769
+ schema: ZodType<T>,
770
+ ): void {
771
+ validateTree(path, (ctx) => {
772
+ const result = schema.safeParse(ctx.value());
773
+ if (result.success) {
774
+ return undefined;
775
+ }
776
+ return result.error.issues.map((issue) => ({
777
+ kind: 'custom' as const,
778
+ message: issue.message,
779
+ // Dynamically walked from the Zod issue path against the field tree's
780
+ // runtime shape; ValidationError.fieldTree accepts undefined for a
781
+ // pathless issue, so a same-shaped object (rather than branching on
782
+ // whether one was found) keeps this a single, uniform return type.
783
+ fieldTree: resolveFieldTree(ctx.fieldTree, issue.path) as
784
+ | ReadonlyFieldTree<unknown>
785
+ | undefined,
786
+ }));
787
+ });
788
+ }
789
+
790
+ function resolveFieldTree(root: unknown, path: readonly PropertyKey[]): unknown {
791
+ return path.reduce<unknown>((node, key) => {
792
+ if (node === null || typeof node !== 'object') {
793
+ return undefined;
794
+ }
795
+ return (node as Record<PropertyKey, unknown>)[key];
796
+ }, root);
797
+ }`;
798
+ function emitRuntime(_source, options) {
799
+ const reactive = options.forms.includes("reactive");
800
+ const signal = options.forms.includes("signal");
801
+ const imports = [];
802
+ if (reactive) {
803
+ imports.push(
804
+ "import type { AbstractControl, ValidatorFn } from '@angular/forms';"
805
+ );
806
+ }
807
+ if (signal) {
808
+ imports.push(
809
+ "import type { ReadonlyFieldTree, SchemaPath } from '@angular/forms/signals';",
810
+ "import { validateTree } from '@angular/forms/signals';"
811
+ );
812
+ }
813
+ imports.push("import type { ZodType } from 'zod';");
814
+ const blocks = [];
815
+ if (reactive) {
816
+ blocks.push(ZOD_VALIDATOR);
817
+ }
818
+ if (signal) {
819
+ blocks.push(ZOD_TREE_VALIDATE);
820
+ }
821
+ return `${[imports.join("\n"), "", ...blocks].join("\n").trimEnd()}
822
+ `;
823
+ }
824
+
825
+ // src/options.ts
826
+ var v = __toESM(require("valibot"), 1);
827
+ var AngularGeneratorOptions = v.object({
828
+ /** Which form surfaces to emit. Default: both. */
829
+ forms: v.optional(v.array(v.picklist(["reactive", "signal"])), [
830
+ "reactive",
831
+ "signal"
832
+ ]),
833
+ /** Relation handling: flat (FK scalars only) or deep (nested FormGroup / FormArray). */
834
+ relations: v.optional(v.picklist(["flat", "deep"]), "flat")
835
+ });
836
+
837
+ // src/generator.ts
838
+ var angularGenerator = (0, import_config.defineGenerator)({
839
+ name: "angular",
840
+ dependsOn: ["zod"],
841
+ optionsSchema: AngularGeneratorOptions,
842
+ generate(ctx, options) {
843
+ const zod = ctx.dependencies.zod;
844
+ if (zod === void 0) {
845
+ throw new Error(
846
+ "gen-angular: 'zod' dependency artifact is missing at runtime despite dependsOn: ['zod']"
847
+ );
848
+ }
849
+ const files = [];
850
+ for (const [namespace, source] of Object.entries(ctx.ir.sources)) {
851
+ const prefix = `${namespace}/angular`;
852
+ const entities = Object.values(source.entities);
853
+ if (entities.length > 0 && options.forms.length > 0) {
854
+ files.push({
855
+ path: `${prefix}/zod-forms.runtime.ts`,
856
+ content: emitRuntime(source, options)
857
+ });
858
+ }
859
+ for (const entity of entities) {
860
+ files.push({
861
+ path: `${prefix}/${entity.name}.form.ts`,
862
+ content: emitEntity(
863
+ entity,
864
+ namespace,
865
+ source,
866
+ options,
867
+ zod,
868
+ ctx.logger
869
+ )
870
+ });
871
+ }
872
+ files.push({
873
+ path: `${prefix}/index.ts`,
874
+ content: emitBarrel(source, options)
875
+ });
876
+ }
877
+ return { files, artifact: buildArtifact(ctx.ir, zod, options) };
878
+ }
879
+ });
880
+ // Annotate the CommonJS export names for ESM import in node:
881
+ 0 && (module.exports = {
882
+ AngularGenError,
883
+ AngularGeneratorOptions,
884
+ MissingZodNamespaceError,
885
+ MissingZodSymbolError,
886
+ angularGenerator
887
+ });
888
+ //# sourceMappingURL=index.cjs.map