@currentjs/gen 0.5.0 → 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 (52) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cli.js +0 -0
  3. package/dist/generators/controllerGenerator.d.ts +7 -0
  4. package/dist/generators/controllerGenerator.js +56 -17
  5. package/dist/generators/domainLayerGenerator.js +17 -7
  6. package/dist/generators/dtoGenerator.js +13 -8
  7. package/dist/generators/serviceGenerator.d.ts +6 -0
  8. package/dist/generators/serviceGenerator.js +219 -23
  9. package/dist/generators/storeGenerator.d.ts +4 -0
  10. package/dist/generators/storeGenerator.js +116 -9
  11. package/dist/generators/templates/appTemplates.js +1 -1
  12. package/dist/generators/templates/data/frontendScriptTemplate +11 -4
  13. package/dist/generators/templates/data/mainViewTemplate +1 -0
  14. package/dist/generators/templates/storeTemplates.d.ts +1 -1
  15. package/dist/generators/templates/storeTemplates.js +3 -26
  16. package/dist/generators/useCaseGenerator.js +6 -3
  17. package/dist/types/configTypes.d.ts +1 -0
  18. package/dist/utils/typeUtils.d.ts +4 -0
  19. package/dist/utils/typeUtils.js +7 -0
  20. package/package.json +1 -1
  21. package/dist/commands/migratePush.d.ts +0 -1
  22. package/dist/commands/migratePush.js +0 -135
  23. package/dist/commands/migrateUpdate.d.ts +0 -1
  24. package/dist/commands/migrateUpdate.js +0 -147
  25. package/dist/commands/newGenerateAll.d.ts +0 -4
  26. package/dist/commands/newGenerateAll.js +0 -336
  27. package/dist/generators/domainModelGenerator.d.ts +0 -41
  28. package/dist/generators/domainModelGenerator.js +0 -242
  29. package/dist/generators/newControllerGenerator.d.ts +0 -55
  30. package/dist/generators/newControllerGenerator.js +0 -644
  31. package/dist/generators/newServiceGenerator.d.ts +0 -19
  32. package/dist/generators/newServiceGenerator.js +0 -266
  33. package/dist/generators/newStoreGenerator.d.ts +0 -39
  34. package/dist/generators/newStoreGenerator.js +0 -408
  35. package/dist/generators/newTemplateGenerator.d.ts +0 -29
  36. package/dist/generators/newTemplateGenerator.js +0 -510
  37. package/dist/generators/storeGeneratorV2.d.ts +0 -31
  38. package/dist/generators/storeGeneratorV2.js +0 -190
  39. package/dist/generators/templates/controllerTemplates.d.ts +0 -43
  40. package/dist/generators/templates/controllerTemplates.js +0 -82
  41. package/dist/generators/templates/newStoreTemplates.d.ts +0 -5
  42. package/dist/generators/templates/newStoreTemplates.js +0 -141
  43. package/dist/generators/templates/serviceTemplates.d.ts +0 -16
  44. package/dist/generators/templates/serviceTemplates.js +0 -59
  45. package/dist/generators/templates/validationTemplates.d.ts +0 -25
  46. package/dist/generators/templates/validationTemplates.js +0 -66
  47. package/dist/generators/templates/viewTemplates.d.ts +0 -25
  48. package/dist/generators/templates/viewTemplates.js +0 -491
  49. package/dist/generators/validationGenerator.d.ts +0 -29
  50. package/dist/generators/validationGenerator.js +0 -250
  51. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  52. package/dist/utils/new_parts_of_migrationUtils.js +0 -164
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.1] - 2026-02-28
4
+
5
+ - fixes pack:
6
+ - aggregate references
7
+ - layout setting in module config
8
+ - SPA layout mismatch bug (if a page uses other layout, its loaded inside current one)
9
+ - local provider imports bug
10
+ - types in generated services (was any)
11
+ - access control issues
12
+
3
13
  ## [0.5.0] – 2026-02-26
4
14
 
5
15
  So many changes were made that we skipped version 0.4:
package/dist/cli.js CHANGED
File without changes
@@ -43,6 +43,13 @@ export declare class ControllerGenerator {
43
43
  * meant for static routes (e.g. /create).
44
44
  */
45
45
  private sortRoutesBySpecificity;
46
+ /**
47
+ * Resolve layout from YAML value.
48
+ * - undefined => use fallback (if provided)
49
+ * - "none" or "" => no layout
50
+ * - other values => use explicit layout name
51
+ */
52
+ private resolveLayout;
46
53
  private generateApiController;
47
54
  private generateWebController;
48
55
  generateFromConfig(config: ModuleConfig): Record<string, string>;
@@ -244,18 +244,26 @@ class ControllerGenerator {
244
244
  }
245
245
  `;
246
246
  }
247
- generateApiEndpointMethod(endpoint, resourceName, childInfo) {
247
+ generateApiEndpointMethod(endpoint, resourceName, useCasesConfig, childInfo) {
248
+ var _a;
248
249
  const { model, action } = this.parseUseCase(endpoint.useCase);
249
250
  const methodName = action;
250
251
  const decorator = this.getHttpDecorator(endpoint.method);
251
252
  const useCaseVar = `${model.toLowerCase()}UseCase`;
252
253
  const inputClass = `${model}${(0, typeUtils_1.capitalize)(action)}Input`;
253
254
  const outputClass = `${model}${(0, typeUtils_1.capitalize)(action)}Output`;
255
+ const useCaseDef = (_a = useCasesConfig[model]) === null || _a === void 0 ? void 0 : _a[action];
256
+ const isVoidOutput = !(useCaseDef === null || useCaseDef === void 0 ? void 0 : useCaseDef.output) || useCaseDef.output === 'void';
254
257
  const dtoImports = new Set();
258
+ const voidOutputDtos = new Set();
255
259
  dtoImports.add(`${model}${(0, typeUtils_1.capitalize)(action)}`);
260
+ if (isVoidOutput) {
261
+ voidOutputDtos.add(`${model}${(0, typeUtils_1.capitalize)(action)}`);
262
+ }
256
263
  // Generate auth check (pre-fetch)
257
264
  const authCheck = this.generateAuthCheck(endpoint.auth);
258
265
  const authLine = authCheck ? `\n ${authCheck}\n` : '';
266
+ const hasOwner = this.hasOwnerAuth(endpoint.auth);
259
267
  // Build parsing logic
260
268
  // For create: root gets ownerId from user, child gets parentId from URL params
261
269
  let parseLogic;
@@ -282,7 +290,6 @@ class ControllerGenerator {
282
290
  // Generate owner checks:
283
291
  // - For mutations (update, delete): PRE-mutation check (before operation)
284
292
  // - For reads (get): POST-fetch check (after fetching)
285
- const hasOwner = this.hasOwnerAuth(endpoint.auth);
286
293
  const isMutation = action === 'update' || action === 'delete';
287
294
  const isRead = action === 'get';
288
295
  // Pre-mutation owner check for write operations
@@ -295,22 +302,25 @@ class ControllerGenerator {
295
302
  : '';
296
303
  // Generate output transformation based on action
297
304
  let outputTransform;
298
- if (action === 'list') {
299
- outputTransform = `return ${outputClass}.from(result);`;
300
- }
301
- else if (action === 'delete') {
305
+ if (isVoidOutput || action === 'delete') {
302
306
  outputTransform = `return result;`;
303
307
  }
308
+ else if (action === 'list') {
309
+ outputTransform = `return ${outputClass}.from(result);`;
310
+ }
304
311
  else {
305
312
  outputTransform = `return ${outputClass}.from(result);`;
306
313
  }
314
+ const useCaseArgs = (hasOwner && action === 'list')
315
+ ? 'input, context.request.user?.id as number'
316
+ : 'input';
307
317
  const method = ` @${decorator}('${endpoint.path}')
308
318
  async ${methodName}(context: IContext): Promise<any> {${authLine}
309
319
  ${parseLogic}${preMutationOwnerCheck}
310
- const result = await this.${useCaseVar}.${action}(input);${postFetchOwnerCheck}
320
+ const result = await this.${useCaseVar}.${action}(${useCaseArgs});${postFetchOwnerCheck}
311
321
  ${outputTransform}
312
322
  }`;
313
- return { method, dtoImports };
323
+ return { method, dtoImports, voidOutputDtos };
314
324
  }
315
325
  generateWebPageMethod(page, resourceName, layout, methodIndex, childInfo, withChildChildren) {
316
326
  const method = page.method || 'GET';
@@ -333,12 +343,16 @@ class ControllerGenerator {
333
343
  const authLine = authCheck ? `\n ${authCheck}\n` : '';
334
344
  // For GET requests with views (display pages)
335
345
  if (method === 'GET' && page.view) {
336
- const renderDecorator = `\n @Render("${page.view}", "${layout}")`;
346
+ const pageLayout = this.resolveLayout(page.layout, layout);
347
+ const renderDecorator = pageLayout
348
+ ? `\n @Render("${page.view}", "${pageLayout}")`
349
+ : `\n @Render("${page.view}")`;
337
350
  if (page.useCase) {
338
351
  const { model, action } = this.parseUseCase(page.useCase);
339
352
  const useCaseVar = `${model.toLowerCase()}UseCase`;
340
353
  const inputClass = `${model}${(0, typeUtils_1.capitalize)(action)}Input`;
341
354
  dtoImports.add(`${model}${(0, typeUtils_1.capitalize)(action)}`);
355
+ const hasOwner = this.hasOwnerAuth(page.auth);
342
356
  let parseLogic;
343
357
  if (page.path.includes(':id')) {
344
358
  parseLogic = `const input = ${inputClass}.parse({ id: context.request.parameters.id });`;
@@ -349,8 +363,6 @@ class ControllerGenerator {
349
363
  else {
350
364
  parseLogic = `const input = ${inputClass}.parse({});`;
351
365
  }
352
- // Generate post-fetch owner check for GET pages (reads only)
353
- const hasOwner = this.hasOwnerAuth(page.auth);
354
366
  const isReadAction = action === 'get' || action === 'list';
355
367
  const postFetchOwnerCheck = (hasOwner && isReadAction)
356
368
  ? this.generatePostFetchOwnerCheck(page.auth, 'result', useCaseVar, childInfo)
@@ -374,12 +386,15 @@ class ControllerGenerator {
374
386
  else {
375
387
  returnExpr = 'result';
376
388
  }
389
+ const useCaseArgs = (hasOwner && action === 'list')
390
+ ? 'input, context.request.user?.id as number'
391
+ : 'input';
377
392
  const loadChildCode = loadChildBlocks.length ? '\n ' + loadChildBlocks.join('\n ') + '\n ' : '';
378
393
  const methodCode = `${renderDecorator}
379
394
  @${decorator}('${page.path}')
380
395
  async ${methodName}(context: IContext): Promise<any> {${authLine}
381
396
  ${parseLogic}
382
- const result = await this.${useCaseVar}.${action}(input);${postFetchOwnerCheck}${loadChildCode}
397
+ const result = await this.${useCaseVar}.${action}(${useCaseArgs});${postFetchOwnerCheck}${loadChildCode}
383
398
  return ${returnExpr};
384
399
  }`;
385
400
  return { method: methodCode, dtoImports };
@@ -491,26 +506,49 @@ class ControllerGenerator {
491
506
  return aParamCount - bParamCount;
492
507
  });
493
508
  }
494
- generateApiController(resourceName, prefix, endpoints, childInfo) {
509
+ /**
510
+ * Resolve layout from YAML value.
511
+ * - undefined => use fallback (if provided)
512
+ * - "none" or "" => no layout
513
+ * - other values => use explicit layout name
514
+ */
515
+ resolveLayout(layout, fallback) {
516
+ if (layout === undefined) {
517
+ return fallback;
518
+ }
519
+ const normalized = layout.trim();
520
+ if (!normalized || normalized.toLowerCase() === 'none') {
521
+ return undefined;
522
+ }
523
+ return normalized;
524
+ }
525
+ generateApiController(resourceName, prefix, endpoints, useCasesConfig, childInfo) {
495
526
  const controllerName = `${resourceName}ApiController`;
496
527
  // Determine which use cases and DTOs are referenced
497
528
  const useCaseModels = new Set();
498
529
  const allDtoImports = new Set();
530
+ const allVoidOutputDtos = new Set();
499
531
  const methods = [];
500
532
  const sortedEndpoints = this.sortRoutesBySpecificity(endpoints);
501
533
  sortedEndpoints.forEach(endpoint => {
502
534
  const { model } = this.parseUseCase(endpoint.useCase);
503
535
  useCaseModels.add(model);
504
- const { method, dtoImports } = this.generateApiEndpointMethod(endpoint, resourceName, childInfo);
536
+ const { method, dtoImports, voidOutputDtos } = this.generateApiEndpointMethod(endpoint, resourceName, useCasesConfig, childInfo);
505
537
  methods.push(method);
506
538
  dtoImports.forEach(d => allDtoImports.add(d));
539
+ voidOutputDtos.forEach(d => allVoidOutputDtos.add(d));
507
540
  });
508
541
  // Generate imports
509
542
  const useCaseImports = Array.from(useCaseModels)
510
543
  .map(model => `import { ${model}UseCase } from '../../application/useCases/${model}UseCase';`)
511
544
  .join('\n');
512
545
  const dtoImportStatements = Array.from(allDtoImports)
513
- .map(dto => `import { ${dto}Input, ${dto}Output } from '../../application/dto/${dto}';`)
546
+ .map(dto => {
547
+ if (allVoidOutputDtos.has(dto)) {
548
+ return `import { ${dto}Input } from '../../application/dto/${dto}';`;
549
+ }
550
+ return `import { ${dto}Input, ${dto}Output } from '../../application/dto/${dto}';`;
551
+ })
514
552
  .join('\n');
515
553
  // Generate constructor parameters
516
554
  const constructorParams = Array.from(useCaseModels)
@@ -602,7 +640,7 @@ ${methods.join('\n\n')}
602
640
  if (config.api) {
603
641
  Object.entries(config.api).forEach(([resourceName, resourceConfig]) => {
604
642
  const childInfo = childEntityMap.get(resourceName);
605
- const code = this.generateApiController(resourceName, resourceConfig.prefix, resourceConfig.endpoints, childInfo);
643
+ const code = this.generateApiController(resourceName, resourceConfig.prefix, resourceConfig.endpoints, config.useCases, childInfo);
606
644
  result[`${resourceName}Api`] = code;
607
645
  });
608
646
  }
@@ -610,7 +648,8 @@ ${methods.join('\n\n')}
610
648
  if (config.web) {
611
649
  Object.entries(config.web).forEach(([resourceName, resourceConfig]) => {
612
650
  const childInfo = childEntityMap.get(resourceName);
613
- const code = this.generateWebController(resourceName, resourceConfig.prefix, resourceConfig.layout || 'main_view', resourceConfig.pages, config, childInfo);
651
+ const moduleLayout = this.resolveLayout(resourceConfig.layout, 'main_view');
652
+ const code = this.generateWebController(resourceName, resourceConfig.prefix, moduleLayout, resourceConfig.pages, config, childInfo);
614
653
  result[`${resourceName}Web`] = code;
615
654
  });
616
655
  }
@@ -146,7 +146,14 @@ class DomainLayerGenerator {
146
146
  })
147
147
  .filter((imp, idx, arr) => arr.indexOf(imp) === idx) // dedupe
148
148
  .join('\n');
149
- const imports = [entityImports, valueObjectImports].filter(Boolean).join('\n');
149
+ // Generate imports for aggregate references in fields (e.g. idea: { type: Idea })
150
+ const aggregateRefImports = fields
151
+ .filter(([, fieldConfig]) => (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates) &&
152
+ fieldConfig.type !== name)
153
+ .map(([, fieldConfig]) => `import { ${fieldConfig.type} } from './${fieldConfig.type}';`)
154
+ .filter((imp, idx, arr) => arr.indexOf(imp) === idx)
155
+ .join('\n');
156
+ const imports = [entityImports, valueObjectImports, aggregateRefImports].filter(Boolean).join('\n');
150
157
  // Generate constructor parameters: id, then ownerId (root) or parentId field (child)
151
158
  const constructorParams = ['public id: number'];
152
159
  if (childInfo) {
@@ -157,9 +164,12 @@ class DomainLayerGenerator {
157
164
  }
158
165
  // Sort fields: required first, then optional
159
166
  // Fields are required by default unless required: false
167
+ // Aggregate references are always treated as optional (store can't populate them from FK alone)
160
168
  const sortedFields = fields.sort((a, b) => {
161
- const aRequired = a[1].required !== false && !a[1].auto;
162
- const bRequired = b[1].required !== false && !b[1].auto;
169
+ const aIsAggRef = (0, typeUtils_1.isAggregateReference)(a[1].type, this.availableAggregates);
170
+ const bIsAggRef = (0, typeUtils_1.isAggregateReference)(b[1].type, this.availableAggregates);
171
+ const aRequired = a[1].required !== false && !a[1].auto && !aIsAggRef;
172
+ const bRequired = b[1].required !== false && !b[1].auto && !bIsAggRef;
163
173
  if (aRequired === bRequired)
164
174
  return 0;
165
175
  return aRequired ? -1 : 1;
@@ -176,9 +186,9 @@ class DomainLayerGenerator {
176
186
  }
177
187
  });
178
188
  sortedFields.forEach(([fieldName, fieldConfig]) => {
189
+ const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
179
190
  const tsType = enumTypeNames[fieldName] || this.mapType(fieldConfig.type);
180
- // Fields are required by default, only optional if explicitly set to required: false
181
- const isOptional = fieldConfig.required === false;
191
+ const isOptional = fieldConfig.required === false || isAggRef;
182
192
  const hasDefault = fieldConfig.auto;
183
193
  let param = `public ${fieldName}`;
184
194
  if (isOptional && !hasDefault) {
@@ -195,10 +205,10 @@ class DomainLayerGenerator {
195
205
  const setterMethods = sortedFields
196
206
  .filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id')
197
207
  .map(([fieldName, fieldConfig]) => {
208
+ const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
198
209
  const tsType = enumTypeNames[fieldName] || this.mapType(fieldConfig.type);
199
210
  const methodName = `set${(0, typeUtils_1.capitalize)(fieldName)}`;
200
- // Fields are required by default, only optional if explicitly set to required: false
201
- const isOptional = fieldConfig.required === false;
211
+ const isOptional = fieldConfig.required === false || isAggRef;
202
212
  return `
203
213
  ${methodName}(${fieldName}: ${tsType}${isOptional ? ' | undefined' : ''}): void {
204
214
  this.${fieldName} = ${fieldName};
@@ -171,15 +171,17 @@ class DtoGenerator {
171
171
  fieldsToInclude.forEach(([fieldName, fieldConfig]) => {
172
172
  if (fieldName === 'id' || fieldConfig.auto)
173
173
  return;
174
- const tsType = this.mapType(fieldConfig.type);
175
- // Fields are required by default unless partial or required: false
176
- const isRequired = !inputConfig.partial && fieldConfig.required !== false;
174
+ const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
175
+ const tsType = isAggRef ? 'number' : this.mapType(fieldConfig.type);
176
+ const effectiveFieldType = isAggRef ? 'number' : fieldConfig.type;
177
+ // Aggregate references are always optional in DTOs; other fields default to required
178
+ const isRequired = !isAggRef && !inputConfig.partial && fieldConfig.required !== false;
177
179
  const optional = isRequired ? '' : '?';
178
180
  fieldDeclarations.push(` readonly ${fieldName}${optional}: ${tsType};`);
179
181
  constructorParams.push(`${fieldName}${optional}: ${tsType}`);
180
182
  constructorAssignments.push(` this.${fieldName} = ${fieldName};`);
181
- validationChecks.push(...this.getValidationCode(fieldName, fieldConfig.type, isRequired));
182
- fieldTransforms.push(` ${fieldName}: ${this.getTransformCode(fieldName, fieldConfig.type)}`);
183
+ validationChecks.push(...this.getValidationCode(fieldName, effectiveFieldType, isRequired));
184
+ fieldTransforms.push(` ${fieldName}: ${this.getTransformCode(fieldName, effectiveFieldType)}`);
183
185
  });
184
186
  }
185
187
  // Handle filters
@@ -267,12 +269,15 @@ ${transformsStr}
267
269
  fieldsToInclude.forEach(([fieldName, fieldConfig]) => {
268
270
  if (fieldName === 'id')
269
271
  return;
270
- const tsType = this.mapType(fieldConfig.type);
271
- const isOptional = fieldConfig.required === false;
272
+ const isAggRef = (0, typeUtils_1.isAggregateReference)(fieldConfig.type, this.availableAggregates);
273
+ const tsType = isAggRef ? 'number' : this.mapType(fieldConfig.type);
274
+ const isOptional = fieldConfig.required === false || isAggRef;
272
275
  const optional = isOptional ? '?' : '';
273
276
  fieldDeclarations.push(` readonly ${fieldName}${optional}: ${tsType};`);
274
277
  constructorParams.push(`${fieldName}${optional}: ${tsType}`);
275
- fromMappings.push(` ${fieldName}: entity.${fieldName}`);
278
+ fromMappings.push(isAggRef
279
+ ? ` ${fieldName}: entity.${fieldName}?.id`
280
+ : ` ${fieldName}: entity.${fieldName}`);
276
281
  });
277
282
  }
278
283
  // Handle includes (nested objects)
@@ -2,6 +2,12 @@ import { ModuleConfig } from '../types/configTypes';
2
2
  export declare class ServiceGenerator {
3
3
  private availableAggregates;
4
4
  private mapType;
5
+ private getDefaultHandlerReturnType;
6
+ private buildHandlerContextMap;
7
+ private deriveInputType;
8
+ private deriveCustomHandlerTypes;
9
+ private getInputDtoFields;
10
+ private computeDtoFieldsForHandler;
5
11
  private generateListHandler;
6
12
  private generateGetHandler;
7
13
  private generateCreateHandler;
@@ -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
  /**