@ronaldjdevfs/forge 1.0.2 → 1.1.0

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 (48) hide show
  1. package/README.md +130 -8
  2. package/package.json +2 -2
  3. package/skills/forge/SKILL.md +86 -15
  4. package/skills/forge/profiles/express-drizzle.md +107 -0
  5. package/skills/forge/profiles/fastify-mongodb.md +103 -0
  6. package/skills/forge/profiles/fastify-prisma.md +81 -0
  7. package/skills/forge/profiles/nestjs-mongodb.md +92 -0
  8. package/skills/forge/profiles/nestjs-postgres.md +98 -0
  9. package/skills/forge/reference/api-design.md +62 -0
  10. package/skills/forge/reference/assay.md +82 -0
  11. package/skills/forge/reference/cast.md +81 -7
  12. package/skills/forge/reference/data-patterns.md +86 -0
  13. package/skills/forge/reference/di-strategies.md +50 -0
  14. package/skills/forge/reference/errors.md +65 -0
  15. package/skills/forge/reference/events.md +95 -0
  16. package/skills/forge/reference/help.md +40 -0
  17. package/skills/forge/reference/hooks.md +62 -0
  18. package/skills/forge/reference/observability.md +66 -0
  19. package/skills/forge/reference/patterns.md +52 -0
  20. package/skills/forge/reference/principles.md +6 -0
  21. package/skills/forge/reference/reforge.md +69 -5
  22. package/skills/forge/reference/relocate.md +15 -2
  23. package/skills/forge/reference/security-patterns.md +87 -0
  24. package/skills/forge/reference/testing-patterns.md +69 -0
  25. package/skills/forge/scripts/assay.mjs +481 -0
  26. package/skills/forge/scripts/context.mjs +147 -43
  27. package/skills/forge/scripts/detect.mjs +371 -22
  28. package/skills/forge/scripts/forge-api.mjs +373 -0
  29. package/skills/forge/scripts/forge-config.mjs +268 -0
  30. package/skills/forge/scripts/forge-signals.mjs +131 -0
  31. package/skills/forge/scripts/forge-state.mjs +97 -0
  32. package/skills/forge/scripts/formatter.mjs +133 -0
  33. package/skills/forge/scripts/graph.mjs +5 -21
  34. package/skills/forge/scripts/hook.mjs +250 -0
  35. package/skills/forge/scripts/inspect.mjs +171 -22
  36. package/skills/forge/scripts/parse-imports.mjs +249 -0
  37. package/skills/forge/scripts/pin.mjs +151 -0
  38. package/skills/forge/scripts/posttool.mjs +224 -0
  39. package/skills/forge/scripts/profile.mjs +124 -20
  40. package/skills/forge/scripts/registry/rules.mjs +344 -0
  41. package/skills/forge/scripts/rename.mjs +669 -0
  42. package/skills/forge/scripts/rollback.mjs +213 -0
  43. package/skills/forge/scripts/update.mjs +114 -0
  44. package/skills/forge/templates/feature/domain-error.ts.md +9 -0
  45. package/skills/forge/templates/feature/domain-event.ts.md +9 -0
  46. package/skills/forge/templates/feature/event-handler.ts.md +10 -0
  47. package/skills/forge/templates/feature/use-case.ts.md +10 -2
  48. package/skills/forge/tests/core.test.mjs +403 -0
@@ -0,0 +1,92 @@
1
+ # Profile: NestJS + MongoDB + Mongoose
2
+
3
+ ## Metadata
4
+
5
+ ```yaml
6
+ framework: NestJS
7
+ runtime: Node 20+
8
+ database: MongoDB
9
+ orm: Mongoose
10
+ di_strategy: framework (NestJS DI)
11
+ architecture: hexagonal-feature
12
+ ```
13
+
14
+ ## DI Strategy
15
+
16
+ NestJS DI nativo con `@Injectable()` y módulos. Conexión Mongoose via `MongooseModule.forRoot()`.
17
+
18
+ ```typescript
19
+ // app.module.ts
20
+ @Module({
21
+ imports: [
22
+ MongooseModule.forRoot(process.env.MONGO_URI),
23
+ CreditModule,
24
+ ],
25
+ })
26
+ export class AppModule {}
27
+ ```
28
+
29
+ ## Project Structure
30
+
31
+ ```
32
+ ├── src/
33
+ │ ├── features/
34
+ │ │ └── credit/
35
+ │ │ ├── domain/
36
+ │ │ │ ├── Credit.entity.ts # Plain domain object
37
+ │ │ │ └── ICredit.repository.ts
38
+ │ │ ├── application/
39
+ │ │ │ └── use-cases/
40
+ │ │ │ └── AddCredit.uc.ts
41
+ │ │ └── adapters/
42
+ │ │ ├── in/http/
43
+ │ │ │ ├── Credit.controller.ts
44
+ │ │ │ └── Credit.module.ts
45
+ │ │ └── out/persistence/
46
+ │ │ ├── Credit.schema.ts # Mongoose schema
47
+ │ │ └── Credit.repository.ts
48
+ │ ├── shared/
49
+ │ ├── app.module.ts
50
+ │ └── main.ts
51
+ ```
52
+
53
+ ## Persistence (Mongoose)
54
+
55
+ ```typescript
56
+ // adapters/out/persistence/Credit.schema.ts
57
+ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
58
+
59
+ @Schema({ timestamps: true })
60
+ export class CreditDocument {
61
+ @Prop({ required: true }) amount: number;
62
+ @Prop({ required: true }) userId: string;
63
+ }
64
+
65
+ export const CreditSchema = SchemaFactory.createForClass(CreditDocument);
66
+ ```
67
+
68
+ ```typescript
69
+ // adapters/out/persistence/credit.module.ts
70
+ @Module({
71
+ imports: [MongooseModule.forFeature([{ name: "Credit", schema: CreditSchema }])],
72
+ providers: [{ provide: ICreditRepository, useClass: CreditRepository }],
73
+ exports: [ICreditRepository],
74
+ })
75
+ export class CreditPersistenceModule {}
76
+ ```
77
+
78
+ ## Testing Strategy
79
+
80
+ ```typescript
81
+ // Test con MongoMemoryServer
82
+ const module = await Test.createTestingModule({
83
+ imports: [
84
+ MongooseModule.forRoot(MongoMemoryServer.create().then(m => m.getUri())),
85
+ CreditModule,
86
+ ],
87
+ }).compile();
88
+ ```
89
+
90
+ ## Naming Conventions
91
+
92
+ Schemas en PascalCase, colecciones en snake_case (Mongoose usa plural automático).
@@ -0,0 +1,98 @@
1
+ # Profile: NestJS + PostgreSQL
2
+
3
+ ## Metadata
4
+
5
+ ```yaml
6
+ framework: NestJS
7
+ runtime: Node 20+
8
+ database: PostgreSQL
9
+ orm: TypeORM / native
10
+ di_strategy: framework (NestJS DI)
11
+ architecture: hexagonal-feature
12
+ ```
13
+
14
+ ## DI Strategy
15
+
16
+ NestJS DI nativo. Módulos con `@Module()`, `@Injectable()` para servicios y repositorios. Providers registrados en cada módulo de feature.
17
+
18
+ ```typescript
19
+ // features/credit/credit.module.ts
20
+ @Module({
21
+ imports: [TypeOrmModule.forFeature([CreditEntity])],
22
+ controllers: [CreditController],
23
+ providers: [
24
+ { provide: ICreditRepository, useClass: CreditRepository },
25
+ AddCredit,
26
+ ],
27
+ })
28
+ export class CreditModule {}
29
+ ```
30
+
31
+ ## Project Structure
32
+
33
+ ```
34
+ ├── src/
35
+ │ ├── features/
36
+ │ │ └── credit/
37
+ │ │ ├── domain/
38
+ │ │ │ ├── Credit.entity.ts # TypeORM entity (dominio anémico aceptable)
39
+ │ │ │ └── ICredit.repository.ts
40
+ │ │ ├── application/
41
+ │ │ │ └── use-cases/
42
+ │ │ │ └── AddCredit.uc.ts
43
+ │ │ └── adapters/
44
+ │ │ ├── in/http/
45
+ │ │ │ ├── Credit.controller.ts
46
+ │ │ │ └── Credit.module.ts
47
+ │ │ └── out/persistence/
48
+ │ │ └── Credit.repository.ts
49
+ │ ├── shared/
50
+ │ ├── app.module.ts
51
+ │ └── main.ts
52
+ ```
53
+
54
+ ## Persistence (TypeORM)
55
+
56
+ ```typescript
57
+ // domain/Credit.entity.ts
58
+ @Entity()
59
+ export class Credit {
60
+ @PrimaryGeneratedColumn("uuid") id: string;
61
+ @Column() amount: number;
62
+ @Column() userId: string;
63
+ @CreateDateColumn() createdAt: Date;
64
+ }
65
+ ```
66
+
67
+ ```typescript
68
+ // adapters/out/persistence/Credit.repository.ts
69
+ @Injectable()
70
+ export class CreditRepository implements ICreditRepository {
71
+ constructor(@InjectRepository(Credit) private readonly repo: Repository<Credit>) {}
72
+
73
+ async create(data: CreditInput): Promise<Credit> {
74
+ const entity = this.repo.create(data);
75
+ return this.repo.save(entity);
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Testing Strategy
81
+
82
+ ```typescript
83
+ // Test unitario sin NestJS
84
+ const mockRepo = { create: jest.fn() };
85
+ const uc = new AddCredit(mockRepo);
86
+
87
+ // Test de integración con Test.createTestingModule
88
+ const module = await Test.createTestingModule({
89
+ providers: [
90
+ AddCredit,
91
+ { provide: ICreditRepository, useClass: MockCreditRepo },
92
+ ],
93
+ }).compile();
94
+ ```
95
+
96
+ ## Naming Conventions
97
+
98
+ Mismas que `nestjs-prisma`. Entidades TypeORM en PascalCase, tablas en snake_case con `@Entity("credit")`.
@@ -0,0 +1,62 @@
1
+ # API Design — Diseño de APIs REST/GraphQL
2
+
3
+ ## Principios arquitectónicos
4
+
5
+ - Las rutas/endpoints pertenecen a `adapters/in/http/`, nunca al dominio.
6
+ - El controller es la capa más delgada posible: parsea, delega, responde.
7
+ - La validación de entrada ocurre en el controller o middleware, no en domain/.
8
+ - El contrato API (OpenAPI/Swagger) se genera desde tipos compartidos.
9
+
10
+ ## REST
11
+
12
+ ### Convenciones
13
+
14
+ | Verbo | Ruta | Controller responde | Use case retorna |
15
+ |-------|------|-------------------|------------------|
16
+ | GET | `/resources` | 200 + array | `Resource[]` |
17
+ | GET | `/resources/:id` | 200 + object / 404 | `Resource \| null` |
18
+ | POST | `/resources` | 201 + object | `Resource` |
19
+ | PUT | `/resources/:id` | 200 + object | `Resource` |
20
+ | PATCH | `/resources/:id` | 200 + object | `Resource` |
21
+ | DELETE | `/resources/:id` | 204 / 404 | `void` |
22
+
23
+ ### Paginación
24
+
25
+ ```ts
26
+ // shared/contracts/IPaginatedResponse.ts
27
+ export interface IPaginatedResponse<T> {
28
+ data: T[];
29
+ meta: {
30
+ total: number;
31
+ page: number;
32
+ limit: number;
33
+ totalPages: number;
34
+ };
35
+ }
36
+ ```
37
+
38
+ ```ts
39
+ // Formato query: GET /users?page=1&limit=20&sort=name:asc&filter=status:active
40
+ ```
41
+
42
+ ### Versionado
43
+
44
+ - Preferir versionado por encabezado (`Accept: application/vnd.api+json;version=2`)
45
+ - Si es por URL: `/api/v1/resources` (carpeta física `adapters/in/http/v1/`)
46
+ - Deprecar con encabezado `Sunset: Sat, 01 Jan 2027 00:00:00 GMT`
47
+
48
+ ## GraphQL
49
+
50
+ - Schema en `adapters/in/graphql/` dentro del feature
51
+ - Resolvers = use cases wrapped; nunca contienen lógica de negocio
52
+ - DataLoader para N+1 en resolvers de listas
53
+ - Scalar types custom para IDs, fechas, emails
54
+
55
+ ## Buenas prácticas
56
+
57
+ - OpenAPI 3.1 como fuente de verdad para REST
58
+ - Validación con zod (compartir schemas entre controller y validación)
59
+ - Rate limiting por ruta/rol en middleware de platform/http/
60
+ - Errores normalizados: `{ error: { code, message, details? } }`
61
+ - Idempotencia en POST con `Idempotency-Key` header
62
+ - HATEOAS opcional, solo para APIs hipermedia
@@ -0,0 +1,82 @@
1
+ # Assay
2
+
3
+ Genera un **ensayo arquitectónico multi-persona** que evalúa el estado actual de la arquitectura desde 5 perspectivas expertas.
4
+
5
+ ## Cuándo usarlo
6
+
7
+ - Después de `forge inspect` para obtener interpretación cualitativa de los resultados
8
+ - Para entender el impacto de las violaciones desde ángulos complementarios
9
+ - Para priorizar acciones de refactor con criterio multi-disciplinario
10
+ - Como herramienta de discusión en revisiones arquitectónicas de equipo
11
+
12
+ ## Las 5 Personas
13
+
14
+ | Persona | Rol | Enfoque |
15
+ |---------|-----|---------|
16
+ | **Jeff Bezos** | Arquitecto de Escalabilidad y Autonomía | Acoplamiento entre features, contratos de API, autonomía de equipos, ciclos de dependencias |
17
+ | **Martin Fowler** | Refinador de Patrones y Deuda Técnica | Dirección de dependencias, code smells, naming, refactoring evolutivo |
18
+ | **El Hacker** | Pragmático y Simplificador | Over-engineering, complejidad innecesaria, abstracciones sin valor, costo real de mantenimiento |
19
+ | **Alex** | Product Manager Técnico | Velocidad de entrega, ROI de la deuda técnica, impacto en roadmap, time-to-market |
20
+ | **Dra. Carter** | Arquitecta Senior — Deuda Técnica y Gobernanza | Sostenibilidad a 3-5 años, consistencia entre equipos, gobernanza y ownership |
21
+
22
+ ## Uso
23
+
24
+ ```bash
25
+ # Ensayo completo con todas las personas
26
+ node .opencode/skills/forge/scripts/assay.mjs
27
+
28
+ # Solo la opinión de una persona
29
+ node .opencode/skills/forge/scripts/assay.mjs --persona=bezos
30
+ node .opencode/skills/forge/scripts/assay.mjs --persona=fowler
31
+ node .opencode/skills/forge/scripts/assay.mjs --persona=hacker
32
+ node .opencode/skills/forge/scripts/assay.mjs --persona=pm
33
+ node .opencode/skills/forge/scripts/assay.mjs --persona=senior
34
+
35
+ # Salida JSON (para consumo por herramientas)
36
+ node .opencode/skills/forge/scripts/assay.mjs --json
37
+
38
+ # Persistir ensayo en .forge/assay/
39
+ node .opencode/skills/forge/scripts/assay.mjs --save
40
+
41
+ # Ver historial de ensayos
42
+ node .opencode/skills/forge/scripts/assay.mjs history
43
+
44
+ # Leer un ensayo previo
45
+ node .opencode/skills/forge/scripts/assay.mjs read assay-2026-06-25T00-00-00.md
46
+ ```
47
+
48
+ ## Formato de salida
49
+
50
+ ```
51
+ ═══ Forge Assay — Ensayo Arquitectónico ═══
52
+ Score: 72/140 (51%) — D
53
+ Violaciones: 8
54
+ Personas: Jeff Bezos, Martin Fowler, El Hacker, Alex, Dra. Carter
55
+ Fecha: 2026-06-25
56
+
57
+ ──── Jeff Bezos — Arquitecto de Escalabilidad y Autonomía ────
58
+ ...
59
+
60
+ ──── Martin Fowler — Refinador de Patrones y Deuda Técnica ────
61
+ ...
62
+
63
+ ──── El Hacker — Pragmático y Simplificador ────
64
+ ...
65
+
66
+ ──── Alex — Product Manager Técnico ────
67
+ ...
68
+
69
+ ──── Dra. Carter — Arquitecta Senior ────
70
+ ...
71
+ ```
72
+
73
+ ## Integración con inspect
74
+
75
+ El ensayo se basa en los datos del último `forge inspect`. Si no hay auditoría previa, el ensayo se genera igual pero con menos datos contextuales.
76
+
77
+ Se recomienda:
78
+ 1. `forge inspect` → obtener score, violaciones, grafo
79
+ 2. `forge assay` → interpretar los resultados desde las 5 perspectivas
80
+ 3. Priorizar acciones basado en las recomendaciones
81
+ 4. Refactorizar con `reforge`, `temper`, o `smelt`
82
+ 5. Repetir el ciclo
@@ -17,11 +17,84 @@ Antes de crear un feature, verificar la existencia de los layers arquitectónico
17
17
 
18
18
  Si alguno no existe, ejecutar `bootstrapPlatform()` automáticamente para crearlos.
19
19
 
20
+ ## ⚠️ Pre-Cast Discovery — 3 Gates Obligatorios
21
+
22
+ **NO escribir código hasta pasar los 3 gates de aprobación.** Cast solía crear scaffolding directamente del nombre del feature. Esto producía features genéricos que requerían refactor posterior. Ahora todo `cast` requiere descubrimiento direccional multi-ronda.
23
+
24
+ Usar las **señales de `forge-signals.mjs`** para contextualizar el descubrimiento:
25
+ - Si hay features existentes, revisar sus entidades y casos de uso para mantener coherencia.
26
+ - Si el perfil está detectado, usarlo para preguntas específicas (ej: "Prisma detectado → schema primero").
27
+ - Si hay archivos modificados en git, considerarlos como contexto del nuevo feature.
28
+
29
+ ### Gate 1: Brief del Feature
30
+
31
+ Hacer **al menos 1 ronda de preguntas** usando la herramienta `question`. No inferir todas las respuestas del prompt inicial. Preguntar:
32
+
33
+ **Ronda 1 (obligatoria — 2-3 preguntas):**
34
+ - ¿Cuál es la entidad principal del dominio? (ej: "Factura", "Suscripción", "Producto")
35
+ - ¿Qué operaciones CRUD necesita? ¿Todas o solo un subconjunto?
36
+ - ¿Este feature se relaciona con algún feature existente? ¿Cuál y cómo?
37
+
38
+ **Ronda 2 (si aplica — preguntar solo si la Ronda 1 dejó dudas):**
39
+ - ¿Necesita eventos de dominio o integración con scheduler?
40
+ - ¿Expone API REST? ¿También GraphQL o gRPC?
41
+ - ¿Requiere cache? ¿Lectura > escritura o viceversa?
42
+
43
+ **No preguntes sobre detalles de implementación (nombre de columnas, puertos, etc.) en esta fase.** Eso se resuelve en el scaffold. El brief es sobre qué hace el feature, no cómo se implementa.
44
+
45
+ **Salida:** Brief del feature confirmado por el usuario.
46
+
47
+ ### Gate 2: Confirmar Estructura
48
+
49
+ Tras el brief, presentar la estructura propuesta al usuario para confirmación:
50
+
51
+ ```
52
+ src/features/<name>/
53
+ ├── domain/
54
+ │ ├── <Name>.entity.ts
55
+ │ └── I<Name>.repository.ts
56
+ ├── application/
57
+ │ ├── use-cases/
58
+ │ │ ├── Create<Name>.uc.ts
59
+ │ │ ├── Get<Name>.uc.ts
60
+ │ │ └── List<Name>.uc.ts (los que apliquen según brief)
61
+ │ └── mappers/
62
+ │ └── <Name>.mapper.ts
63
+ └── adapters/
64
+ ├── in/http/
65
+ │ ├── <Name>.controller.ts
66
+ │ └── <name>.routes.ts
67
+ └── out/persistence/
68
+ ├── <Name>.schema.ts
69
+ └── <Name>.repository.ts
70
+ ```
71
+
72
+ Preguntar: "¿Esta estructura cubre el dominio? ¿Falta algo, sobra algo?"
73
+
74
+ **No avanzar sin confirmación explícita del usuario.**
75
+
76
+ ### Gate 3: Confirmar Wiring
77
+
78
+ Antes de escribir código, confirmar las decisiones de integración:
79
+
80
+ - **Repository**: "Inyecto `I<Name>Repository` vía interfaz. Implementación concreta en `adapters/out/persistence/`. ¿OK?"
81
+ - **Controller**: "El controller parsea, llama al use case, responde. Sin lógica de negocio. ¿OK?"
82
+ - **DI**: Según perfil: "Uso `@injectable()` + `@inject(Token)` con tsyringe" o "DI manual en bootstrap". ¿OK?
83
+ - **Routing**: "Registro las rutas en el enrutador principal de HTTP. ¿OK?"
84
+
85
+ Preguntar: "¿Confirmas este wiring antes de generar el feature?"
86
+
87
+ **Sin Gate 3 confirmado, no se escribe ni un archivo.**
88
+
89
+ ---
90
+
20
91
  ## Flujo
21
92
 
22
93
  1. Verificar que `src/platform/`, `src/shared/`, `src/infra/` existan (crearlos si no)
23
- 2. Determinar el nombre del feature (formato: kebab-case)
24
- 3. Crear estructura de directorios:
94
+ 2. **Ejecutar Pre-Cast Discovery** (3 gates obligatorios)
95
+ 3. Determinar el nombre del feature (formato: kebab-case) — ya debería estar claro del brief
96
+ 4. Opcional: persistir brief en `.forge/features/<name>/brief.md` para futuras referencias
97
+ 5. Crear estructura de directorios:
25
98
  ```
26
99
  src/features/<name>/
27
100
  ├── domain/
@@ -32,18 +105,18 @@ Si alguno no existe, ejecutar `bootstrapPlatform()` automáticamente para crearl
32
105
  ├── in/http/
33
106
  └── out/persistence/
34
107
  ```
35
- 4. Crear archivos del feature en este orden (ver `templates/feature/`):
108
+ 6. Crear archivos del feature en este orden (ver `templates/feature/`):
36
109
  - `<Name>.entity.ts` — interfaz de dominio
37
110
  - `I<Name>Repository.ts` — puerto de repositorio
38
111
  - `<Name>.mapper.ts` — mapper dominio ↔ persistencia
39
112
  - `<Name>Schema.ts` — schema de BD (según perfil)
40
113
  - `<Name>Repository.ts` — implementación del repositorio
41
- - Use cases (`Create.ts`, `Get.ts`, `List.ts`, `Update.ts`, `Delete.ts`)
114
+ - Use cases (`Create.ts`, `Get.ts`, `List.ts`, `Update.ts`, `Delete.ts` — según brief)
42
115
  - `<Name>Controller.ts` — controlador HTTP
43
116
  - `<name>.routes.ts` — rutas HTTP
44
- 5. Registrar rutas en el enrutador principal
45
- 6. Ejecutar `forge quench` para verificar el feature
46
- 7. Actualizar `ARCHITECTURE.md`
117
+ 7. Registrar rutas en el enrutador principal
118
+ 8. Ejecutar `forge quench` para verificar el feature
119
+ 9. Actualizar `ARCHITECTURE.md` + estado persistente
47
120
 
48
121
  ## Convenciones
49
122
 
@@ -75,3 +148,4 @@ Usar el perfil detectado para determinar:
75
148
  - `forge quench` — verificar que no hay violaciones
76
149
  - `forge inspect` — confirmar puntuación
77
150
  - `ARCHITECTURE.md` actualizado automáticamente
151
+ - `.forge/state.json` actualizado automáticamente
@@ -0,0 +1,86 @@
1
+ # Data Patterns — Repository, Unit of Work, CQRS, Event Sourcing
2
+
3
+ ## Repository Pattern
4
+
5
+ ### Reglas
6
+
7
+ - La interfaz del repositorio está en `domain/` del feature.
8
+ - La implementación está en `adapters/out/persistence/`.
9
+ - El use case recibe la interfaz por constructor y nunca sabe qué BD hay detrás.
10
+
11
+ ```ts
12
+ // domain/IUser.repository.ts
13
+ export interface IUserRepository {
14
+ findById(id: string): Promise<UserEntity | null>;
15
+ findByEmail(email: string): Promise<UserEntity | null>;
16
+ save(user: UserEntity): Promise<UserEntity>;
17
+ delete(id: string): Promise<void>;
18
+ }
19
+ ```
20
+
21
+ ```ts
22
+ // adapters/out/persistence/PostgresUser.repository.ts
23
+ export class PostgresUserRepository implements IUserRepository {
24
+ constructor(private readonly db: PrismaClient) {}
25
+ async findById(id: string) { … }
26
+ }
27
+ ```
28
+
29
+ ## Unit of Work
30
+
31
+ Para operaciones que requieren transacción entre múltiples repositorios.
32
+
33
+ ```ts
34
+ // domain/IUnitOfWork.ts
35
+ export interface IUnitOfWork {
36
+ withTransaction<T>(fn: (uow: { userRepo: IUserRepository; accountRepo: IAccountRepository }) => Promise<T>): Promise<T>;
37
+ }
38
+ ```
39
+
40
+ ## CQRS
41
+
42
+ Separar commands (escritura) de queries (lectura) solo cuando el modelo de lectura difiere significativamente del de escritura.
43
+
44
+ | Aspecto | Commands | Queries |
45
+ |---------|----------|---------|
46
+ | Feature dir | `application/use-cases/` | `application/queries/` |
47
+ | Retorno | `void` o ID | DTOs planos |
48
+ | Cache | No | Sí |
49
+ | Repositorio | Domain interface | Query interface separada |
50
+
51
+ ## Event Sourcing
52
+
53
+ Para features que requieren auditoría completa o reconstrucción de estado histórico.
54
+
55
+ - Eventos en `domain/events/` dentro del feature
56
+ - Aggregate root: solo persiste eventos, nunca estado actual
57
+ - Proyecciones: tablas/materialized views que reflejan el estado actual
58
+ - Snapshotting cada N eventos para performance
59
+
60
+ ### Estructura
61
+
62
+ ```
63
+ features/<name>/
64
+ domain/
65
+ events/
66
+ UserCreated.event.ts
67
+ UserEmailChanged.event.ts
68
+ UserDeleted.event.ts
69
+ ```
70
+
71
+ ## Cuándo usar cada patrón
72
+
73
+ | Patrón | Cuándo usarlo |
74
+ |--------|---------------|
75
+ | Repository | Siempre. Es el estándar para acceso a datos. |
76
+ | Unit of Work | Cuando una operación modifica 2+ agregados en una transacción. |
77
+ | CQRS | Cuando las queries de lectura son significativamente distintas a los comandos de escritura. |
78
+ | Event Sourcing | Solo cuando necesitas auditoría forense o reconstrucción de estado histórico. |
79
+
80
+ ## Buenas prácticas
81
+
82
+ - Repositorios devuelven entidades de dominio, no schemas de BD
83
+ - Siempre hay un mapper entre schema de BD y entidad de dominio
84
+ - Transacciones en el Unit of Work, no en el use case ni en el repositorio individual
85
+ - CQRS no significa event sourcing; son patrones independientes
86
+ - Event sourcing sin snapshotting es inviable a escala
@@ -0,0 +1,50 @@
1
+ # DI Strategies — Inyección de dependencias
2
+
3
+ ## Principios
4
+
5
+ - Constructor injection sobre service locators o decoradores mágicos.
6
+ - El dominio nunca importa el contenedor. Solo application y adapters.
7
+ - Interfaces en domain/; implementaciones en adapters/out/ o platform/.
8
+
9
+ ## Estrategias según tamaño del proyecto
10
+
11
+ | Tamaño | Estrategia | Cuándo usarla |
12
+ |--------|-----------|---------------|
13
+ | Pequeño (< 10 features) | DI Manual | Wires explícitos en composición root (src/app.ts) |
14
+ | Mediano (10-30 features) | Contenedor ligero | awilix, tsyringe, o el DI del framework |
15
+ | Grande (> 30 features) | Contenedor con módulos | NestJS modular, inversify con módulos lazy |
16
+
17
+ ## DI Manual
18
+
19
+ ```ts
20
+ // src/app.ts — Composition Root
21
+ import { PostgresUserRepository } from "./features/users/adapters/out/persistence/PostgresUser.repository.js";
22
+ import { CreateUserUseCase } from "./features/users/application/use-cases/CreateUser.uc.js";
23
+ import { UserController } from "./features/users/adapters/in/http/User.controller.js";
24
+
25
+ const userRepo = new PostgresUserRepository(db);
26
+ const createUserUc = new CreateUserUseCase(userRepo);
27
+ const userController = new UserController(createUserUc);
28
+
29
+ router.post("/users", (req, res) => userController.create(req, res));
30
+ ```
31
+
32
+ ## Contenedor (tsyringe)
33
+
34
+ ```ts
35
+ // di/Container.ts
36
+ import { container } from "tsyringe";
37
+ import { IUserRepository } from "@/features/users/domain/IUser.repository.js";
38
+ import { PostgresUserRepository } from "@/features/users/adapters/out/persistence/PostgresUser.repository.js";
39
+
40
+ container.registerSingleton<IUserRepository>("IUserRepository", PostgresUserRepository);
41
+ ```
42
+
43
+ ## Buenas prácticas
44
+
45
+ - Composition Root único y lo más cercano al entrypoint posible
46
+ - Solo el Composition Root conoce todas las implementaciones concretas
47
+ - Preferir interfaces sobre clases abstractas en domain/
48
+ - Evitar `container.resolve()` fuera del Composition Root
49
+ - Usar tokens (strings o símbolos) para identificar dependencias
50
+ - Testear use cases con mocks manuales sin necesidad del contenedor
@@ -0,0 +1,65 @@
1
+ # Errors — Manejo de errores en arquitectura hexagonal
2
+
3
+ ## Principios
4
+
5
+ - Los errores de dominio pertenecen al **dominio**, no al framework ni a HTTP.
6
+ - Los casos de uso lanzan errores de dominio; los adapters HTTP los traducen a respuestas HTTP.
7
+ - Nunca lanzar `throw new Error("...")` desde domain/application. Usar errores tipados.
8
+
9
+ ## Estructura
10
+
11
+ ```
12
+ src/shared/errors/
13
+ ├── AppError.ts → Clase base abstracta (opcional)
14
+ ├── NotFoundError.ts → 404
15
+ ├── ValidationError.ts → 400 / 422
16
+ ├── UnauthorizedError.ts → 401
17
+ ├── ForbiddenError.ts → 403
18
+ ├── ConflictError.ts → 409
19
+ └── DomainError.ts → 500 genérico de dominio
20
+ ```
21
+
22
+ ## Ejemplo
23
+
24
+ ```ts
25
+ // shared/errors/NotFoundError.ts
26
+ export class NotFoundError extends Error {
27
+ constructor(public readonly entity: string, public readonly id: string) {
28
+ super(`${entity} with id ${id} not found`);
29
+ this.name = "NotFoundError";
30
+ }
31
+ }
32
+ ```
33
+
34
+ ```ts
35
+ // features/users/domain/IUser.repository.ts
36
+ import { NotFoundError } from "@/shared/errors/NotFoundError.js";
37
+
38
+ export interface IUserRepository {
39
+ findById(id: string): Promise<UserEntity | null>;
40
+ // el use case decide si lanza NotFoundError
41
+ }
42
+ ```
43
+
44
+ ```ts
45
+ // features/users/adapters/in/http/User.controller.ts
46
+ import { NotFoundError } from "@/shared/errors/NotFoundError.js";
47
+
48
+ async getUser(req, res, next) {
49
+ try {
50
+ const user = await this.getUserUc.execute(req.params.id);
51
+ res.json(user);
52
+ } catch (err) {
53
+ if (err instanceof NotFoundError) return res.status(404).json({ error: err.message });
54
+ next(err);
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Buenas prácticas
60
+
61
+ - Errores de dominio en `domain/` (o shared/errors si son transversales)
62
+ - `Result<T, E>` pattern para casos donde el error es parte del flujo esperado
63
+ - Error handler centralizado en platform/http/ para errores no capturados
64
+ - Loggear errores en el adapter, no en el dominio
65
+ - Códigos de error consistentes: `DOMAIN_ENTITY_NOT_FOUND`, `VALIDATION_INVALID_EMAIL`