eva4j 1.0.13 → 1.0.14

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 (44) hide show
  1. package/AGENTS.md +51 -9
  2. package/DOMAIN_YAML_GUIDE.md +150 -0
  3. package/bin/eva4j.js +31 -1
  4. package/design-system.md +797 -0
  5. package/docs/commands/EVALUATE_SYSTEM.md +542 -0
  6. package/docs/commands/GENERATE_ENTITIES.md +196 -0
  7. package/docs/commands/INDEX.md +10 -1
  8. package/examples/domain-endpoints-relations.yaml +353 -0
  9. package/examples/domain-endpoints-versioned.yaml +144 -0
  10. package/examples/domain-endpoints.yaml +135 -0
  11. package/examples/system.yaml +289 -0
  12. package/package.json +1 -1
  13. package/src/commands/create.js +6 -3
  14. package/src/commands/evaluate-system.js +384 -0
  15. package/src/commands/generate-entities.js +677 -14
  16. package/src/commands/generate-kafka-event.js +59 -5
  17. package/src/commands/generate-system.js +243 -0
  18. package/src/generators/base-generator.js +9 -1
  19. package/src/utils/naming.js +3 -2
  20. package/src/utils/system-validator.js +314 -0
  21. package/src/utils/yaml-to-entity.js +31 -2
  22. package/templates/aggregate/AggregateRepository.java.ejs +5 -0
  23. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +9 -0
  24. package/templates/aggregate/DomainEventHandler.java.ejs +24 -20
  25. package/templates/aggregate/JpaRepository.java.ejs +5 -0
  26. package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1103 -0
  27. package/templates/base/root/skill-build-domain-yaml.ejs +292 -0
  28. package/templates/base/root/skill-build-system-yaml.ejs +252 -0
  29. package/templates/base/root/system.yaml.ejs +97 -0
  30. package/templates/crud/EndpointsController.java.ejs +178 -0
  31. package/templates/crud/FindByQuery.java.ejs +17 -0
  32. package/templates/crud/FindByQueryHandler.java.ejs +57 -0
  33. package/templates/crud/ScaffoldCommand.java.ejs +12 -0
  34. package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
  35. package/templates/crud/ScaffoldQuery.java.ejs +12 -0
  36. package/templates/crud/ScaffoldQueryHandler.java.ejs +40 -0
  37. package/templates/crud/SubEntityAddCommand.java.ejs +17 -0
  38. package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
  39. package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
  40. package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
  41. package/templates/crud/TransitionCommand.java.ejs +9 -0
  42. package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
  43. package/templates/evaluate/report.html.ejs +971 -0
  44. package/templates/kafka-event/Event.java.ejs +7 -0
package/AGENTS.md CHANGED
@@ -457,9 +457,13 @@ aggregates:
457
457
  fields:
458
458
  - name: userId
459
459
  type: String
460
- # kafka: true # opcional publica a Kafka tras commit
460
+ # Nota: el flag kafka: true ya no es necesario.
461
+ # Si el proyecto tiene un broker instalado (eva add kafka-client),
462
+ # eva g entities cablea automáticamente todos los eventos declarados.
461
463
  ```
462
464
 
465
+ El `domain.yaml` también soporta una sección `endpoints:` opcional (sibling de `aggregates:`) para declarar los endpoints REST. Ver sección [⚡ Características Avanzadas](#-características-avanzadas-del-domainyaml) para detalles.
466
+
463
467
  ---
464
468
 
465
469
  ## ⚡ Características Avanzadas del domain.yaml
@@ -523,26 +527,51 @@ aggregates:
523
527
  - name: Order
524
528
  entities: [...]
525
529
  events:
526
- - name: OrderConfirmedEvent
530
+ - name: OrderConfirmed
527
531
  fields:
528
532
  - name: orderId
529
533
  type: String
530
534
  - name: confirmedAt
531
535
  type: LocalDateTime
532
- kafka: true # opcional — genera publicación a MessageBroker
533
536
  ```
534
537
 
535
- Genera `OrderConfirmedEvent.java` (en `domain/models/events/`) que extiende `DomainEvent`, y `OrderDomainEventHandler.java` (en `application/usecases/`) con `@TransactionalEventListener(AFTER_COMMIT)`.
538
+ Genera `OrderConfirmed.java` (en `domain/models/events/`) que extiende `DomainEvent`, y `OrderDomainEventHandler.java` (en `application/usecases/`) con `@TransactionalEventListener(AFTER_COMMIT)`.
539
+
540
+ **Auto-wiring de broker:** Si el proyecto tiene un broker de mensajería instalado (`eva add kafka-client`), `eva g entities` genera automáticamente la capa de Integration Events para **todos** los eventos declarados — sin necesidad de ejecutar `eva g kafka-event` por separado:
541
+
542
+ | Archivo generado | Descripción |
543
+ |---|---|
544
+ | `application/events/OrderConfirmedIntegrationEvent.java` | Record broker-facing (Integration Event) |
545
+ | `application/ports/MessageBroker.java` | Puerto broker-agnóstico (creado/actualizado) |
546
+ | `infrastructure/adapters/kafkaMessageBroker/…` | Adaptador Kafka (creado/actualizado) |
547
+ | `shared/…/kafkaConfig/KafkaConfig.java` | Bean `NewTopic` (actualizado) |
548
+ | `parameters/*/kafka.yaml` | Configuración de topic (actualizada) |
549
+
550
+ **Domain Event vs Integration Event:**
551
+ - **Domain Event** (`domain/models/events/OrderConfirmed.java`) — señal interna del bounded context. Nunca depende de infraestructura.
552
+ - **Integration Event** (`application/events/OrderConfirmedIntegrationEvent.java`) — proyección para el broker. Cambiar de Kafka a RabbitMQ solo requiere cambiar el adaptador `MessageBroker`; los Domain Events no se modifican nunca.
553
+
554
+ El `DomainEventHandler` mapea un Domain Event a un Integration Event:
555
+ ```java
556
+ @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
557
+ public void onOrderConfirmed(OrderConfirmed event) {
558
+ messageBroker.publishOrderConfirmedIntegrationEvent(
559
+ new OrderConfirmedIntegrationEvent(event.getOrderId(), event.getConfirmedAt())
560
+ );
561
+ }
562
+ ```
536
563
 
537
564
  Publicar desde la entidad raíz usando `raise()` heredado:
538
565
 
539
566
  ```java
540
567
  public void confirm() {
541
568
  this.status = this.status.transitionTo(OrderStatus.CONFIRMED);
542
- raise(new OrderConfirmedEvent(this.id, this.id, LocalDateTime.now()));
569
+ raise(new OrderConfirmed(this.id, LocalDateTime.now()));
543
570
  }
544
571
  ```
545
572
 
573
+ **Nota:** el flag `kafka: true` por evento ya no es necesario — todos los eventos se cablearán automáticamente cuando haya un broker instalado.
574
+
546
575
  ---
547
576
 
548
577
  ## �️ Soft Delete
@@ -956,7 +985,18 @@ private String customerId;
956
985
  2. **SI** el módulo requiere ciclo de vida → usar `transitions` + `initialValue` en el enum
957
986
  3. **SI** un valor tiene lógica de negocio → declararlo como `valueObject` con `methods`
958
987
  4. **SI** ocurren hechos relevantes de negocio → declarar `events[]` en el agregado
959
- 5. **DESPUÉS** de generar el `domain.yaml`ejecutar `eva g entities <module>`
988
+ 5. **SI** el módulo expone endpoints REST específicos declarar `endpoints:` con versiones y operaciones
989
+ 6. **DESPUÉS** de generar el `domain.yaml` → ejecutar `eva g entities <module>`
990
+
991
+ ### Al Usar `endpoints:` en domain.yaml
992
+
993
+ 1. **SIEMPRE** declarar `endpoints:` cuando el API REST tiene comportamientos custom (confirmar, cancelar, activar, etc.)
994
+ 2. **NUNCA** usar `endpoints:` si solo necesitas CRUD estándar — el flujo interactivo es más simple
995
+ 3. **SIEMPRE** usar PascalCase para los nombres de `useCase` (ej: `ConfirmOrder`, no `confirmOrder`)
996
+ 4. **CONOCER** cuáles son los 5 use cases estándar por aggregate: `Create{E}`, `Update{E}`, `Delete{E}`, `Get{E}`, `FindAll{E}s` — estos generan implementación completa
997
+ 5. **SABER** que cualquier otro nombre genera un **scaffold** con `UnsupportedOperationException` — el desarrollador debe implementar el handler
998
+ 6. **APLICAR** la regla anti-duplicado: si el mismo useCase aparece en v1 y v2, se genera solo una vez
999
+ 7. **NOMBRAR** los controladores según la convención: `{Aggregate}{VersionCapitalized}Controller` (ej: `OrderV1Controller`)
960
1000
 
961
1001
  ### Al Generar Código de Dominio
962
1002
 
@@ -1188,10 +1228,12 @@ Al generar o modificar código, verificar:
1188
1228
  - [ ] Enum con ciclo de vida → usar `transitions` + `initialValue`, no setters manuales
1189
1229
  - [ ] Value Object con comportamiento → declarar `methods` en lugar de lógica en entidad
1190
1230
  - [ ] Evento de dominio → declarar en `events[]`, publicar con `raise()` en método de negocio
1191
- - [ ] Evento con Kafkaagregar `kafka: true` al evento
1231
+ - [ ] Evento con broker**no** usar `kafka: true`; si `eva add kafka-client` está instalado, `eva g entities` auto-cablea todos los eventos
1232
+ - [ ] Distinguir entre Domain Event (`domain/models/events/X.java`) e Integration Event (`application/events/XIntegrationEvent.java`) — cambios de broker solo afectan al adaptador `MessageBroker`
1233
+ - [ ] Endpoints REST específicos → declarar `endpoints:` con versiones y operaciones; usar nombres estándar para implementación completa
1192
1234
 
1193
1235
  ---
1194
1236
 
1195
- **Última actualización:** 2026-03-02
1196
- **Versión de eva4j:** 1.0.12
1237
+ **Última actualización:** 2026-03-11
1238
+ **Versión de eva4j:** 1.0.13
1197
1239
  **Estado:** Documento de referencia para agentes IA
@@ -13,6 +13,7 @@
13
13
  - [Validaciones JSR-303](#validaciones-jsr-303)
14
14
  - [Relaciones](#relaciones)
15
15
  - [Tipos de Datos](#tipos-de-datos)
16
+ - [Sección endpoints](#sección-endpoints)
16
17
  - [Ejemplos Completos](#ejemplos-completos)
17
18
 
18
19
  ---
@@ -2441,6 +2442,155 @@ private List<AddressJpa> addresses = new ArrayList<>();
2441
2442
 
2442
2443
  ---
2443
2444
 
2445
+ ## Sección endpoints
2446
+
2447
+ La sección `endpoints:` es **opcional** y se declara como clave hermana de `aggregates:` en el YAML. Cuando está presente, controla **qué use cases y controladores REST se generan**. Cuando está ausente, el generador usa el flujo interactivo tradicional (5 CRUD fijos por aggregate root).
2448
+
2449
+ ### Comportamiento condicional
2450
+
2451
+ | Condición | Comportamiento |
2452
+ |-----------|---------------|
2453
+ | `endpoints:` **ausente** | Pregunta interactiva "¿Generar CRUD?" → genera 5 use cases estándar |
2454
+ | `endpoints:` **presente** | Genera automáticamente solo los use cases declarados en `operations[]` |
2455
+
2456
+ ### Sintaxis
2457
+
2458
+ ```yaml
2459
+ # Sección endpoints: sibling de aggregates:
2460
+ endpoints:
2461
+ basePath: /orders # Ruta base (incluida en @RequestMapping "/api/{version}{basePath}")
2462
+ versions:
2463
+ - version: v1 # Versión del API (ej: v1, v2, v1-beta)
2464
+ operations:
2465
+ - method: GET # HTTP method (GET, POST, PUT, PATCH, DELETE)
2466
+ path: /{id} # Path relativo al basePath (/ para la raíz)
2467
+ useCase: GetOrder # Nombre del use case (PascalCase)
2468
+ description: "Obtener pedido por ID" # Descripción para Swagger
2469
+ ```
2470
+
2471
+ ### Campos de `endpoints:`
2472
+
2473
+ | Campo | Tipo | Requerido | Descripción |
2474
+ |-------|------|-----------|-------------|
2475
+ | `basePath` | String | Sí | Ruta base del recurso (ej: `/orders`) |
2476
+ | `versions` | Array | Sí | Lista de versiones de API |
2477
+ | `versions[].version` | String | Sí | Identificador de versión (ej: `v1`) |
2478
+ | `versions[].operations` | Array | Sí | Lista de endpoints a generar |
2479
+ | `operations[].method` | String | Sí | Verbo HTTP: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
2480
+ | `operations[].path` | String | Sí | Path relativo (ej: `/`, `/{id}`, `/{id}/confirm`) |
2481
+ | `operations[].useCase` | String | Sí | Nombre del use case en PascalCase |
2482
+ | `operations[].description` | String | No | Descripción para la anotación `@Operation` de Swagger |
2483
+
2484
+ ### Tipo inferido (`type`)
2485
+
2486
+ El tipo del use case se infiere automáticamente del método HTTP:
2487
+
2488
+ | HTTP method | Tipo inferido | Genera |
2489
+ |-------------|--------------|--------|
2490
+ | `GET` | `query` | `{UseCaseName}Query` + `{UseCaseName}QueryHandler` |
2491
+ | `POST`, `PUT`, `PATCH`, `DELETE` | `command` | `{UseCaseName}Command` + `{UseCaseName}CommandHandler` |
2492
+
2493
+ ### Use cases estándar vs. scaffold
2494
+
2495
+ | Categoría | Nombres Match | Generado |
2496
+ |-----------|---------------|---------|
2497
+ | **Estándar** | `Create{Aggregate}`, `Update{Aggregate}`, `Delete{Aggregate}`, `Get{Aggregate}`, `FindAll{Aggregate}s` | Implementación completa con lógica de repositorio |
2498
+ | **Scaffold** | Cualquier otro nombre (`ConfirmOrder`, `ActivateProduct`, etc.) | Clase con `// TODO` — el desarrollador completa la lógica |
2499
+
2500
+ Los use cases estándar reutilizan los templates CRUD existentes (implementación idéntica al flujo sin `endpoints:`). Los scaffolds generan archivos con `UnsupportedOperationException` y comentarios guía.
2501
+
2502
+ ### Regla anti-duplicado (multi-versión)
2503
+
2504
+ Cuando el mismo `useCase` aparece en múltiples versiones (ej: `CreateProduct` en v1 y v2), el generador crea el Command/Query + Handler **solo una vez** (en la primera versión donde aparece). Los controladores de las versiones posteriores importan y referencian el mismo use case sin regenerarlo.
2505
+
2506
+ ```yaml
2507
+ endpoints:
2508
+ basePath: /products
2509
+ versions:
2510
+ - version: v1
2511
+ operations:
2512
+ - { method: POST, path: /, useCase: CreateProduct } # ← genera CreateProductCommand + Handler
2513
+
2514
+ - version: v2
2515
+ operations:
2516
+ - { method: POST, path: /, useCase: CreateProduct } # ← NO regenera, solo referencia en V2Controller
2517
+ - { method: PUT, path: /{id}/activate, useCase: ActivateProduct } # ← nuevo scaffold
2518
+ ```
2519
+
2520
+ ### Nombres de controladores generados
2521
+
2522
+ Con `endpoints:`, el controlador se nombra `{Aggregate}{VersionCapitalized}Controller`:
2523
+
2524
+ | Aggregate | Version | Clase generada | Archivo |
2525
+ |-----------|---------|---------------|---------|
2526
+ | `Order` | `v1` | `OrderV1Controller` | `controllers/order/v1/OrderV1Controller.java` |
2527
+ | `Product` | `v2` | `ProductV2Controller` | `controllers/product/v2/ProductV2Controller.java` |
2528
+
2529
+ > Sin `endpoints:`, el controlador se llama `{Aggregate}Controller` y usa la versión ingresada en el prompt.
2530
+
2531
+ ### Ejemplo básico (una versión)
2532
+
2533
+ ```yaml
2534
+ aggregates:
2535
+ - name: Order
2536
+ entities:
2537
+ - name: order
2538
+ isRoot: true
2539
+ tableName: orders
2540
+ fields:
2541
+ - { name: id, type: String }
2542
+ - { name: orderNumber, type: String }
2543
+ - { name: status, type: OrderStatus, readOnly: true }
2544
+
2545
+ enums:
2546
+ - name: OrderStatus
2547
+ initialValue: PENDING
2548
+ values: [PENDING, CONFIRMED, SHIPPED, CANCELLED]
2549
+
2550
+ endpoints:
2551
+ basePath: /orders
2552
+ versions:
2553
+ - version: v1
2554
+ operations:
2555
+ - { method: GET, path: /{id}, useCase: GetOrder, description: "Obtener pedido" }
2556
+ - { method: GET, path: /, useCase: FindAllOrders, description: "Listar pedidos" }
2557
+ - { method: POST, path: /, useCase: CreateOrder, description: "Crear pedido" }
2558
+ - { method: DELETE, path: /{id}, useCase: DeleteOrder, description: "Eliminar pedido" }
2559
+ - { method: PUT, path: /{id}/confirm, useCase: ConfirmOrder, description: "Confirmar pedido" }
2560
+ ```
2561
+
2562
+ **Archivos generados:**
2563
+ ```
2564
+ application/
2565
+ commands/
2566
+ CreateOrderCommand.java ← estándar (completo)
2567
+ DeleteOrderCommand.java ← estándar (completo)
2568
+ ConfirmOrderCommand.java ← scaffold (TODO)
2569
+ queries/
2570
+ GetOrderQuery.java ← estándar (completo)
2571
+ FindAllOrdersQuery.java ← NOTA: no es estándar (estándar sería FindAllOrders s)
2572
+ → scaffold (TODO)
2573
+ usecases/
2574
+ CreateOrderCommandHandler.java ← estándar
2575
+ DeleteOrderCommandHandler.java ← estándar
2576
+ ConfirmOrderCommandHandler.java ← scaffold
2577
+ GetOrderQueryHandler.java ← estándar
2578
+ FindAllOrdersQueryHandler.java ← scaffold
2579
+ dtos/
2580
+ OrderResponseDto.java
2581
+ mappers/
2582
+ OrderApplicationMapper.java
2583
+ infrastructure/rest/controllers/order/
2584
+ v1/
2585
+ OrderV1Controller.java ← controller con 5 métodos declarados
2586
+ ```
2587
+
2588
+ ### Ejemplo multi-versión
2589
+
2590
+ Ver [`examples/domain-endpoints-versioned.yaml`](examples/domain-endpoints-versioned.yaml) para un ejemplo completo con v1 y v2, incluyendo la regla anti-duplicado y scaffolds.
2591
+
2592
+ ---
2593
+
2444
2594
  ## Ejemplos Completos
2445
2595
 
2446
2596
  ### Ejemplo 1: E-Commerce (Order)
package/bin/eva4j.js CHANGED
@@ -16,6 +16,8 @@ const generateRecordCommand = require('../src/commands/generate-record');
16
16
  const generateEntitiesCommand = require('../src/commands/generate-entities');
17
17
  const generateTemporalFlowCommand = require('../src/commands/generate-temporal-flow');
18
18
  const generateTemporalActivityCommand = require('../src/commands/generate-temporal-activity');
19
+ const generateSystemCommand = require('../src/commands/generate-system');
20
+ const evaluateSystemCommand = require('../src/commands/evaluate-system');
19
21
  const infoCommand = require('../src/commands/info');
20
22
  const detachCommand = require('../src/commands/detach');
21
23
 
@@ -254,6 +256,16 @@ program
254
256
  return;
255
257
  }
256
258
 
259
+ if (type === 'system') {
260
+ try {
261
+ await generateSystemCommand();
262
+ } catch (error) {
263
+ console.error(chalk.red('Error:'), error.message);
264
+ process.exit(1);
265
+ }
266
+ return;
267
+ }
268
+
257
269
  console.error(chalk.red(`❌ Unknown type: ${type}`));
258
270
  console.log(chalk.yellow('\nUsage:'));
259
271
  console.log(chalk.gray(' eva4j generate usecase <name> <module>'));
@@ -265,6 +277,7 @@ program
265
277
  console.log(chalk.gray(' eva4j generate resource <module>'));
266
278
  console.log(chalk.gray(' eva4j generate record'));
267
279
  console.log(chalk.gray(' eva4j generate entities <module>'));
280
+ console.log(chalk.gray(' eva4j generate system'));
268
281
  console.log(chalk.gray('\nExamples:'));
269
282
  console.log(chalk.gray(' eva4j generate usecase create-provider provider'));
270
283
  console.log(chalk.gray(' eva4j g http-exchange user-service-port user'));
@@ -274,10 +287,26 @@ program
274
287
  console.log(chalk.gray(' eva4j g temporal-activity order register-order'));
275
288
  console.log(chalk.gray(' eva4j g resource product'));
276
289
  console.log(chalk.gray(' eva4j g record # Reads JSON from clipboard'));
277
- console.log(chalk.gray(' eva4j g entities order # Generates from domain.yaml\n'));
290
+ console.log(chalk.gray(' eva4j g entities order # Generates from domain.yaml'));
291
+ console.log(chalk.gray(' eva4j g system # Bootstrap from system.yaml\n'));
278
292
  process.exit(1);
279
293
  });
280
294
 
295
+ // Evaluate command
296
+ program
297
+ .command('evaluate <type>')
298
+ .description('Validate and visualize project artifacts. type: system')
299
+ .option('--port <port>', 'Port for the web server (default: 3000)')
300
+ .option('--output <path>', 'Output path for the HTML report (default: ./system-report.html)')
301
+ .action(async (type, options) => {
302
+ try {
303
+ await evaluateSystemCommand(type, options);
304
+ } catch (error) {
305
+ console.error(chalk.red('Error:'), error.message);
306
+ process.exit(1);
307
+ }
308
+ });
309
+
281
310
  // Info command
282
311
  program
283
312
  .command('info')
@@ -322,6 +351,7 @@ program.on('--help', () => {
322
351
  console.log(chalk.gray(' $ eva4j g record'));
323
352
  console.log(chalk.gray(' $ eva4j detach user'));
324
353
  console.log(chalk.gray(' $ eva4j info'));
354
+ console.log(chalk.gray(' $ eva4j evaluate system'));
325
355
  console.log('');
326
356
  console.log(chalk.blue('For more information, visit:'));
327
357
  console.log(chalk.gray(' https://github.com/your-repo/eva4j'));