eva4j 1.0.14 → 1.0.16

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 (139) hide show
  1. package/.agents/skills/skill-creator/LICENSE.txt +202 -0
  2. package/.agents/skills/skill-creator/SKILL.md +485 -0
  3. package/.agents/skills/skill-creator/agents/analyzer.md +274 -0
  4. package/.agents/skills/skill-creator/agents/comparator.md +202 -0
  5. package/.agents/skills/skill-creator/agents/grader.md +223 -0
  6. package/.agents/skills/skill-creator/assets/eval_review.html +146 -0
  7. package/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  8. package/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  9. package/.agents/skills/skill-creator/references/schemas.md +430 -0
  10. package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
  11. package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  12. package/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
  13. package/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
  14. package/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
  15. package/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
  16. package/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
  17. package/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
  18. package/.agents/skills/skill-creator/scripts/utils.py +47 -0
  19. package/AGENTS.md +268 -6
  20. package/COMMAND_EVALUATION.md +15 -16
  21. package/DOMAIN_YAML_GUIDE.md +430 -14
  22. package/FUTURE_FEATURES.md +1627 -1168
  23. package/README.md +461 -13
  24. package/bin/eva4j.js +32 -14
  25. package/config/defaults.json +1 -0
  26. package/docs/commands/EVALUATE_SYSTEM.md +746 -261
  27. package/docs/commands/EXPORT_DIAGRAM.md +153 -0
  28. package/docs/commands/GENERATE_ENTITIES.md +599 -6
  29. package/docs/commands/INDEX.md +7 -0
  30. package/examples/domain-events.yaml +166 -20
  31. package/examples/domain-listeners.yaml +212 -0
  32. package/examples/domain-one-to-many.yaml +1 -0
  33. package/examples/domain-one-to-one.yaml +1 -0
  34. package/examples/domain-ports.yaml +414 -0
  35. package/examples/domain-soft-delete.yaml +47 -44
  36. package/examples/system/notification.yaml +147 -0
  37. package/examples/system/product.yaml +185 -0
  38. package/examples/system/system.yaml +112 -0
  39. package/examples/system-report.html +971 -0
  40. package/examples/system.yaml +46 -3
  41. package/package.json +2 -1
  42. package/src/agents/design-reviewer.agent.md +163 -0
  43. package/src/commands/build.js +714 -0
  44. package/src/commands/create.js +1 -0
  45. package/src/commands/detach.js +149 -108
  46. package/src/commands/evaluate-system.js +234 -8
  47. package/src/commands/export-diagram.js +522 -0
  48. package/src/commands/generate-entities.js +685 -66
  49. package/src/commands/generate-http-exchange.js +2 -0
  50. package/src/commands/generate-kafka-event.js +43 -10
  51. package/src/generators/base-generator.js +18 -6
  52. package/src/generators/postman-generator.js +188 -0
  53. package/src/generators/shared-generator.js +12 -2
  54. package/src/skills/build-system-yaml/SKILL.md +323 -0
  55. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +410 -0
  56. package/src/skills/build-system-yaml/references/module-spec.md +367 -0
  57. package/src/skills/build-system-yaml/references/system-yaml-spec.md +178 -0
  58. package/src/utils/config-manager.js +54 -0
  59. package/src/utils/context-builder.js +1 -0
  60. package/src/utils/domain-diagram.js +192 -0
  61. package/src/utils/domain-validator.js +1020 -0
  62. package/src/utils/fake-data.js +376 -0
  63. package/src/utils/system-validator.js +319 -199
  64. package/src/utils/yaml-to-entity.js +272 -7
  65. package/templates/aggregate/AggregateMapper.java.ejs +3 -2
  66. package/templates/aggregate/AggregateRepository.java.ejs +3 -2
  67. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +6 -5
  68. package/templates/aggregate/AggregateRoot.java.ejs +60 -2
  69. package/templates/aggregate/DomainEventHandler.java.ejs +4 -1
  70. package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
  71. package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
  72. package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
  73. package/templates/base/docker/Dockerfile.ejs +21 -0
  74. package/templates/base/gradle/build.gradle.ejs +3 -2
  75. package/templates/base/root/AGENTS.md.ejs +306 -45
  76. package/templates/crud/ApplicationMapper.java.ejs +4 -0
  77. package/templates/crud/Controller.java.ejs +4 -4
  78. package/templates/crud/CreateCommand.java.ejs +4 -0
  79. package/templates/crud/CreateCommandHandler.java.ejs +3 -6
  80. package/templates/crud/CreateItemDto.java.ejs +4 -0
  81. package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
  82. package/templates/crud/DeleteCommandHandler.java.ejs +12 -6
  83. package/templates/crud/EndpointsController.java.ejs +6 -6
  84. package/templates/crud/FindByQuery.java.ejs +1 -1
  85. package/templates/crud/FindByQueryHandler.java.ejs +3 -9
  86. package/templates/crud/GetQueryHandler.java.ejs +2 -5
  87. package/templates/crud/ListQuery.java.ejs +1 -1
  88. package/templates/crud/ListQueryHandler.java.ejs +8 -14
  89. package/templates/crud/ScaffoldCommandHandler.java.ejs +2 -4
  90. package/templates/crud/ScaffoldQuery.java.ejs +3 -2
  91. package/templates/crud/ScaffoldQueryHandler.java.ejs +5 -6
  92. package/templates/crud/SubEntityAddCommand.java.ejs +4 -0
  93. package/templates/crud/SubEntityAddCommandHandler.java.ejs +2 -6
  94. package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +2 -7
  95. package/templates/crud/TransitionCommandHandler.java.ejs +2 -6
  96. package/templates/crud/UpdateCommand.java.ejs +4 -0
  97. package/templates/crud/UpdateCommandHandler.java.ejs +2 -5
  98. package/templates/evaluate/report.html.ejs +394 -2
  99. package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
  100. package/templates/kafka-event/Event.java.ejs +9 -0
  101. package/templates/kafka-listener/KafkaController.java.ejs +1 -1
  102. package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
  103. package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
  104. package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
  105. package/templates/kafka-listener/ListenerCommandHandler.java.ejs +25 -0
  106. package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
  107. package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
  108. package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
  109. package/templates/mock/MockEvent.java.ejs +10 -0
  110. package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
  111. package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
  112. package/templates/mock/SpringEventListener.java.ejs +61 -0
  113. package/templates/ports/PortDomainModel.java.ejs +35 -0
  114. package/templates/ports/PortFeignAdapter.java.ejs +67 -0
  115. package/templates/ports/PortFeignClient.java.ejs +45 -0
  116. package/templates/ports/PortFeignConfig.java.ejs +24 -0
  117. package/templates/ports/PortInterface.java.ejs +45 -0
  118. package/templates/ports/PortNestedType.java.ejs +28 -0
  119. package/templates/ports/PortRequestDto.java.ejs +30 -0
  120. package/templates/ports/PortResponseDto.java.ejs +28 -0
  121. package/templates/postman/Collection.json.ejs +1 -1
  122. package/templates/postman/UnifiedCollection.json.ejs +185 -0
  123. package/templates/shared/annotations/LogAfter.java.ejs +2 -0
  124. package/templates/shared/annotations/LogBefore.java.ejs +2 -0
  125. package/templates/shared/annotations/LogExceptions.java.ejs +2 -0
  126. package/templates/shared/annotations/LogLevel.java.ejs +8 -0
  127. package/templates/shared/annotations/LogTimer.java.ejs +1 -0
  128. package/templates/shared/annotations/Loggable.java.ejs +17 -0
  129. package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
  130. package/templates/shared/configurations/loggerConfig/HandlerLogs.java.ejs +120 -32
  131. package/templates/shared/errorMessage/ErrorResponse.java.ejs +23 -0
  132. package/templates/shared/handlerException/HandlerExceptions.java.ejs +99 -79
  133. package/src/commands/generate-system.js +0 -243
  134. package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +0 -1103
  135. package/templates/base/root/skill-build-domain-yaml.ejs +0 -292
  136. package/templates/base/root/skill-build-system-yaml.ejs +0 -252
  137. package/templates/shared/errorMessage/ErrorMessage.java.ejs +0 -5
  138. package/templates/shared/errorMessage/FullErrorMessage.java.ejs +0 -9
  139. package/templates/shared/errorMessage/ShortErrorMessage.java.ejs +0 -6
@@ -1,12 +1,11 @@
1
1
  package <%= packageName %>.shared.infrastructure.handlerException;
2
2
 
3
-
4
3
  import <%= packageName %>.shared.domain.customExceptions.*;
5
- import <%= packageName %>.shared.domain.errorMessage.ErrorMessage;
6
- import <%= packageName %>.shared.domain.errorMessage.FullErrorMessage;
7
- import <%= packageName %>.shared.domain.errorMessage.ShortErrorMessage;
4
+ import <%= packageName %>.shared.domain.errorMessage.ErrorResponse;
8
5
  import feign.RetryableException;
9
6
  import jakarta.validation.ConstraintViolationException;
7
+ import org.slf4j.Logger;
8
+ import org.slf4j.LoggerFactory;
10
9
  import org.springframework.dao.DataIntegrityViolationException;
11
10
  import org.springframework.http.HttpStatus;
12
11
  import org.springframework.http.converter.HttpMessageNotReadableException;
@@ -25,140 +24,161 @@ import org.springframework.web.server.MethodNotAllowedException;
25
24
  import org.springframework.web.server.ServerWebInputException;
26
25
  import org.yaml.snakeyaml.constructor.DuplicateKeyException;
27
26
 
28
- import java.util.ArrayList;
29
27
  import java.util.List;
30
28
 
31
29
  @RestControllerAdvice
32
30
  public class HandlerExceptions {
33
31
 
32
+ private static final Logger log = LoggerFactory.getLogger(HandlerExceptions.class);
33
+
34
+ // ── Validation errors ──────────────────────────────────────────
35
+
34
36
  @ResponseStatus(HttpStatus.BAD_REQUEST)
35
37
  @ExceptionHandler(MethodArgumentNotValidException.class)
36
38
  @ResponseBody
37
- public ErrorMessage onMethodArgumentNotValidException(MethodArgumentNotValidException ex){
38
- List<String> messagesErrors = new ArrayList<>();
39
- ex.getBindingResult().getFieldErrors().forEach(error -> {
40
- messagesErrors.add(error.getField() + " " + error.getDefaultMessage());
41
- });
42
- return new FullErrorMessage(messagesErrors,"BadRequest",HttpStatus.BAD_REQUEST.value());
39
+ public ErrorResponse onMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
40
+ List<String> details = ex.getBindingResult().getFieldErrors().stream()
41
+ .map(error -> error.getField() + " " + error.getDefaultMessage())
42
+ .toList();
43
+ return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request", "Validation failed", details);
43
44
  }
44
45
 
45
-
46
46
  @ResponseStatus(HttpStatus.BAD_REQUEST)
47
47
  @ExceptionHandler(ConstraintViolationException.class)
48
48
  @ResponseBody
49
- public ErrorMessage onConstraintValidationException(ConstraintViolationException ex){
50
- List<String> errors = new ArrayList<>();
51
- ex.getConstraintViolations().forEach( error -> {
52
- errors.add(error.getMessage());
53
- });
54
- return new FullErrorMessage(errors,"Bad Request",HttpStatus.BAD_REQUEST.value());
49
+ public ErrorResponse onConstraintValidationException(ConstraintViolationException ex) {
50
+ List<String> details = ex.getConstraintViolations().stream()
51
+ .map(v -> v.getMessage())
52
+ .toList();
53
+ return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request", "Constraint violation", details);
55
54
  }
56
55
 
57
- //Spring Exceptions
58
- @ResponseStatus(HttpStatus.BAD_REQUEST)
59
- @ExceptionHandler({
60
- DuplicateKeyException.class,
61
- MethodNotAllowedException.class,
62
- ServerWebInputException.class,
63
- MethodArgumentTypeMismatchException.class,
64
- HttpMessageNotReadableException.class,
65
- DataIntegrityViolationException.class
66
- })
56
+ @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
57
+ @ExceptionHandler(ValidationException.class)
67
58
  @ResponseBody
68
- public ErrorMessage onSpringBadRequest(Exception ex){
69
- return new FullErrorMessage( List.of(ex.getClass().getSimpleName()), "Bad Request",HttpStatus.BAD_REQUEST.value());
59
+ public ErrorResponse onValidationException(ValidationException ex) {
60
+ String message = ex.getRowNumber() != null
61
+ ? "Row " + ex.getRowNumber() + ": " + ex.getMessage()
62
+ : ex.getMessage();
63
+ return new ErrorResponse(HttpStatus.UNPROCESSABLE_ENTITY.value(), "Validation Error", message);
70
64
  }
71
65
 
72
- @ResponseStatus(HttpStatus.FORBIDDEN)
73
- @ExceptionHandler(
74
- AuthorizationDeniedException.class
75
- )
76
- @ResponseBody
77
- public ErrorMessage onAuthorizationDeniedException( AuthorizationDeniedException ex) {
78
- if (ex.getMessage() == null) return new ShortErrorMessage("Forbidden", HttpStatus.FORBIDDEN.value());
79
- return new FullErrorMessage(List.of(ex.getMessage()), "Forbidden", HttpStatus.FORBIDDEN.value());
80
- }
66
+ // ── Spring framework errors ────────────────────────────────────
81
67
 
82
- // custom exceptions
83
- //------------------
84
68
  @ResponseStatus(HttpStatus.BAD_REQUEST)
85
- @ExceptionHandler(BadRequestException.class)
69
+ @ExceptionHandler({
70
+ DuplicateKeyException.class,
71
+ MethodNotAllowedException.class,
72
+ ServerWebInputException.class,
73
+ MethodArgumentTypeMismatchException.class,
74
+ HttpMessageNotReadableException.class
75
+ })
86
76
  @ResponseBody
87
- public ErrorMessage onBadRequestException(BadRequestException ex) {
88
- if (ex.getMessage() == null) return new ShortErrorMessage("Bad Request", HttpStatus.BAD_REQUEST.value());
89
- return new FullErrorMessage(List.of(ex.getMessage()),"Bad Request", HttpStatus.BAD_REQUEST.value());
77
+ public ErrorResponse onSpringBadRequest(Exception ex) {
78
+ return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request", "Malformed request");
90
79
  }
91
80
 
92
- @ResponseStatus(HttpStatus.NOT_FOUND)
93
- @ExceptionHandler(NotFoundException.class)
81
+ @ResponseStatus(HttpStatus.CONFLICT)
82
+ @ExceptionHandler(DataIntegrityViolationException.class)
94
83
  @ResponseBody
95
- public ErrorMessage onNotFoundException(NotFoundException ex) {
96
- if (ex.getMessage() == null) return new ShortErrorMessage("Not Found", HttpStatus.NOT_FOUND.value());
97
- return new FullErrorMessage(List.of(ex.getMessage()), "Not Found", HttpStatus.NOT_FOUND.value());
84
+ public ErrorResponse onDataIntegrityViolation(DataIntegrityViolationException ex) {
85
+ return new ErrorResponse(HttpStatus.CONFLICT.value(), "Conflict", "Data integrity violation — a constraint was not satisfied");
98
86
  }
99
87
 
88
+ // ── Security errors ────────────────────────────────────────────
89
+
100
90
  @ResponseStatus(HttpStatus.UNAUTHORIZED)
101
91
  @ExceptionHandler({
102
- UnauthorizedException.class,
103
- BadCredentialsException.class,
104
- DisabledException.class,
105
- LockedException.class,
106
- InternalAuthenticationServiceException.class
92
+ UnauthorizedException.class,
93
+ BadCredentialsException.class,
94
+ DisabledException.class,
95
+ LockedException.class,
96
+ InternalAuthenticationServiceException.class
107
97
  })
108
98
  @ResponseBody
109
- public ErrorMessage onUnauthorizedException(Exception ex) {
110
- if (ex.getMessage() == null) return new ShortErrorMessage("Unauthorized", HttpStatus.UNAUTHORIZED.value());
111
- return new FullErrorMessage(List.of(ex.getMessage()), "Unauthorized", HttpStatus.UNAUTHORIZED.value());
99
+ public ErrorResponse onUnauthorizedException(Exception ex) {
100
+ return new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), "Unauthorized",
101
+ ex.getMessage() != null ? ex.getMessage() : "Authentication required");
112
102
  }
113
103
 
114
104
  @ResponseStatus(HttpStatus.FORBIDDEN)
115
- @ExceptionHandler(
116
- ForbiddenException.class
117
- )
105
+ @ExceptionHandler({ForbiddenException.class, AuthorizationDeniedException.class})
118
106
  @ResponseBody
119
- public ErrorMessage onForbiddenException( ForbiddenException ex) {
120
- if (ex.getMessage() == null) return new ShortErrorMessage("Forbidden", HttpStatus.FORBIDDEN.value());
121
- return new FullErrorMessage(List.of(ex.getMessage()), "Forbidden", HttpStatus.FORBIDDEN.value());
107
+ public ErrorResponse onForbiddenException(Exception ex) {
108
+ return new ErrorResponse(HttpStatus.FORBIDDEN.value(), "Forbidden",
109
+ ex.getMessage() != null ? ex.getMessage() : "Access denied");
110
+ }
111
+
112
+ // ── Custom domain exceptions ───────────────────────────────────
113
+
114
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
115
+ @ExceptionHandler(BadRequestException.class)
116
+ @ResponseBody
117
+ public ErrorResponse onBadRequestException(BadRequestException ex) {
118
+ return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request",
119
+ ex.getMessage() != null ? ex.getMessage() : "Bad request");
120
+ }
121
+
122
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
123
+ @ExceptionHandler(ImportFileException.class)
124
+ @ResponseBody
125
+ public ErrorResponse onImportFileException(ImportFileException ex) {
126
+ return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Import Error",
127
+ ex.getMessage() != null ? ex.getMessage() : "File import failed");
128
+ }
129
+
130
+ @ResponseStatus(HttpStatus.NOT_FOUND)
131
+ @ExceptionHandler(NotFoundException.class)
132
+ @ResponseBody
133
+ public ErrorResponse onNotFoundException(NotFoundException ex) {
134
+ return new ErrorResponse(HttpStatus.NOT_FOUND.value(), "Not Found",
135
+ ex.getMessage() != null ? ex.getMessage() : "Resource not found");
122
136
  }
123
137
 
124
138
  @ResponseStatus(HttpStatus.CONFLICT)
125
139
  @ExceptionHandler(ConflictException.class)
126
140
  @ResponseBody
127
- public ErrorMessage onConflictException(ConflictException ex) {
128
- if (ex.getMessage() == null) return new ShortErrorMessage("Conflict", HttpStatus.CONFLICT.value());
129
- return new FullErrorMessage(List.of(ex.getMessage()), "Conflict", HttpStatus.CONFLICT.value());
141
+ public ErrorResponse onConflictException(ConflictException ex) {
142
+ return new ErrorResponse(HttpStatus.CONFLICT.value(), "Conflict",
143
+ ex.getMessage() != null ? ex.getMessage() : "Resource conflict");
130
144
  }
131
145
 
132
146
  @ResponseStatus(HttpStatus.CONFLICT)
133
147
  @ExceptionHandler(InvalidStateTransitionException.class)
134
148
  @ResponseBody
135
- public ErrorMessage onInvalidStateTransitionException(InvalidStateTransitionException ex) {
136
- if (ex.getMessage() == null) return new ShortErrorMessage("Invalid State Transition", HttpStatus.CONFLICT.value());
137
- return new FullErrorMessage(List.of(ex.getMessage()), "Invalid State Transition", HttpStatus.CONFLICT.value());
149
+ public ErrorResponse onInvalidStateTransitionException(InvalidStateTransitionException ex) {
150
+ return new ErrorResponse(HttpStatus.CONFLICT.value(), "Invalid State Transition",
151
+ ex.getMessage() != null ? ex.getMessage() : "State transition not allowed");
138
152
  }
139
153
 
140
154
  @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
141
155
  @ExceptionHandler(BusinessException.class)
142
156
  @ResponseBody
143
- public ErrorMessage onBusinessException(BusinessException ex) {
144
- if (ex.getMessage() == null) return new ShortErrorMessage("Business Rule Violation", HttpStatus.UNPROCESSABLE_ENTITY.value());
145
- return new FullErrorMessage(List.of(ex.getMessage()), "Business Rule Violation", HttpStatus.UNPROCESSABLE_ENTITY.value());
157
+ public ErrorResponse onBusinessException(BusinessException ex) {
158
+ return new ErrorResponse(HttpStatus.UNPROCESSABLE_ENTITY.value(), "Business Rule Violation",
159
+ ex.getMessage() != null ? ex.getMessage() : "A business rule was violated");
146
160
  }
147
161
 
148
- //error de feign client
162
+ // ── External service errors ────────────────────────────────────
163
+
149
164
  @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
150
165
  @ExceptionHandler(RetryableException.class)
151
166
  @ResponseBody
152
- public ErrorMessage onRetryableException(RetryableException ex) {
153
- return new ShortErrorMessage(ex.request().url() + " not available", HttpStatus.SERVICE_UNAVAILABLE.value());
167
+ public ErrorResponse onRetryableException(RetryableException ex) {
168
+ log.error("External service unavailable: {}", ex.request().url(), ex);
169
+ return new ErrorResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), "Service Unavailable",
170
+ "An external service is temporarily unavailable");
154
171
  }
155
172
 
156
- //Internal Server Error
173
+ // ── Catch-all ──────────────────────────────────────────────────
174
+
157
175
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
158
176
  @ExceptionHandler(Exception.class)
159
177
  @ResponseBody
160
- public ErrorMessage onServerError(Exception ex){
161
- return new FullErrorMessage(List.of(ex.getClass().getSimpleName()) ,"Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR.value());
178
+ public ErrorResponse onServerError(Exception ex) {
179
+ log.error("Unhandled exception", ex);
180
+ return new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error",
181
+ "An unexpected error occurred");
162
182
  }
163
183
 
164
184
  }
@@ -1,243 +0,0 @@
1
- 'use strict';
2
-
3
- const chalk = require('chalk');
4
- const path = require('path');
5
- const fs = require('fs-extra');
6
- const yaml = require('js-yaml');
7
- const pluralize = require('pluralize');
8
-
9
- const ConfigManager = require('../utils/config-manager');
10
- const { isEva4jProject } = require('../utils/validator');
11
- const { toCamelCase, toPascalCase, toPackagePath } = require('../utils/naming');
12
-
13
- const addModuleCommand = require('./add-module');
14
- const addKafkaClientCommand = require('./add-kafka-client');
15
-
16
- // Supported brokers → add-client command mapping
17
- const BROKER_CLIENT_COMMANDS = {
18
- kafka: addKafkaClientCommand,
19
- };
20
-
21
- async function generateSystemCommand() {
22
- const projectDir = process.cwd();
23
-
24
- if (!(await isEva4jProject(projectDir))) {
25
- console.error(chalk.red('āŒ Not in an eva4j project directory'));
26
- console.error(chalk.gray('Run this command inside a project created with eva4j'));
27
- process.exit(1);
28
- }
29
-
30
- // ── Read system.yaml ──────────────────────────────────────────────────────
31
- const systemYamlPath = path.join(projectDir, 'system.yaml');
32
- if (!(await fs.pathExists(systemYamlPath))) {
33
- console.error(chalk.red('āŒ system.yaml not found in project root'));
34
- console.error(chalk.gray('Create a system.yaml file first'));
35
- process.exit(1);
36
- }
37
-
38
- let systemConfig;
39
- try {
40
- const content = await fs.readFile(systemYamlPath, 'utf-8');
41
- systemConfig = yaml.load(content);
42
- } catch (err) {
43
- console.error(chalk.red('āŒ Failed to parse system.yaml:'), err.message);
44
- process.exit(1);
45
- }
46
-
47
- const { messaging, modules = [] } = systemConfig;
48
-
49
- if (!modules.length) {
50
- console.log(chalk.yellow('āš ļø No modules defined in system.yaml'));
51
- process.exit(0);
52
- }
53
-
54
- console.log(chalk.blue('\nšŸš€ eva generate system\n'));
55
-
56
- const configManager = new ConfigManager(projectDir);
57
-
58
- // ── Step 1: Add modules ───────────────────────────────────────────────────
59
- console.log(chalk.blue('\nšŸ“¦ Adding modules...\n'));
60
-
61
- for (const mod of modules) {
62
- const modulePackageName = toCamelCase(mod.name);
63
- const alreadyExists = await configManager.moduleExists(modulePackageName);
64
-
65
- if (alreadyExists) {
66
- console.log(chalk.gray(` āœ“ Module '${mod.name}' already exists, skipping`));
67
- } else {
68
- await addModuleCommand(mod.name, {});
69
- }
70
- }
71
-
72
- // ── Step 2: Messaging broker client ──────────────────────────────────────
73
- if (messaging && messaging.enabled === true) {
74
- const broker = messaging.broker;
75
- const addClientFn = BROKER_CLIENT_COMMANDS[broker];
76
-
77
- if (!addClientFn) {
78
- console.log(chalk.yellow(` āš ļø Broker '${broker}' is not yet supported. Skipping client setup.`));
79
- } else {
80
- const alreadyInstalled = await configManager.featureExists(broker);
81
- if (alreadyInstalled) {
82
- console.log(chalk.gray(` āœ“ ${broker}-client already installed, skipping\n`));
83
- } else {
84
- console.log(chalk.blue(`\nšŸ“” Adding ${broker}-client...\n`));
85
- await addClientFn();
86
- }
87
- }
88
- }
89
-
90
- // ── Step 3: Generate domain.yaml per module ───────────────────────────────
91
- const projectConfig = await configManager.loadProjectConfig();
92
- if (!projectConfig) {
93
- console.error(chalk.red('āŒ Could not load project configuration'));
94
- process.exit(1);
95
- }
96
-
97
- const packagePath = toPackagePath(projectConfig.packageName);
98
-
99
- console.log(chalk.blue('\nšŸ“„ Generating domain.yaml skeletons...\n'));
100
-
101
- for (const mod of modules) {
102
- const modulePackageName = toCamelCase(mod.name);
103
- const moduleDir = path.join(projectDir, 'src', 'main', 'java', packagePath, modulePackageName);
104
- const domainYamlPath = path.join(moduleDir, 'domain.yaml');
105
-
106
- const existed = await fs.pathExists(domainYamlPath);
107
- const content = buildDomainYaml(mod, systemConfig);
108
- await fs.writeFile(domainYamlPath, content, 'utf-8');
109
-
110
- if (existed) {
111
- console.log(chalk.yellow(` ā™»ļø ${modulePackageName}/domain.yaml overwritten`));
112
- } else {
113
- console.log(chalk.green(` ✨ ${modulePackageName}/domain.yaml created`));
114
- }
115
- }
116
-
117
- console.log(chalk.blue('\nāœ… System bootstrap complete!\n'));
118
- console.log(chalk.white('Next steps:'));
119
- console.log(chalk.gray(" 1. Edit each module's domain.yaml — add fields, enums, and refine the aggregate"));
120
- console.log(chalk.gray(' 2. Run: eva g entities <module> (for each module)'));
121
- console.log(chalk.gray('\n Tip: run eva system validate to check cross-module consistency'));
122
- console.log();
123
- }
124
-
125
- // ── Domain YAML builder ───────────────────────────────────────────────────────
126
-
127
- function buildDomainYaml(mod, systemConfig) {
128
- const integrations = systemConfig.integrations || {};
129
- const asyncEvents = integrations.async || [];
130
- const syncCalls = integrations.sync || [];
131
-
132
- const moduleName = mod.name;
133
- const aggregateName = toPascalCase(pluralize.singular(moduleName));
134
- const entityName = aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1);
135
- const tableName = moduleName.replace(/-/g, '_');
136
-
137
- // Events this module produces
138
- const producedEvents = asyncEvents.filter(e => e.producer === moduleName);
139
- // Sync calls this module makes as caller
140
- const outboundPorts = syncCalls.filter(s => s.caller === moduleName);
141
- // REST endpoints exposed by this module
142
- const exposes = mod.exposes || [];
143
-
144
- const lines = [];
145
- const today = new Date().toISOString().split('T')[0];
146
-
147
- lines.push(`# domain.yaml — ${moduleName}`);
148
- lines.push(`# Generated by: eva generate system (${today})`);
149
- lines.push(`#`);
150
- lines.push(`# TODO: Complete this file:`);
151
- lines.push(`# - Add entity fields under aggregates[].entities[].fields`);
152
- if (producedEvents.length) lines.push(`# - Add event fields under aggregates[].events[].fields`);
153
- if (outboundPorts.length) lines.push(`# - Add response shapes to ports[].methods[].response`);
154
- if (exposes.length) lines.push(`# - Verify endpoints[].operations match the use cases in aggregates[]`);
155
- lines.push(``);
156
-
157
- // ── aggregates ────────────────────────────────────────────────────────────
158
- lines.push(`aggregates:`);
159
- lines.push(` - name: ${aggregateName}`);
160
- lines.push(` entities:`);
161
- lines.push(` - name: ${entityName}`);
162
- lines.push(` isRoot: true`);
163
- lines.push(` tableName: ${tableName}`);
164
- lines.push(` audit:`);
165
- lines.push(` enabled: true`);
166
- lines.push(` fields:`);
167
- lines.push(` - name: id`);
168
- lines.push(` type: String`);
169
- lines.push(` # TODO: add more fields`);
170
-
171
- if (producedEvents.length) {
172
- lines.push(` events:`);
173
- for (const ev of producedEvents) {
174
- lines.push(` - name: ${ev.event}`);
175
- lines.push(` fields: [] # TODO: add event fields`);
176
- lines.push(` kafka: true`);
177
- }
178
- }
179
-
180
- lines.push(``);
181
-
182
- // ── endpoints ─────────────────────────────────────────────────────────────
183
- if (exposes.length) {
184
- // Derive basePath from the first exposed endpoint (e.g. /orders/{id} → /orders)
185
- const basePath = '/' + (exposes[0].path || '').replace(/^\//, '').split('/')[0];
186
- lines.push(`endpoints:`);
187
- lines.push(` basePath: ${basePath}`);
188
- lines.push(` versions:`);
189
- lines.push(` - version: v1`);
190
- lines.push(` operations:`);
191
- for (const ep of exposes) {
192
- lines.push(` - useCase: ${ep.useCase}`);
193
- lines.push(` method: ${ep.method}`);
194
- lines.push(` path: ${ep.path}`);
195
- if (ep.description) lines.push(` description: "${ep.description}"`);
196
- }
197
- lines.push(``);
198
- }
199
-
200
- // ── ports ─────────────────────────────────────────────────────────────────
201
- if (outboundPorts.length) {
202
- lines.push(`ports:`);
203
- for (const port of outboundPorts) {
204
- lines.push(` - name: ${port.port}`);
205
- lines.push(` target: ${port.calls} # from system.yaml integrations.sync`);
206
- lines.push(` methods:`);
207
- for (const endpoint of (port.using || [])) {
208
- const methodName = deriveMethodName(endpoint);
209
- lines.push(` - name: ${methodName} # TODO: rename if needed`);
210
- lines.push(` http: ${endpoint}`);
211
- lines.push(` response: [] # TODO: add response fields`);
212
- }
213
- }
214
- lines.push(``);
215
- }
216
-
217
- return lines.join('\n');
218
- }
219
-
220
- /**
221
- * Derive a camelCase Java method name from an HTTP entry like "GET /customers/{id}"
222
- */
223
- function deriveMethodName(httpEntry) {
224
- const parts = httpEntry.trim().split(/\s+/);
225
- const method = (parts[0] || 'GET').toUpperCase();
226
- const urlPath = parts[1] || '/';
227
-
228
- const segments = urlPath.split('/').filter(s => s.length > 0);
229
- const hasId = segments.some(s => s.charAt(0) === '{');
230
- const resourceSegments = segments.filter(s => s.charAt(0) !== '{');
231
- const lastResource = resourceSegments[resourceSegments.length - 1] || 'resource';
232
- const singular = pluralize.singular(lastResource);
233
- const pascal = toPascalCase(singular);
234
-
235
- if (method === 'GET' && hasId) return `find${pascal}ById`;
236
- if (method === 'GET') return `findAll${toPascalCase(lastResource)}`;
237
- if (method === 'POST') return `create${pascal}`;
238
- if (method === 'PUT' || method === 'PATCH') return `update${pascal}`;
239
- if (method === 'DELETE') return `delete${pascal}`;
240
- return toCamelCase(`${method.toLowerCase()}_${lastResource}`);
241
- }
242
-
243
- module.exports = generateSystemCommand;