@ronaldjdevfs/forge 1.0.1
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.
- package/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +338 -0
- package/logo.png +0 -0
- package/package.json +47 -0
- package/skills/forge/SKILL.md +207 -0
- package/skills/forge/command/forge.md +110 -0
- package/skills/forge/profiles/express-mongodb.md +289 -0
- package/skills/forge/profiles/express-postgres.md +164 -0
- package/skills/forge/profiles/express-prisma.md +116 -0
- package/skills/forge/profiles/fastify-postgres.md +110 -0
- package/skills/forge/profiles/nestjs-prisma.md +160 -0
- package/skills/forge/reference/cast.md +77 -0
- package/skills/forge/reference/chain.md +73 -0
- package/skills/forge/reference/forge.md +44 -0
- package/skills/forge/reference/inscribe.md +129 -0
- package/skills/forge/reference/inspect.md +58 -0
- package/skills/forge/reference/patterns.md +108 -0
- package/skills/forge/reference/principles.md +29 -0
- package/skills/forge/reference/quench.md +74 -0
- package/skills/forge/reference/reforge.md +40 -0
- package/skills/forge/reference/relocate.md +38 -0
- package/skills/forge/reference/smelt.md +31 -0
- package/skills/forge/reference/temper.md +49 -0
- package/skills/forge/scripts/architecture.mjs +176 -0
- package/skills/forge/scripts/armorer.mjs +421 -0
- package/skills/forge/scripts/bootstrap.mjs +187 -0
- package/skills/forge/scripts/chain.mjs +258 -0
- package/skills/forge/scripts/context.mjs +237 -0
- package/skills/forge/scripts/detect.mjs +843 -0
- package/skills/forge/scripts/graph.mjs +594 -0
- package/skills/forge/scripts/inspect.mjs +193 -0
- package/skills/forge/scripts/profile.mjs +92 -0
- package/skills/forge/templates/feature/controller.ts.md +66 -0
- package/skills/forge/templates/feature/entity.ts.md +11 -0
- package/skills/forge/templates/feature/mapper.ts.md +18 -0
- package/skills/forge/templates/feature/repository-impl.ts.md +59 -0
- package/skills/forge/templates/feature/repository-interface.ts.md +12 -0
- package/skills/forge/templates/feature/routes.ts.md +17 -0
- package/skills/forge/templates/feature/schema.ts.md +16 -0
- package/skills/forge/templates/feature/use-case.ts.md +19 -0
- package/skills/forge/templates/infra/mail.ts.md +31 -0
- package/skills/forge/templates/infra/mongodb.ts.md +12 -0
- package/skills/forge/templates/infra/prisma.ts.md +21 -0
- package/skills/forge/templates/infra/redis.ts.md +30 -0
- package/skills/forge/templates/platform/config.ts.md +37 -0
- package/skills/forge/templates/platform/database.ts.md +40 -0
- package/skills/forge/templates/platform/di.ts.md +18 -0
- package/skills/forge/templates/platform/http.ts.md +29 -0
- package/skills/forge/templates/platform/logger.ts.md +38 -0
- package/skills/forge/templates/platform/server.ts.md +25 -0
- package/skills/forge/templates/shared/contract.ts.md +20 -0
- package/skills/forge/templates/shared/error.ts.md +27 -0
- package/skills/forge/templates/shared/type.ts.md +18 -0
- package/skills/forge/templates/shared/util.ts.md +23 -0
- package/src/cli.js +179 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forge
|
|
3
|
+
description: >
|
|
4
|
+
Architecture Operating System especializado en diseñar, construir, auditar,
|
|
5
|
+
proteger y evolucionar arquitecturas backend escalables basadas en features
|
|
6
|
+
(vertical slices), hexagonal architecture y DDD pragmático. Triggers:
|
|
7
|
+
"arquitectura", "migrar", "refactorizar", "features", "hexagonal",
|
|
8
|
+
"puertos y adaptadores", "clean architecture", "cast", "inspect",
|
|
9
|
+
"quench", "chain", "grafo", "graph", "nodo", "architecture graph",
|
|
10
|
+
"violaciones", "ownership", "platform", "infraestructura". Excluye
|
|
11
|
+
infraestructura (Docker, CI/CD), optimización de queries y cambios de
|
|
12
|
+
lógica de negocio sin reestructuración.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Forge — Backend Architecture Operating System
|
|
16
|
+
|
|
17
|
+
Forge es un **sistema operativo arquitectónico**. Diseña, construye, audita, protege y evoluciona arquitecturas backend completas. Opera sobre cualquier stack moderno modelando **cuatro dominios arquitectónicos**: **Platform**, **Features**, **Shared** e **Infrastructure**.
|
|
18
|
+
|
|
19
|
+
No es un template ni una guía. Es un orquestador.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Modelo Arquitectónico
|
|
24
|
+
|
|
25
|
+
Todo backend se modela en cuatro dominios:
|
|
26
|
+
|
|
27
|
+
| Layer | Propósito | Ejemplos |
|
|
28
|
+
|-------|-----------|----------|
|
|
29
|
+
| **Platform** | Backbone técnico global | config, database, http, server, logger, cache, security, events, di |
|
|
30
|
+
| **Features** | Capacidades de negocio | auth, users, payments |
|
|
31
|
+
| **Shared** | Componentes reutilizables puros | errors, contracts, types, utils |
|
|
32
|
+
| **Infrastructure** | Implementaciones concretas | prisma, mongodb, redis, mail |
|
|
33
|
+
|
|
34
|
+
### Dependency Rules
|
|
35
|
+
|
|
36
|
+
Permitido:
|
|
37
|
+
- `feature → platform`
|
|
38
|
+
- `feature → shared`
|
|
39
|
+
- `platform → infra`
|
|
40
|
+
- `adapter → infra`
|
|
41
|
+
- `feature → domain`
|
|
42
|
+
|
|
43
|
+
Prohibido:
|
|
44
|
+
- `feature → infra`
|
|
45
|
+
- `platform → feature`
|
|
46
|
+
- `shared → feature`
|
|
47
|
+
- `shared → infra`
|
|
48
|
+
- `domain → infra`
|
|
49
|
+
- `domain → platform`
|
|
50
|
+
- `infra → feature`
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Architecture Guidance
|
|
55
|
+
|
|
56
|
+
Ver `reference/principles.md` para el manifiesto completo y los 12 principios inquebrantables.
|
|
57
|
+
Ver `reference/patterns.md` para las convenciones de nomenclatura (PascalCase.artifact, kebab dirs, etc.).
|
|
58
|
+
En esencia:
|
|
59
|
+
|
|
60
|
+
| Principio | Regla |
|
|
61
|
+
|---|---|
|
|
62
|
+
| Cuatro dominios arquitectónicos | Platform, Features, Shared, Infrastructure con ownership estricto |
|
|
63
|
+
| Unidades autónomas | Cada feature es dueño de su dominio, aplicación y adapters |
|
|
64
|
+
| Dependencias unidireccionales | adapters → application → domain → (nada) |
|
|
65
|
+
| Cero lógica en controllers | El controller parsea, delega y responde |
|
|
66
|
+
| Cero BD fuera de repositorios | Los repositories son la única puerta a datos |
|
|
67
|
+
| DI disciplinada | Constructor injection, sin service locators |
|
|
68
|
+
| Cero acoplamiento directo entre features | Siempre vía interfaces inyectadas |
|
|
69
|
+
| Ownership obligatorio | Cada componente tiene un único propietario arquitectónico |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Boot Sequence (OBLIGATORIO — ejecutar siempre antes de responder)
|
|
74
|
+
|
|
75
|
+
ANTES de cualquier acción, Forge DEBE ejecutar esta secuencia. Si no lo haces, puedes dar respuestas incorrectas:
|
|
76
|
+
|
|
77
|
+
1. **context.mjs** — Detectar stack, platform, features, shared, infra, grafo, estado
|
|
78
|
+
2. **armorer.mjs** — Detectar ownership, huérfanos, duplicados, mal ubicados
|
|
79
|
+
3. **profile.mjs** — Determinar perfil tecnológico
|
|
80
|
+
4. **graph.mjs** — Construir grafo arquitectónico global (4 capas + 9 reglas)
|
|
81
|
+
5. **chain.mjs** — Analizar dependencias multi-capa
|
|
82
|
+
6. **inspect.mjs** — Auditoría completa con ownership + platform
|
|
83
|
+
7. **architecture.mjs** — Generar/actualizar ARCHITECTURE.md
|
|
84
|
+
8. **Ejecutar comando solicitado** — cast, quench, temper, etc.
|
|
85
|
+
9. **Actualizar ARCHITECTURE.md** — Reflejar nuevo estado
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Template de setup que debes ejecutar:
|
|
89
|
+
ctx=$(node .opencode/skills/forge/scripts/context.mjs --json 2>/dev/null)
|
|
90
|
+
armorer=$(node .opencode/skills/forge/scripts/armorer.mjs --json 2>/dev/null)
|
|
91
|
+
profile=$(node .opencode/skills/forge/scripts/profile.mjs --extended 2>/dev/null)
|
|
92
|
+
graph=$(node .opencode/skills/forge/scripts/graph.mjs --json 2>/dev/null)
|
|
93
|
+
deps=$(node .opencode/skills/forge/scripts/chain.mjs --json 2>/dev/null)
|
|
94
|
+
inspect=$(node .opencode/skills/forge/scripts/inspect.mjs --json 2>/dev/null)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Command Routing
|
|
100
|
+
|
|
101
|
+
| Lenguaje natural | Comando | Archivo |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| "inicializar", "setup", "empezar" | `forge` | `reference/forge.md` |
|
|
104
|
+
| "crear feature", "nuevo dominio" | `cast` | `reference/cast.md` |
|
|
105
|
+
| "inspeccionar", "diagnóstico", "evaluar" | `inspect` | `reference/inspect.md` |
|
|
106
|
+
| "trasladar", "mover", "reestructurar feature" | `relocate` | `reference/relocate.md` |
|
|
107
|
+
| "refactorizar", "rediseñar", "cambiar estructura" | `reforge` | `reference/reforge.md` |
|
|
108
|
+
| "verificar", "quench", "checklist" | `quench` | `reference/quench.md` |
|
|
109
|
+
| "templar", "endurecer", "mejorar" | `temper` | `reference/temper.md` |
|
|
110
|
+
| "cadena", "grafo", "acoplamiento" | `chain` | `scripts/chain.mjs` |
|
|
111
|
+
| "inscribir", "grabar", "ARCHITECTURE.md" | `inscribe` | `reference/inscribe.md` |
|
|
112
|
+
| "grafo", "graph", "nodo", "violaciones", "risk score" | `graph` | `scripts/graph.mjs` |
|
|
113
|
+
| "fundir", "compartir", "mover a shared" | `smelt` | `reference/smelt.md` |
|
|
114
|
+
| "ownership", "huérfanos", "armorer" | `inspect` | (incluido en auditoría) |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Execution Flow
|
|
119
|
+
|
|
120
|
+
Para cada comando, Forge sigue este flujo:
|
|
121
|
+
|
|
122
|
+
1. **Contexto**: Ejecutar `context.mjs` + `armorer.mjs` + `profile.mjs`
|
|
123
|
+
2. **Grafo**: Ejecutar `graph.mjs` + `chain.mjs`
|
|
124
|
+
3. **Auditoría**: Ejecutar `inspect.mjs`
|
|
125
|
+
4. **Referencia**: Cargar `reference/<command>.md`
|
|
126
|
+
5. **Ejecutar**: Aplicar el flujo definido en la referencia, usando los scripts según corresponda
|
|
127
|
+
6. **Verificar**: Ejecutar `scripts/detect.mjs` para verificar que no se introdujeron violaciones
|
|
128
|
+
7. **Actualizar ARCHITECTURE.md**: Reflejar el nuevo estado (`architecture.mjs`)
|
|
129
|
+
8. **Reportar**: Mostrar resultado al usuario con severidades
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Routing Rules
|
|
134
|
+
|
|
135
|
+
- Si el lenguaje natural matchea exactamente un comando, ejecutarlo directamente.
|
|
136
|
+
- Si hay ambigüedad, preguntar al usuario: "¿Quieres decir: cast, relocate o reforge?"
|
|
137
|
+
- Si el comando requiere un perfil que no está detectado, preguntar antes de continuar.
|
|
138
|
+
- Si el proyecto no tiene `src/features/` y el comando es `cast`, sugerir `forge` primero.
|
|
139
|
+
- Si el proyecto no tiene `src/platform/`, ejecutar `bootstrapPlatform()` automáticamente.
|
|
140
|
+
- Si `ARCHITECTURE.md` está desactualizado (fecha de auditoría > 7 días), sugerir `forge inscribe`.
|
|
141
|
+
- Todos los resultados se muestran con severidades: `[CRITICAL]`, `[ERROR]`, `[WARNING]`, `[INFO]`, `[SUGGESTION]`.
|
|
142
|
+
|
|
143
|
+
## ARCHITECTURE.md
|
|
144
|
+
|
|
145
|
+
Forge mantiene un archivo `ARCHITECTURE.md` en la raíz del proyecto con el contexto persistente. Contiene:
|
|
146
|
+
|
|
147
|
+
```md
|
|
148
|
+
# Architecture State
|
|
149
|
+
|
|
150
|
+
Project Name: <name>
|
|
151
|
+
Framework: <detectado>
|
|
152
|
+
Runtime: <detectado>
|
|
153
|
+
Database: <detectado>
|
|
154
|
+
ORM: <detectado>
|
|
155
|
+
DI Strategy: <detectado>
|
|
156
|
+
Profile: <detectado>
|
|
157
|
+
Architecture: hexagonal-feature (Platform + Features + Shared + Infra)
|
|
158
|
+
Last Audit: <fecha> (score: <puntaje>)
|
|
159
|
+
|
|
160
|
+
## Platform
|
|
161
|
+
- platform/config/
|
|
162
|
+
- platform/server/
|
|
163
|
+
...
|
|
164
|
+
|
|
165
|
+
## Features
|
|
166
|
+
- features/users/
|
|
167
|
+
...
|
|
168
|
+
|
|
169
|
+
## Shared
|
|
170
|
+
- shared/errors/
|
|
171
|
+
...
|
|
172
|
+
|
|
173
|
+
## Infrastructure
|
|
174
|
+
- infra/prisma/
|
|
175
|
+
...
|
|
176
|
+
|
|
177
|
+
## Ownership
|
|
178
|
+
Health: healthy | degraded | critical
|
|
179
|
+
Score: 0-100
|
|
180
|
+
Orphans: 0
|
|
181
|
+
Duplicates: 0
|
|
182
|
+
Misplaced: 0
|
|
183
|
+
|
|
184
|
+
## Architecture Graph
|
|
185
|
+
...
|
|
186
|
+
|
|
187
|
+
## Dependency Health
|
|
188
|
+
...
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
El agente DEBE leer este archivo al inicio de cada interacción y actualizarlo al finalizar cada comando.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Module Index
|
|
196
|
+
|
|
197
|
+
| Módulo | Propósito |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `reference/principles.md` | Manifiesto y 12 principios inquebrantables |
|
|
200
|
+
| `reference/patterns.md` | Convenciones de nomenclatura globales (PascalCase.artifact, kebab dirs, etc.) |
|
|
201
|
+
| `profiles/` | Perfiles tecnológicos detallados (Express, Fastify, NestJS, etc.) |
|
|
202
|
+
| `scripts/` | Scripts de análisis: context, detect, inspect, chain, profile, graph, architecture, armorer, bootstrap |
|
|
203
|
+
| `templates/feature/` | Templates de feature (entity, repository, uc, controller, routes, schema, mapper) |
|
|
204
|
+
| `templates/platform/` | Templates de platform (config, server, database, logger, http, di) |
|
|
205
|
+
| `templates/shared/` | Templates de shared (errors, contracts, types, utils) |
|
|
206
|
+
| `templates/infra/` | Templates de infra (prisma, mongodb, redis, mail) |
|
|
207
|
+
| `command/forge.md` | Definición del comando `/forge` para opencode |
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Forge — Backend Architecture OS. Subcomandos: forge, cast, inspect, relocate, reforge, quench, temper, chain, inscribe, smelt.
|
|
3
|
+
agent: build
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Ejecuta herramientas de Forge según el subcomando especificado en $ARGUMENTS.
|
|
7
|
+
|
|
8
|
+
## Build
|
|
9
|
+
|
|
10
|
+
### forge
|
|
11
|
+
|
|
12
|
+
Inicializa el proyecto arquitectónicamente (incluye platform, shared, infra).
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
node .opencode/skills/forge/scripts/context.mjs
|
|
16
|
+
node .opencode/skills/forge/scripts/bootstrap.mjs
|
|
17
|
+
node .opencode/skills/forge/scripts/armorer.mjs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### cast
|
|
21
|
+
|
|
22
|
+
Crea un nuevo feature (verifica platform/shared/infra primero).
|
|
23
|
+
|
|
24
|
+
### relocate
|
|
25
|
+
|
|
26
|
+
Migra un feature existente.
|
|
27
|
+
|
|
28
|
+
### inscribe
|
|
29
|
+
|
|
30
|
+
Genera ARCHITECTURE.md con grafo arquitectónico, ownership y platform.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
node .opencode/skills/forge/scripts/architecture.mjs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### architecture
|
|
37
|
+
|
|
38
|
+
Construye el grafo arquitectónico del proyecto (4 capas: platform, feature, shared, infra).
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
node .opencode/skills/forge/scripts/graph.mjs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Para salida JSON:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
node .opencode/skills/forge/scripts/graph.mjs --json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### smelt
|
|
51
|
+
|
|
52
|
+
Extrae código reutilizable a shared/.
|
|
53
|
+
|
|
54
|
+
### bootstrap
|
|
55
|
+
|
|
56
|
+
Inicializa platform, shared e infra layers (interno, se ejecuta automáticamente).
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
node .opencode/skills/forge/scripts/bootstrap.mjs
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Evaluate
|
|
63
|
+
|
|
64
|
+
### inspect
|
|
65
|
+
|
|
66
|
+
Inspecciona la conformidad arquitectónica (incluye ownership y platform).
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
node .opencode/skills/forge/scripts/inspect.mjs
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Para salida JSON:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
node .opencode/skills/forge/scripts/inspect.mjs --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### quench
|
|
79
|
+
|
|
80
|
+
Verifica reglas arquitectónicas.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
node .opencode/skills/forge/scripts/detect.mjs
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### chain
|
|
87
|
+
|
|
88
|
+
Analiza dependencias multi-capa (platform, features, shared, infra).
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
node .opencode/skills/forge/scripts/chain.mjs
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### armorer
|
|
95
|
+
|
|
96
|
+
Detecta ownership, huérfanos, duplicados y mal ubicados.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
node .opencode/skills/forge/scripts/armorer.mjs
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Refine
|
|
103
|
+
|
|
104
|
+
### reforge
|
|
105
|
+
|
|
106
|
+
Refactoriza la arquitectura de un feature.
|
|
107
|
+
|
|
108
|
+
### temper
|
|
109
|
+
|
|
110
|
+
Fortalece la arquitectura (DI, seguridad, consistencia).
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Profile: Express + MongoDB + Mongoose
|
|
2
|
+
|
|
3
|
+
## Metadata
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
framework: Express
|
|
7
|
+
runtime: Node 20+
|
|
8
|
+
database: MongoDB
|
|
9
|
+
orm: Mongoose
|
|
10
|
+
di_strategy: tsyringe
|
|
11
|
+
architecture: hexagonal-feature
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Project Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
├── src/
|
|
18
|
+
│ ├── features/
|
|
19
|
+
│ │ └── <domain>/
|
|
20
|
+
│ │ ├── domain/
|
|
21
|
+
│ │ │ ├── <Domain>.entity.ts # Interfaz pura del dominio
|
|
22
|
+
│ │ │ └── I<Domain>Repository.ts # Puerto de repositorio
|
|
23
|
+
│ │ ├── application/
|
|
24
|
+
│ │ │ ├── use-cases/
|
|
25
|
+
│ │ │ │ ├── Create.ts / Add.ts
|
|
26
|
+
│ │ │ │ ├── Get.ts / FindById.ts
|
|
27
|
+
│ │ │ │ ├── List.ts
|
|
28
|
+
│ │ │ │ ├── Update.ts
|
|
29
|
+
│ │ │ │ └── Delete.ts
|
|
30
|
+
│ │ │ ├── dto/ # Data Transfer Objects
|
|
31
|
+
│ │ │ │ └── <Domain>DTO.ts
|
|
32
|
+
│ │ │ └── mappers/
|
|
33
|
+
│ │ │ └── <Domain>.mapper.ts # Dominio ↔ Persistencia
|
|
34
|
+
│ │ └── adapters/
|
|
35
|
+
│ │ ├── in/http/
|
|
36
|
+
│ │ │ ├── <Domain>Controller.ts
|
|
37
|
+
│ │ │ └── <domain>.routes.ts
|
|
38
|
+
│ │ └── out/
|
|
39
|
+
│ │ └── persistence/
|
|
40
|
+
│ │ ├── <Domain>Repository.ts
|
|
41
|
+
│ │ └── <Domain>Schema.ts
|
|
42
|
+
│ ├── shared/
|
|
43
|
+
│ │ ├── errors/
|
|
44
|
+
│ │ ├── port/ # Puertos globales (ILogger, IHttpClient, etc.)
|
|
45
|
+
│ │ └── utils/
|
|
46
|
+
│ ├── infrastructure/
|
|
47
|
+
│ │ └── db.ts # Conexión MongoDB
|
|
48
|
+
│ ├── adapters/out/ # Adapters transversales
|
|
49
|
+
│ │ ├── email/
|
|
50
|
+
│ │ ├── scheduler/
|
|
51
|
+
│ │ └── auth/
|
|
52
|
+
│ ├── setting/ # Configuración global
|
|
53
|
+
│ ├── app.ts # Express app + DI container bootstrap
|
|
54
|
+
│ └── server.ts # Entry point
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## DI Setup (tsyringe)
|
|
58
|
+
|
|
59
|
+
### Dependencies
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"tsyringe": "^4.8.0",
|
|
65
|
+
"reflect-metadata": "^0.2.2"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### tsconfig
|
|
71
|
+
|
|
72
|
+
```jsonc
|
|
73
|
+
{
|
|
74
|
+
"compilerOptions": {
|
|
75
|
+
"experimentalDecorators": true,
|
|
76
|
+
"emitDecoratorMetadata": true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Entry point (server.ts)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import "reflect-metadata"; // Antes que cualquier otro import
|
|
85
|
+
import { createApp } from "./app.js";
|
|
86
|
+
// ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Container registration (app.ts)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { container } from "tsyringe";
|
|
93
|
+
|
|
94
|
+
// Interfaces se registran como singletons
|
|
95
|
+
container.registerSingleton<ICreditRepository>(
|
|
96
|
+
ICreditRepository as symbol,
|
|
97
|
+
CreditRepository
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## DI Rules
|
|
102
|
+
|
|
103
|
+
- `@injectable()` en toda clase con dependencias (use cases, controllers, repositories)
|
|
104
|
+
- `@inject(Token)` con tokens de clase, nunca strings
|
|
105
|
+
- `container.resolve()` solo en bootstrap (app.ts, routes)
|
|
106
|
+
- Prohibido `container.resolve()` en use cases, entities o adapters
|
|
107
|
+
- Prohibido `new UseCase(dep1, dep2)` en features migrados a DI
|
|
108
|
+
|
|
109
|
+
## Routes & Controllers
|
|
110
|
+
|
|
111
|
+
### Controller pattern
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
@injectable()
|
|
115
|
+
export class CreditController {
|
|
116
|
+
constructor(
|
|
117
|
+
@inject(AddCredit) private readonly addCredit: AddCredit,
|
|
118
|
+
@inject(GetCredit) private readonly getCredit: GetCredit,
|
|
119
|
+
) {}
|
|
120
|
+
|
|
121
|
+
add = async (req: Request, res: Response, next: NextFunction) => {
|
|
122
|
+
try {
|
|
123
|
+
const data = req.body;
|
|
124
|
+
const result = await this.addCredit.execute(data);
|
|
125
|
+
res.status(201).json({ data: result });
|
|
126
|
+
} catch (error) {
|
|
127
|
+
next(error);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Routes pattern
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { Router } from "express";
|
|
137
|
+
import { container } from "tsyringe";
|
|
138
|
+
import { CreditController } from "./CreditController.js";
|
|
139
|
+
|
|
140
|
+
const router = Router();
|
|
141
|
+
const controller = container.resolve(CreditController);
|
|
142
|
+
|
|
143
|
+
router.post("/", controller.add);
|
|
144
|
+
router.get("/:id", controller.getById);
|
|
145
|
+
|
|
146
|
+
export default router;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Persistence (Mongoose)
|
|
150
|
+
|
|
151
|
+
### Schema pattern
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { model, Schema } from "mongoose";
|
|
155
|
+
import type { Credit } from "../../../domain/Credit.entity.js";
|
|
156
|
+
|
|
157
|
+
const creditSchema = new Schema<Credit>(
|
|
158
|
+
{ /* fields */ },
|
|
159
|
+
{ timestamps: true }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
export default model<Credit>("Credit", creditSchema);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Repository pattern
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
@injectable()
|
|
169
|
+
export class CreditRepository implements ICreditRepository {
|
|
170
|
+
async create(data: Partial<Credit>): Promise<Credit> {
|
|
171
|
+
const doc = await CreditModel.create(data);
|
|
172
|
+
return CreditMapper.toDomain(doc.toObject());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async findById(id: string): Promise<Credit | null> {
|
|
176
|
+
const doc = await CreditModel.findById(id).lean();
|
|
177
|
+
return doc ? CreditMapper.toDomain(doc) : null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Transactions
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { ClientSession } from "mongoose";
|
|
186
|
+
|
|
187
|
+
@injectable()
|
|
188
|
+
export class CreditRepository implements ICreditRepository {
|
|
189
|
+
async create(data: Partial<Credit>, session?: ClientSession): Promise<Credit> {
|
|
190
|
+
const doc = session
|
|
191
|
+
? (await CreditModel.create([data], { session }))[0]
|
|
192
|
+
: await CreditModel.create(data);
|
|
193
|
+
return CreditMapper.toDomain(doc.toObject());
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Usar transacciones desde el use case:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import mongoose from "mongoose";
|
|
202
|
+
|
|
203
|
+
@injectable()
|
|
204
|
+
export class CreateCredit {
|
|
205
|
+
async execute(data: CreditInput): Promise<Credit> {
|
|
206
|
+
const session = await mongoose.startSession();
|
|
207
|
+
try {
|
|
208
|
+
session.startTransaction();
|
|
209
|
+
const credit = await this.creditRepo.create(data, session);
|
|
210
|
+
await session.commitTransaction();
|
|
211
|
+
return credit;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
await session.abortTransaction();
|
|
214
|
+
throw error;
|
|
215
|
+
} finally {
|
|
216
|
+
session.endSession();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Testing Strategy
|
|
223
|
+
|
|
224
|
+
### Unit test (use case)
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import "reflect-metadata";
|
|
228
|
+
import { container } from "tsyringe";
|
|
229
|
+
import { ICreditRepository } from "@/features/credit/domain/ICreditRepository.js";
|
|
230
|
+
|
|
231
|
+
const mockRepo = { create: jest.fn() };
|
|
232
|
+
|
|
233
|
+
beforeEach(() => {
|
|
234
|
+
container.registerInstance(ICreditRepository as symbol, mockRepo);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
afterEach(() => {
|
|
238
|
+
container.clearInstances();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("creates a credit", async () => {
|
|
242
|
+
mockRepo.create.mockResolvedValue({ id: "1" });
|
|
243
|
+
const useCase = container.resolve(AddCredit);
|
|
244
|
+
const result = await useCase.execute({ amount: 100 });
|
|
245
|
+
expect(result.id).toBe("1");
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Integration test (repository)
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import mongoose from "mongoose";
|
|
253
|
+
import { CreditRepository } from "./CreditRepository.js";
|
|
254
|
+
|
|
255
|
+
beforeAll(async () => {
|
|
256
|
+
await mongoose.connect(global.__MONGO_URI__);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
afterAll(async () => {
|
|
260
|
+
await mongoose.disconnect();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("persists a credit", async () => {
|
|
264
|
+
const repo = new CreditRepository();
|
|
265
|
+
const credit = await repo.create({ amount: 100 });
|
|
266
|
+
expect(credit.id).toBeDefined();
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Naming Conventions
|
|
271
|
+
|
|
272
|
+
| Elemento | Convención | Ejemplo |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| Entidad (archivo) | `<Domain>.entity.ts` | `Credit.entity.ts` |
|
|
275
|
+
| Interfaz repositorio | `I<Domain>Repository.ts` | `ICreditRepository.ts` |
|
|
276
|
+
| Use case (archivo) | `Create.ts`, `Get.ts`, etc. | `Create.ts` |
|
|
277
|
+
| Controller | `<Domain>Controller.ts` | `CreditController.ts` |
|
|
278
|
+
| Routes | `<domain>.routes.ts` | `credit.routes.ts` |
|
|
279
|
+
| Schema | `<Domain>Schema.ts` | `CreditSchema.ts` |
|
|
280
|
+
| Repository impl | `<Domain>Repository.ts` | `CreditRepository.ts` |
|
|
281
|
+
| Mapper | `<Domain>.mapper.ts` | `Credit.mapper.ts` |
|
|
282
|
+
| DTO | `<Domain>DTO.ts` | `CreditDTO.ts` |
|
|
283
|
+
|
|
284
|
+
## Imports
|
|
285
|
+
|
|
286
|
+
- Usar `@/` alias para imports absolutos (mapeado a `src/`)
|
|
287
|
+
- Extensión `.js` en imports locales (ESM)
|
|
288
|
+
- En imports dentro del mismo feature, usar rutas relativas: `../../domain/X.entity.js`
|
|
289
|
+
- Para imports entre features, inyectar la interfaz, nunca importar directamente
|