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
@@ -0,0 +1,185 @@
1
+ <%
2
+ /**
3
+ * UnifiedCollection.json.ejs
4
+ *
5
+ * Generates a single Postman 2.1.0 collection covering ALL modules in the
6
+ * system. The hierarchy is:
7
+ *
8
+ * {SystemName} API
9
+ * └── {ModuleName}
10
+ * └── {AggregateName}
11
+ * ├── POST Create {Agg}
12
+ * ├── GET Get {Agg} by ID
13
+ * ├── GET Get All {Agg}s
14
+ * ├── PUT Update {Agg}
15
+ * ├── DELETE Delete {Agg}
16
+ * └── PUT {Custom} {Agg} (confirm, cancel, …)
17
+ *
18
+ * Context expected:
19
+ * - systemName : string
20
+ * - collectionId : string (UUID)
21
+ * - port : number
22
+ * - modules : Array<ModuleCtx>
23
+ *
24
+ * ModuleCtx:
25
+ * - name : string (module name, e.g. "product")
26
+ * - aggregates : Array<AggregateCtx>
27
+ *
28
+ * AggregateCtx:
29
+ * - name : string (PascalCase, e.g. "Product")
30
+ * - trackUser : boolean
31
+ * - idType : string ("String" | "UUID" | "Long")
32
+ * - exampleId : string
33
+ * - operations : Array<OperationCtx> (when endpoints: section exists)
34
+ * - defaultCrud : boolean (true when no endpoints: section)
35
+ * - bodies : { [useCase]: object } (pre-generated JSON bodies keyed by useCase)
36
+ *
37
+ * OperationCtx:
38
+ * - useCase : string
39
+ * - method : string (GET, POST, PUT, PATCH, DELETE)
40
+ * - path : string (e.g. "/{id}" or "/publish")
41
+ * - basePath : string (e.g. "/products")
42
+ * - version : string (e.g. "v1")
43
+ */
44
+
45
+ function buildUrl(port, version, basePath, opPath) {
46
+ // Normalise: remove leading slash from basePath if present, and from opPath
47
+ let bPath = basePath.replace(/^\//, '');
48
+ let oPath = opPath.replace(/^\//, '');
49
+
50
+ // Build full path segments
51
+ const segments = ['api', version];
52
+ if (bPath) segments.push(...bPath.split('/').filter(Boolean));
53
+ if (oPath) segments.push(...oPath.split('/').filter(Boolean));
54
+
55
+ const raw = 'http://localhost:' + port + '/' + segments.join('/');
56
+ return { raw, segments, port: String(port) };
57
+ }
58
+
59
+ function hasBody(method) {
60
+ return method === 'POST' || method === 'PUT' || method === 'PATCH';
61
+ }
62
+
63
+ // ----- Build the items tree ------------------------------------------------
64
+ const moduleItems = [];
65
+
66
+ for (const mod of modules) {
67
+ const aggregateItems = [];
68
+
69
+ for (const agg of mod.aggregates) {
70
+ const requestItems = [];
71
+
72
+ if (agg.operations && agg.operations.length > 0) {
73
+ // Endpoint-driven: use declared operations
74
+ for (const op of agg.operations) {
75
+ const url = buildUrl(port, op.version, op.basePath, op.path);
76
+ // Replace {id} with example ID
77
+ const rawUrl = url.raw.replace(/\{id\}/g, agg.exampleId);
78
+ const pathSegments = url.segments.map(s => s === '{id}' ? agg.exampleId : s);
79
+
80
+ const headers = [{ key: 'Content-Type', value: 'application/json', type: 'text' }];
81
+ if (agg.trackUser) {
82
+ headers.push({ key: 'X-User', value: 'system', type: 'text' });
83
+ }
84
+
85
+ const item = {
86
+ name: op.useCase,
87
+ request: {
88
+ method: op.method,
89
+ header: headers,
90
+ url: {
91
+ raw: rawUrl,
92
+ protocol: 'http',
93
+ host: ['localhost'],
94
+ port: String(port),
95
+ path: pathSegments
96
+ }
97
+ },
98
+ response: []
99
+ };
100
+
101
+ if (hasBody(op.method) && agg.bodies[op.useCase]) {
102
+ item.request.body = {
103
+ mode: 'raw',
104
+ raw: JSON.stringify(agg.bodies[op.useCase], null, 2),
105
+ options: { raw: { language: 'json' } }
106
+ };
107
+ }
108
+
109
+ requestItems.push(item);
110
+ }
111
+ } else if (agg.defaultCrud) {
112
+ // No endpoints section → generate 5 standard CRUD requests
113
+ const resource = agg.resourceNameKebab;
114
+ const crudOps = [
115
+ { useCase: 'Create ' + agg.name, method: 'POST', path: '' },
116
+ { useCase: 'Get ' + agg.name + ' by ID', method: 'GET', path: '/' + agg.exampleId },
117
+ { useCase: 'Get All ' + agg.name + 's', method: 'GET', path: '' },
118
+ { useCase: 'Update ' + agg.name, method: 'PUT', path: '/' + agg.exampleId },
119
+ { useCase: 'Delete ' + agg.name, method: 'DELETE', path: '/' + agg.exampleId },
120
+ ];
121
+
122
+ for (const op of crudOps) {
123
+ const url = buildUrl(port, 'v1', resource, op.path);
124
+
125
+ const headers = [{ key: 'Content-Type', value: 'application/json', type: 'text' }];
126
+ if (agg.trackUser) {
127
+ headers.push({ key: 'X-User', value: 'system', type: 'text' });
128
+ }
129
+
130
+ const item = {
131
+ name: op.useCase,
132
+ request: {
133
+ method: op.method,
134
+ header: headers,
135
+ url: {
136
+ raw: url.raw,
137
+ protocol: 'http',
138
+ host: ['localhost'],
139
+ port: String(port),
140
+ path: url.segments
141
+ }
142
+ },
143
+ response: []
144
+ };
145
+
146
+ if (hasBody(op.method) && agg.bodies['default']) {
147
+ item.request.body = {
148
+ mode: 'raw',
149
+ raw: JSON.stringify(agg.bodies['default'], null, 2),
150
+ options: { raw: { language: 'json' } }
151
+ };
152
+ }
153
+
154
+ requestItems.push(item);
155
+ }
156
+ }
157
+
158
+ if (requestItems.length > 0) {
159
+ aggregateItems.push({
160
+ name: agg.name,
161
+ item: requestItems
162
+ });
163
+ }
164
+ }
165
+
166
+ if (aggregateItems.length > 0) {
167
+ moduleItems.push({
168
+ name: mod.name,
169
+ item: aggregateItems
170
+ });
171
+ }
172
+ }
173
+
174
+ const collection = {
175
+ info: {
176
+ _postman_id: collectionId,
177
+ name: systemName + ' API',
178
+ description: 'Auto-generated unified API collection for all modules in ' + systemName + '.\nGenerated by eva4j with realistic fake data.',
179
+ schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json',
180
+ version: '1.0.0'
181
+ },
182
+ item: moduleItems
183
+ };
184
+ %>
185
+ <%- JSON.stringify(collection, null, 2) %>
@@ -8,4 +8,6 @@ import java.lang.annotation.Target;
8
8
  @Target(ElementType.METHOD)
9
9
  @Retention(RetentionPolicy.RUNTIME)
10
10
  public @interface LogAfter {
11
+ LogLevel level() default LogLevel.INFO;
12
+ String[] excludeArgs() default {};
11
13
  }
@@ -8,4 +8,6 @@ import java.lang.annotation.Target;
8
8
  @Target(ElementType.METHOD)
9
9
  @Retention(RetentionPolicy.RUNTIME)
10
10
  public @interface LogBefore {
11
+ LogLevel level() default LogLevel.INFO;
12
+ String[] excludeArgs() default {};
11
13
  }
@@ -8,4 +8,6 @@ import java.lang.annotation.Target;
8
8
  @Target(ElementType.METHOD)
9
9
  @Retention(RetentionPolicy.RUNTIME)
10
10
  public @interface LogExceptions {
11
+ LogLevel level() default LogLevel.WARN;
12
+ String[] excludeArgs() default {};
11
13
  }
@@ -0,0 +1,8 @@
1
+ package <%= packageName %>.shared.domain.annotations;
2
+
3
+ public enum LogLevel {
4
+ TRACE,
5
+ DEBUG,
6
+ INFO,
7
+ WARN;
8
+ }
@@ -8,4 +8,5 @@ import java.lang.annotation.Target;
8
8
  @Target(ElementType.METHOD)
9
9
  @Retention(RetentionPolicy.RUNTIME)
10
10
  public @interface LogTimer {
11
+ LogLevel level() default LogLevel.INFO;
11
12
  }
@@ -0,0 +1,17 @@
1
+ package <%= packageName %>.shared.domain.annotations;
2
+
3
+ import java.lang.annotation.ElementType;
4
+ import java.lang.annotation.Retention;
5
+ import java.lang.annotation.RetentionPolicy;
6
+ import java.lang.annotation.Target;
7
+
8
+ @Target(ElementType.METHOD)
9
+ @Retention(RetentionPolicy.RUNTIME)
10
+ public @interface Loggable {
11
+ boolean before() default true;
12
+ boolean after() default true;
13
+ boolean exceptions() default true;
14
+ boolean timer() default false;
15
+ LogLevel level() default LogLevel.INFO;
16
+ String[] excludeArgs() default {};
17
+ }
@@ -0,0 +1,109 @@
1
+ package <%= packageName %>.shared.infrastructure.configurations.eventPublicationConfig;
2
+
3
+ import java.util.Map;
4
+ import javax.sql.DataSource;
5
+
6
+ import org.hibernate.boot.model.relational.Namespace;
7
+ import org.hibernate.boot.model.relational.Sequence;
8
+ import org.hibernate.mapping.Table;
9
+ import org.hibernate.tool.schema.spi.SchemaFilter;
10
+ import org.hibernate.tool.schema.spi.SchemaFilterProvider;
11
+ import org.slf4j.Logger;
12
+ import org.slf4j.LoggerFactory;
13
+ import org.springframework.boot.ApplicationRunner;
14
+ import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
15
+ import org.springframework.context.annotation.Bean;
16
+ import org.springframework.context.annotation.Configuration;
17
+
18
+ /**
19
+ * Fixes Spring Modulith JPA event_publication table columns.
20
+ * <p>
21
+ * Hibernate maps the internal {@code JpaEventPublication} String fields
22
+ * as {@code varchar(255)} by default, which is too short for serialised
23
+ * domain event payloads.
24
+ * <p>
25
+ * This config does two things:
26
+ * <ol>
27
+ * <li>Excludes the {@code event_publication} table from Hibernate DDL auto
28
+ * so it never tries to ALTER columns back to {@code varchar(255)}.</li>
29
+ * <li>Runs an {@link ApplicationRunner} that ensures the columns are {@code TEXT}.</li>
30
+ * </ol>
31
+ */
32
+ @Configuration
33
+ public class EventPublicationSchemaConfig implements HibernatePropertiesCustomizer {
34
+
35
+ private static final Logger log = LoggerFactory.getLogger(EventPublicationSchemaConfig.class);
36
+
37
+ @Override
38
+ public void customize(Map<String, Object> hibernateProperties) {
39
+ hibernateProperties.put(
40
+ "hibernate.hbm2ddl.schema_filter_provider",
41
+ new EventPublicationFilterProvider()
42
+ );
43
+ }
44
+
45
+ @Bean
46
+ ApplicationRunner eventPublicationSchemaFix(DataSource dataSource) {
47
+ return args -> {
48
+ try (var conn = dataSource.getConnection()) {
49
+ var db = conn.getMetaData().getDatabaseProductName().toLowerCase();
50
+ String[] statements;
51
+
52
+ if (db.contains("postgre")) {
53
+ statements = new String[]{
54
+ "CREATE TABLE IF NOT EXISTS event_publication (id UUID NOT NULL PRIMARY KEY, completion_date TIMESTAMP, event_type TEXT NOT NULL, listener_id TEXT NOT NULL, publication_date TIMESTAMP, serialized_event TEXT NOT NULL)",
55
+ "ALTER TABLE event_publication ALTER COLUMN serialized_event TYPE TEXT",
56
+ "ALTER TABLE event_publication ALTER COLUMN event_type TYPE TEXT",
57
+ "ALTER TABLE event_publication ALTER COLUMN listener_id TYPE TEXT"
58
+ };
59
+ } else if (db.contains("mysql") || db.contains("maria")) {
60
+ statements = new String[]{
61
+ "CREATE TABLE IF NOT EXISTS event_publication (id VARCHAR(36) NOT NULL PRIMARY KEY, completion_date DATETIME, event_type TEXT NOT NULL, listener_id TEXT NOT NULL, publication_date DATETIME, serialized_event TEXT NOT NULL)",
62
+ "ALTER TABLE event_publication MODIFY COLUMN serialized_event TEXT",
63
+ "ALTER TABLE event_publication MODIFY COLUMN event_type TEXT",
64
+ "ALTER TABLE event_publication MODIFY COLUMN listener_id TEXT"
65
+ };
66
+ } else {
67
+ // H2 and other databases
68
+ statements = new String[]{
69
+ "CREATE TABLE IF NOT EXISTS event_publication (id UUID NOT NULL PRIMARY KEY, completion_date TIMESTAMP, event_type TEXT NOT NULL, listener_id TEXT NOT NULL, publication_date TIMESTAMP, serialized_event TEXT NOT NULL)",
70
+ "ALTER TABLE IF EXISTS event_publication ALTER COLUMN serialized_event TEXT",
71
+ "ALTER TABLE IF EXISTS event_publication ALTER COLUMN event_type TEXT",
72
+ "ALTER TABLE IF EXISTS event_publication ALTER COLUMN listener_id TEXT"
73
+ };
74
+ }
75
+
76
+ for (var sql : statements) {
77
+ try (var stmt = conn.createStatement()) {
78
+ stmt.execute(sql);
79
+ } catch (Exception e) {
80
+ // Column already TEXT or statement not applicable — safe to ignore
81
+ }
82
+ }
83
+ log.debug("event_publication columns verified (TEXT)");
84
+ }
85
+ };
86
+ }
87
+
88
+ // ── Hibernate Schema Filter ────────────────────────────────────────────
89
+ // Excludes event_publication from ddl-auto so Hibernate never tries
90
+ // to ALTER its columns back to varchar(255).
91
+
92
+ static class EventPublicationFilterProvider implements SchemaFilterProvider {
93
+ private static final SchemaFilter FILTER = new EventPublicationFilter();
94
+
95
+ @Override public SchemaFilter getCreateFilter() { return FILTER; }
96
+ @Override public SchemaFilter getDropFilter() { return FILTER; }
97
+ @Override public SchemaFilter getMigrateFilter() { return FILTER; }
98
+ @Override public SchemaFilter getValidateFilter() { return FILTER; }
99
+ @Override public SchemaFilter getTruncatorFilter() { return FILTER; }
100
+ }
101
+
102
+ static class EventPublicationFilter implements SchemaFilter {
103
+ @Override public boolean includeNamespace(Namespace namespace) { return true; }
104
+ @Override public boolean includeSequence(Sequence sequence) { return true; }
105
+ @Override public boolean includeTable(Table table) {
106
+ return !"event_publication".equalsIgnoreCase(table.getName());
107
+ }
108
+ }
109
+ }
@@ -1,56 +1,144 @@
1
1
  package <%= packageName %>.shared.infrastructure.configurations.loggerConfig;
2
2
 
3
- import org.apache.logging.log4j.LogManager;
3
+ import <%= packageName %>.shared.domain.annotations.*;
4
4
  import org.aspectj.lang.JoinPoint;
5
5
  import org.aspectj.lang.ProceedingJoinPoint;
6
6
  import org.aspectj.lang.annotation.*;
7
+ import org.aspectj.lang.reflect.MethodSignature;
8
+ import org.slf4j.Logger;
9
+ import org.slf4j.LoggerFactory;
7
10
  import org.springframework.stereotype.Component;
8
11
 
12
+ import java.util.Set;
13
+ import java.util.StringJoiner;
9
14
 
10
15
  @Component
11
16
  @Aspect
12
17
  public class HandlerLogs {
13
18
 
14
- @Before("@annotation(<%= packageName %>.shared.domain.annotations.LogBefore)")
15
- public void executionBeforeMethod(JoinPoint joinPoint){
16
- LogManager.getLogger(joinPoint.getSignature().getDeclaringTypeName()).info(
17
- "Method {} executed with {} arguments",
18
- joinPoint.getSignature().getName(),
19
- joinPoint.getArgs()
20
- );
19
+ // ── Individual annotations ──────────────────────────────────────
20
+
21
+ @Before("@annotation(logBefore)")
22
+ public void executionBeforeMethod(JoinPoint joinPoint, LogBefore logBefore) {
23
+ Logger logger = getLogger(joinPoint);
24
+ log(logger, logBefore.level(),
25
+ "▶ Entering {} with args: [{}]",
26
+ joinPoint.getSignature().getName(),
27
+ formatArgs(joinPoint, Set.of(logBefore.excludeArgs())));
21
28
  }
22
29
 
23
- @AfterReturning(pointcut = "@annotation(<%= packageName %>.shared.domain.annotations.LogAfter)")
24
- public void executionSuccessMethod(JoinPoint joinPoint){
25
- LogManager.getLogger(joinPoint.getSignature().getDeclaringTypeName()).info(
26
- "Method {} executed with {} arguments",
27
- joinPoint.getSignature().getName(),
28
- joinPoint.getArgs()
29
- );
30
+ @AfterReturning(pointcut = "@annotation(logAfter)", returning = "result")
31
+ public void executionSuccessMethod(JoinPoint joinPoint, LogAfter logAfter, Object result) {
32
+ Logger logger = getLogger(joinPoint);
33
+ log(logger, logAfter.level(),
34
+ "◀ Completed {} with args: [{}] | return: {}",
35
+ joinPoint.getSignature().getName(),
36
+ formatArgs(joinPoint, Set.of(logAfter.excludeArgs())),
37
+ result);
30
38
  }
31
39
 
32
- @AfterThrowing(pointcut = "@annotation(<%= packageName %>.shared.domain.annotations.LogExceptions)", throwing = "exception")
33
- public void executionFailMethod(JoinPoint joinPoint, Exception exception){
34
- LogManager.getLogger(joinPoint.getSignature().getDeclaringTypeName()).error(
35
- "Method {} executed with {} arguments throw exception: {}",
36
- joinPoint.getSignature().getName(),
37
- joinPoint.getArgs(),
38
- exception.getClass().getSimpleName()
39
- );
40
+ @AfterThrowing(pointcut = "@annotation(logExceptions)", throwing = "exception")
41
+ public void executionFailMethod(JoinPoint joinPoint, LogExceptions logExceptions, Exception exception) {
42
+ Logger logger = getLogger(joinPoint);
43
+ log(logger, logExceptions.level(),
44
+ "✖ Method {} failed with args: [{}] | exception: {} | message: {}",
45
+ joinPoint.getSignature().getName(),
46
+ formatArgs(joinPoint, Set.of(logExceptions.excludeArgs())),
47
+ exception.getClass().getSimpleName(),
48
+ exception.getMessage());
40
49
  }
41
50
 
42
- @Around("@annotation(<%= packageName %>.shared.domain.annotations.LogTimer)")
43
- public Object executionTimeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
51
+ @Around("@annotation(logTimer)")
52
+ public Object executionTimeMethod(ProceedingJoinPoint joinPoint, LogTimer logTimer) throws Throwable {
44
53
  long start = System.currentTimeMillis();
45
- Object proceed = joinPoint.proceed();
54
+ Object result = joinPoint.proceed();
46
55
  long executionTime = System.currentTimeMillis() - start;
47
- LogManager.getLogger(joinPoint.getSignature().getDeclaringTypeName()).info(
48
- "Method {} executed in {} ms",
49
- joinPoint.getSignature().getName(),
50
- executionTime
51
- );
52
- return proceed;
56
+ Logger logger = getLogger(joinPoint);
57
+ log(logger, logTimer.level(),
58
+ "⏱ Method {} executed in {} ms",
59
+ joinPoint.getSignature().getName(),
60
+ executionTime);
61
+ return result;
53
62
  }
54
63
 
64
+ // ── Unified @Loggable annotation ───────────────────────────────
65
+
66
+ @Around("@annotation(loggable)")
67
+ public Object executionLoggableMethod(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
68
+ Logger logger = getLogger(joinPoint);
69
+ String methodName = joinPoint.getSignature().getName();
70
+ Set<String> excluded = Set.of(loggable.excludeArgs());
71
+
72
+ if (loggable.before()) {
73
+ log(logger, loggable.level(),
74
+ "▶ Entering {} with args: [{}]",
75
+ methodName,
76
+ formatArgs(joinPoint, excluded));
77
+ }
78
+
79
+ long start = loggable.timer() ? System.currentTimeMillis() : 0;
80
+
81
+ try {
82
+ Object result = joinPoint.proceed();
83
+
84
+ if (loggable.after()) {
85
+ log(logger, loggable.level(),
86
+ "◀ Completed {} with args: [{}] | return: {}",
87
+ methodName,
88
+ formatArgs(joinPoint, excluded),
89
+ result);
90
+ }
91
+
92
+ if (loggable.timer()) {
93
+ long executionTime = System.currentTimeMillis() - start;
94
+ log(logger, loggable.level(),
95
+ "⏱ Method {} executed in {} ms",
96
+ methodName,
97
+ executionTime);
98
+ }
99
+
100
+ return result;
101
+ } catch (Exception exception) {
102
+ if (loggable.exceptions()) {
103
+ logger.warn("✖ Method {} failed with args: [{}] | exception: {} | message: {}",
104
+ methodName,
105
+ formatArgs(joinPoint, excluded),
106
+ exception.getClass().getSimpleName(),
107
+ exception.getMessage());
108
+ }
109
+ throw exception;
110
+ }
111
+ }
55
112
 
113
+ // ── Private helpers ────────────────────────────────────────────
114
+
115
+ private Logger getLogger(JoinPoint joinPoint) {
116
+ return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName());
117
+ }
118
+
119
+ private String formatArgs(JoinPoint joinPoint, Set<String> excludedArgs) {
120
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
121
+ String[] paramNames = signature.getParameterNames();
122
+ Object[] args = joinPoint.getArgs();
123
+
124
+ if (paramNames == null || paramNames.length == 0) {
125
+ return "";
126
+ }
127
+
128
+ StringJoiner joiner = new StringJoiner(", ");
129
+ for (int i = 0; i < paramNames.length; i++) {
130
+ joiner.add(paramNames[i] + "=" +
131
+ (excludedArgs.contains(paramNames[i]) ? "[PROTECTED]" : args[i]));
132
+ }
133
+ return joiner.toString();
134
+ }
135
+
136
+ private void log(Logger logger, LogLevel level, String message, Object... args) {
137
+ switch (level) {
138
+ case TRACE -> logger.trace(message, args);
139
+ case DEBUG -> logger.debug(message, args);
140
+ case INFO -> logger.info(message, args);
141
+ case WARN -> logger.warn(message, args);
142
+ }
143
+ }
56
144
  }
@@ -0,0 +1,23 @@
1
+ package <%= packageName %>.shared.domain.errorMessage;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonInclude;
4
+
5
+ import java.time.LocalDateTime;
6
+ import java.util.List;
7
+
8
+ @JsonInclude(JsonInclude.Include.NON_NULL)
9
+ public record ErrorResponse(
10
+ LocalDateTime timestamp,
11
+ int status,
12
+ String error,
13
+ String message,
14
+ List<String> details
15
+ ) {
16
+ public ErrorResponse(int status, String error, String message) {
17
+ this(LocalDateTime.now(), status, error, message, null);
18
+ }
19
+
20
+ public ErrorResponse(int status, String error, String message, List<String> details) {
21
+ this(LocalDateTime.now(), status, error, message, details);
22
+ }
23
+ }