eva4j 1.0.11 → 1.0.13

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 (73) hide show
  1. package/AGENTS.md +441 -14
  2. package/DOMAIN_YAML_GUIDE.md +425 -21
  3. package/FUTURE_FEATURES.md +315 -115
  4. package/QUICK_REFERENCE.md +101 -153
  5. package/README.md +77 -70
  6. package/bin/eva4j.js +57 -1
  7. package/config/defaults.json +3 -0
  8. package/docs/commands/GENERATE_ENTITIES.md +662 -1968
  9. package/docs/commands/GENERATE_HTTP_EXCHANGE.md +274 -450
  10. package/docs/commands/GENERATE_KAFKA_EVENT.md +219 -498
  11. package/docs/commands/GENERATE_KAFKA_LISTENER.md +18 -18
  12. package/docs/commands/GENERATE_RECORD.md +335 -311
  13. package/docs/commands/GENERATE_TEMPORAL_ACTIVITY.md +174 -0
  14. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +237 -0
  15. package/docs/commands/GENERATE_USECASE.md +216 -282
  16. package/docs/commands/INDEX.md +36 -7
  17. package/examples/doctor-evaluation.yaml +3 -3
  18. package/examples/domain-audit-complete.yaml +2 -2
  19. package/examples/domain-collections.yaml +2 -2
  20. package/examples/domain-ecommerce.yaml +2 -2
  21. package/examples/domain-events.yaml +201 -0
  22. package/examples/domain-field-visibility.yaml +11 -5
  23. package/examples/domain-multi-aggregate.yaml +12 -6
  24. package/examples/domain-one-to-many.yaml +1 -1
  25. package/examples/domain-one-to-one.yaml +1 -1
  26. package/examples/domain-secondary-onetomany.yaml +1 -1
  27. package/examples/domain-secondary-onetoone.yaml +1 -1
  28. package/examples/domain-simple.yaml +1 -1
  29. package/examples/domain-soft-delete.yaml +3 -3
  30. package/examples/domain-transitions.yaml +1 -1
  31. package/examples/domain-value-objects.yaml +1 -1
  32. package/package.json +2 -2
  33. package/src/commands/add-kafka-client.js +3 -1
  34. package/src/commands/add-temporal-client.js +286 -0
  35. package/src/commands/generate-entities.js +75 -4
  36. package/src/commands/generate-kafka-event.js +273 -89
  37. package/src/commands/generate-temporal-activity.js +228 -0
  38. package/src/commands/generate-temporal-flow.js +216 -0
  39. package/src/generators/module-generator.js +1 -0
  40. package/src/generators/shared-generator.js +26 -0
  41. package/src/utils/yaml-to-entity.js +93 -4
  42. package/templates/aggregate/AggregateRepository.java.ejs +3 -2
  43. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +15 -7
  44. package/templates/aggregate/AggregateRoot.java.ejs +38 -2
  45. package/templates/aggregate/DomainEntity.java.ejs +6 -2
  46. package/templates/aggregate/DomainEventHandler.java.ejs +62 -0
  47. package/templates/aggregate/DomainEventRecord.java.ejs +50 -0
  48. package/templates/aggregate/JpaAggregateRoot.java.ejs +3 -1
  49. package/templates/aggregate/JpaEntity.java.ejs +3 -1
  50. package/templates/base/docker/kafka-services.yaml.ejs +2 -2
  51. package/templates/base/docker/temporal-services.yaml.ejs +29 -0
  52. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +9 -0
  53. package/templates/base/resources/parameters/local/temporal.yaml.ejs +9 -0
  54. package/templates/base/resources/parameters/production/temporal.yaml.ejs +9 -0
  55. package/templates/base/resources/parameters/test/temporal.yaml.ejs +9 -0
  56. package/templates/base/root/AGENTS.md.ejs +916 -51
  57. package/templates/crud/Controller.java.ejs +36 -6
  58. package/templates/crud/ListQuery.java.ejs +6 -2
  59. package/templates/crud/ListQueryHandler.java.ejs +24 -10
  60. package/templates/crud/UpdateCommand.java.ejs +52 -0
  61. package/templates/crud/UpdateCommandHandler.java.ejs +105 -0
  62. package/templates/kafka-event/DomainEventHandlerMethod.ejs +1 -0
  63. package/templates/kafka-event/Event.java.ejs +23 -0
  64. package/templates/shared/application/dtos/PagedResponse.java.ejs +30 -0
  65. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +104 -0
  66. package/templates/shared/domain/DomainEvent.java.ejs +40 -0
  67. package/templates/shared/interfaces/HeavyActivity.java.ejs +4 -0
  68. package/templates/shared/interfaces/LightActivity.java.ejs +4 -0
  69. package/templates/temporal-activity/ActivityImpl.java.ejs +14 -0
  70. package/templates/temporal-activity/ActivityInterface.java.ejs +11 -0
  71. package/templates/temporal-flow/WorkFlowImpl.java.ejs +64 -0
  72. package/templates/temporal-flow/WorkFlowInterface.java.ejs +19 -0
  73. package/templates/temporal-flow/WorkFlowService.java.ejs +49 -0
@@ -0,0 +1,286 @@
1
+ const ora = require('ora');
2
+ const chalk = require('chalk');
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const yaml = require('js-yaml');
6
+ const ConfigManager = require('../utils/config-manager');
7
+ const { isEva4jProject } = require('../utils/validator');
8
+ const { toPackagePath } = require('../utils/naming');
9
+ const { renderAndWrite, renderTemplate } = require('../utils/template-engine');
10
+ const defaults = require('../../config/defaults.json');
11
+
12
+ const TEMPORAL_SDK_VERSION = defaults.temporalSdkVersion;
13
+
14
+ async function addTemporalClientCommand() {
15
+ const projectDir = process.cwd();
16
+
17
+ // Validate we're in an eva4j project
18
+ if (!(await isEva4jProject(projectDir))) {
19
+ console.error(chalk.red('❌ Not in an eva4j project directory'));
20
+ console.error(chalk.gray('Run this command inside a project created with eva4j'));
21
+ process.exit(1);
22
+ }
23
+
24
+ // Check if temporal is already installed
25
+ const configManager = new ConfigManager(projectDir);
26
+ if (await configManager.featureExists('temporal')) {
27
+ console.error(chalk.red('❌ Temporal client is already installed in this project'));
28
+ console.log(chalk.gray('\nTemporal dependencies and configuration already exist.'));
29
+ process.exit(1);
30
+ }
31
+
32
+ // Load project configuration
33
+ const projectConfig = await configManager.loadProjectConfig();
34
+ if (!projectConfig) {
35
+ console.error(chalk.red('❌ Could not load project configuration'));
36
+ console.error(chalk.gray('Make sure .eva4j.json exists in the project root'));
37
+ process.exit(1);
38
+ }
39
+
40
+ const { packageName, projectName, groupId, artifactId } = projectConfig;
41
+ const packagePath = toPackagePath(packageName);
42
+
43
+ // Check if shared module exists
44
+ const sharedPath = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
45
+ if (!(await fs.pathExists(sharedPath))) {
46
+ console.error(chalk.red('❌ Shared module not found'));
47
+ console.error(chalk.gray('Create at least one module first using: eva4j add module <name>'));
48
+ process.exit(1);
49
+ }
50
+
51
+ const spinner = ora('Adding Temporal client support...').start();
52
+
53
+ try {
54
+ const context = {
55
+ packageName,
56
+ packagePath,
57
+ projectName,
58
+ groupId,
59
+ artifactId,
60
+ temporalDockerVersion: defaults.temporalDockerVersion
61
+ };
62
+
63
+ // 1. Add dependency to build.gradle
64
+ spinner.text = 'Adding Temporal dependency to build.gradle...';
65
+ await addTemporalDependency(projectDir);
66
+
67
+ // 2. Generate temporal.yaml files for all environments
68
+ spinner.text = 'Generating Temporal configuration files...';
69
+ await generateTemporalConfigFiles(projectDir, context);
70
+
71
+ // 3. Add temporal.yaml imports to application-*.yaml files
72
+ spinner.text = 'Updating application configuration files...';
73
+ await addTemporalImports(projectDir);
74
+
75
+ // 4. Generate activity marker interfaces
76
+ spinner.text = 'Generating activity interfaces...';
77
+ await generateActivityInterfaces(projectDir, context);
78
+
79
+ // 5. Generate TemporalConfig.java
80
+ spinner.text = 'Generating TemporalConfig class...';
81
+ await generateTemporalConfigClass(projectDir, context);
82
+
83
+ // 6. Update docker-compose.yaml if it exists
84
+ spinner.text = 'Updating docker-compose.yaml...';
85
+ await updateDockerCompose(projectDir, context);
86
+
87
+ // 7. Save feature to configuration
88
+ await configManager.addFeature('temporal');
89
+
90
+ spinner.succeed(chalk.green('Temporal client support added successfully! ✨'));
91
+
92
+ console.log(chalk.blue('\n📦 Added components:'));
93
+ console.log(chalk.gray(' ├── build.gradle (io.temporal:temporal-sdk:' + TEMPORAL_SDK_VERSION + ')'));
94
+ console.log(chalk.gray(' ├── docker-compose.yaml (Temporal cluster)'));
95
+ console.log(chalk.gray(' ├── src/main/resources/parameters/'));
96
+ console.log(chalk.gray(' │ ├── local/temporal.yaml'));
97
+ console.log(chalk.gray(' │ ├── develop/temporal.yaml'));
98
+ console.log(chalk.gray(' │ ├── test/temporal.yaml'));
99
+ console.log(chalk.gray(' │ └── production/temporal.yaml'));
100
+ console.log(chalk.gray(' ├── shared/domain/interfaces/HeavyActivity.java'));
101
+ console.log(chalk.gray(' ├── shared/domain/interfaces/LightActivity.java'));
102
+ console.log(chalk.gray(' └── shared/infrastructure/configurations/temporalConfig/TemporalConfig.java'));
103
+
104
+ console.log(chalk.blue('\n✅ Temporal client configured successfully!'));
105
+ console.log(chalk.white('\n Service URL: localhost:7233'));
106
+ console.log(chalk.white(' Namespace: default'));
107
+ console.log(chalk.white(' Temporal UI: http://localhost:8088'));
108
+ console.log(chalk.yellow('\n ⚠️ Register your workflow implementation types in TemporalConfig.java'));
109
+ console.log(chalk.gray(' Run "docker-compose up -d" to start the Temporal cluster'));
110
+ console.log(chalk.gray(' Update temporal.yaml files to customize service URLs per environment'));
111
+ console.log();
112
+
113
+ } catch (error) {
114
+ spinner.fail(chalk.red('Failed to add Temporal client support'));
115
+ console.error(chalk.red('\n❌ Error:'), error.message);
116
+ if (error.stack) {
117
+ console.error(chalk.gray(error.stack));
118
+ }
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Add Temporal SDK dependency to build.gradle
125
+ */
126
+ async function addTemporalDependency(projectDir) {
127
+ const buildGradlePath = path.join(projectDir, 'build.gradle');
128
+ let buildGradleContent = await fs.readFile(buildGradlePath, 'utf-8');
129
+
130
+ // Check if dependency already exists
131
+ if (buildGradleContent.includes('temporal-sdk')) {
132
+ return; // Already added
133
+ }
134
+
135
+ // Find the dependencies block and add Temporal dependency after spring-modulith-starter-core
136
+ const dependenciesMatch = buildGradleContent.match(
137
+ /(dependencies\s*\{[^}]*)(implementation 'org\.springframework\.modulith:spring-modulith-starter-core'[^\n]*\n)/s
138
+ );
139
+
140
+ if (!dependenciesMatch) {
141
+ throw new Error('Could not find dependencies block in build.gradle');
142
+ }
143
+
144
+ const temporalDependency = `\n\t// Temporal\n\timplementation 'io.temporal:temporal-sdk:${TEMPORAL_SDK_VERSION}'\n\n\t`;
145
+
146
+ buildGradleContent = buildGradleContent.replace(
147
+ dependenciesMatch[0],
148
+ dependenciesMatch[1] + dependenciesMatch[2] + temporalDependency
149
+ );
150
+
151
+ await fs.writeFile(buildGradlePath, buildGradleContent, 'utf-8');
152
+ }
153
+
154
+ /**
155
+ * Generate temporal.yaml configuration files for all environments
156
+ */
157
+ async function generateTemporalConfigFiles(projectDir, context) {
158
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'base', 'resources', 'parameters');
159
+ const environments = ['local', 'develop', 'test', 'production'];
160
+
161
+ for (const env of environments) {
162
+ const outputPath = path.join(projectDir, 'src', 'main', 'resources', 'parameters', env, 'temporal.yaml');
163
+ const templateFile = path.join(templatePath, env, 'temporal.yaml.ejs');
164
+
165
+ await renderAndWrite(templateFile, outputPath, context);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Add temporal.yaml imports to application-*.yaml files
171
+ */
172
+ async function addTemporalImports(projectDir) {
173
+ const resourcesDir = path.join(projectDir, 'src', 'main', 'resources');
174
+ const environments = ['local', 'develop', 'test', 'production'];
175
+
176
+ for (const env of environments) {
177
+ const appYmlPath = path.join(resourcesDir, `application-${env}.yaml`);
178
+
179
+ if (await fs.pathExists(appYmlPath)) {
180
+ let content = await fs.readFile(appYmlPath, 'utf-8');
181
+
182
+ // Check if temporal.yaml import already exists
183
+ if (content.includes('temporal.yaml')) {
184
+ continue;
185
+ }
186
+
187
+ // Add temporal.yaml import after existing imports
188
+ const importPattern = /(spring:\s*\n\s*config:\s*\n\s*import:\s*\n(?:\s*-\s*"[^"]+"\s*\n)*)/;
189
+
190
+ if (importPattern.test(content)) {
191
+ content = content.replace(
192
+ importPattern,
193
+ `$1 - "classpath:parameters/${env}/temporal.yaml"\n`
194
+ );
195
+ } else {
196
+ // If no imports section exists, add it
197
+ content = `spring:\n config:\n import:\n - "classpath:parameters/${env}/temporal.yaml"\n\n` + content;
198
+ }
199
+
200
+ await fs.writeFile(appYmlPath, content, 'utf-8');
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Generate HeavyActivity and LightActivity marker interfaces
207
+ */
208
+ async function generateActivityInterfaces(projectDir, context) {
209
+ const interfacesTemplateDir = path.join(__dirname, '..', '..', 'templates', 'shared', 'interfaces');
210
+ const interfacesOutputDir = path.join(
211
+ projectDir, 'src', 'main', 'java', context.packagePath, 'shared', 'domain', 'interfaces'
212
+ );
213
+
214
+ const interfaces = ['HeavyActivity', 'LightActivity'];
215
+
216
+ for (const iface of interfaces) {
217
+ const templateFile = path.join(interfacesTemplateDir, `${iface}.java.ejs`);
218
+ const outputFile = path.join(interfacesOutputDir, `${iface}.java`);
219
+ await renderAndWrite(templateFile, outputFile, context);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Generate TemporalConfig.java class
225
+ */
226
+ async function generateTemporalConfigClass(projectDir, context) {
227
+ const templatePath = path.join(
228
+ __dirname, '..', '..', 'templates', 'shared', 'configurations', 'temporalConfig', 'TemporalConfig.java.ejs'
229
+ );
230
+ const outputPath = path.join(
231
+ projectDir, 'src', 'main', 'java', context.packagePath,
232
+ 'shared', 'infrastructure', 'configurations', 'temporalConfig', 'TemporalConfig.java'
233
+ );
234
+
235
+ await renderAndWrite(templatePath, outputPath, context);
236
+ }
237
+
238
+ /**
239
+ * Update docker-compose.yaml to add Temporal services
240
+ */
241
+ async function updateDockerCompose(projectDir, context) {
242
+ const dockerComposePath = path.join(projectDir, 'docker-compose.yaml');
243
+
244
+ // Check if docker-compose.yaml exists
245
+ if (!(await fs.pathExists(dockerComposePath))) {
246
+ return; // No docker-compose to update
247
+ }
248
+
249
+ let dockerComposeContent = await fs.readFile(dockerComposePath, 'utf-8');
250
+
251
+ // Check if Temporal services already exist
252
+ if (dockerComposeContent.includes('temporal:') || dockerComposeContent.includes('temporalio')) {
253
+ return; // Temporal already configured
254
+ }
255
+
256
+ // Parse existing docker-compose.yaml
257
+ const dockerComposeObj = yaml.load(dockerComposeContent);
258
+
259
+ // Ensure services section exists
260
+ if (!dockerComposeObj.services) {
261
+ dockerComposeObj.services = {};
262
+ }
263
+
264
+ // Render Temporal services template
265
+ const temporalTemplateContent = await renderTemplate(
266
+ path.join(__dirname, '..', '..', 'templates', 'base', 'docker', 'temporal-services.yaml.ejs'),
267
+ context
268
+ );
269
+
270
+ // Parse the rendered Temporal services
271
+ const temporalServices = yaml.load(temporalTemplateContent);
272
+
273
+ // Merge Temporal services into existing docker-compose
274
+ Object.assign(dockerComposeObj.services, temporalServices);
275
+
276
+ // Write updated docker-compose.yaml
277
+ const updatedYaml = yaml.dump(dockerComposeObj, {
278
+ indent: 2,
279
+ lineWidth: -1,
280
+ noRefs: true
281
+ });
282
+
283
+ await fs.writeFile(dockerComposePath, updatedYaml, 'utf-8');
284
+ }
285
+
286
+ module.exports = addTemporalClientCommand;
@@ -190,10 +190,19 @@ async function generateEntitiesCommand(moduleName, options = {}) {
190
190
  agg.secondaryEntities.some(e => e.audit && e.audit.trackUser)
191
191
  );
192
192
 
193
+ // Always generate PagedResponse shared DTO (used by all ListQueryHandlers)
194
+ const sharedBasePath = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
195
+ const sharedGenerator = new SharedGenerator({ packageName, packagePath });
196
+ await sharedGenerator.generatePagedResponse(sharedBasePath);
197
+
198
+ // Check if any aggregate declares domain events and generate shared DomainEvent base class
199
+ const hasDomainEventsInModule = aggregates.some(agg => agg.domainEvents && agg.domainEvents.length > 0);
200
+ if (hasDomainEventsInModule) {
201
+ await sharedGenerator.generateDomainEvent(sharedBasePath);
202
+ }
203
+
193
204
  // Generate audit-related shared components if needed
194
205
  if (hasAuditableEntities || hasTrackUserEntities) {
195
- const sharedBasePath = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
196
- const sharedGenerator = new SharedGenerator({ packageName, packagePath });
197
206
 
198
207
  // Always generate base AuditableEntity if any audit is enabled
199
208
  if (hasAuditableEntities) {
@@ -241,6 +250,11 @@ async function generateEntitiesCommand(moduleName, options = {}) {
241
250
  agg.valueObjects.forEach(vo => {
242
251
  console.log(chalk.gray(` │ └── ${vo.name} (VO)`));
243
252
  });
253
+ if (agg.domainEvents && agg.domainEvents.length > 0) {
254
+ agg.domainEvents.forEach(event => {
255
+ console.log(chalk.gray(` │ └── ${event.name} (Event${event.kafka ? ' · kafka' : ''})`))
256
+ });
257
+ }
244
258
  });
245
259
  console.log();
246
260
 
@@ -284,7 +298,8 @@ async function generateEntitiesCommand(moduleName, options = {}) {
284
298
  imports: rootEntity.imports,
285
299
  valueObjects,
286
300
  aggregateMethods: aggregate.aggregateMethods,
287
- auditable: rootEntity.auditable
301
+ auditable: rootEntity.auditable,
302
+ domainEvents: aggregate.domainEvents || []
288
303
  };
289
304
 
290
305
  await renderAndWrite(
@@ -449,7 +464,8 @@ async function generateEntitiesCommand(moduleName, options = {}) {
449
464
  packageName,
450
465
  moduleName,
451
466
  aggregateName,
452
- rootEntity
467
+ rootEntity,
468
+ hasDomainEvents: (aggregate.domainEvents || []).length > 0
453
469
  };
454
470
 
455
471
  await renderAndWrite(
@@ -459,6 +475,45 @@ async function generateEntitiesCommand(moduleName, options = {}) {
459
475
  writeOptions
460
476
  );
461
477
  generatedFiles.push({ type: 'Repository Impl', name: `${rootEntity.name}RepositoryImpl`, path: `${moduleName}/infrastructure/database/repositories/${rootEntity.name}RepositoryImpl.java` });
478
+
479
+ // 9. Generate Domain Events (if declared in domain.yaml)
480
+ const aggregateDomainEvents = aggregate.domainEvents || [];
481
+ if (aggregateDomainEvents.length > 0) {
482
+
483
+ for (const event of aggregateDomainEvents) {
484
+ const eventContext = {
485
+ packageName,
486
+ moduleName,
487
+ aggregateName,
488
+ name: event.name,
489
+ fields: event.fields,
490
+ kafka: event.kafka
491
+ };
492
+ await renderAndWrite(
493
+ path.join(__dirname, '..', '..', 'templates', 'aggregate', 'DomainEventRecord.java.ejs'),
494
+ path.join(moduleBasePath, 'domain', 'models', 'events', `${event.name}.java`),
495
+ eventContext,
496
+ writeOptions
497
+ );
498
+ generatedFiles.push({ type: 'Domain Event', name: event.name, path: `${moduleName}/domain/models/events/${event.name}.java` });
499
+ }
500
+
501
+ // Generate the bridge handler
502
+ const handlerContext = {
503
+ packageName,
504
+ moduleName,
505
+ aggregateName,
506
+ domainEvents: aggregateDomainEvents,
507
+ hasKafkaEvents: aggregateDomainEvents.some(e => e.kafka)
508
+ };
509
+ await renderAndWrite(
510
+ path.join(__dirname, '..', '..', 'templates', 'aggregate', 'DomainEventHandler.java.ejs'),
511
+ path.join(moduleBasePath, 'application', 'usecases', `${aggregateName}DomainEventHandler.java`),
512
+ handlerContext,
513
+ writeOptions
514
+ );
515
+ generatedFiles.push({ type: 'Domain Event Handler', name: `${aggregateName}DomainEventHandler`, path: `${moduleName}/application/usecases/${aggregateName}DomainEventHandler.java` });
516
+ }
462
517
  }
463
518
 
464
519
  spinner.succeed(chalk.green(`Generated ${generatedFiles.length} files! ✨`));
@@ -813,6 +868,14 @@ async function generateCrudResources(aggregate, moduleName, moduleBasePath, pack
813
868
  writeOptions
814
869
  );
815
870
  generatedFiles.push({ type: 'Command', name: `Delete${aggregateName}Command`, path: `${moduleName}/application/commands/Delete${aggregateName}Command.java` });
871
+
872
+ await renderAndWrite(
873
+ path.join(templatesDir, 'UpdateCommand.java.ejs'),
874
+ path.join(moduleBasePath, 'application', 'commands', `Update${aggregateName}Command.java`),
875
+ { ...baseContext, imports: commandAppImports },
876
+ writeOptions
877
+ );
878
+ generatedFiles.push({ type: 'Command', name: `Update${aggregateName}Command`, path: `${moduleName}/application/commands/Update${aggregateName}Command.java` });
816
879
 
817
880
  // 3. Generate Queries
818
881
  await renderAndWrite(
@@ -869,6 +932,14 @@ async function generateCrudResources(aggregate, moduleName, moduleBasePath, pack
869
932
  writeOptions
870
933
  );
871
934
  generatedFiles.push({ type: 'Handler', name: `Delete${aggregateName}CommandHandler`, path: `${moduleName}/application/usecases/Delete${aggregateName}CommandHandler.java` });
935
+
936
+ await renderAndWrite(
937
+ path.join(templatesDir, 'UpdateCommandHandler.java.ejs'),
938
+ path.join(moduleBasePath, 'application', 'usecases', `Update${aggregateName}CommandHandler.java`),
939
+ baseContext,
940
+ writeOptions
941
+ );
942
+ generatedFiles.push({ type: 'Handler', name: `Update${aggregateName}CommandHandler`, path: `${moduleName}/application/usecases/Update${aggregateName}CommandHandler.java` });
872
943
 
873
944
  // 5. Generate DTOs
874
945
  const responseDtoContext = {