@currentjs/gen 0.5.3 ā 0.5.5
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 +8 -0
- package/dist/commands/migrateCommit.js +11 -5
- package/dist/generators/domainLayerGenerator.js +7 -8
- package/dist/generators/dtoGenerator.js +5 -6
- package/dist/generators/storeGenerator.d.ts +14 -0
- package/dist/generators/storeGenerator.js +147 -32
- package/dist/generators/templateGenerator.d.ts +15 -0
- package/dist/generators/templateGenerator.js +215 -10
- package/dist/generators/templates/storeTemplates.js +24 -17
- package/dist/utils/migrationUtils.d.ts +11 -5
- package/dist/utils/migrationUtils.js +72 -27
- package/dist/utils/typeUtils.d.ts +29 -1
- package/dist/utils/typeUtils.js +103 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -42,11 +42,12 @@ const cliUtils_1 = require("../utils/cliUtils");
|
|
|
42
42
|
const commandUtils_1 = require("../utils/commandUtils");
|
|
43
43
|
const configTypes_1 = require("../types/configTypes");
|
|
44
44
|
const migrationUtils_1 = require("../utils/migrationUtils");
|
|
45
|
-
function
|
|
45
|
+
function collectSchemaFromModules(appYamlPath) {
|
|
46
46
|
const appConfig = (0, commandUtils_1.loadAppConfig)(appYamlPath);
|
|
47
47
|
const moduleEntries = (0, commandUtils_1.getModuleEntries)(appConfig);
|
|
48
48
|
const projectRoot = path.dirname(appYamlPath);
|
|
49
49
|
const allAggregates = {};
|
|
50
|
+
const allValueObjects = new Set();
|
|
50
51
|
const sources = [];
|
|
51
52
|
for (const entry of moduleEntries) {
|
|
52
53
|
const moduleYamlPath = path.isAbsolute(entry.path)
|
|
@@ -67,13 +68,18 @@ function collectAggregatesFromModules(appYamlPath) {
|
|
|
67
68
|
const aggregates = moduleConfig.domain.aggregates;
|
|
68
69
|
const count = Object.keys(aggregates).length;
|
|
69
70
|
Object.assign(allAggregates, aggregates);
|
|
71
|
+
if (moduleConfig.domain.valueObjects) {
|
|
72
|
+
for (const voName of Object.keys(moduleConfig.domain.valueObjects)) {
|
|
73
|
+
allValueObjects.add(voName);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
70
76
|
sources.push(`${entry.name} (${count} aggregate(s))`);
|
|
71
77
|
}
|
|
72
78
|
if (sources.length > 0) {
|
|
73
79
|
// eslint-disable-next-line no-console
|
|
74
80
|
console.log(colors_1.colors.gray(` Sources: ${sources.join(', ')}`));
|
|
75
81
|
}
|
|
76
|
-
return allAggregates;
|
|
82
|
+
return { aggregates: allAggregates, valueObjects: allValueObjects };
|
|
77
83
|
}
|
|
78
84
|
function handleMigrateCommit(yamlPath) {
|
|
79
85
|
try {
|
|
@@ -94,7 +100,7 @@ function handleMigrateCommit(yamlPath) {
|
|
|
94
100
|
}
|
|
95
101
|
// eslint-disable-next-line no-console
|
|
96
102
|
console.log(colors_1.colors.cyan('\nš Collecting aggregates from all modules...'));
|
|
97
|
-
const currentAggregates =
|
|
103
|
+
const { aggregates: currentAggregates, valueObjects: currentValueObjects } = collectSchemaFromModules(resolvedYamlPath);
|
|
98
104
|
if (Object.keys(currentAggregates).length === 0) {
|
|
99
105
|
// eslint-disable-next-line no-console
|
|
100
106
|
console.log(colors_1.colors.yellow('ā ļø No aggregates found in module configuration.'));
|
|
@@ -113,7 +119,7 @@ function handleMigrateCommit(yamlPath) {
|
|
|
113
119
|
}
|
|
114
120
|
// eslint-disable-next-line no-console
|
|
115
121
|
console.log(colors_1.colors.cyan('\nš Comparing schemas...'));
|
|
116
|
-
const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentAggregates);
|
|
122
|
+
const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentAggregates, currentValueObjects);
|
|
117
123
|
if (sqlStatements.length === 0 || sqlStatements.every(s => s.trim() === '' || s.startsWith('--'))) {
|
|
118
124
|
// eslint-disable-next-line no-console
|
|
119
125
|
console.log(colors_1.colors.yellow('ā ļø No changes detected. Schema is up to date.'));
|
|
@@ -132,7 +138,7 @@ function handleMigrateCommit(yamlPath) {
|
|
|
132
138
|
const newState = {
|
|
133
139
|
aggregates: currentAggregates,
|
|
134
140
|
version: timestamp,
|
|
135
|
-
timestamp: new Date().toISOString()
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
136
142
|
};
|
|
137
143
|
(0, migrationUtils_1.saveSchemaState)(stateFilePath, newState);
|
|
138
144
|
// eslint-disable-next-line no-console
|
|
@@ -137,14 +137,13 @@ class DomainLayerGenerator {
|
|
|
137
137
|
.filter(entityName => entityName !== name && allAggregates[entityName])
|
|
138
138
|
.map(entityName => `import { ${entityName} } from './${entityName}';`)
|
|
139
139
|
.join('\n');
|
|
140
|
-
// Generate imports for value objects used in fields
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.filter((imp, idx, arr) => arr.indexOf(imp) === idx) // dedupe
|
|
140
|
+
// Generate imports for value objects used in fields (including compound types like Foo[] and Foo | Bar)
|
|
141
|
+
const referencedVoNames = new Set();
|
|
142
|
+
fields.forEach(([, fieldConfig]) => {
|
|
143
|
+
(0, typeUtils_1.getReferencedValueObjects)(fieldConfig.type, this.availableValueObjects).forEach(name => referencedVoNames.add(name));
|
|
144
|
+
});
|
|
145
|
+
const valueObjectImports = [...referencedVoNames]
|
|
146
|
+
.map(voName => `import { ${voName} } from '../valueObjects/${voName}';`)
|
|
148
147
|
.join('\n');
|
|
149
148
|
// Generate imports for aggregate references in fields (e.g. idea: { type: Idea })
|
|
150
149
|
const aggregateRefImports = fields
|
|
@@ -51,8 +51,7 @@ class DtoGenerator {
|
|
|
51
51
|
return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates, this.availableValueObjects);
|
|
52
52
|
}
|
|
53
53
|
isValueObjectType(yamlType) {
|
|
54
|
-
|
|
55
|
-
return this.availableValueObjects.has(capitalizedType);
|
|
54
|
+
return (0, typeUtils_1.isValueObjectFieldType)(yamlType, this.availableValueObjects);
|
|
56
55
|
}
|
|
57
56
|
getValidationCode(fieldName, fieldType, isRequired) {
|
|
58
57
|
const checks = [];
|
|
@@ -431,11 +430,11 @@ ${mappingsStr}
|
|
|
431
430
|
if (outputConfig.pick && outputConfig.pick.length > 0) {
|
|
432
431
|
fieldsToCheck = fieldsToCheck.filter(([fieldName]) => outputConfig.pick.includes(fieldName));
|
|
433
432
|
}
|
|
434
|
-
// Check each field for value object types
|
|
433
|
+
// Check each field for value object types (including compound types like Foo[] and Foo | Bar)
|
|
435
434
|
fieldsToCheck.forEach(([, fieldConfig]) => {
|
|
436
|
-
|
|
437
|
-
valueObjects.add(
|
|
438
|
-
}
|
|
435
|
+
(0, typeUtils_1.getReferencedValueObjects)(fieldConfig.type, this.availableValueObjects).forEach(voName => {
|
|
436
|
+
valueObjects.add(voName);
|
|
437
|
+
});
|
|
439
438
|
});
|
|
440
439
|
}
|
|
441
440
|
// Check include for child entities
|
|
@@ -4,6 +4,8 @@ export declare class StoreGenerator {
|
|
|
4
4
|
private availableAggregates;
|
|
5
5
|
private isAggregateField;
|
|
6
6
|
private isValueObjectType;
|
|
7
|
+
private isArrayVoType;
|
|
8
|
+
private isUnionVoType;
|
|
7
9
|
private getValueObjectName;
|
|
8
10
|
private getValueObjectConfig;
|
|
9
11
|
/**
|
|
@@ -21,8 +23,20 @@ export declare class StoreGenerator {
|
|
|
21
23
|
private mapTypeToRowType;
|
|
22
24
|
/** Single line for serializing a value object field to DB (insert/update). */
|
|
23
25
|
private generateValueObjectSerialization;
|
|
26
|
+
/** Serialization for an array-of-VOs field. */
|
|
27
|
+
private generateArrayVoSerialization;
|
|
28
|
+
/** Serialization for a union-of-VOs field. Uses _type discriminator. */
|
|
29
|
+
private generateUnionVoSerialization;
|
|
24
30
|
/** Single line for deserializing a value object from row (rowToModel). */
|
|
25
31
|
private generateValueObjectDeserialization;
|
|
32
|
+
/** Deserialization for an array-of-VOs field. */
|
|
33
|
+
private generateArrayVoDeserialization;
|
|
34
|
+
/** Deserialization for a union-of-VOs field. Uses _type discriminator. */
|
|
35
|
+
private generateUnionVoDeserialization;
|
|
36
|
+
/** Serialization for an array-of-union-VOs field. Each element tagged with _type discriminator. */
|
|
37
|
+
private generateArrayUnionVoSerialization;
|
|
38
|
+
/** Deserialization for an array-of-union-VOs field. Each element reconstructed via _type. */
|
|
39
|
+
private generateArrayUnionVoDeserialization;
|
|
26
40
|
/** Single line for datetime conversion: toDate (row->model) or toMySQL (entity->row). */
|
|
27
41
|
private generateDatetimeConversion;
|
|
28
42
|
private replaceTemplateVars;
|
|
@@ -52,8 +52,16 @@ class StoreGenerator {
|
|
|
52
52
|
return (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
|
|
53
53
|
}
|
|
54
54
|
isValueObjectType(fieldType) {
|
|
55
|
-
const
|
|
56
|
-
return this.availableValueObjects.has(
|
|
55
|
+
const { baseTypes } = (0, typeUtils_1.parseFieldType)(fieldType);
|
|
56
|
+
return baseTypes.some(bt => this.availableValueObjects.has((0, typeUtils_1.capitalize)(bt)));
|
|
57
|
+
}
|
|
58
|
+
isArrayVoType(fieldType) {
|
|
59
|
+
const parsed = (0, typeUtils_1.parseFieldType)(fieldType);
|
|
60
|
+
return parsed.isArray && parsed.baseTypes.some(bt => this.availableValueObjects.has((0, typeUtils_1.capitalize)(bt)));
|
|
61
|
+
}
|
|
62
|
+
isUnionVoType(fieldType) {
|
|
63
|
+
const parsed = (0, typeUtils_1.parseFieldType)(fieldType);
|
|
64
|
+
return parsed.isUnion && parsed.baseTypes.some(bt => this.availableValueObjects.has((0, typeUtils_1.capitalize)(bt)));
|
|
57
65
|
}
|
|
58
66
|
getValueObjectName(fieldType) {
|
|
59
67
|
return (0, typeUtils_1.capitalize)(fieldType);
|
|
@@ -105,12 +113,24 @@ class StoreGenerator {
|
|
|
105
113
|
}
|
|
106
114
|
return ` ${fieldName}: entity.${fieldName} ? JSON.stringify(entity.${fieldName}) : undefined`;
|
|
107
115
|
}
|
|
116
|
+
/** Serialization for an array-of-VOs field. */
|
|
117
|
+
generateArrayVoSerialization(fieldName) {
|
|
118
|
+
return ` ${fieldName}: entity.${fieldName} ? JSON.stringify(entity.${fieldName}) : undefined`;
|
|
119
|
+
}
|
|
120
|
+
/** Serialization for a union-of-VOs field. Uses _type discriminator. */
|
|
121
|
+
generateUnionVoSerialization(fieldName, unionVoNames) {
|
|
122
|
+
const checks = unionVoNames
|
|
123
|
+
.map(voName => `entity.${fieldName} instanceof ${voName} ? '${voName}'`)
|
|
124
|
+
.join(' : ');
|
|
125
|
+
const discriminator = `${checks} : 'unknown'`;
|
|
126
|
+
return ` ${fieldName}: entity.${fieldName} ? JSON.stringify({ _type: ${discriminator}, ...entity.${fieldName} }) : undefined`;
|
|
127
|
+
}
|
|
108
128
|
/** Single line for deserializing a value object from row (rowToModel). */
|
|
109
129
|
generateValueObjectDeserialization(fieldName, voName, voConfig) {
|
|
110
130
|
const voFields = Object.keys(voConfig.fields);
|
|
111
131
|
if (voFields.length > 1) {
|
|
112
132
|
const voArgs = voFields.map(f => `parsed.${f}`).join(', ');
|
|
113
|
-
return ` row.${fieldName} ? (() => { const parsed =
|
|
133
|
+
return ` row.${fieldName} ? (() => { const parsed = this.ensureParsed(row.${fieldName}); return new ${voName}(${voArgs}); })() : undefined`;
|
|
114
134
|
}
|
|
115
135
|
if (voFields.length === 1) {
|
|
116
136
|
const singleFieldType = voConfig.fields[voFields[0]];
|
|
@@ -121,7 +141,47 @@ class StoreGenerator {
|
|
|
121
141
|
}
|
|
122
142
|
return ` row.${fieldName} ? new ${voName}(row.${fieldName}) : undefined`;
|
|
123
143
|
}
|
|
124
|
-
return ` row.${fieldName} ? new ${voName}(...Object.values(
|
|
144
|
+
return ` row.${fieldName} ? new ${voName}(...Object.values(this.ensureParsed(row.${fieldName}))) : undefined`;
|
|
145
|
+
}
|
|
146
|
+
/** Deserialization for an array-of-VOs field. */
|
|
147
|
+
generateArrayVoDeserialization(fieldName, voName, voConfig) {
|
|
148
|
+
const voFields = Object.keys(voConfig.fields);
|
|
149
|
+
const itemArgs = voFields.length > 0
|
|
150
|
+
? voFields.map(f => `item.${f}`).join(', ')
|
|
151
|
+
: '...Object.values(item)';
|
|
152
|
+
return ` row.${fieldName} ? (this.ensureParsed(row.${fieldName}) as any[]).map((item: any) => new ${voName}(${itemArgs})) : []`;
|
|
153
|
+
}
|
|
154
|
+
/** Deserialization for a union-of-VOs field. Uses _type discriminator. */
|
|
155
|
+
generateUnionVoDeserialization(fieldName, unionVoNames, unionVoConfigs) {
|
|
156
|
+
const cases = unionVoNames.map(voName => {
|
|
157
|
+
const cfg = unionVoConfigs[voName];
|
|
158
|
+
const voFields = cfg ? Object.keys(cfg.fields) : [];
|
|
159
|
+
const args = voFields.length > 0
|
|
160
|
+
? voFields.map(f => `parsed.${f}`).join(', ')
|
|
161
|
+
: '...Object.values(parsed)';
|
|
162
|
+
return `if (parsed._type === '${voName}') return new ${voName}(${args});`;
|
|
163
|
+
}).join(' ');
|
|
164
|
+
return ` row.${fieldName} ? (() => { const parsed = this.ensureParsed(row.${fieldName}); ${cases} return undefined; })() : undefined`;
|
|
165
|
+
}
|
|
166
|
+
/** Serialization for an array-of-union-VOs field. Each element tagged with _type discriminator. */
|
|
167
|
+
generateArrayUnionVoSerialization(fieldName, unionVoNames) {
|
|
168
|
+
const checks = unionVoNames
|
|
169
|
+
.map(voName => `item instanceof ${voName} ? '${voName}'`)
|
|
170
|
+
.join(' : ');
|
|
171
|
+
const discriminator = `${checks} : 'unknown'`;
|
|
172
|
+
return ` ${fieldName}: entity.${fieldName} ? JSON.stringify(entity.${fieldName}.map((item: any) => ({ _type: ${discriminator}, ...item }))) : undefined`;
|
|
173
|
+
}
|
|
174
|
+
/** Deserialization for an array-of-union-VOs field. Each element reconstructed via _type. */
|
|
175
|
+
generateArrayUnionVoDeserialization(fieldName, unionVoNames, unionVoConfigs) {
|
|
176
|
+
const cases = unionVoNames.map(voName => {
|
|
177
|
+
const cfg = unionVoConfigs[voName];
|
|
178
|
+
const voFields = cfg ? Object.keys(cfg.fields) : [];
|
|
179
|
+
const args = voFields.length > 0
|
|
180
|
+
? voFields.map(f => `item.${f}`).join(', ')
|
|
181
|
+
: '...Object.values(item)';
|
|
182
|
+
return `if (item._type === '${voName}') return new ${voName}(${args});`;
|
|
183
|
+
}).join(' ');
|
|
184
|
+
return ` row.${fieldName} ? (this.ensureParsed(row.${fieldName}) as any[]).map((item: any) => { ${cases} return undefined; }) : []`;
|
|
125
185
|
}
|
|
126
186
|
/** Single line for datetime conversion: toDate (row->model) or toMySQL (entity->row). */
|
|
127
187
|
generateDatetimeConversion(fieldName, direction) {
|
|
@@ -144,7 +204,7 @@ class StoreGenerator {
|
|
|
144
204
|
result.push(` ${ownerOrParentField}: number;`);
|
|
145
205
|
fields.forEach(([fieldName, fieldConfig]) => {
|
|
146
206
|
if (this.isAggregateField(fieldConfig)) {
|
|
147
|
-
result.push(` ${fieldName}
|
|
207
|
+
result.push(` ${fieldName}Id?: number;`);
|
|
148
208
|
return;
|
|
149
209
|
}
|
|
150
210
|
const tsType = this.mapTypeToRowType(fieldConfig.type);
|
|
@@ -156,7 +216,7 @@ class StoreGenerator {
|
|
|
156
216
|
generateFieldNamesStr(fields, childInfo) {
|
|
157
217
|
const fieldNames = ['id'];
|
|
158
218
|
fieldNames.push(childInfo ? childInfo.parentIdField : 'ownerId');
|
|
159
|
-
fieldNames.push(...fields.map(([name, config]) => this.isAggregateField(config) ? `${name}
|
|
219
|
+
fieldNames.push(...fields.map(([name, config]) => this.isAggregateField(config) ? `${name}Id` : name));
|
|
160
220
|
return fieldNames.map(f => `\\\`${f}\\\``).join(', ');
|
|
161
221
|
}
|
|
162
222
|
generateRowToModelMapping(modelName, fields, childInfo) {
|
|
@@ -173,7 +233,7 @@ class StoreGenerator {
|
|
|
173
233
|
}
|
|
174
234
|
// Handle aggregate reference - create stub from FK
|
|
175
235
|
if (this.isAggregateField(fieldConfig)) {
|
|
176
|
-
result.push(` row.${fieldName}
|
|
236
|
+
result.push(` row.${fieldName}Id != null ? ({ id: row.${fieldName}Id } as unknown as ${fieldType}) : undefined`);
|
|
177
237
|
return;
|
|
178
238
|
}
|
|
179
239
|
// Handle datetime/date conversion
|
|
@@ -186,15 +246,44 @@ class StoreGenerator {
|
|
|
186
246
|
result.push(` Boolean(row.${fieldName})`);
|
|
187
247
|
return;
|
|
188
248
|
}
|
|
189
|
-
// Handle value object conversion - deserialize from JSON
|
|
249
|
+
// Handle value object conversion - deserialize from JSON (simple, array, union, or array-of-union)
|
|
190
250
|
if (this.isValueObjectType(fieldType)) {
|
|
251
|
+
const parsed = (0, typeUtils_1.parseFieldType)(fieldType);
|
|
252
|
+
if (parsed.isArray && parsed.isUnion) {
|
|
253
|
+
const unionVoNames = parsed.baseTypes.map(bt => (0, typeUtils_1.capitalize)(bt)).filter(name => this.availableValueObjects.has(name));
|
|
254
|
+
const unionVoConfigs = {};
|
|
255
|
+
unionVoNames.forEach(name => {
|
|
256
|
+
const cfg = this.availableValueObjects.get(name);
|
|
257
|
+
if (cfg)
|
|
258
|
+
unionVoConfigs[name] = cfg;
|
|
259
|
+
});
|
|
260
|
+
result.push(this.generateArrayUnionVoDeserialization(fieldName, unionVoNames, unionVoConfigs));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (parsed.isArray) {
|
|
264
|
+
const voName = (0, typeUtils_1.capitalize)(parsed.baseTypes[0]);
|
|
265
|
+
const voConfig = this.availableValueObjects.get(voName);
|
|
266
|
+
result.push(this.generateArrayVoDeserialization(fieldName, voName, voConfig || { fields: {} }));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (parsed.isUnion) {
|
|
270
|
+
const unionVoNames = parsed.baseTypes.map(bt => (0, typeUtils_1.capitalize)(bt)).filter(name => this.availableValueObjects.has(name));
|
|
271
|
+
const unionVoConfigs = {};
|
|
272
|
+
unionVoNames.forEach(name => {
|
|
273
|
+
const cfg = this.availableValueObjects.get(name);
|
|
274
|
+
if (cfg)
|
|
275
|
+
unionVoConfigs[name] = cfg;
|
|
276
|
+
});
|
|
277
|
+
result.push(this.generateUnionVoDeserialization(fieldName, unionVoNames, unionVoConfigs));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
191
280
|
const voName = this.getValueObjectName(fieldType);
|
|
192
281
|
const voConfig = this.getValueObjectConfig(fieldType);
|
|
193
282
|
if (voConfig) {
|
|
194
283
|
result.push(this.generateValueObjectDeserialization(fieldName, voName, voConfig));
|
|
195
284
|
}
|
|
196
285
|
else {
|
|
197
|
-
result.push(` row.${fieldName} ? new ${voName}(...Object.values(
|
|
286
|
+
result.push(` row.${fieldName} ? new ${voName}(...Object.values(this.ensureParsed(row.${fieldName}))) : undefined`);
|
|
198
287
|
}
|
|
199
288
|
return;
|
|
200
289
|
}
|
|
@@ -210,7 +299,7 @@ class StoreGenerator {
|
|
|
210
299
|
const fieldType = fieldConfig.type;
|
|
211
300
|
// Handle aggregate reference - extract FK id
|
|
212
301
|
if (this.isAggregateField(fieldConfig)) {
|
|
213
|
-
result.push(` ${fieldName}
|
|
302
|
+
result.push(` ${fieldName}Id: entity.${fieldName}?.id`);
|
|
214
303
|
return;
|
|
215
304
|
}
|
|
216
305
|
// Handle datetime/date - convert Date to MySQL DATETIME format
|
|
@@ -218,8 +307,23 @@ class StoreGenerator {
|
|
|
218
307
|
result.push(this.generateDatetimeConversion(fieldName, 'toMySQL'));
|
|
219
308
|
return;
|
|
220
309
|
}
|
|
221
|
-
// Handle value object - serialize to JSON
|
|
310
|
+
// Handle value object - serialize to JSON (simple, array, union, or array-of-union)
|
|
222
311
|
if (this.isValueObjectType(fieldType)) {
|
|
312
|
+
const parsed = (0, typeUtils_1.parseFieldType)(fieldType);
|
|
313
|
+
if (parsed.isArray && parsed.isUnion) {
|
|
314
|
+
const unionVoNames = parsed.baseTypes.map(bt => (0, typeUtils_1.capitalize)(bt)).filter(name => this.availableValueObjects.has(name));
|
|
315
|
+
result.push(this.generateArrayUnionVoSerialization(fieldName, unionVoNames));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (parsed.isArray) {
|
|
319
|
+
result.push(this.generateArrayVoSerialization(fieldName));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (parsed.isUnion) {
|
|
323
|
+
const unionVoNames = parsed.baseTypes.map(bt => (0, typeUtils_1.capitalize)(bt)).filter(name => this.availableValueObjects.has(name));
|
|
324
|
+
result.push(this.generateUnionVoSerialization(fieldName, unionVoNames));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
223
327
|
const voConfig = this.getValueObjectConfig(fieldType);
|
|
224
328
|
if (voConfig) {
|
|
225
329
|
result.push(this.generateValueObjectSerialization(fieldName, this.getValueObjectName(fieldType), voConfig));
|
|
@@ -238,12 +342,24 @@ class StoreGenerator {
|
|
|
238
342
|
.map(([fieldName, fieldConfig]) => {
|
|
239
343
|
const fieldType = fieldConfig.type;
|
|
240
344
|
if (this.isAggregateField(fieldConfig)) {
|
|
241
|
-
return ` ${fieldName}
|
|
345
|
+
return ` ${fieldName}Id: entity.${fieldName}?.id`;
|
|
242
346
|
}
|
|
243
347
|
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
244
348
|
return this.generateDatetimeConversion(fieldName, 'toMySQL');
|
|
245
349
|
}
|
|
246
350
|
if (this.isValueObjectType(fieldType)) {
|
|
351
|
+
const parsed = (0, typeUtils_1.parseFieldType)(fieldType);
|
|
352
|
+
if (parsed.isArray && parsed.isUnion) {
|
|
353
|
+
const unionVoNames = parsed.baseTypes.map(bt => (0, typeUtils_1.capitalize)(bt)).filter(name => this.availableValueObjects.has(name));
|
|
354
|
+
return this.generateArrayUnionVoSerialization(fieldName, unionVoNames);
|
|
355
|
+
}
|
|
356
|
+
if (parsed.isArray) {
|
|
357
|
+
return this.generateArrayVoSerialization(fieldName);
|
|
358
|
+
}
|
|
359
|
+
if (parsed.isUnion) {
|
|
360
|
+
const unionVoNames = parsed.baseTypes.map(bt => (0, typeUtils_1.capitalize)(bt)).filter(name => this.availableValueObjects.has(name));
|
|
361
|
+
return this.generateUnionVoSerialization(fieldName, unionVoNames);
|
|
362
|
+
}
|
|
247
363
|
const voConfig = this.getValueObjectConfig(fieldType);
|
|
248
364
|
if (voConfig) {
|
|
249
365
|
return this.generateValueObjectSerialization(fieldName, this.getValueObjectName(fieldType), voConfig);
|
|
@@ -255,31 +371,30 @@ class StoreGenerator {
|
|
|
255
371
|
.join(',\n');
|
|
256
372
|
}
|
|
257
373
|
generateUpdateFieldsArray(fields) {
|
|
258
|
-
return JSON.stringify(fields.map(([name, config]) => this.isAggregateField(config) ? `${name}
|
|
374
|
+
return JSON.stringify(fields.map(([name, config]) => this.isAggregateField(config) ? `${name}Id` : name));
|
|
259
375
|
}
|
|
260
376
|
generateValueObjectImports(fields) {
|
|
261
377
|
const imports = [];
|
|
262
378
|
fields.forEach(([, fieldConfig]) => {
|
|
263
|
-
if (this.isValueObjectType(fieldConfig.type))
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
379
|
+
if (!this.isValueObjectType(fieldConfig.type))
|
|
380
|
+
return;
|
|
381
|
+
// Collect all VO names referenced in this field type (handles Foo[], Foo | Bar)
|
|
382
|
+
const referencedVoNames = (0, typeUtils_1.getReferencedValueObjects)(fieldConfig.type, this.availableValueObjects);
|
|
383
|
+
referencedVoNames.forEach(voName => {
|
|
384
|
+
const voConfig = this.availableValueObjects.get(voName);
|
|
267
385
|
const importItems = [voName];
|
|
268
|
-
// Also import
|
|
269
|
-
if (voConfig) {
|
|
386
|
+
// Also import enum type alias if present (only for simple single-VO fields)
|
|
387
|
+
if (voConfig && !(0, typeUtils_1.parseFieldType)(fieldConfig.type).isArray && !(0, typeUtils_1.parseFieldType)(fieldConfig.type).isUnion) {
|
|
270
388
|
const enumTypeName = this.getValueObjectFieldTypeName(voName, voConfig);
|
|
271
|
-
if (enumTypeName)
|
|
389
|
+
if (enumTypeName)
|
|
272
390
|
importItems.push(enumTypeName);
|
|
273
|
-
}
|
|
274
391
|
}
|
|
275
392
|
imports.push(`import { ${importItems.join(', ')} } from '../../domain/valueObjects/${voName}';`);
|
|
276
|
-
}
|
|
393
|
+
});
|
|
277
394
|
});
|
|
278
|
-
// Dedupe imports
|
|
279
395
|
const uniqueImports = [...new Set(imports)];
|
|
280
|
-
if (uniqueImports.length === 0)
|
|
396
|
+
if (uniqueImports.length === 0)
|
|
281
397
|
return '';
|
|
282
|
-
}
|
|
283
398
|
return '\n' + uniqueImports.join('\n');
|
|
284
399
|
}
|
|
285
400
|
generateAggregateRefImports(modelName, fields) {
|
|
@@ -308,7 +423,7 @@ class StoreGenerator {
|
|
|
308
423
|
const offset = (page - 1) * limit;${ownerFilter}
|
|
309
424
|
const params: Record<string, any> = { limit: String(limit), offset: String(offset) };${ownerParamsSetup}
|
|
310
425
|
const result = await this.db.query(
|
|
311
|
-
\`SELECT ${fieldNamesStr} FROM \\\`\${this.tableName}\\\` WHERE
|
|
426
|
+
\`SELECT ${fieldNamesStr} FROM \\\`\${this.tableName}\\\` WHERE deletedAt IS NULL${ownerFilterRef} LIMIT :limit OFFSET :offset\`,
|
|
312
427
|
params
|
|
313
428
|
);
|
|
314
429
|
|
|
@@ -320,7 +435,7 @@ class StoreGenerator {
|
|
|
320
435
|
const getAll = ` async getAll(${isRoot ? 'ownerId?: number' : ''}): Promise<${modelName}[]> {${ownerFilter}
|
|
321
436
|
const params: Record<string, any> = {};${ownerParamsSetup}
|
|
322
437
|
const result = await this.db.query(
|
|
323
|
-
\`SELECT ${fieldNamesStr} FROM \\\`\${this.tableName}\\\` WHERE
|
|
438
|
+
\`SELECT ${fieldNamesStr} FROM \\\`\${this.tableName}\\\` WHERE deletedAt IS NULL${ownerFilterRef}\`,
|
|
324
439
|
params
|
|
325
440
|
);
|
|
326
441
|
|
|
@@ -332,7 +447,7 @@ class StoreGenerator {
|
|
|
332
447
|
const count = ` async count(${isRoot ? 'ownerId?: number' : ''}): Promise<number> {${ownerFilter}
|
|
333
448
|
const params: Record<string, any> = {};${ownerParamsSetup}
|
|
334
449
|
const result = await this.db.query(
|
|
335
|
-
\`SELECT COUNT(*) as count FROM \\\`\${this.tableName}\\\` WHERE
|
|
450
|
+
\`SELECT COUNT(*) as count FROM \\\`\${this.tableName}\\\` WHERE deletedAt IS NULL${ownerFilterRef}\`,
|
|
336
451
|
params
|
|
337
452
|
);
|
|
338
453
|
|
|
@@ -346,13 +461,13 @@ class StoreGenerator {
|
|
|
346
461
|
generateGetByParentIdMethod(modelName, fields, childInfo) {
|
|
347
462
|
if (!childInfo)
|
|
348
463
|
return '';
|
|
349
|
-
const fieldList = ['id', childInfo.parentIdField, ...fields.map(([name, config]) => this.isAggregateField(config) ? `${name}
|
|
464
|
+
const fieldList = ['id', childInfo.parentIdField, ...fields.map(([name, config]) => this.isAggregateField(config) ? `${name}Id` : name)].map(f => '\\`' + f + '\\`').join(', ');
|
|
350
465
|
const parentIdField = childInfo.parentIdField;
|
|
351
466
|
return `
|
|
352
467
|
|
|
353
468
|
async getByParentId(parentId: number): Promise<${modelName}[]> {
|
|
354
469
|
const result = await this.db.query(
|
|
355
|
-
\`SELECT ${fieldList} FROM \\\`\${this.tableName}\\\` WHERE \\\`${parentIdField}\\\` = :parentId AND
|
|
470
|
+
\`SELECT ${fieldList} FROM \\\`\${this.tableName}\\\` WHERE \\\`${parentIdField}\\\` = :parentId AND deletedAt IS NULL\`,
|
|
356
471
|
{ parentId }
|
|
357
472
|
);
|
|
358
473
|
|
|
@@ -374,7 +489,7 @@ class StoreGenerator {
|
|
|
374
489
|
*/
|
|
375
490
|
async getResourceOwner(id: number): Promise<number | null> {
|
|
376
491
|
const result = await this.db.query(
|
|
377
|
-
\`SELECT p.ownerId FROM \\\`\${this.tableName}\\\` c INNER JOIN \\\`${parentTable}\\\` p ON p.id = c.\\\`${parentIdField}\\\` WHERE c.id = :id AND c.
|
|
492
|
+
\`SELECT p.ownerId FROM \\\`\${this.tableName}\\\` c INNER JOIN \\\`${parentTable}\\\` p ON p.id = c.\\\`${parentIdField}\\\` WHERE c.id = :id AND c.deletedAt IS NULL\`,
|
|
378
493
|
{ id }
|
|
379
494
|
);
|
|
380
495
|
|
|
@@ -392,7 +507,7 @@ class StoreGenerator {
|
|
|
392
507
|
*/
|
|
393
508
|
async getResourceOwner(id: number): Promise<number | null> {
|
|
394
509
|
const result = await this.db.query(
|
|
395
|
-
\`SELECT ownerId FROM \\\`\${this.tableName}\\\` WHERE id = :id AND
|
|
510
|
+
\`SELECT ownerId FROM \\\`\${this.tableName}\\\` WHERE id = :id AND deletedAt IS NULL\`,
|
|
396
511
|
{ id }
|
|
397
512
|
);
|
|
398
513
|
|
|
@@ -19,6 +19,21 @@ export declare class TemplateGenerator {
|
|
|
19
19
|
private renderEditTemplate;
|
|
20
20
|
private getInputType;
|
|
21
21
|
private renderValueObjectField;
|
|
22
|
+
/**
|
|
23
|
+
* Render an array-of-VOs field as checkboxes.
|
|
24
|
+
* If the VO has a single enum field: one checkbox per enum value.
|
|
25
|
+
* If the VO has multiple / non-enum fields: one labeled checkbox group per VO subfield.
|
|
26
|
+
*/
|
|
27
|
+
private renderArrayVoField;
|
|
28
|
+
/**
|
|
29
|
+
* Render a union-of-VOs field as a type selector with sub-fields for each VO type.
|
|
30
|
+
*/
|
|
31
|
+
private renderUnionVoField;
|
|
32
|
+
/**
|
|
33
|
+
* Render an array-of-union-VOs field as a repeatable group where each item
|
|
34
|
+
* has a type selector and conditionally-shown sub-fields per VO type.
|
|
35
|
+
*/
|
|
36
|
+
private renderArrayUnionVoField;
|
|
22
37
|
private renderFormField;
|
|
23
38
|
private getEnumValuesMap;
|
|
24
39
|
generateFromConfig(config: ModuleConfig): Record<string, string>;
|