@currentjs/gen 0.5.0 → 0.5.2
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 +19 -0
- package/README.md +374 -996
- package/dist/cli.js +28 -10
- package/dist/commands/createModel.d.ts +1 -0
- package/dist/commands/createModel.js +764 -0
- package/dist/commands/createModule.js +13 -0
- package/dist/commands/generateAll.d.ts +1 -0
- package/dist/commands/generateAll.js +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/{createApp.js → init.js} +2 -2
- package/dist/commands/migrateCommit.js +33 -68
- package/dist/generators/controllerGenerator.d.ts +7 -0
- package/dist/generators/controllerGenerator.js +56 -17
- package/dist/generators/domainLayerGenerator.js +51 -8
- package/dist/generators/dtoGenerator.js +13 -8
- package/dist/generators/serviceGenerator.d.ts +6 -0
- package/dist/generators/serviceGenerator.js +219 -23
- package/dist/generators/storeGenerator.d.ts +4 -0
- package/dist/generators/storeGenerator.js +116 -9
- package/dist/generators/templateGenerator.d.ts +1 -0
- package/dist/generators/templateGenerator.js +8 -2
- package/dist/generators/templates/appTemplates.js +1 -1
- package/dist/generators/templates/data/cursorRulesTemplate +11 -755
- package/dist/generators/templates/data/frontendScriptTemplate +11 -4
- package/dist/generators/templates/data/mainViewTemplate +1 -0
- package/dist/generators/templates/storeTemplates.d.ts +1 -1
- package/dist/generators/templates/storeTemplates.js +3 -26
- package/dist/generators/useCaseGenerator.js +6 -3
- package/dist/types/configTypes.d.ts +6 -0
- package/dist/utils/migrationUtils.d.ts +9 -19
- package/dist/utils/migrationUtils.js +80 -110
- package/dist/utils/promptUtils.d.ts +37 -0
- package/dist/utils/promptUtils.js +149 -0
- package/dist/utils/typeUtils.d.ts +4 -0
- package/dist/utils/typeUtils.js +7 -0
- package/package.json +1 -1
- package/dist/commands/createApp.d.ts +0 -1
- package/dist/commands/migratePush.d.ts +0 -1
- package/dist/commands/migratePush.js +0 -135
- package/dist/commands/migrateUpdate.d.ts +0 -1
- package/dist/commands/migrateUpdate.js +0 -147
- package/dist/commands/newGenerateAll.d.ts +0 -4
- package/dist/commands/newGenerateAll.js +0 -336
- package/dist/generators/domainModelGenerator.d.ts +0 -41
- package/dist/generators/domainModelGenerator.js +0 -242
- package/dist/generators/newControllerGenerator.d.ts +0 -55
- package/dist/generators/newControllerGenerator.js +0 -644
- package/dist/generators/newServiceGenerator.d.ts +0 -19
- package/dist/generators/newServiceGenerator.js +0 -266
- package/dist/generators/newStoreGenerator.d.ts +0 -39
- package/dist/generators/newStoreGenerator.js +0 -408
- package/dist/generators/newTemplateGenerator.d.ts +0 -29
- package/dist/generators/newTemplateGenerator.js +0 -510
- package/dist/generators/storeGeneratorV2.d.ts +0 -31
- package/dist/generators/storeGeneratorV2.js +0 -190
- package/dist/generators/templates/controllerTemplates.d.ts +0 -43
- package/dist/generators/templates/controllerTemplates.js +0 -82
- package/dist/generators/templates/newStoreTemplates.d.ts +0 -5
- package/dist/generators/templates/newStoreTemplates.js +0 -141
- package/dist/generators/templates/serviceTemplates.d.ts +0 -16
- package/dist/generators/templates/serviceTemplates.js +0 -59
- package/dist/generators/templates/validationTemplates.d.ts +0 -25
- package/dist/generators/templates/validationTemplates.js +0 -66
- package/dist/generators/templates/viewTemplates.d.ts +0 -25
- package/dist/generators/templates/viewTemplates.js +0 -491
- package/dist/generators/validationGenerator.d.ts +0 -29
- package/dist/generators/validationGenerator.js +0 -250
- package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
- package/dist/utils/new_parts_of_migrationUtils.js +0 -164
- package/howto.md +0 -667
|
@@ -49,13 +49,146 @@ class ServiceGenerator {
|
|
|
49
49
|
mapType(yamlType) {
|
|
50
50
|
return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates);
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
getDefaultHandlerReturnType(actionName, modelName) {
|
|
53
|
+
switch (actionName) {
|
|
54
|
+
case 'create':
|
|
55
|
+
case 'get':
|
|
56
|
+
case 'update':
|
|
57
|
+
return modelName;
|
|
58
|
+
case 'delete':
|
|
59
|
+
return '{ success: boolean; message: string }';
|
|
60
|
+
case 'list':
|
|
61
|
+
return `{ items: ${modelName}[]; total: number; page: number; limit: number }`;
|
|
62
|
+
default:
|
|
63
|
+
return modelName;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
buildHandlerContextMap(modelName, useCases) {
|
|
67
|
+
const contextMap = new Map();
|
|
68
|
+
Object.entries(useCases).forEach(([actionName, useCaseConfig]) => {
|
|
69
|
+
const inputDtoType = `${modelName}${(0, typeUtils_1.capitalize)(actionName)}Input`;
|
|
70
|
+
let useCaseReturnType;
|
|
71
|
+
if (useCaseConfig.output === 'void') {
|
|
72
|
+
useCaseReturnType = '{ success: boolean; message: string }';
|
|
73
|
+
}
|
|
74
|
+
else if (actionName === 'list') {
|
|
75
|
+
useCaseReturnType = `{ items: ${modelName}[]; total: number; page: number; limit: number }`;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
useCaseReturnType = modelName;
|
|
79
|
+
}
|
|
80
|
+
useCaseConfig.handlers.forEach((handler, index) => {
|
|
81
|
+
const isFirst = index === 0;
|
|
82
|
+
const isLast = index === useCaseConfig.handlers.length - 1;
|
|
83
|
+
let prevHandlerReturnType = null;
|
|
84
|
+
if (!isFirst) {
|
|
85
|
+
const prevHandler = useCaseConfig.handlers[index - 1];
|
|
86
|
+
if (prevHandler.startsWith('default:')) {
|
|
87
|
+
prevHandlerReturnType = this.getDefaultHandlerReturnType(prevHandler.replace('default:', ''), modelName);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
prevHandlerReturnType = modelName;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const context = {
|
|
94
|
+
actionName,
|
|
95
|
+
index,
|
|
96
|
+
isFirst,
|
|
97
|
+
isLast,
|
|
98
|
+
prevHandlerReturnType,
|
|
99
|
+
inputDtoType,
|
|
100
|
+
useCaseReturnType,
|
|
101
|
+
inputConfig: useCaseConfig.input
|
|
102
|
+
};
|
|
103
|
+
const existing = contextMap.get(handler) || [];
|
|
104
|
+
existing.push(context);
|
|
105
|
+
contextMap.set(handler, existing);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
return contextMap;
|
|
109
|
+
}
|
|
110
|
+
deriveInputType(contexts) {
|
|
111
|
+
const inputTypes = [...new Set(contexts.map(c => c.inputDtoType))];
|
|
112
|
+
return inputTypes.join(' | ');
|
|
113
|
+
}
|
|
114
|
+
deriveCustomHandlerTypes(contexts, modelName) {
|
|
115
|
+
const inputTypes = [...new Set(contexts.map(c => c.inputDtoType))];
|
|
116
|
+
const resultTypeParts = new Set();
|
|
117
|
+
contexts.forEach(c => {
|
|
118
|
+
if (c.isFirst) {
|
|
119
|
+
resultTypeParts.add('null');
|
|
120
|
+
}
|
|
121
|
+
if (c.prevHandlerReturnType) {
|
|
122
|
+
resultTypeParts.add(c.prevHandlerReturnType);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const returnTypeParts = new Set();
|
|
126
|
+
contexts.forEach(c => {
|
|
127
|
+
if (c.isLast) {
|
|
128
|
+
returnTypeParts.add(c.useCaseReturnType);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
returnTypeParts.add(modelName);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
inputType: inputTypes.join(' | '),
|
|
136
|
+
resultType: [...resultTypeParts].join(' | '),
|
|
137
|
+
returnType: [...returnTypeParts].join(' | ')
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
getInputDtoFields(inputConfig, aggregateConfig, childInfo) {
|
|
141
|
+
const fields = new Set();
|
|
142
|
+
if (!inputConfig)
|
|
143
|
+
return fields;
|
|
144
|
+
if (!inputConfig.identifier && !inputConfig.partial) {
|
|
145
|
+
fields.add(childInfo ? childInfo.parentIdField : 'ownerId');
|
|
146
|
+
}
|
|
147
|
+
if (inputConfig.from) {
|
|
148
|
+
let fieldNames = Object.keys(aggregateConfig.fields)
|
|
149
|
+
.filter(f => !aggregateConfig.fields[f].auto && f !== 'id');
|
|
150
|
+
if (inputConfig.pick && inputConfig.pick.length > 0) {
|
|
151
|
+
fieldNames = fieldNames.filter(f => inputConfig.pick.includes(f));
|
|
152
|
+
}
|
|
153
|
+
if (inputConfig.omit && inputConfig.omit.length > 0) {
|
|
154
|
+
fieldNames = fieldNames.filter(f => !inputConfig.omit.includes(f));
|
|
155
|
+
}
|
|
156
|
+
fieldNames.forEach(f => fields.add(f));
|
|
157
|
+
}
|
|
158
|
+
if (inputConfig.add) {
|
|
159
|
+
Object.keys(inputConfig.add).forEach(f => fields.add(f));
|
|
160
|
+
}
|
|
161
|
+
return fields;
|
|
162
|
+
}
|
|
163
|
+
computeDtoFieldsForHandler(contexts, aggregateConfig, childInfo) {
|
|
164
|
+
const fieldSets = contexts.map(ctx => this.getInputDtoFields(ctx.inputConfig, aggregateConfig, childInfo));
|
|
165
|
+
if (fieldSets.length === 0) {
|
|
166
|
+
return new Set(Object.keys(aggregateConfig.fields).filter(f => !aggregateConfig.fields[f].auto && f !== 'id'));
|
|
167
|
+
}
|
|
168
|
+
const result = new Set(fieldSets[0]);
|
|
169
|
+
for (let i = 1; i < fieldSets.length; i++) {
|
|
170
|
+
for (const field of result) {
|
|
171
|
+
if (!fieldSets[i].has(field)) {
|
|
172
|
+
result.delete(field);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
generateListHandler(modelName, storeName, hasPagination) {
|
|
179
|
+
const returnType = `{ items: ${modelName}[]; total: number; page: number; limit: number }`;
|
|
180
|
+
if (hasPagination) {
|
|
181
|
+
return ` async list(page: number = 1, limit: number = 20, ownerId?: number): Promise<${returnType}> {
|
|
54
182
|
const [items, total] = await Promise.all([
|
|
55
|
-
this.${storeName}.
|
|
56
|
-
this.${storeName}.count()
|
|
183
|
+
this.${storeName}.getPaginated(page, limit, ownerId),
|
|
184
|
+
this.${storeName}.count(ownerId)
|
|
57
185
|
]);
|
|
58
186
|
return { items, total, page, limit };
|
|
187
|
+
}`;
|
|
188
|
+
}
|
|
189
|
+
return ` async list(ownerId?: number): Promise<${returnType}> {
|
|
190
|
+
const items = await this.${storeName}.getAll(ownerId);
|
|
191
|
+
return { items, total: items.length, page: 1, limit: items.length };
|
|
59
192
|
}`;
|
|
60
193
|
}
|
|
61
194
|
generateGetHandler(modelName, storeName, entityLower) {
|
|
@@ -67,35 +200,60 @@ class ServiceGenerator {
|
|
|
67
200
|
return ${entityLower};
|
|
68
201
|
}`;
|
|
69
202
|
}
|
|
70
|
-
generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo) {
|
|
203
|
+
generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo, inputType, dtoFields) {
|
|
71
204
|
const firstArgField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
72
205
|
const fields = Object.entries(aggregateConfig.fields)
|
|
73
206
|
.filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id')
|
|
74
207
|
.sort((a, b) => {
|
|
75
|
-
const
|
|
76
|
-
const
|
|
208
|
+
const aIsAggRef = (0, typeUtils_1.isAggregateReference)(a[1].type, this.availableAggregates);
|
|
209
|
+
const bIsAggRef = (0, typeUtils_1.isAggregateReference)(b[1].type, this.availableAggregates);
|
|
210
|
+
const aRequired = a[1].required !== false && !aIsAggRef;
|
|
211
|
+
const bRequired = b[1].required !== false && !bIsAggRef;
|
|
77
212
|
if (aRequired === bRequired)
|
|
78
213
|
return 0;
|
|
79
214
|
return aRequired ? -1 : 1;
|
|
80
215
|
});
|
|
81
|
-
const fieldArgs = fields.map(([fieldName]) =>
|
|
216
|
+
const fieldArgs = fields.map(([fieldName, fieldConfig]) => {
|
|
217
|
+
if (!dtoFields.has(fieldName)) {
|
|
218
|
+
return 'undefined';
|
|
219
|
+
}
|
|
220
|
+
if ((0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates)) {
|
|
221
|
+
return `input.${fieldName} != null ? ({ id: input.${fieldName} } as unknown as ${fieldConfig.type}) : undefined`;
|
|
222
|
+
}
|
|
223
|
+
if (fieldConfig.type === 'enum' && fieldConfig.values && fieldConfig.values.length > 0) {
|
|
224
|
+
const enumTypeName = `${modelName}${(0, typeUtils_1.capitalize)(fieldName)}`;
|
|
225
|
+
return `input.${fieldName} as ${enumTypeName}`;
|
|
226
|
+
}
|
|
227
|
+
return `input.${fieldName}`;
|
|
228
|
+
}).join(', ');
|
|
82
229
|
const constructorArgs = `input.${firstArgField}, ${fieldArgs}`;
|
|
83
|
-
return ` async create(input:
|
|
230
|
+
return ` async create(input: ${inputType}): Promise<${modelName}> {
|
|
84
231
|
const ${entityLower} = new ${modelName}(0, ${constructorArgs});
|
|
85
232
|
return await this.${storeName}.insert(${entityLower});
|
|
86
233
|
}`;
|
|
87
234
|
}
|
|
88
|
-
generateUpdateHandler(modelName, storeName, aggregateConfig) {
|
|
235
|
+
generateUpdateHandler(modelName, storeName, aggregateConfig, inputType, dtoFields) {
|
|
89
236
|
const setterCalls = Object.entries(aggregateConfig.fields)
|
|
90
|
-
.filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id')
|
|
91
|
-
.map(([fieldName]) => {
|
|
237
|
+
.filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id' && dtoFields.has(fieldName))
|
|
238
|
+
.map(([fieldName, fieldConfig]) => {
|
|
92
239
|
const methodName = `set${(0, typeUtils_1.capitalize)(fieldName)}`;
|
|
240
|
+
if ((0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates)) {
|
|
241
|
+
return ` if (input.${fieldName} !== undefined) {
|
|
242
|
+
existing${modelName}.${methodName}(input.${fieldName} != null ? ({ id: input.${fieldName} } as unknown as ${fieldConfig.type}) : undefined);
|
|
243
|
+
}`;
|
|
244
|
+
}
|
|
245
|
+
if (fieldConfig.type === 'enum' && fieldConfig.values && fieldConfig.values.length > 0) {
|
|
246
|
+
const enumTypeName = `${modelName}${(0, typeUtils_1.capitalize)(fieldName)}`;
|
|
247
|
+
return ` if (input.${fieldName} !== undefined) {
|
|
248
|
+
existing${modelName}.${methodName}(input.${fieldName} as ${enumTypeName});
|
|
249
|
+
}`;
|
|
250
|
+
}
|
|
93
251
|
return ` if (input.${fieldName} !== undefined) {
|
|
94
252
|
existing${modelName}.${methodName}(input.${fieldName});
|
|
95
253
|
}`;
|
|
96
254
|
})
|
|
97
255
|
.join('\n');
|
|
98
|
-
return ` async update(id: number, input:
|
|
256
|
+
return ` async update(id: number, input: ${inputType}): Promise<${modelName}> {
|
|
99
257
|
const existing${modelName} = await this.${storeName}.getById(id);
|
|
100
258
|
if (!existing${modelName}) {
|
|
101
259
|
throw new Error('${modelName} not found');
|
|
@@ -115,29 +273,30 @@ ${setterCalls}
|
|
|
115
273
|
return { success: true, message: '${modelName} deleted successfully' };
|
|
116
274
|
}`;
|
|
117
275
|
}
|
|
118
|
-
generateDefaultHandlerMethod(modelName, actionName, aggregateConfig, childInfo) {
|
|
276
|
+
generateDefaultHandlerMethod(modelName, actionName, aggregateConfig, childInfo, inputType, dtoFields, listConfig) {
|
|
277
|
+
var _a;
|
|
119
278
|
const entityLower = modelName.toLowerCase();
|
|
120
279
|
const storeName = `${entityLower}Store`;
|
|
121
280
|
switch (actionName) {
|
|
122
281
|
case 'list':
|
|
123
|
-
return this.generateListHandler(modelName, storeName);
|
|
282
|
+
return this.generateListHandler(modelName, storeName, (_a = listConfig === null || listConfig === void 0 ? void 0 : listConfig.hasPagination) !== null && _a !== void 0 ? _a : true);
|
|
124
283
|
case 'get':
|
|
125
284
|
return this.generateGetHandler(modelName, storeName, entityLower);
|
|
126
285
|
case 'create':
|
|
127
|
-
return this.generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo);
|
|
286
|
+
return this.generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo, inputType, dtoFields);
|
|
128
287
|
case 'update':
|
|
129
|
-
return this.generateUpdateHandler(modelName, storeName, aggregateConfig);
|
|
288
|
+
return this.generateUpdateHandler(modelName, storeName, aggregateConfig, inputType, dtoFields);
|
|
130
289
|
case 'delete':
|
|
131
290
|
return this.generateDeleteHandler(modelName, storeName);
|
|
132
291
|
default:
|
|
133
|
-
return ` async ${actionName}(input:
|
|
292
|
+
return ` async ${actionName}(input: ${inputType}): Promise<${modelName}> {
|
|
134
293
|
// TODO: Implement default ${actionName} handler
|
|
135
294
|
throw new Error('Not implemented');
|
|
136
295
|
}`;
|
|
137
296
|
}
|
|
138
297
|
}
|
|
139
|
-
generateCustomHandlerMethod(modelName, handlerName) {
|
|
140
|
-
return ` async ${handlerName}(result:
|
|
298
|
+
generateCustomHandlerMethod(modelName, handlerName, resultType, inputType, returnType) {
|
|
299
|
+
return ` async ${handlerName}(result: ${resultType}, input: ${inputType}): Promise<${returnType}> {
|
|
141
300
|
// TODO: Implement custom ${handlerName} handler
|
|
142
301
|
// This method receives the result from the previous handler (or null if first)
|
|
143
302
|
// and the input context
|
|
@@ -177,17 +336,41 @@ ${setterCalls}
|
|
|
177
336
|
const serviceName = `${modelName}Service`;
|
|
178
337
|
const storeName = `${modelName}Store`;
|
|
179
338
|
const storeVar = `${modelName.toLowerCase()}Store`;
|
|
339
|
+
// Build handler-to-context map for type inference
|
|
340
|
+
const handlerContextMap = this.buildHandlerContextMap(modelName, useCases);
|
|
180
341
|
// Collect all unique handlers
|
|
181
342
|
const handlers = this.collectHandlers(useCases);
|
|
343
|
+
// Collect DTO types needed for imports
|
|
344
|
+
const dtoTypes = new Set();
|
|
345
|
+
const enumTypeNames = new Set();
|
|
182
346
|
// Generate methods for each handler
|
|
183
347
|
const methods = [];
|
|
184
348
|
handlers.forEach(handler => {
|
|
349
|
+
var _a, _b;
|
|
350
|
+
const contexts = handlerContextMap.get(handler) || [];
|
|
185
351
|
if (handler.startsWith('default:')) {
|
|
186
352
|
const actionName = handler.replace('default:', '');
|
|
187
|
-
|
|
353
|
+
const inputType = this.deriveInputType(contexts);
|
|
354
|
+
const dtoFields = this.computeDtoFieldsForHandler(contexts, aggregateConfig, childInfo);
|
|
355
|
+
if (actionName !== 'list' && actionName !== 'get' && actionName !== 'delete') {
|
|
356
|
+
contexts.forEach(c => dtoTypes.add(c.inputDtoType));
|
|
357
|
+
}
|
|
358
|
+
if (actionName === 'create' || actionName === 'update') {
|
|
359
|
+
for (const [fieldName, fieldConfig] of Object.entries(aggregateConfig.fields)) {
|
|
360
|
+
if (fieldConfig.type === 'enum' && fieldConfig.values && fieldConfig.values.length > 0 && dtoFields.has(fieldName)) {
|
|
361
|
+
enumTypeNames.add(`${modelName}${(0, typeUtils_1.capitalize)(fieldName)}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const listConfig = actionName === 'list'
|
|
366
|
+
? { hasPagination: !!((_b = (_a = contexts[0]) === null || _a === void 0 ? void 0 : _a.inputConfig) === null || _b === void 0 ? void 0 : _b.pagination) }
|
|
367
|
+
: undefined;
|
|
368
|
+
methods.push(this.generateDefaultHandlerMethod(modelName, actionName, aggregateConfig, childInfo, inputType, dtoFields, listConfig));
|
|
188
369
|
}
|
|
189
370
|
else {
|
|
190
|
-
|
|
371
|
+
const { inputType, resultType, returnType } = this.deriveCustomHandlerTypes(contexts, modelName);
|
|
372
|
+
contexts.forEach(c => dtoTypes.add(c.inputDtoType));
|
|
373
|
+
methods.push(this.generateCustomHandlerMethod(modelName, handler, resultType, inputType, returnType));
|
|
191
374
|
}
|
|
192
375
|
});
|
|
193
376
|
const listByParentMethod = this.generateListByParentMethod(modelName, childInfo);
|
|
@@ -198,8 +381,21 @@ ${setterCalls}
|
|
|
198
381
|
if (getResourceOwnerMethod) {
|
|
199
382
|
methods.push(getResourceOwnerMethod);
|
|
200
383
|
}
|
|
384
|
+
// Collect imports for aggregate reference types used in fields
|
|
385
|
+
const aggRefImports = Object.entries(aggregateConfig.fields)
|
|
386
|
+
.filter(([, fc]) => (0, typeUtils_1.isAggregateReference)(fc.type, this.availableAggregates) && fc.type !== modelName)
|
|
387
|
+
.map(([, fc]) => `import { ${fc.type} } from '../../domain/entities/${fc.type}';`)
|
|
388
|
+
.filter((imp, idx, arr) => arr.indexOf(imp) === idx);
|
|
389
|
+
const aggRefImportStr = aggRefImports.length > 0 ? '\n' + aggRefImports.join('\n') : '';
|
|
390
|
+
// Generate DTO import statements
|
|
391
|
+
const dtoImports = [...dtoTypes].map(dtoType => {
|
|
392
|
+
const fileSuffix = dtoType.replace(modelName, '').replace('Input', '');
|
|
393
|
+
return `import { ${dtoType} } from '../dto/${modelName}${fileSuffix}';`;
|
|
394
|
+
}).join('\n');
|
|
395
|
+
const dtoImportStr = dtoImports ? '\n' + dtoImports : '';
|
|
396
|
+
const entityImports = [modelName, ...enumTypeNames].join(', ');
|
|
201
397
|
return `import { Injectable } from '../../../../system';
|
|
202
|
-
import { ${
|
|
398
|
+
import { ${entityImports} } from '../../domain/entities/${modelName}';${aggRefImportStr}${dtoImportStr}
|
|
203
399
|
import { ${storeName} } from '../../infrastructure/stores/${storeName}';
|
|
204
400
|
|
|
205
401
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ModuleConfig } from '../types/configTypes';
|
|
2
2
|
export declare class StoreGenerator {
|
|
3
3
|
private availableValueObjects;
|
|
4
|
+
private availableAggregates;
|
|
5
|
+
private isAggregateField;
|
|
4
6
|
private isValueObjectType;
|
|
5
7
|
private getValueObjectName;
|
|
6
8
|
private getValueObjectConfig;
|
|
@@ -31,6 +33,8 @@ export declare class StoreGenerator {
|
|
|
31
33
|
private generateUpdateDataMapping;
|
|
32
34
|
private generateUpdateFieldsArray;
|
|
33
35
|
private generateValueObjectImports;
|
|
36
|
+
private generateAggregateRefImports;
|
|
37
|
+
private generateListMethods;
|
|
34
38
|
private generateGetByParentIdMethod;
|
|
35
39
|
private generateGetResourceOwnerMethod;
|
|
36
40
|
private generateStore;
|
|
@@ -46,6 +46,10 @@ const typeUtils_1 = require("../utils/typeUtils");
|
|
|
46
46
|
class StoreGenerator {
|
|
47
47
|
constructor() {
|
|
48
48
|
this.availableValueObjects = new Map();
|
|
49
|
+
this.availableAggregates = new Set();
|
|
50
|
+
}
|
|
51
|
+
isAggregateField(fieldConfig) {
|
|
52
|
+
return (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
|
|
49
53
|
}
|
|
50
54
|
isValueObjectType(fieldType) {
|
|
51
55
|
const capitalizedType = (0, typeUtils_1.capitalize)(fieldType);
|
|
@@ -64,8 +68,8 @@ class StoreGenerator {
|
|
|
64
68
|
*/
|
|
65
69
|
sortFieldsForConstructor(fields) {
|
|
66
70
|
return [...fields].sort((a, b) => {
|
|
67
|
-
const aRequired = a[1].required !== false && !a[1].auto;
|
|
68
|
-
const bRequired = b[1].required !== false && !b[1].auto;
|
|
71
|
+
const aRequired = a[1].required !== false && !a[1].auto && !this.isAggregateField(a[1]);
|
|
72
|
+
const bRequired = b[1].required !== false && !b[1].auto && !this.isAggregateField(b[1]);
|
|
69
73
|
if (aRequired === bRequired)
|
|
70
74
|
return 0;
|
|
71
75
|
return aRequired ? -1 : 1;
|
|
@@ -139,6 +143,10 @@ class StoreGenerator {
|
|
|
139
143
|
const ownerOrParentField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
140
144
|
result.push(` ${ownerOrParentField}: number;`);
|
|
141
145
|
fields.forEach(([fieldName, fieldConfig]) => {
|
|
146
|
+
if (this.isAggregateField(fieldConfig)) {
|
|
147
|
+
result.push(` ${fieldName}_id?: number;`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
142
150
|
const tsType = this.mapTypeToRowType(fieldConfig.type);
|
|
143
151
|
const isOptional = !fieldConfig.required;
|
|
144
152
|
result.push(` ${fieldName}${isOptional ? '?' : ''}: ${tsType};`);
|
|
@@ -148,15 +156,26 @@ class StoreGenerator {
|
|
|
148
156
|
generateFieldNamesStr(fields, childInfo) {
|
|
149
157
|
const fieldNames = ['id'];
|
|
150
158
|
fieldNames.push(childInfo ? childInfo.parentIdField : 'ownerId');
|
|
151
|
-
fieldNames.push(...fields.map(([name]) => name));
|
|
159
|
+
fieldNames.push(...fields.map(([name, config]) => this.isAggregateField(config) ? `${name}_id` : name));
|
|
152
160
|
return fieldNames.map(f => `\\\`${f}\\\``).join(', ');
|
|
153
161
|
}
|
|
154
|
-
generateRowToModelMapping(fields, childInfo) {
|
|
162
|
+
generateRowToModelMapping(modelName, fields, childInfo) {
|
|
155
163
|
const result = [];
|
|
156
164
|
const ownerOrParentField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
157
165
|
result.push(` row.${ownerOrParentField}`);
|
|
158
166
|
fields.forEach(([fieldName, fieldConfig]) => {
|
|
159
167
|
const fieldType = fieldConfig.type;
|
|
168
|
+
// Handle enum type - cast string to the generated union type
|
|
169
|
+
if (fieldType === 'enum' && fieldConfig.values && fieldConfig.values.length > 0) {
|
|
170
|
+
const enumTypeName = `${modelName}${(0, typeUtils_1.capitalize)(fieldName)}`;
|
|
171
|
+
result.push(` row.${fieldName} as ${enumTypeName}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Handle aggregate reference - create stub from FK
|
|
175
|
+
if (this.isAggregateField(fieldConfig)) {
|
|
176
|
+
result.push(` row.${fieldName}_id != null ? ({ id: row.${fieldName}_id } as unknown as ${fieldType}) : undefined`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
160
179
|
// Handle datetime/date conversion
|
|
161
180
|
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
162
181
|
result.push(this.generateDatetimeConversion(fieldName, 'toDate'));
|
|
@@ -189,6 +208,11 @@ class StoreGenerator {
|
|
|
189
208
|
result.push(` ${ownerOrParentField}: entity.${ownerOrParentField}`);
|
|
190
209
|
fields.forEach(([fieldName, fieldConfig]) => {
|
|
191
210
|
const fieldType = fieldConfig.type;
|
|
211
|
+
// Handle aggregate reference - extract FK id
|
|
212
|
+
if (this.isAggregateField(fieldConfig)) {
|
|
213
|
+
result.push(` ${fieldName}_id: entity.${fieldName}?.id`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
192
216
|
// Handle datetime/date - convert Date to MySQL DATETIME format
|
|
193
217
|
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
194
218
|
result.push(this.generateDatetimeConversion(fieldName, 'toMySQL'));
|
|
@@ -213,6 +237,9 @@ class StoreGenerator {
|
|
|
213
237
|
return fields
|
|
214
238
|
.map(([fieldName, fieldConfig]) => {
|
|
215
239
|
const fieldType = fieldConfig.type;
|
|
240
|
+
if (this.isAggregateField(fieldConfig)) {
|
|
241
|
+
return ` ${fieldName}_id: entity.${fieldName}?.id`;
|
|
242
|
+
}
|
|
216
243
|
if (fieldType === 'datetime' || fieldType === 'date') {
|
|
217
244
|
return this.generateDatetimeConversion(fieldName, 'toMySQL');
|
|
218
245
|
}
|
|
@@ -228,7 +255,7 @@ class StoreGenerator {
|
|
|
228
255
|
.join(',\n');
|
|
229
256
|
}
|
|
230
257
|
generateUpdateFieldsArray(fields) {
|
|
231
|
-
return JSON.stringify(fields.map(([name]) => name));
|
|
258
|
+
return JSON.stringify(fields.map(([name, config]) => this.isAggregateField(config) ? `${name}_id` : name));
|
|
232
259
|
}
|
|
233
260
|
generateValueObjectImports(fields) {
|
|
234
261
|
const imports = [];
|
|
@@ -255,10 +282,71 @@ class StoreGenerator {
|
|
|
255
282
|
}
|
|
256
283
|
return '\n' + uniqueImports.join('\n');
|
|
257
284
|
}
|
|
285
|
+
generateAggregateRefImports(modelName, fields) {
|
|
286
|
+
const imports = [];
|
|
287
|
+
fields.forEach(([, fieldConfig]) => {
|
|
288
|
+
if (this.isAggregateField(fieldConfig) && fieldConfig.type !== modelName) {
|
|
289
|
+
imports.push(`import { ${fieldConfig.type} } from '../../domain/entities/${fieldConfig.type}';`);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
const uniqueImports = [...new Set(imports)];
|
|
293
|
+
if (uniqueImports.length === 0)
|
|
294
|
+
return '';
|
|
295
|
+
return '\n' + uniqueImports.join('\n');
|
|
296
|
+
}
|
|
297
|
+
generateListMethods(modelName, fieldNamesStr, childInfo) {
|
|
298
|
+
const isRoot = !childInfo;
|
|
299
|
+
const ownerParam = isRoot ? ', ownerId?: number' : '';
|
|
300
|
+
const ownerFilter = isRoot
|
|
301
|
+
? `\n const ownerFilter = ownerId != null ? ' AND \\\`ownerId\\\` = :ownerId' : '';`
|
|
302
|
+
: '';
|
|
303
|
+
const ownerFilterRef = isRoot ? '\${ownerFilter}' : '';
|
|
304
|
+
const ownerParamsSetup = isRoot
|
|
305
|
+
? `\n if (ownerId != null) params.ownerId = ownerId;`
|
|
306
|
+
: '';
|
|
307
|
+
const getPaginated = ` async getPaginated(page: number = 1, limit: number = 20${ownerParam}): Promise<${modelName}[]> {
|
|
308
|
+
const offset = (page - 1) * limit;${ownerFilter}
|
|
309
|
+
const params: Record<string, any> = { limit: String(limit), offset: String(offset) };${ownerParamsSetup}
|
|
310
|
+
const result = await this.db.query(
|
|
311
|
+
\`SELECT ${fieldNamesStr} FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL${ownerFilterRef} LIMIT :limit OFFSET :offset\`,
|
|
312
|
+
params
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (result.success && result.data) {
|
|
316
|
+
return result.data.map((row: ${modelName}Row) => this.rowToModel(row));
|
|
317
|
+
}
|
|
318
|
+
return [];
|
|
319
|
+
}`;
|
|
320
|
+
const getAll = ` async getAll(${isRoot ? 'ownerId?: number' : ''}): Promise<${modelName}[]> {${ownerFilter}
|
|
321
|
+
const params: Record<string, any> = {};${ownerParamsSetup}
|
|
322
|
+
const result = await this.db.query(
|
|
323
|
+
\`SELECT ${fieldNamesStr} FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL${ownerFilterRef}\`,
|
|
324
|
+
params
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
if (result.success && result.data) {
|
|
328
|
+
return result.data.map((row: ${modelName}Row) => this.rowToModel(row));
|
|
329
|
+
}
|
|
330
|
+
return [];
|
|
331
|
+
}`;
|
|
332
|
+
const count = ` async count(${isRoot ? 'ownerId?: number' : ''}): Promise<number> {${ownerFilter}
|
|
333
|
+
const params: Record<string, any> = {};${ownerParamsSetup}
|
|
334
|
+
const result = await this.db.query(
|
|
335
|
+
\`SELECT COUNT(*) as count FROM \\\`\${this.tableName}\\\` WHERE deleted_at IS NULL${ownerFilterRef}\`,
|
|
336
|
+
params
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
340
|
+
return parseInt(result.data[0].count, 10);
|
|
341
|
+
}
|
|
342
|
+
return 0;
|
|
343
|
+
}`;
|
|
344
|
+
return `${getPaginated}\n\n${getAll}\n\n${count}`;
|
|
345
|
+
}
|
|
258
346
|
generateGetByParentIdMethod(modelName, fields, childInfo) {
|
|
259
347
|
if (!childInfo)
|
|
260
348
|
return '';
|
|
261
|
-
const fieldList = ['id', childInfo.parentIdField, ...fields.map(([name]) => name)].map(f => '\\`' + f + '\\`').join(', ');
|
|
349
|
+
const fieldList = ['id', childInfo.parentIdField, ...fields.map(([name, config]) => this.isAggregateField(config) ? `${name}_id` : name)].map(f => '\\`' + f + '\\`').join(', ');
|
|
262
350
|
const parentIdField = childInfo.parentIdField;
|
|
263
351
|
return `
|
|
264
352
|
|
|
@@ -319,26 +407,38 @@ class StoreGenerator {
|
|
|
319
407
|
const fields = Object.entries(aggregateConfig.fields);
|
|
320
408
|
// Sort fields for rowToModel to match entity constructor order (required first, optional second)
|
|
321
409
|
const sortedFields = this.sortFieldsForConstructor(fields);
|
|
410
|
+
const fieldNamesStr = this.generateFieldNamesStr(fields, childInfo);
|
|
322
411
|
const variables = {
|
|
323
412
|
ENTITY_NAME: modelName,
|
|
324
413
|
TABLE_NAME: tableName,
|
|
325
414
|
ROW_FIELDS: this.generateRowFields(fields, childInfo),
|
|
326
|
-
FIELD_NAMES:
|
|
327
|
-
ROW_TO_MODEL_MAPPING: this.generateRowToModelMapping(sortedFields, childInfo),
|
|
415
|
+
FIELD_NAMES: fieldNamesStr,
|
|
416
|
+
ROW_TO_MODEL_MAPPING: this.generateRowToModelMapping(modelName, sortedFields, childInfo),
|
|
328
417
|
INSERT_DATA_MAPPING: this.generateInsertDataMapping(fields, childInfo),
|
|
329
418
|
UPDATE_DATA_MAPPING: this.generateUpdateDataMapping(fields),
|
|
330
419
|
UPDATE_FIELDS_ARRAY: this.generateUpdateFieldsArray(fields),
|
|
331
420
|
VALUE_OBJECT_IMPORTS: this.generateValueObjectImports(fields),
|
|
421
|
+
AGGREGATE_REF_IMPORTS: this.generateAggregateRefImports(modelName, fields),
|
|
422
|
+
LIST_METHODS: this.generateListMethods(modelName, fieldNamesStr, childInfo),
|
|
332
423
|
GET_BY_PARENT_ID_METHOD: this.generateGetByParentIdMethod(modelName, fields, childInfo),
|
|
333
424
|
GET_RESOURCE_OWNER_METHOD: this.generateGetResourceOwnerMethod(childInfo)
|
|
334
425
|
};
|
|
335
426
|
const rowInterface = this.replaceTemplateVars(storeTemplates_1.storeTemplates.rowInterface, variables);
|
|
336
427
|
const storeClass = this.replaceTemplateVars(storeTemplates_1.storeTemplates.storeClass, variables);
|
|
428
|
+
// Build entity import items: entity name + any enum type names
|
|
429
|
+
const entityImportItems = [modelName];
|
|
430
|
+
fields.forEach(([fieldName, fieldConfig]) => {
|
|
431
|
+
if (fieldConfig.type === 'enum' && fieldConfig.values && fieldConfig.values.length > 0) {
|
|
432
|
+
entityImportItems.push(`${modelName}${(0, typeUtils_1.capitalize)(fieldName)}`);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
337
435
|
return this.replaceTemplateVars(storeTemplates_1.storeFileTemplate, {
|
|
338
436
|
ENTITY_NAME: modelName,
|
|
437
|
+
ENTITY_IMPORT_ITEMS: entityImportItems.join(', '),
|
|
339
438
|
ROW_INTERFACE: rowInterface,
|
|
340
439
|
STORE_CLASS: storeClass,
|
|
341
|
-
VALUE_OBJECT_IMPORTS: variables.VALUE_OBJECT_IMPORTS
|
|
440
|
+
VALUE_OBJECT_IMPORTS: variables.VALUE_OBJECT_IMPORTS,
|
|
441
|
+
AGGREGATE_REF_IMPORTS: variables.AGGREGATE_REF_IMPORTS
|
|
342
442
|
});
|
|
343
443
|
}
|
|
344
444
|
generateFromConfig(config) {
|
|
@@ -350,6 +450,13 @@ class StoreGenerator {
|
|
|
350
450
|
this.availableValueObjects.set(name, voConfig);
|
|
351
451
|
});
|
|
352
452
|
}
|
|
453
|
+
// Collect all aggregate names for detecting entity references
|
|
454
|
+
this.availableAggregates.clear();
|
|
455
|
+
if (config.domain.aggregates) {
|
|
456
|
+
Object.keys(config.domain.aggregates).forEach(name => {
|
|
457
|
+
this.availableAggregates.add(name);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
353
460
|
// Generate a store for each aggregate
|
|
354
461
|
const childEntityMap = (0, childEntityUtils_1.buildChildEntityMap)(config);
|
|
355
462
|
if (config.domain.aggregates) {
|
|
@@ -458,16 +458,22 @@ ${options}
|
|
|
458
458
|
return this.generateFromConfig(config);
|
|
459
459
|
}
|
|
460
460
|
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
461
|
+
let isGenerated = false;
|
|
461
462
|
const templatesByName = this.generateFromYamlFile(yamlFilePath);
|
|
462
463
|
const viewsDir = path.join(moduleDir, 'views');
|
|
463
464
|
fs.mkdirSync(viewsDir, { recursive: true });
|
|
464
465
|
for (const [name, content] of Object.entries(templatesByName)) {
|
|
465
466
|
const filePath = path.join(viewsDir, `${name}.html`);
|
|
467
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.onlyIfMissing) && fs.existsSync(filePath))
|
|
468
|
+
continue;
|
|
466
469
|
// eslint-disable-next-line no-await-in-loop
|
|
467
470
|
await (0, generationRegistry_1.writeGeneratedFile)(filePath, content, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
|
|
471
|
+
isGenerated = true;
|
|
472
|
+
}
|
|
473
|
+
if (isGenerated) {
|
|
474
|
+
// eslint-disable-next-line no-console
|
|
475
|
+
console.log('\n' + colors_1.colors.green('Template files generated successfully!') + '\n');
|
|
468
476
|
}
|
|
469
|
-
// eslint-disable-next-line no-console
|
|
470
|
-
console.log('\n' + colors_1.colors.green('Template files generated successfully!') + '\n');
|
|
471
477
|
}
|
|
472
478
|
}
|
|
473
479
|
exports.TemplateGenerator = TemplateGenerator;
|