@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +374 -996
  3. package/dist/cli.js +28 -10
  4. package/dist/commands/createModel.d.ts +1 -0
  5. package/dist/commands/createModel.js +764 -0
  6. package/dist/commands/createModule.js +13 -0
  7. package/dist/commands/generateAll.d.ts +1 -0
  8. package/dist/commands/generateAll.js +1 -1
  9. package/dist/commands/init.d.ts +1 -0
  10. package/dist/commands/{createApp.js → init.js} +2 -2
  11. package/dist/commands/migrateCommit.js +33 -68
  12. package/dist/generators/controllerGenerator.d.ts +7 -0
  13. package/dist/generators/controllerGenerator.js +56 -17
  14. package/dist/generators/domainLayerGenerator.js +51 -8
  15. package/dist/generators/dtoGenerator.js +13 -8
  16. package/dist/generators/serviceGenerator.d.ts +6 -0
  17. package/dist/generators/serviceGenerator.js +219 -23
  18. package/dist/generators/storeGenerator.d.ts +4 -0
  19. package/dist/generators/storeGenerator.js +116 -9
  20. package/dist/generators/templateGenerator.d.ts +1 -0
  21. package/dist/generators/templateGenerator.js +8 -2
  22. package/dist/generators/templates/appTemplates.js +1 -1
  23. package/dist/generators/templates/data/cursorRulesTemplate +11 -755
  24. package/dist/generators/templates/data/frontendScriptTemplate +11 -4
  25. package/dist/generators/templates/data/mainViewTemplate +1 -0
  26. package/dist/generators/templates/storeTemplates.d.ts +1 -1
  27. package/dist/generators/templates/storeTemplates.js +3 -26
  28. package/dist/generators/useCaseGenerator.js +6 -3
  29. package/dist/types/configTypes.d.ts +6 -0
  30. package/dist/utils/migrationUtils.d.ts +9 -19
  31. package/dist/utils/migrationUtils.js +80 -110
  32. package/dist/utils/promptUtils.d.ts +37 -0
  33. package/dist/utils/promptUtils.js +149 -0
  34. package/dist/utils/typeUtils.d.ts +4 -0
  35. package/dist/utils/typeUtils.js +7 -0
  36. package/package.json +1 -1
  37. package/dist/commands/createApp.d.ts +0 -1
  38. package/dist/commands/migratePush.d.ts +0 -1
  39. package/dist/commands/migratePush.js +0 -135
  40. package/dist/commands/migrateUpdate.d.ts +0 -1
  41. package/dist/commands/migrateUpdate.js +0 -147
  42. package/dist/commands/newGenerateAll.d.ts +0 -4
  43. package/dist/commands/newGenerateAll.js +0 -336
  44. package/dist/generators/domainModelGenerator.d.ts +0 -41
  45. package/dist/generators/domainModelGenerator.js +0 -242
  46. package/dist/generators/newControllerGenerator.d.ts +0 -55
  47. package/dist/generators/newControllerGenerator.js +0 -644
  48. package/dist/generators/newServiceGenerator.d.ts +0 -19
  49. package/dist/generators/newServiceGenerator.js +0 -266
  50. package/dist/generators/newStoreGenerator.d.ts +0 -39
  51. package/dist/generators/newStoreGenerator.js +0 -408
  52. package/dist/generators/newTemplateGenerator.d.ts +0 -29
  53. package/dist/generators/newTemplateGenerator.js +0 -510
  54. package/dist/generators/storeGeneratorV2.d.ts +0 -31
  55. package/dist/generators/storeGeneratorV2.js +0 -190
  56. package/dist/generators/templates/controllerTemplates.d.ts +0 -43
  57. package/dist/generators/templates/controllerTemplates.js +0 -82
  58. package/dist/generators/templates/newStoreTemplates.d.ts +0 -5
  59. package/dist/generators/templates/newStoreTemplates.js +0 -141
  60. package/dist/generators/templates/serviceTemplates.d.ts +0 -16
  61. package/dist/generators/templates/serviceTemplates.js +0 -59
  62. package/dist/generators/templates/validationTemplates.d.ts +0 -25
  63. package/dist/generators/templates/validationTemplates.js +0 -66
  64. package/dist/generators/templates/viewTemplates.d.ts +0 -25
  65. package/dist/generators/templates/viewTemplates.js +0 -491
  66. package/dist/generators/validationGenerator.d.ts +0 -29
  67. package/dist/generators/validationGenerator.js +0 -250
  68. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  69. package/dist/utils/new_parts_of_migrationUtils.js +0 -164
  70. 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
- generateListHandler(modelName, storeName) {
53
- return ` async list(page: number = 1, limit: number = 20): Promise<{ items: ${modelName}[]; total: number; page: number; limit: number }> {
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}.getAll(page, limit),
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 aRequired = a[1].required !== false;
76
- const bRequired = b[1].required !== false;
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]) => `input.${fieldName}`).join(', ');
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: any): Promise<${modelName}> {
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: any): Promise<${modelName}> {
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: any): Promise<any> {
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: any, input: any): Promise<any> {
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
- methods.push(this.generateDefaultHandlerMethod(modelName, actionName, aggregateConfig, childInfo));
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
- methods.push(this.generateCustomHandlerMethod(modelName, handler));
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 { ${modelName} } from '../../domain/entities/${modelName}';
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: this.generateFieldNamesStr(fields, childInfo),
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) {
@@ -26,5 +26,6 @@ export declare class TemplateGenerator {
26
26
  generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
27
27
  force?: boolean;
28
28
  skipOnConflict?: boolean;
29
+ onlyIfMissing?: boolean;
29
30
  }): Promise<void>;
30
31
  }
@@ -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;
@@ -60,7 +60,7 @@ const packageJsonTemplate = (appName) => JSON.stringify({
60
60
  devDependencies: {
61
61
  typescript: '^5.6.3',
62
62
  '@types/node': '^22.7.4',
63
- '@koz1024/path-fixer': '^0.2.1',
63
+ '@koz1024/path-fixer': '^0.2.2',
64
64
  },
65
65
  type: 'module'
66
66
  }, null, 2);