@currentjs/gen 0.1.2 → 0.2.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.
- package/CHANGELOG.md +30 -0
- package/README.md +327 -251
- package/dist/commands/createModule.js +7 -5
- package/dist/generators/controllerGenerator.d.ts +3 -0
- package/dist/generators/controllerGenerator.js +141 -27
- package/dist/generators/serviceGenerator.d.ts +3 -1
- package/dist/generators/serviceGenerator.js +223 -50
- package/dist/generators/templates/appTemplates.d.ts +1 -1
- package/dist/generators/templates/appTemplates.js +71 -38
- package/dist/generators/templates/serviceTemplates.d.ts +1 -0
- package/dist/generators/templates/serviceTemplates.js +5 -0
- package/howto.md +48 -29
- package/package.json +1 -1
|
@@ -50,6 +50,7 @@ function moduleYamlTemplate(moduleName) {
|
|
|
50
50
|
],
|
|
51
51
|
api: {
|
|
52
52
|
prefix: `/api/${lower}`,
|
|
53
|
+
model: entityName,
|
|
53
54
|
endpoints: [
|
|
54
55
|
{ method: 'GET', path: '/', action: 'list' },
|
|
55
56
|
{ method: 'GET', path: '/:id', action: 'get' },
|
|
@@ -60,6 +61,7 @@ function moduleYamlTemplate(moduleName) {
|
|
|
60
61
|
},
|
|
61
62
|
routes: {
|
|
62
63
|
prefix: `/${lower}`,
|
|
64
|
+
model: entityName,
|
|
63
65
|
strategy: ['back', 'toast'],
|
|
64
66
|
endpoints: [
|
|
65
67
|
//bug: the order of the endpoints is important (to fix it in the router)
|
|
@@ -70,11 +72,11 @@ function moduleYamlTemplate(moduleName) {
|
|
|
70
72
|
]
|
|
71
73
|
},
|
|
72
74
|
actions: {
|
|
73
|
-
list: { handlers: [
|
|
74
|
-
get: { handlers: [
|
|
75
|
-
create: { handlers: [
|
|
76
|
-
update: { handlers: [
|
|
77
|
-
delete: { handlers: [
|
|
75
|
+
list: { handlers: [`${entityName}:default:list`] },
|
|
76
|
+
get: { handlers: [`${entityName}:default:get`] },
|
|
77
|
+
create: { handlers: [`${entityName}:default:create`] },
|
|
78
|
+
update: { handlers: [`${entityName}:default:update`] },
|
|
79
|
+
delete: { handlers: [`${entityName}:default:delete`] }
|
|
78
80
|
},
|
|
79
81
|
permissions: []
|
|
80
82
|
};
|
|
@@ -5,12 +5,15 @@ export declare class ControllerGenerator {
|
|
|
5
5
|
private needsUserParam;
|
|
6
6
|
private getHttpMethodName;
|
|
7
7
|
private getHttpDecorator;
|
|
8
|
+
private getMethodCallParams;
|
|
9
|
+
private generateParameterExtractions;
|
|
8
10
|
private generateMethodImplementation;
|
|
9
11
|
private replaceTemplateVars;
|
|
10
12
|
private generateControllerMethod;
|
|
11
13
|
private getWebMethodName;
|
|
12
14
|
private getPathSuffix;
|
|
13
15
|
private getReturnType;
|
|
16
|
+
private generateControllerForModel;
|
|
14
17
|
private generateController;
|
|
15
18
|
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
16
19
|
generateAndSaveFiles(yamlFilePath?: string, outputDir?: string, opts?: {
|
|
@@ -91,7 +91,92 @@ class ControllerGenerator {
|
|
|
91
91
|
return 'Get';
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
getMethodCallParams(action, entityName) {
|
|
95
|
+
const dtoType = entityName ? `${entityName}DTO` : 'any';
|
|
96
|
+
switch (action) {
|
|
97
|
+
case 'list':
|
|
98
|
+
return 'page, limit';
|
|
99
|
+
case 'get':
|
|
100
|
+
case 'getById':
|
|
101
|
+
return 'id';
|
|
102
|
+
case 'create':
|
|
103
|
+
return `context.request.body as ${dtoType}`;
|
|
104
|
+
case 'update':
|
|
105
|
+
return `id, context.request.body as ${dtoType}`;
|
|
106
|
+
case 'delete':
|
|
107
|
+
return 'id';
|
|
108
|
+
default:
|
|
109
|
+
return '/* custom params */';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
generateParameterExtractions(action) {
|
|
113
|
+
switch (action) {
|
|
114
|
+
case 'list':
|
|
115
|
+
return `
|
|
116
|
+
// Extract pagination from URL parameters
|
|
117
|
+
const page = parseInt(context.request.parameters.page as string) || 1;
|
|
118
|
+
const limit = parseInt(context.request.parameters.limit as string) || 10;`;
|
|
119
|
+
case 'get':
|
|
120
|
+
case 'getById':
|
|
121
|
+
case 'update':
|
|
122
|
+
case 'delete':
|
|
123
|
+
return `
|
|
124
|
+
const id = parseInt(context.request.parameters.id as string);
|
|
125
|
+
if (isNaN(id)) {
|
|
126
|
+
throw new Error('Invalid ID parameter');
|
|
127
|
+
}`;
|
|
128
|
+
case 'create':
|
|
129
|
+
return ''; // No additional parameter extraction needed for create
|
|
130
|
+
default:
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
generateMethodImplementation(action, entityName, hasUserParam, actions) {
|
|
135
|
+
// Check if we have action handlers
|
|
136
|
+
if (actions && actions[action] && actions[action].handlers) {
|
|
137
|
+
const handlers = actions[action].handlers;
|
|
138
|
+
const entityLower = entityName.toLowerCase();
|
|
139
|
+
// Filter handlers that apply to this model
|
|
140
|
+
const modelHandlers = handlers.filter(h => h.startsWith(`${entityLower}:`) || h.startsWith(`${entityName}:`));
|
|
141
|
+
if (modelHandlers.length === 0) {
|
|
142
|
+
return `// TODO: No valid handlers found for action ${action}. Use ${entityName}:default:${action} or ${entityName}:customMethodName format.`;
|
|
143
|
+
}
|
|
144
|
+
const userExtraction = hasUserParam ? controllerTemplates_1.controllerTemplates.userExtraction : '';
|
|
145
|
+
const userParam = hasUserParam ? ', user' : '';
|
|
146
|
+
// Generate parameter extraction based on action type
|
|
147
|
+
const paramExtractions = this.generateParameterExtractions(action);
|
|
148
|
+
// Generate step-by-step calls for each handler
|
|
149
|
+
const handlerCalls = modelHandlers.map((handler, index) => {
|
|
150
|
+
const parts = handler.split(':');
|
|
151
|
+
let methodName;
|
|
152
|
+
let params;
|
|
153
|
+
if (parts.length === 3 && parts[1] === 'default') {
|
|
154
|
+
// Format: modelname:default:action
|
|
155
|
+
const actionName = parts[2];
|
|
156
|
+
methodName = actionName === 'getById' ? 'get' : actionName; // Use 'get' instead of 'getById'
|
|
157
|
+
// For default handlers, use extracted parameters
|
|
158
|
+
params = this.getMethodCallParams(actionName, entityName) + userParam;
|
|
159
|
+
}
|
|
160
|
+
else if (parts.length === 2) {
|
|
161
|
+
// Format: modelname:custommethod
|
|
162
|
+
methodName = parts[1];
|
|
163
|
+
// For custom handlers, pass result from previous handler (or null) and context
|
|
164
|
+
const prevResult = index === 0 ? 'null' : `result${index}`;
|
|
165
|
+
params = `${prevResult}, context${userParam}`;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
return `// Invalid handler format: ${handler}`;
|
|
169
|
+
}
|
|
170
|
+
const isLast = index === modelHandlers.length - 1;
|
|
171
|
+
const resultVar = isLast ? 'result' : `result${index + 1}`;
|
|
172
|
+
return `const ${resultVar} = await this.${entityLower}Service.${methodName}(${params});`;
|
|
173
|
+
}).join('\n ');
|
|
174
|
+
// For multiple handlers, return the last result
|
|
175
|
+
const returnStatement = '\n return result;';
|
|
176
|
+
return `${userExtraction}${paramExtractions}
|
|
177
|
+
${handlerCalls}${returnStatement}`;
|
|
178
|
+
}
|
|
179
|
+
// Fallback to existing template lookup
|
|
95
180
|
const template = controllerTemplates_1.controllerTemplates.methodImplementations[action];
|
|
96
181
|
if (!template) {
|
|
97
182
|
return ` // TODO: Implement ${action} method\n return {} as any;`;
|
|
@@ -112,11 +197,11 @@ class ControllerGenerator {
|
|
|
112
197
|
});
|
|
113
198
|
return result;
|
|
114
199
|
}
|
|
115
|
-
generateControllerMethod(endpoint, entityName, roles, hasPermissions, kind) {
|
|
200
|
+
generateControllerMethod(endpoint, entityName, roles, hasPermissions, kind, actions) {
|
|
116
201
|
const methodName = kind === 'web' ? this.getWebMethodName(endpoint) : this.getHttpMethodName(endpoint.method, endpoint.action);
|
|
117
202
|
const httpDecorator = kind === 'web' ? 'Get' : this.getHttpDecorator(endpoint.method);
|
|
118
203
|
const needsUser = this.needsUserParam(roles);
|
|
119
|
-
const methodImplementation = this.generateMethodImplementation(endpoint.action, entityName, needsUser);
|
|
204
|
+
const methodImplementation = this.generateMethodImplementation(endpoint.action, entityName, needsUser, actions);
|
|
120
205
|
const returnType = this.getReturnType(endpoint.action, entityName);
|
|
121
206
|
const renderDecorator = kind === 'web' && endpoint.view
|
|
122
207
|
? `\n @Render("${endpoint.view}"${endpoint.layout ? `, "${endpoint.layout}"` : ', "main_view"'})`
|
|
@@ -169,17 +254,27 @@ class ControllerGenerator {
|
|
|
169
254
|
return 'any';
|
|
170
255
|
}
|
|
171
256
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return '';
|
|
175
|
-
}
|
|
257
|
+
generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, kind) {
|
|
258
|
+
var _a;
|
|
176
259
|
const isApi = kind === 'api';
|
|
177
260
|
const cfgRaw = isApi ? moduleConfig.api : moduleConfig.routes;
|
|
178
261
|
let cfg = cfgRaw;
|
|
262
|
+
const entityName = model.name;
|
|
263
|
+
const entityLower = entityName.toLowerCase();
|
|
264
|
+
// Determine if we should generate a controller for this model
|
|
265
|
+
const configModel = (cfg === null || cfg === void 0 ? void 0 : cfg.model) || (moduleConfig.models && moduleConfig.models[0] ? moduleConfig.models[0].name : null);
|
|
266
|
+
const topLevelMatches = !configModel || configModel === entityName || configModel.toLowerCase() === entityLower;
|
|
267
|
+
// Also check if any endpoints specifically target this model
|
|
268
|
+
const hasEndpointForThisModel = ((_a = cfg === null || cfg === void 0 ? void 0 : cfg.endpoints) === null || _a === void 0 ? void 0 : _a.some(endpoint => {
|
|
269
|
+
const endpointModel = endpoint.model || configModel;
|
|
270
|
+
return endpointModel === entityName || (endpointModel === null || endpointModel === void 0 ? void 0 : endpointModel.toLowerCase()) === entityLower;
|
|
271
|
+
})) || false;
|
|
272
|
+
const shouldGenerateForThisModel = topLevelMatches || hasEndpointForThisModel;
|
|
273
|
+
if (!shouldGenerateForThisModel) {
|
|
274
|
+
return '';
|
|
275
|
+
}
|
|
179
276
|
if (!isApi) {
|
|
180
277
|
// Ensure sensible defaults for Web routes: list, detail, create (empty), edit (get)
|
|
181
|
-
const entityName = moduleConfig.models[0].name;
|
|
182
|
-
const entityLower = entityName.toLowerCase();
|
|
183
278
|
if (!cfgRaw || !cfgRaw.endpoints || cfgRaw.endpoints.length === 0) {
|
|
184
279
|
cfg = {
|
|
185
280
|
prefix: `/${entityLower}`,
|
|
@@ -201,19 +296,22 @@ class ControllerGenerator {
|
|
|
201
296
|
}
|
|
202
297
|
if (!cfg)
|
|
203
298
|
return '';
|
|
204
|
-
const entityName = moduleConfig.models[0].name;
|
|
205
|
-
const entityLower = entityName.toLowerCase();
|
|
206
299
|
const controllerBase = (cfg.prefix || `/${isApi ? 'api/' : ''}${entityLower}`).replace(/\/$/, '');
|
|
207
300
|
const actionPermissions = this.getActionPermissions(moduleName, moduleConfig);
|
|
208
301
|
const hasPermissions = hasGlobalPermissions && !!(moduleConfig.permissions && moduleConfig.permissions.length > 0);
|
|
209
|
-
|
|
302
|
+
// Filter endpoints that apply to this model
|
|
303
|
+
const modelEndpoints = (cfg.endpoints || []).filter(endpoint => {
|
|
304
|
+
const endpointModel = endpoint.model || (cfg === null || cfg === void 0 ? void 0 : cfg.model) || (moduleConfig.models && moduleConfig.models[0] ? moduleConfig.models[0].name : null);
|
|
305
|
+
return endpointModel === entityName || (endpointModel === null || endpointModel === void 0 ? void 0 : endpointModel.toLowerCase()) === entityLower;
|
|
306
|
+
});
|
|
307
|
+
const controllerMethods = modelEndpoints
|
|
210
308
|
.filter(endpoint => {
|
|
211
309
|
const roles = actionPermissions[endpoint.action] || [];
|
|
212
310
|
return this.shouldGenerateMethod(endpoint.action, roles);
|
|
213
311
|
})
|
|
214
312
|
.map(endpoint => {
|
|
215
313
|
const roles = actionPermissions[endpoint.action] || [];
|
|
216
|
-
return this.generateControllerMethod(endpoint, entityName, roles, hasPermissions, kind);
|
|
314
|
+
return this.generateControllerMethod(endpoint, entityName, roles, hasPermissions, kind, moduleConfig.actions);
|
|
217
315
|
})
|
|
218
316
|
.join('\n\n');
|
|
219
317
|
const controllerClass = this.replaceTemplateVars(controllerTemplates_1.controllerTemplates.controllerClass, {
|
|
@@ -229,6 +327,13 @@ class ControllerGenerator {
|
|
|
229
327
|
CONTROLLER_CLASS: controllerClass
|
|
230
328
|
});
|
|
231
329
|
}
|
|
330
|
+
generateController(moduleName, moduleConfig, hasGlobalPermissions, kind) {
|
|
331
|
+
// Legacy method for backward compatibility - use first model
|
|
332
|
+
if (!moduleConfig.models || moduleConfig.models.length === 0) {
|
|
333
|
+
return '';
|
|
334
|
+
}
|
|
335
|
+
return this.generateControllerForModel(moduleConfig.models[0], moduleName, moduleConfig, hasGlobalPermissions, kind);
|
|
336
|
+
}
|
|
232
337
|
generateFromYamlFile(yamlFilePath) {
|
|
233
338
|
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
234
339
|
const config = (0, yaml_1.parse)(yamlContent);
|
|
@@ -236,24 +341,33 @@ class ControllerGenerator {
|
|
|
236
341
|
const hasGlobalPermissions = this.hasPermissions(config);
|
|
237
342
|
if (config.modules) {
|
|
238
343
|
Object.entries(config.modules).forEach(([moduleName, moduleConfig]) => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
344
|
+
if (moduleConfig.models && moduleConfig.models.length > 0) {
|
|
345
|
+
// Generate controllers for each model
|
|
346
|
+
moduleConfig.models.forEach(model => {
|
|
347
|
+
const apiControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'api');
|
|
348
|
+
if (apiControllerCode)
|
|
349
|
+
result[`${model.name}Api`] = apiControllerCode;
|
|
350
|
+
const webControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'web');
|
|
351
|
+
if (webControllerCode)
|
|
352
|
+
result[`${model.name}Web`] = webControllerCode;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
246
355
|
});
|
|
247
356
|
}
|
|
248
357
|
else {
|
|
358
|
+
const moduleName = 'Module';
|
|
249
359
|
const moduleConfig = config;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
360
|
+
if (moduleConfig.models && moduleConfig.models.length > 0) {
|
|
361
|
+
// Generate controllers for each model
|
|
362
|
+
moduleConfig.models.forEach(model => {
|
|
363
|
+
const apiControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'api');
|
|
364
|
+
if (apiControllerCode)
|
|
365
|
+
result[`${model.name}Api`] = apiControllerCode;
|
|
366
|
+
const webControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'web');
|
|
367
|
+
if (webControllerCode)
|
|
368
|
+
result[`${model.name}Web`] = webControllerCode;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
257
371
|
}
|
|
258
372
|
return result;
|
|
259
373
|
}
|
|
@@ -27,7 +27,9 @@ export declare class ServiceGenerator {
|
|
|
27
27
|
private generateConstructorArgs;
|
|
28
28
|
private generateUpdateSetterCalls;
|
|
29
29
|
private replaceTemplateVars;
|
|
30
|
-
private
|
|
30
|
+
private getServiceMethodName;
|
|
31
|
+
private generateHandlerMethod;
|
|
32
|
+
generateServiceForModel(model: ModelConfig, moduleName: string, moduleConfig: ModuleConfig, hasGlobalPermissions: boolean): string;
|
|
31
33
|
generateService(moduleName: string, moduleConfig: ModuleConfig, hasGlobalPermissions: boolean): string;
|
|
32
34
|
private generateCustomImports;
|
|
33
35
|
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|