@currentjs/gen 0.3.2 → 0.5.1

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 (60) hide show
  1. package/CHANGELOG.md +18 -609
  2. package/README.md +623 -427
  3. package/dist/cli.js +2 -1
  4. package/dist/commands/commit.js +25 -42
  5. package/dist/commands/createApp.js +1 -0
  6. package/dist/commands/createModule.js +151 -45
  7. package/dist/commands/diff.js +27 -40
  8. package/dist/commands/generateAll.js +141 -291
  9. package/dist/commands/migrateCommit.js +6 -18
  10. package/dist/generators/controllerGenerator.d.ts +50 -19
  11. package/dist/generators/controllerGenerator.js +588 -331
  12. package/dist/generators/domainLayerGenerator.d.ts +21 -0
  13. package/dist/generators/domainLayerGenerator.js +286 -0
  14. package/dist/generators/dtoGenerator.d.ts +21 -0
  15. package/dist/generators/dtoGenerator.js +523 -0
  16. package/dist/generators/serviceGenerator.d.ts +22 -51
  17. package/dist/generators/serviceGenerator.js +345 -568
  18. package/dist/generators/storeGenerator.d.ts +39 -32
  19. package/dist/generators/storeGenerator.js +396 -236
  20. package/dist/generators/templateGenerator.d.ts +21 -21
  21. package/dist/generators/templateGenerator.js +393 -268
  22. package/dist/generators/templates/appTemplates.d.ts +3 -1
  23. package/dist/generators/templates/appTemplates.js +16 -11
  24. package/dist/generators/templates/data/appYamlTemplate +5 -2
  25. package/dist/generators/templates/data/cursorRulesTemplate +315 -221
  26. package/dist/generators/templates/data/frontendScriptTemplate +56 -15
  27. package/dist/generators/templates/data/mainViewTemplate +2 -1
  28. package/dist/generators/templates/data/systemTsTemplate +5 -0
  29. package/dist/generators/templates/index.d.ts +0 -3
  30. package/dist/generators/templates/index.js +0 -3
  31. package/dist/generators/templates/storeTemplates.d.ts +1 -5
  32. package/dist/generators/templates/storeTemplates.js +84 -224
  33. package/dist/generators/useCaseGenerator.d.ts +13 -0
  34. package/dist/generators/useCaseGenerator.js +191 -0
  35. package/dist/types/configTypes.d.ts +149 -0
  36. package/dist/types/configTypes.js +10 -0
  37. package/dist/utils/childEntityUtils.d.ts +18 -0
  38. package/dist/utils/childEntityUtils.js +78 -0
  39. package/dist/utils/commandUtils.d.ts +43 -0
  40. package/dist/utils/commandUtils.js +124 -0
  41. package/dist/utils/commitUtils.d.ts +4 -1
  42. package/dist/utils/constants.d.ts +10 -0
  43. package/dist/utils/constants.js +13 -1
  44. package/dist/utils/diResolver.d.ts +32 -0
  45. package/dist/utils/diResolver.js +204 -0
  46. package/dist/utils/typeUtils.d.ts +23 -0
  47. package/dist/utils/typeUtils.js +77 -0
  48. package/package.json +7 -3
  49. package/dist/generators/domainModelGenerator.d.ts +0 -41
  50. package/dist/generators/domainModelGenerator.js +0 -242
  51. package/dist/generators/templates/controllerTemplates.d.ts +0 -43
  52. package/dist/generators/templates/controllerTemplates.js +0 -82
  53. package/dist/generators/templates/serviceTemplates.d.ts +0 -16
  54. package/dist/generators/templates/serviceTemplates.js +0 -59
  55. package/dist/generators/templates/validationTemplates.d.ts +0 -25
  56. package/dist/generators/templates/validationTemplates.js +0 -66
  57. package/dist/generators/templates/viewTemplates.d.ts +0 -25
  58. package/dist/generators/templates/viewTemplates.js +0 -491
  59. package/dist/generators/validationGenerator.d.ts +0 -29
  60. package/dist/generators/validationGenerator.js +0 -250
@@ -37,640 +37,417 @@ exports.ServiceGenerator = void 0;
37
37
  const yaml_1 = require("yaml");
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
- const serviceTemplates_1 = require("./templates/serviceTemplates");
41
40
  const generationRegistry_1 = require("../utils/generationRegistry");
42
41
  const colors_1 = require("../utils/colors");
43
- const constants_1 = require("../utils/constants");
42
+ const configTypes_1 = require("../types/configTypes");
43
+ const childEntityUtils_1 = require("../utils/childEntityUtils");
44
+ const typeUtils_1 = require("../utils/typeUtils");
44
45
  class ServiceGenerator {
45
46
  constructor() {
46
- this.availableModels = new Set();
47
+ this.availableAggregates = new Map();
47
48
  }
48
- setAvailableModels(models) {
49
- this.availableModels.clear();
50
- models.forEach(model => {
51
- this.availableModels.add(model.name);
52
- });
53
- }
54
- isRelationshipField(field) {
55
- return this.availableModels.has(field.type);
56
- }
57
- getForeignKeyFieldName(field) {
58
- // Convention: fieldName + 'Id' (e.g., owner -> ownerId)
59
- return field.name + 'Id';
60
- }
61
- hasPermissions(config) {
62
- if (config.modules) {
63
- return Object.values(config.modules).some(module => module.permissions && module.permissions.length > 0);
64
- }
65
- const module = config;
66
- return !!(module.permissions && module.permissions.length > 0);
67
- }
68
- getActionPermissions(moduleName, moduleConfig) {
69
- if (!moduleConfig.permissions || !moduleConfig.actions) {
70
- return {};
71
- }
72
- const actionPermissions = {};
73
- // Initialize all actions with empty permissions
74
- Object.keys(moduleConfig.actions).forEach(action => {
75
- actionPermissions[action] = [];
76
- });
77
- // Fill in permissions for each action
78
- (moduleConfig.permissions || []).forEach(permission => {
79
- (permission.actions || []).forEach(action => {
80
- if (actionPermissions[action]) {
81
- actionPermissions[action].push(permission.role);
82
- }
83
- });
84
- });
85
- return actionPermissions;
86
- }
87
- generatePermissionCheck(action, roles, entityName) {
88
- if (roles.length === 0 || roles.includes('all')) {
89
- return '';
90
- }
91
- if (roles.includes('none')) {
92
- return " throw new Error('This action is not permitted');";
93
- }
94
- const nonOwnerRoles = roles.filter(role => !['all', 'none', 'owner'].includes(role));
95
- const rolesArray = nonOwnerRoles.map(role => `'${role}'`).join(', ');
96
- // Special-case: list action with owner → rely on store-level filtering; only enforce static role checks for other roles
97
- if (action === 'list' && roles.includes('owner')) {
98
- if (nonOwnerRoles.length === 0) {
99
- return '';
100
- }
101
- return serviceTemplates_1.serviceTemplates.permissionCheck
102
- .replace(/{{REQUIRED_ROLES}}/g, roles.join(', '))
103
- .replace(/{{ACTION_NAME}}/g, action)
104
- .replace(/{{ROLES_ARRAY}}/g, rolesArray);
105
- }
106
- // For resource-based owner permission (non-list), fetch resource first and verify ownership
107
- if (roles.includes('owner')) {
108
- const isResourceBased = ['get', 'update', 'delete'].includes(action);
109
- if (!isResourceBased) {
110
- // Not a resource-based action; fall back to static role checks only (if any non-owner roles)
111
- if (nonOwnerRoles.length === 0) {
112
- return '';
113
- }
114
- return serviceTemplates_1.serviceTemplates.permissionCheck
115
- .replace(/{{REQUIRED_ROLES}}/g, roles.join(', '))
116
- .replace(/{{ACTION_NAME}}/g, action)
117
- .replace(/{{ROLES_ARRAY}}/g, rolesArray);
118
- }
119
- const entityLower = entityName.toLowerCase();
120
- const resourceIdParam = this.getResourceIdForAction(action);
121
- const notFoundMessage = `${entityName} not found`;
122
- // If there are additional roles besides owner, allow them to bypass owner check
123
- const maybeRoleBypass = nonOwnerRoles.length > 0
124
- ? ` const allowedRoles = [${rolesArray}];
125
- if (allowedRoles.includes(user.role)) {
126
- // Explicit role allowed; skip owner check
127
- } else {
128
- const ${entityLower} = await this.${entityLower}Store.getById(${resourceIdParam});
129
- if (!${entityLower}) {
130
- throw new Error('${notFoundMessage}');
131
- }
132
- if ((${entityLower} as any).userId !== user.id) {
133
- throw new Error('You can only access your own resources');
134
- }
135
- }`
136
- : ` const ${entityLower} = await this.${entityLower}Store.getById(${resourceIdParam});
137
- if (!${entityLower}) {
138
- throw new Error('${notFoundMessage}');
49
+ mapType(yamlType) {
50
+ return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates);
139
51
  }
140
- if ((${entityLower} as any).userId !== user.id) {
141
- throw new Error('You can only access your own resources');
142
- }`;
143
- return maybeRoleBypass;
144
- }
145
- // Static role check for non-owner roles
146
- return serviceTemplates_1.serviceTemplates.permissionCheck
147
- .replace(/{{REQUIRED_ROLES}}/g, roles.join(', '))
148
- .replace(/{{ACTION_NAME}}/g, action)
149
- .replace(/{{ROLES_ARRAY}}/g, rolesArray);
150
- }
151
- getResourceIdForAction(action) {
152
- switch (action) {
153
- case 'get':
154
- case 'update':
155
- case 'delete':
156
- return 'id';
157
- default:
158
- return 'id';
159
- }
160
- }
161
- generateMethodParams(action, entityName) {
162
- const entityParam = `${entityName.toLowerCase()}Data`;
163
- switch (action) {
164
- case 'list':
165
- return 'page: number = 1, limit: number = 10';
166
- case 'get':
167
- case 'getById':
168
- return 'id: number';
52
+ getDefaultHandlerReturnType(actionName, modelName) {
53
+ switch (actionName) {
169
54
  case 'create':
170
- return `${entityParam}: ${entityName}DTO`;
171
- case 'update':
172
- return `id: number, ${entityParam}: ${entityName}DTO`;
173
- case 'delete':
174
- return 'id: number';
175
- default:
176
- return '';
177
- }
178
- }
179
- generateReturnType(action, entityName) {
180
- switch (action) {
181
- case 'list':
182
- return `${entityName}[]`;
183
55
  case 'get':
184
- case 'getById':
185
- case 'create':
186
56
  case 'update':
187
- return entityName;
57
+ return modelName;
188
58
  case 'delete':
189
59
  return '{ success: boolean; message: string }';
60
+ case 'list':
61
+ return `{ items: ${modelName}[]; total: number; page: number; limit: number }`;
190
62
  default:
191
- return 'void';
63
+ return modelName;
192
64
  }
193
65
  }
194
- generateMethodImplementation(action, entityName, handlers, moduleName, roles, moduleConfig) {
195
- if (!handlers || handlers.length === 0) {
196
- return `// TODO: Implement ${action} method`;
197
- }
198
- const implementations = [];
199
- const entityLower = entityName.toLowerCase();
200
- handlers.forEach(handler => {
201
- var _a, _b;
202
- // Handle new explicit format: modelname:default:action or modelname:custommethod
203
- if (handler.includes(':')) {
204
- const parts = handler.split(':');
205
- if (parts.length === 3 && parts[1] === 'default') {
206
- // Format: modelname:default:action
207
- const [modelName, , actionName] = parts;
208
- // Check if this handler applies to current model
209
- if (modelName.toLowerCase() === entityLower || modelName === entityName) {
210
- const template = serviceTemplates_1.serviceTemplates.defaultImplementations[actionName];
211
- if (template) {
212
- let processedTemplate = template
213
- .replace(/{{ENTITY_NAME}}/g, entityName)
214
- .replace(/{{ENTITY_LOWER}}/g, entityLower);
215
- // Handle constructor args for create action
216
- if (actionName === 'create') {
217
- const relationshipLoading = this.generateRelationshipLoading(moduleConfig, entityName);
218
- const constructorArgs = this.generateConstructorArgs(moduleConfig, entityName);
219
- if (relationshipLoading) {
220
- // Insert relationship loading before the entity creation
221
- processedTemplate = relationshipLoading + '\n ' + processedTemplate;
222
- }
223
- processedTemplate = processedTemplate.replace(/{{CONSTRUCTOR_ARGS}}/g, constructorArgs);
224
- }
225
- // Handle setter calls for update action
226
- if (actionName === 'update') {
227
- const setterCalls = this.generateUpdateSetterCalls(moduleConfig, entityName, true);
228
- processedTemplate = processedTemplate.replace(/{{UPDATE_SETTER_CALLS}}/g, setterCalls);
229
- }
230
- // Special-case: list action with only owner role → fetch by userId
231
- if (actionName === 'list') {
232
- const onlyOwner = roles.length > 0 && roles.every(r => r === 'owner');
233
- if (onlyOwner) {
234
- processedTemplate = `const ${entityLower}s = await this.${entityLower}Store.getAllByUserId(user.id, page, limit);\n return ${entityLower}s;`;
235
- }
236
- }
237
- implementations.push(processedTemplate);
238
- }
239
- else {
240
- implementations.push(`// TODO: Implement default ${actionName} method for ${entityName}`);
241
- }
242
- }
243
- else {
244
- // Cross-model default call
245
- const targetModel = (_a = moduleConfig.models) === null || _a === void 0 ? void 0 : _a.find(m => m.name.toLowerCase() === modelName.toLowerCase() || m.name === modelName);
246
- if (targetModel) {
247
- const targetServiceVar = `${targetModel.name.toLowerCase()}Service`;
248
- const customImpl = `// Cross-model call to ${targetModel.name}Service
249
- const result = await this.${targetServiceVar}.${actionName}(${this.getMethodCallParams(action)});
250
- return result;`;
251
- implementations.push(customImpl);
252
- }
253
- else {
254
- implementations.push(`// TODO: Model ${modelName} not found for handler ${handler}`);
255
- }
256
- }
257
- }
258
- else if (parts.length === 2) {
259
- // Format: modelname:custommethod
260
- const [modelName, methodName] = parts;
261
- // Check if this handler applies to current model
262
- if (modelName.toLowerCase() === entityLower || modelName === entityName) {
263
- // Same model - this indicates a custom method should be implemented in this service
264
- implementations.push(`// TODO: Implement custom ${methodName} method for ${entityName}`);
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);
265
88
  }
266
89
  else {
267
- // Cross-model custom call
268
- const targetModel = (_b = moduleConfig.models) === null || _b === void 0 ? void 0 : _b.find(m => m.name.toLowerCase() === modelName.toLowerCase() || m.name === modelName);
269
- if (targetModel) {
270
- const targetServiceVar = `${targetModel.name.toLowerCase()}Service`;
271
- const customImpl = `// Cross-model call to ${targetModel.name}Service
272
- const result = await this.${targetServiceVar}.${methodName}(${this.getMethodCallParams(action)});
273
- return result;`;
274
- implementations.push(customImpl);
275
- }
276
- else {
277
- implementations.push(`// TODO: Model ${modelName} not found for handler ${handler}`);
278
- }
90
+ prevHandlerReturnType = modelName;
279
91
  }
280
92
  }
281
- else {
282
- implementations.push(`// TODO: Invalid handler format ${handler}. Use modelname:default:action or modelname:custommethod`);
283
- }
284
- }
285
- else {
286
- implementations.push(`// TODO: Invalid handler format ${handler}. Use modelname:default:action or modelname:custommethod`);
287
- }
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
+ });
288
107
  });
289
- return implementations.join('\n ');
290
- }
291
- extractFunctionName(filePath) {
292
- const parts = filePath.split('/');
293
- const fileName = parts[parts.length - 1];
294
- return fileName;
108
+ return contextMap;
295
109
  }
296
- getMethodCallParams(action) {
297
- switch (action) {
298
- case 'list':
299
- return 'page, limit';
300
- case 'get':
301
- return 'id';
302
- case 'create':
303
- return 'userData';
304
- case 'update':
305
- return 'id, updates';
306
- case 'delete':
307
- return 'id';
308
- default:
309
- return '/* custom params */';
310
- }
110
+ deriveInputType(contexts) {
111
+ const inputTypes = [...new Set(contexts.map(c => c.inputDtoType))];
112
+ return inputTypes.join(' | ');
311
113
  }
312
- sortFieldsByRequired(fields) {
313
- // Sort fields: required fields first, then optional fields
314
- // This must match the order used in domainModelGenerator
315
- return [...fields].sort((a, b) => {
316
- const aRequired = a.required !== false && !a.auto;
317
- const bRequired = b.required !== false && !b.auto;
318
- if (aRequired === bRequired) {
319
- return 0; // Keep original order if both have same required status
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);
320
123
  }
321
- return aRequired ? -1 : 1; // Required fields come first
322
124
  });
323
- }
324
- generateConstructorArgs(moduleConfig, entityName) {
325
- if (!moduleConfig.models || moduleConfig.models.length === 0) {
326
- return '';
327
- }
328
- // Find the correct model by entityName instead of always using first model
329
- const model = moduleConfig.models.find((m) => m.name === entityName) || moduleConfig.models[0];
330
- const entityLower = entityName.toLowerCase();
331
- // Sort fields to match the constructor parameter order
332
- const sortedFields = this.sortFieldsByRequired(model.fields);
333
- const args = [];
334
- sortedFields
335
- .filter(field => !field.auto && field.name !== 'id')
336
- .forEach(field => {
337
- // For relationship fields, reference the loaded object variable
338
- if (this.isRelationshipField(field)) {
339
- args.push(`${field.name}Object`);
125
+ const returnTypeParts = new Set();
126
+ contexts.forEach(c => {
127
+ if (c.isLast) {
128
+ returnTypeParts.add(c.useCaseReturnType);
340
129
  }
341
130
  else {
342
- args.push(`${entityLower}Data.${field.name}`);
131
+ returnTypeParts.add(modelName);
343
132
  }
344
133
  });
345
- return args.join(', ');
134
+ return {
135
+ inputType: inputTypes.join(' | '),
136
+ resultType: [...resultTypeParts].join(' | '),
137
+ returnType: [...returnTypeParts].join(' | ')
138
+ };
346
139
  }
347
- generateRelationshipLoading(moduleConfig, entityName) {
348
- if (!moduleConfig.models || moduleConfig.models.length === 0) {
349
- return '';
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');
350
146
  }
351
- const model = moduleConfig.models.find((m) => m.name === entityName) || moduleConfig.models[0];
352
- const entityLower = entityName.toLowerCase();
353
- const relationshipFields = model.fields.filter(f => this.isRelationshipField(f));
354
- if (relationshipFields.length === 0) {
355
- return '';
356
- }
357
- const loadingCode = relationshipFields.map(field => {
358
- const foreignKeyName = this.getForeignKeyFieldName(field);
359
- const relatedModel = field.type;
360
- const relatedModelLower = relatedModel.toLowerCase();
361
- const varName = `${field.name}Object`;
362
- if (field.required) {
363
- return `const ${varName} = await this.${relatedModelLower}Store.getById(${entityLower}Data.${foreignKeyName});
364
- if (!${varName}) {
365
- throw new Error('${relatedModel} not found with id ' + ${entityLower}Data.${foreignKeyName});
366
- }`;
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));
367
152
  }
368
- else {
369
- return `const ${varName} = ${entityLower}Data.${foreignKeyName}
370
- ? await this.${relatedModelLower}Store.getById(${entityLower}Data.${foreignKeyName})
371
- : null;`;
153
+ if (inputConfig.omit && inputConfig.omit.length > 0) {
154
+ fieldNames = fieldNames.filter(f => !inputConfig.omit.includes(f));
372
155
  }
373
- }).join('\n ');
374
- return ' // Load relationship objects\n ' + loadingCode;
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;
375
162
  }
376
- generateUpdateSetterCalls(moduleConfig, entityName, includeRelationshipLoading = false) {
377
- if (!moduleConfig.models || moduleConfig.models.length === 0) {
378
- return '';
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'));
379
167
  }
380
- // Find the correct model by entityName instead of always using first model
381
- const model = moduleConfig.models.find(m => m.name === entityName) || moduleConfig.models[0];
382
- const entityLower = entityName.toLowerCase();
383
- let code = '';
384
- // Add relationship loading if requested
385
- if (includeRelationshipLoading) {
386
- const relationshipLoading = this.generateRelationshipLoading(moduleConfig, entityName);
387
- if (relationshipLoading) {
388
- code = relationshipLoading + '\n ';
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
+ }
389
174
  }
390
175
  }
391
- const setterCalls = model.fields
392
- .filter(field => !field.auto && field.name !== 'id')
393
- .map(field => {
394
- // For relationship fields, set the loaded object
395
- if (this.isRelationshipField(field)) {
396
- const foreignKeyName = this.getForeignKeyFieldName(field);
397
- const methodName = `set${field.name.charAt(0).toUpperCase() + field.name.slice(1)}`;
398
- return `if (${entityLower}Data.${foreignKeyName} !== undefined) {
399
- existing${entityName}.${methodName}(${field.name}Object);
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}> {
182
+ const [items, total] = await Promise.all([
183
+ this.${storeName}.getPaginated(page, limit, ownerId),
184
+ this.${storeName}.count(ownerId)
185
+ ]);
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 };
192
+ }`;
193
+ }
194
+ generateGetHandler(modelName, storeName, entityLower) {
195
+ return ` async get(id: number): Promise<${modelName}> {
196
+ const ${entityLower} = await this.${storeName}.getById(id);
197
+ if (!${entityLower}) {
198
+ throw new Error('${modelName} not found');
199
+ }
200
+ return ${entityLower};
201
+ }`;
202
+ }
203
+ generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo, inputType, dtoFields) {
204
+ const firstArgField = childInfo ? childInfo.parentIdField : 'ownerId';
205
+ const fields = Object.entries(aggregateConfig.fields)
206
+ .filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id')
207
+ .sort((a, b) => {
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;
212
+ if (aRequired === bRequired)
213
+ return 0;
214
+ return aRequired ? -1 : 1;
215
+ });
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(', ');
229
+ const constructorArgs = `input.${firstArgField}, ${fieldArgs}`;
230
+ return ` async create(input: ${inputType}): Promise<${modelName}> {
231
+ const ${entityLower} = new ${modelName}(0, ${constructorArgs});
232
+ return await this.${storeName}.insert(${entityLower});
233
+ }`;
234
+ }
235
+ generateUpdateHandler(modelName, storeName, aggregateConfig, inputType, dtoFields) {
236
+ const setterCalls = Object.entries(aggregateConfig.fields)
237
+ .filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id' && dtoFields.has(fieldName))
238
+ .map(([fieldName, fieldConfig]) => {
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);
400
243
  }`;
401
244
  }
402
- else {
403
- const methodName = `set${field.name.charAt(0).toUpperCase() + field.name.slice(1)}`;
404
- return `if (${entityLower}Data.${field.name} !== undefined) {
405
- existing${entityName}.${methodName}(${entityLower}Data.${field.name});
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});
406
249
  }`;
407
250
  }
251
+ return ` if (input.${fieldName} !== undefined) {
252
+ existing${modelName}.${methodName}(input.${fieldName});
253
+ }`;
408
254
  })
409
- .join('\n ');
410
- return code + setterCalls;
255
+ .join('\n');
256
+ return ` async update(id: number, input: ${inputType}): Promise<${modelName}> {
257
+ const existing${modelName} = await this.${storeName}.getById(id);
258
+ if (!existing${modelName}) {
259
+ throw new Error('${modelName} not found');
411
260
  }
412
- replaceTemplateVars(template, variables) {
413
- let result = template;
414
- Object.entries(variables).forEach(([key, value]) => {
415
- const regex = new RegExp(`{{${key}}}`, 'g');
416
- result = result.replace(regex, String(value));
417
- });
418
- return result;
261
+
262
+ ${setterCalls}
263
+
264
+ return await this.${storeName}.update(id, existing${modelName});
265
+ }`;
419
266
  }
420
- getServiceMethodName(action, entityName, handlers) {
421
- const entityLower = entityName.toLowerCase();
422
- // Find handler that applies to this model
423
- const relevantHandler = handlers.find(h => {
424
- const parts = h.split(':');
425
- if (parts.length === 3 && parts[1] === 'default') {
426
- const [modelName] = parts;
427
- return modelName.toLowerCase() === entityLower || modelName === entityName;
428
- }
429
- else if (parts.length === 2) {
430
- const [modelName] = parts;
431
- return modelName.toLowerCase() === entityLower || modelName === entityName;
432
- }
433
- return false;
434
- });
435
- if (relevantHandler) {
436
- const parts = relevantHandler.split(':');
437
- if (parts.length === 3 && parts[1] === 'default') {
438
- // Format: modelname:default:action -> use action name as method
439
- return parts[2];
440
- }
441
- else if (parts.length === 2) {
442
- // Format: modelname:custommethod -> use custom method name
443
- return parts[1];
444
- }
445
- }
446
- // Fallback to action name
447
- return action;
267
+ generateDeleteHandler(modelName, storeName) {
268
+ return ` async delete(id: number): Promise<{ success: boolean; message: string }> {
269
+ const success = await this.${storeName}.softDelete(id);
270
+ if (!success) {
271
+ throw new Error('${modelName} not found or could not be deleted');
448
272
  }
449
- generateHandlerMethod(handler, entityName, roles, hasPermissions, moduleConfig) {
450
- const parts = handler.split(':');
451
- if (parts.length < 2) {
452
- return '';
453
- }
454
- let methodName;
455
- let isDefault = false;
456
- let actionName = '';
457
- if (parts.length === 3 && parts[1] === 'default') {
458
- // Format: modelname:default:action
459
- actionName = parts[2];
460
- methodName = actionName === 'getById' ? 'get' : actionName; // Use 'get' instead of 'getById'
461
- isDefault = true;
462
- }
463
- else if (parts.length === 2) {
464
- // Format: modelname:custommethod
465
- methodName = parts[1];
466
- isDefault = false;
273
+ return { success: true, message: '${modelName} deleted successfully' };
274
+ }`;
275
+ }
276
+ generateDefaultHandlerMethod(modelName, actionName, aggregateConfig, childInfo, inputType, dtoFields, listConfig) {
277
+ var _a;
278
+ const entityLower = modelName.toLowerCase();
279
+ const storeName = `${entityLower}Store`;
280
+ switch (actionName) {
281
+ case 'list':
282
+ return this.generateListHandler(modelName, storeName, (_a = listConfig === null || listConfig === void 0 ? void 0 : listConfig.hasPagination) !== null && _a !== void 0 ? _a : true);
283
+ case 'get':
284
+ return this.generateGetHandler(modelName, storeName, entityLower);
285
+ case 'create':
286
+ return this.generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo, inputType, dtoFields);
287
+ case 'update':
288
+ return this.generateUpdateHandler(modelName, storeName, aggregateConfig, inputType, dtoFields);
289
+ case 'delete':
290
+ return this.generateDeleteHandler(modelName, storeName);
291
+ default:
292
+ return ` async ${actionName}(input: ${inputType}): Promise<${modelName}> {
293
+ // TODO: Implement default ${actionName} handler
294
+ throw new Error('Not implemented');
295
+ }`;
467
296
  }
468
- else {
297
+ }
298
+ generateCustomHandlerMethod(modelName, handlerName, resultType, inputType, returnType) {
299
+ return ` async ${handlerName}(result: ${resultType}, input: ${inputType}): Promise<${returnType}> {
300
+ // TODO: Implement custom ${handlerName} handler
301
+ // This method receives the result from the previous handler (or null if first)
302
+ // and the input context
303
+ return result;
304
+ }`;
305
+ }
306
+ collectHandlers(useCases) {
307
+ const handlers = new Set();
308
+ Object.values(useCases).forEach(useCaseConfig => {
309
+ useCaseConfig.handlers.forEach(handler => {
310
+ handlers.add(handler);
311
+ });
312
+ });
313
+ return handlers;
314
+ }
315
+ generateListByParentMethod(modelName, childInfo) {
316
+ if (!childInfo)
469
317
  return '';
470
- }
471
- const entityLower = entityName.toLowerCase();
472
- // Generate method parameters based on whether it's default or custom
473
- let methodParams;
474
- let returnType;
475
- let methodImplementation;
476
- if (isDefault) {
477
- // For default handlers, use standard parameters
478
- methodParams = this.generateMethodParams(actionName, entityName);
479
- returnType = this.generateReturnType(actionName, entityName);
480
- // Get the default implementation template
481
- const template = serviceTemplates_1.serviceTemplates.defaultImplementations[actionName];
482
- if (template) {
483
- methodImplementation = template
484
- .replace(/{{ENTITY_NAME}}/g, entityName)
485
- .replace(/{{ENTITY_LOWER}}/g, entityLower);
486
- // Handle constructor args for create action
487
- if (actionName === 'create') {
488
- const relationshipLoading = this.generateRelationshipLoading(moduleConfig, entityName);
489
- const constructorArgs = this.generateConstructorArgs(moduleConfig, entityName);
490
- if (relationshipLoading) {
491
- // Insert relationship loading before the entity creation
492
- methodImplementation = relationshipLoading + '\n ' + methodImplementation;
493
- }
494
- methodImplementation = methodImplementation.replace(/{{CONSTRUCTOR_ARGS}}/g, constructorArgs);
495
- }
496
- // Handle setter calls for update action
497
- if (actionName === 'update') {
498
- const setterCalls = this.generateUpdateSetterCalls(moduleConfig, entityName, true);
499
- methodImplementation = methodImplementation.replace(/{{UPDATE_SETTER_CALLS}}/g, setterCalls);
318
+ const storeVar = `${modelName.toLowerCase()}Store`;
319
+ return `
320
+ async listByParent(parentId: number): Promise<${modelName}[]> {
321
+ return await this.${storeVar}.getByParentId(parentId);
322
+ }`;
323
+ }
324
+ generateGetResourceOwnerMethod(modelName) {
325
+ const storeVar = `${modelName.toLowerCase()}Store`;
326
+ return `
327
+ /**
328
+ * Get the owner ID of a resource by its ID.
329
+ * Used for pre-mutation authorization checks.
330
+ */
331
+ async getResourceOwner(id: number): Promise<number | null> {
332
+ return await this.${storeVar}.getResourceOwner(id);
333
+ }`;
334
+ }
335
+ generateService(modelName, useCases, aggregateConfig, childInfo) {
336
+ const serviceName = `${modelName}Service`;
337
+ const storeName = `${modelName}Store`;
338
+ const storeVar = `${modelName.toLowerCase()}Store`;
339
+ // Build handler-to-context map for type inference
340
+ const handlerContextMap = this.buildHandlerContextMap(modelName, useCases);
341
+ // Collect all unique handlers
342
+ const handlers = this.collectHandlers(useCases);
343
+ // Collect DTO types needed for imports
344
+ const dtoTypes = new Set();
345
+ const enumTypeNames = new Set();
346
+ // Generate methods for each handler
347
+ const methods = [];
348
+ handlers.forEach(handler => {
349
+ var _a, _b;
350
+ const contexts = handlerContextMap.get(handler) || [];
351
+ if (handler.startsWith('default:')) {
352
+ const actionName = handler.replace('default:', '');
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));
500
357
  }
501
- // Special-case: list action with only owner role → fetch by userId
502
- if (actionName === 'list') {
503
- const onlyOwner = roles.length > 0 && roles.every(r => r === 'owner');
504
- if (onlyOwner) {
505
- methodImplementation = `const ${entityLower}s = await this.${entityLower}Store.getAllByUserId(user.id, page, limit);\n return ${entityLower}s;`;
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
+ }
506
363
  }
507
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));
508
369
  }
509
370
  else {
510
- methodImplementation = `// TODO: Implement default ${actionName} method for ${entityName}`;
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));
511
374
  }
512
- }
513
- else {
514
- // For custom handlers, use result and context parameters
515
- methodParams = 'result: any, context: any';
516
- returnType = 'any';
517
- methodImplementation = `// TODO: Implement custom ${methodName} method for ${entityName}`;
518
- }
519
- const permissionCheck = isDefault ? this.generatePermissionCheck(actionName, roles, entityName) : '';
520
- const hasUserParam = roles.length > 0 && !roles.includes('all');
521
- const variables = {
522
- METHOD_NAME: methodName,
523
- METHOD_PARAMS: methodParams,
524
- USER_PARAM: hasUserParam ? ', user: AuthenticatedUser' : '',
525
- RETURN_TYPE: returnType,
526
- PERMISSION_CHECK: permissionCheck,
527
- METHOD_IMPLEMENTATION: methodImplementation
528
- };
529
- return this.replaceTemplateVars(serviceTemplates_1.serviceTemplates.serviceMethod, variables);
530
- }
531
- generateServiceForModel(model, moduleName, moduleConfig, hasGlobalPermissions) {
532
- if (!moduleConfig.actions) {
533
- return '';
534
- }
535
- const entityName = model.name;
536
- const entityLower = entityName.toLowerCase();
537
- const actionPermissions = this.getActionPermissions(moduleName, moduleConfig);
538
- const hasPermissions = !!(hasGlobalPermissions && moduleConfig.permissions && moduleConfig.permissions.length > 0);
539
- // Collect all unique handlers for this model
540
- const modelHandlers = new Set();
541
- Object.entries(moduleConfig.actions || {}).forEach(([action, actionConfig]) => {
542
- const handlers = actionConfig.handlers || [];
543
- handlers.forEach(handler => {
544
- // Only include handlers that apply to this model
545
- if (handler.startsWith(`${entityLower}:`) || handler.startsWith(`${entityName}:`)) {
546
- modelHandlers.add(handler);
547
- }
548
- });
549
- });
550
- // Generate a method for each unique handler
551
- const serviceMethods = Array.from(modelHandlers)
552
- .map(handler => {
553
- // Determine which actions use this handler to get permissions
554
- const actionsUsingHandler = Object.entries(moduleConfig.actions || {})
555
- .filter(([, actionConfig]) => (actionConfig.handlers || []).includes(handler))
556
- .map(([action]) => action);
557
- // Use permissions from the first action that uses this handler
558
- const firstAction = actionsUsingHandler[0];
559
- const roles = firstAction ? (actionPermissions[firstAction] || []) : [];
560
- return this.generateHandlerMethod(handler, entityName, roles, hasPermissions, moduleConfig);
561
- })
562
- .filter(method => method) // Remove empty methods
563
- .join('\n\n');
564
- // Add foreign store constructor params
565
- const foreignStoreParams = this.generateForeignStoreConstructorParams(model);
566
- const serviceClass = this.replaceTemplateVars(serviceTemplates_1.serviceTemplates.serviceClass, {
567
- ENTITY_NAME: entityName,
568
- ENTITY_LOWER: entityLower,
569
- AUTH_SERVICE_PARAM: foreignStoreParams,
570
- SERVICE_METHODS: serviceMethods
571
375
  });
572
- const customImports = this.generateCustomImports(moduleConfig);
573
- const foreignStoreImports = this.generateForeignStoreImports(model);
574
- return this.replaceTemplateVars(serviceTemplates_1.serviceFileTemplate, {
575
- ENTITY_NAME: entityName,
576
- PERMISSIONS_IMPORT: hasPermissions ? "\nimport type { AuthenticatedUser } from '@currentjs/router';" : '',
577
- CUSTOM_IMPORTS: customImports + foreignStoreImports,
578
- SERVICE_CLASS: serviceClass
579
- });
580
- }
581
- generateService(moduleName, moduleConfig, hasGlobalPermissions) {
582
- // Legacy method for backward compatibility - use first model
583
- if (!moduleConfig.models || moduleConfig.models.length === 0) {
584
- return '';
376
+ const listByParentMethod = this.generateListByParentMethod(modelName, childInfo);
377
+ if (listByParentMethod) {
378
+ methods.push(listByParentMethod);
585
379
  }
586
- return this.generateServiceForModel(moduleConfig.models[0], moduleName, moduleConfig, hasGlobalPermissions);
587
- }
588
- generateForeignStoreImports(model) {
589
- const relationshipFields = model.fields.filter(f => this.isRelationshipField(f));
590
- if (relationshipFields.length === 0) {
591
- return '';
380
+ const getResourceOwnerMethod = this.generateGetResourceOwnerMethod(modelName);
381
+ if (getResourceOwnerMethod) {
382
+ methods.push(getResourceOwnerMethod);
592
383
  }
593
- const imports = relationshipFields.map(field => {
594
- const relatedModel = field.type;
595
- return `import { ${relatedModel}Store } from '../../infrastructure/stores/${relatedModel}Store';`;
596
- });
597
- return '\n' + imports.join('\n');
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(', ');
397
+ return `import { Injectable } from '../../../../system';
398
+ import { ${entityImports} } from '../../domain/entities/${modelName}';${aggRefImportStr}${dtoImportStr}
399
+ import { ${storeName} } from '../../infrastructure/stores/${storeName}';
400
+
401
+ /**
402
+ * Service layer for ${modelName}
403
+ * Contains business logic handlers that can be composed in use cases
404
+ */
405
+ @Injectable()
406
+ export class ${serviceName} {
407
+ constructor(
408
+ private ${storeVar}: ${storeName}
409
+ ) {}
410
+
411
+ ${methods.join('\n\n')}
412
+ }`;
598
413
  }
599
- generateForeignStoreConstructorParams(model) {
600
- const relationshipFields = model.fields.filter(f => this.isRelationshipField(f));
601
- if (relationshipFields.length === 0) {
602
- return '';
414
+ generateFromConfig(config) {
415
+ const result = {};
416
+ // Collect all aggregates
417
+ if (config.domain.aggregates) {
418
+ Object.entries(config.domain.aggregates).forEach(([name, aggConfig]) => {
419
+ this.availableAggregates.set(name, aggConfig);
420
+ });
603
421
  }
604
- const params = relationshipFields.map(field => {
605
- const relatedModel = field.type;
606
- const relatedModelLower = relatedModel.toLowerCase();
607
- return `,\n private ${relatedModelLower}Store: ${relatedModel}Store`;
608
- });
609
- return params.join('');
610
- }
611
- generateCustomImports(moduleConfig) {
612
- if (!moduleConfig.actions)
613
- return '';
614
- const imports = [];
615
- Object.values(moduleConfig.actions).forEach(actionConfig => {
616
- if (actionConfig.handlers) {
617
- actionConfig.handlers.forEach(handler => {
618
- if (handler.startsWith(constants_1.PATH_PATTERNS.MODULES_DIRECTIVE)) {
619
- const functionName = this.extractFunctionName(handler);
620
- const importPath = handler.replace(constants_1.PATH_PATTERNS.MODULES_DIRECTIVE, '../../');
621
- imports.push(`import ${functionName} from '${importPath}';`);
622
- }
623
- });
422
+ const childEntityMap = (0, childEntityUtils_1.buildChildEntityMap)(config);
423
+ // Generate a Service file for each model
424
+ Object.entries(config.useCases).forEach(([modelName, useCases]) => {
425
+ const aggregateConfig = this.availableAggregates.get(modelName);
426
+ if (!aggregateConfig) {
427
+ console.warn(`Warning: No aggregate found for model ${modelName}`);
428
+ return;
624
429
  }
430
+ const childInfo = childEntityMap.get(modelName);
431
+ result[modelName] = this.generateService(modelName, useCases, aggregateConfig, childInfo);
625
432
  });
626
- return imports.length > 0 ? '\n' + imports.join('\n') : '';
433
+ return result;
627
434
  }
628
435
  generateFromYamlFile(yamlFilePath) {
629
436
  const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
630
437
  const config = (0, yaml_1.parse)(yamlContent);
631
- const result = {};
632
- const hasGlobalPermissions = this.hasPermissions(config);
633
- if (config.modules) {
634
- Object.entries(config.modules).forEach(([moduleName, moduleConfig]) => {
635
- if (moduleConfig.models && moduleConfig.models.length > 0) {
636
- // Set available models for relationship detection
637
- this.setAvailableModels(moduleConfig.models);
638
- // Generate a service for each model
639
- moduleConfig.models.forEach(model => {
640
- const serviceCode = this.generateServiceForModel(model, moduleName, moduleConfig, hasGlobalPermissions);
641
- if (serviceCode) {
642
- result[model.name] = serviceCode;
643
- }
644
- });
645
- }
646
- });
438
+ if (!(0, configTypes_1.isValidModuleConfig)(config)) {
439
+ throw new Error('Configuration does not match new module format. Expected useCases structure.');
647
440
  }
648
- else {
649
- const moduleName = 'Module';
650
- const moduleConfig = config;
651
- if (moduleConfig.models && moduleConfig.models.length > 0) {
652
- // Set available models for relationship detection
653
- this.setAvailableModels(moduleConfig.models);
654
- // Generate a service for each model
655
- moduleConfig.models.forEach(model => {
656
- const serviceCode = this.generateServiceForModel(model, moduleName, moduleConfig, hasGlobalPermissions);
657
- if (serviceCode) {
658
- result[model.name] = serviceCode;
659
- }
660
- });
661
- }
662
- }
663
- return result;
441
+ return this.generateFromConfig(config);
664
442
  }
665
- async generateAndSaveFiles(yamlFilePath = constants_1.COMMON_FILES.APP_YAML, outputDir = 'application', opts) {
666
- const services = this.generateFromYamlFile(yamlFilePath);
667
- const servicesDir = path.join(outputDir, 'services');
443
+ async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
444
+ const servicesByModel = this.generateFromYamlFile(yamlFilePath);
445
+ const servicesDir = path.join(moduleDir, 'application', 'services');
668
446
  fs.mkdirSync(servicesDir, { recursive: true });
669
- for (const [moduleName, serviceCode] of Object.entries(services)) {
670
- const fileName = `${moduleName}Service.ts`;
671
- const filePath = path.join(servicesDir, fileName);
447
+ for (const [modelName, code] of Object.entries(servicesByModel)) {
448
+ const filePath = path.join(servicesDir, `${modelName}Service.ts`);
672
449
  // eslint-disable-next-line no-await-in-loop
673
- await (0, generationRegistry_1.writeGeneratedFile)(filePath, serviceCode, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
450
+ await (0, generationRegistry_1.writeGeneratedFile)(filePath, code, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
674
451
  }
675
452
  // eslint-disable-next-line no-console
676
453
  console.log('\n' + colors_1.colors.green('Service files generated successfully!') + '\n');