arckode-framework 1.3.1 → 1.4.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.
- package/adapters/jwt.ts +6 -4
- package/adapters/mysql.ts +7 -2
- package/adapters/postgres.ts +37 -0
- package/adapters/sqlite.ts +7 -1
- package/adapters/vendor.d.ts +48 -0
- package/cli/analyze/checks.ts +333 -0
- package/cli/analyze/index.ts +44 -0
- package/cli/analyze/report.ts +107 -0
- package/cli/analyze/types.ts +46 -0
- package/cli/analyze/utils.ts +36 -0
- package/cli/analyze.ts +2 -647
- package/cli/commands/db-migrate.ts +213 -89
- package/cli/commands/db-seed.ts +97 -32
- package/cli/commands/db-utils.ts +192 -0
- package/cli/commands/new.ts +175 -0
- package/cli/commands/routes.ts +94 -0
- package/cli/index.ts +57 -404
- package/cli/stubs/claude-md-stub.ts +21 -8
- package/cli/stubs/module/core.ts +162 -0
- package/cli/stubs/module/data.ts +171 -0
- package/cli/stubs/module/index.ts +5 -0
- package/cli/stubs/module/service.ts +198 -0
- package/cli/stubs/module/types.ts +12 -0
- package/cli/stubs/module-stub.ts +2 -552
- package/kernel/auth.ts +114 -0
- package/kernel/cache.ts +37 -0
- package/kernel/config.ts +129 -0
- package/kernel/container.ts +64 -0
- package/kernel/db/orm-migrate.ts +136 -0
- package/kernel/db/orm-repository.ts +45 -0
- package/kernel/db/orm-utils.ts +93 -0
- package/kernel/db/orm.ts +254 -0
- package/kernel/db/transactor.ts +17 -0
- package/kernel/db/types.ts +72 -0
- package/kernel/errors.ts +102 -0
- package/kernel/framework.default.ts +41 -0
- package/kernel/framework.ts +8 -2144
- package/kernel/http/router.ts +131 -0
- package/kernel/http/server.ts +303 -0
- package/kernel/http/types.ts +56 -0
- package/kernel/index.ts +25 -0
- package/kernel/logger.ts +50 -0
- package/kernel/middlewares.ts +38 -21
- package/kernel/modules/create-module.ts +5 -0
- package/kernel/modules/system.ts +149 -0
- package/kernel/modules/types.ts +46 -0
- package/kernel/seeds.ts +48 -0
- package/kernel/static.ts +11 -2
- package/kernel/testing.ts +8 -3
- package/kernel/validator.ts +116 -0
- package/modules/events/index.ts +19 -3
- package/modules/mail/index.ts +14 -2
- package/modules/storage/local-adapter.ts +19 -5
- package/modules/ws/index.ts +123 -18
- package/package.json +8 -11
- package/skills/auth/SKILL.md +36 -220
- package/skills/cli/SKILL.md +32 -251
- package/skills/config/SKILL.md +30 -239
- package/skills/connectors/SKILL.md +32 -295
- package/skills/helpers/SKILL.md +26 -195
- package/skills/middlewares/SKILL.md +30 -267
- package/skills/orm/SKILL.md +42 -349
- package/skills/realtime/SKILL.md +22 -297
- package/skills/services/SKILL.md +40 -183
- package/skills/testing/SKILL.md +34 -266
|
@@ -1,282 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Middlewares — HTTP Pipeline
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Stack
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Express 4.x (request → middleware chain → controller)
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Orden canónico
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
compression,
|
|
17
|
-
requireAuth,
|
|
18
|
-
} from 'arckode-framework/middlewares'
|
|
10
|
+
app.use(cors) // 1. CORS
|
|
11
|
+
app.use(rateLimit) // 2. Rate limit
|
|
12
|
+
app.use(authenticate) // 3. Auth/JWT
|
|
13
|
+
app.use(bodyParser) // 4. Body parser
|
|
14
|
+
app.use(validateSchema) // 5. Validación
|
|
15
|
+
app.use(cors) // ❌ tarde — rate limit sin CORS
|
|
19
16
|
```
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
## Built-in
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
| Middleware | Propósito | Notas |
|
|
21
|
+
|------------|-----------|-------|
|
|
22
|
+
| `corsMw` | CORS headers | Configurar origins por env |
|
|
23
|
+
| `rateLimitMw` | Rate limiting | Ventana por IP |
|
|
24
|
+
| `authMw` | JWT verify | Setea `req.user` |
|
|
25
|
+
| `validateSchemaMw` | Schema validation | POST/PUT/PATCH |
|
|
26
|
+
| `errorHandlerMw` | Catch + format | Último en cadena |
|
|
27
|
+
| `requestLoggerMw` | Log request | Método, path, status, ms |
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
## Middleware personalizado
|
|
26
30
|
|
|
27
31
|
```ts
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
router.use(cors({ origins: ['https://mi-app.com'], credentials: true })) // 2. CORS
|
|
33
|
-
router.use(bodyLimit(2 * 1024 * 1024)) // 3. Rechazar bodies > 2MB antes de parsear
|
|
34
|
-
router.use(requestLogger(logger)) // 4. Loguear método, path, status, duración
|
|
35
|
-
router.use(compression()) // 5. Gzip para responses > 1KB (opcional)
|
|
36
|
-
// rate limit: ver sección 3 — puede ser global o por ruta
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**El orden importa:** si `timeout` va al final, no puede cortar las fases anteriores.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## 3. CORS
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
// Permitir cualquier origen (solo para desarrollo/APIs públicas)
|
|
47
|
-
router.use(cors())
|
|
48
|
-
|
|
49
|
-
// Origenes específicos (producción)
|
|
50
|
-
router.use(cors({
|
|
51
|
-
origins: ['https://mi-app.com', 'https://admin.mi-app.com'],
|
|
52
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
53
|
-
headers: ['Content-Type', 'Authorization'],
|
|
54
|
-
credentials: true, // necesario si el frontend envía cookies o Authorization
|
|
55
|
-
}))
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**Preflight:** el middleware maneja `OPTIONS` automáticamente y retorna 204.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## 4. RATE LIMIT
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
// Global: 100 requests por minuto por IP
|
|
66
|
-
const limiter = rateLimit({ windowMs: 60_000, max: 100 })
|
|
67
|
-
router.use(limiter)
|
|
68
|
-
|
|
69
|
-
// Por ruta específica (más restrictivo para endpoints sensibles)
|
|
70
|
-
const loginLimiter = rateLimit({
|
|
71
|
-
windowMs: 15 * 60_000, // 15 minutos
|
|
72
|
-
max: 5, // máximo 5 intentos
|
|
73
|
-
keyBy: (req) => req.headers['x-forwarded-for'] as string ?? 'anon',
|
|
74
|
-
})
|
|
75
|
-
router.post('/auth/login', [loginLimiter], req => controller.login(req))
|
|
76
|
-
|
|
77
|
-
// Rate limit por usuario autenticado (después de que auth middleware corra)
|
|
78
|
-
const userLimiter = rateLimit({
|
|
79
|
-
windowMs: 60_000,
|
|
80
|
-
max: 200,
|
|
81
|
-
keyBy: (req) => req.user?.id ?? req.headers['x-forwarded-for'] as string ?? 'anon',
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
// Resetear límite de un usuario (ej: tras verificación de captcha)
|
|
85
|
-
limiter.reset('192.168.1.1')
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Importante:** El rate limiter usa memoria en proceso — no persiste entre reinicios ni entre múltiples instancias. Para producción distribuida → implementar `RateLimitAdapter` con Redis.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 5. REQUEST LOGGER
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
// Loguea: "POST /auth/login", "POST /auth/login 200 43ms"
|
|
96
|
-
router.use(requestLogger(logger))
|
|
97
|
-
|
|
98
|
-
// El logger hijo del módulo también puede usarse
|
|
99
|
-
router.use(requestLogger(logger.child('http')))
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Output en logs:
|
|
103
|
-
```
|
|
104
|
-
[app.http] POST /auth/login { requestId: 'abc-123' }
|
|
105
|
-
[app.http] POST /auth/login 200 43ms { requestId: 'abc-123', status: 200, duration: 43 }
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## 6. BODY LIMIT
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
// Default: 1MB
|
|
114
|
-
router.use(bodyLimit())
|
|
115
|
-
|
|
116
|
-
// Custom: 10MB (para endpoints de upload)
|
|
117
|
-
router.use(bodyLimit(10 * 1024 * 1024))
|
|
118
|
-
|
|
119
|
-
// Por ruta (más permisivo solo en upload)
|
|
120
|
-
router.post('/archivos/upload', [bodyLimit(50 * 1024 * 1024)], req => controller.upload(req))
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Retorna **413** si el body supera el límite.
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## 7. TIMEOUT
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
// Default: 5 segundos
|
|
131
|
-
router.use(timeout())
|
|
132
|
-
|
|
133
|
-
// Custom por aplicación
|
|
134
|
-
router.use(timeout(10_000)) // 10s
|
|
135
|
-
|
|
136
|
-
// Más permisivo para endpoints lentos (reportes, exports)
|
|
137
|
-
router.get('/reportes/export', [timeout(60_000)], req => controller.export(req))
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Lanza `InternalError` si el handler no responde en tiempo.
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## 8. COMPRESSION (gzip)
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
// Comprime responses JSON > 1KB si el cliente acepta gzip
|
|
148
|
-
router.use(compression())
|
|
149
|
-
|
|
150
|
-
// Custom threshold
|
|
151
|
-
router.use(compression({ threshold: 512 })) // comprimir si > 512 bytes
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
**No aplica a:** SSE streams, responses `null`, responses que ya tienen `Content-Encoding`.
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## 9. MIDDLEWARES POR RUTA
|
|
159
|
-
|
|
160
|
-
```ts
|
|
161
|
-
// Un middleware
|
|
162
|
-
router.get('/admin', [auth.authenticate('admin')], req => controller.dashboard(req))
|
|
163
|
-
|
|
164
|
-
// Múltiples middlewares (se ejecutan en orden)
|
|
165
|
-
router.post(
|
|
166
|
-
'/auth/login',
|
|
167
|
-
[loginLimiter, bodyLimit(10_000)],
|
|
168
|
-
req => controller.login(req)
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
// Auth + role específico
|
|
172
|
-
router.delete(
|
|
173
|
-
'/usuarios/:id',
|
|
174
|
-
[auth.authenticate('admin', 'superadmin')],
|
|
175
|
-
req => controller.eliminar(req)
|
|
176
|
-
)
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
## 10. CREAR UN MIDDLEWARE PROPIO
|
|
182
|
-
|
|
183
|
-
```ts
|
|
184
|
-
import type { MiddlewareHandler } from 'arckode-framework'
|
|
185
|
-
|
|
186
|
-
// Middleware que verifica un API key en el header
|
|
187
|
-
export function apiKeyAuth(validKeys: string[]): MiddlewareHandler {
|
|
188
|
-
return async (req, next) => {
|
|
189
|
-
const key = req.headers['x-api-key'] as string
|
|
190
|
-
if (!key || !validKeys.includes(key)) {
|
|
191
|
-
return { status: 401, body: { error: 'API key inválida' } }
|
|
192
|
-
}
|
|
193
|
-
return next() // siempre llamar next() para continuar la cadena
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Middleware de auditoría
|
|
198
|
-
export function auditLog(repo: RepositoryAdapter<AuditoriaDTO>): MiddlewareHandler {
|
|
199
|
-
return async (req, next) => {
|
|
200
|
-
const res = await next()
|
|
201
|
-
if (req.method !== 'GET' && res.status < 400) {
|
|
202
|
-
await repo.create({
|
|
203
|
-
userId: req.user?.id ?? 'anon',
|
|
204
|
-
action: `${req.method} ${req.path}`,
|
|
205
|
-
status: res.status,
|
|
206
|
-
} as any)
|
|
207
|
-
}
|
|
208
|
-
return res
|
|
209
|
-
}
|
|
32
|
+
// src/shared/http/middlewares/audit.middleware.ts
|
|
33
|
+
export function auditMiddleware(req, res, next) {
|
|
34
|
+
console.log(`[AUDIT] ${req.method} ${req.path}`)
|
|
35
|
+
next()
|
|
210
36
|
}
|
|
211
|
-
|
|
212
|
-
// Registrar
|
|
213
|
-
router.use(apiKeyAuth(['key-secreta-1', 'key-secreta-2']))
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
**Regla:** siempre llamar `await next()` y retornar su resultado — excepto cuando querés cortar la cadena (retornar error directo, como en el ejemplo de apiKeyAuth).
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## 11. CADENA DE EJECUCIÓN
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
Request entrante
|
|
224
|
-
→ timeout (global)
|
|
225
|
-
→ cors (global)
|
|
226
|
-
→ bodyLimit (global)
|
|
227
|
-
→ requestLogger (global)
|
|
228
|
-
→ compression (global)
|
|
229
|
-
→ loginLimiter (por ruta, si aplica)
|
|
230
|
-
→ auth.authenticate (por ruta, si aplica)
|
|
231
|
-
→ handler (controller)
|
|
232
|
-
← Response saliente
|
|
233
37
|
```
|
|
234
38
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## 12. ANTI-PATRONES
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
// ❌ Middleware global que modifica req.body con lógica de negocio
|
|
243
|
-
router.use(async (req, next) => {
|
|
244
|
-
req.body.precio = req.body.precio * 1.21 // impuesto — NO acá, va en el service
|
|
245
|
-
return next()
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// ❌ Olvidar llamar next() — la request queda colgada
|
|
249
|
-
router.use(async (req, next) => {
|
|
250
|
-
if (req.headers['x-tenant']) {
|
|
251
|
-
// ... pero nunca llama next()
|
|
252
|
-
}
|
|
253
|
-
// cuelga para requests sin x-tenant
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
// ❌ Lógica de autenticación duplicada en cada handler
|
|
257
|
-
router.get('/datos', async (req) => {
|
|
258
|
-
const token = req.headers['authorization']
|
|
259
|
-
if (!token) return { status: 401, body: { error: '...' } } // NO
|
|
260
|
-
// usar auth.authenticate() como middleware por ruta
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
// ✅ Cortar la cadena cuando es correcto (no llamar next)
|
|
264
|
-
router.use(async (req, next) => {
|
|
265
|
-
if (req.method === 'OPTIONS') {
|
|
266
|
-
return { status: 204, body: null } // cortar la cadena intencionalmente
|
|
267
|
-
}
|
|
268
|
-
return next()
|
|
269
|
-
})
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## 13. CHECKLIST MIDDLEWARES
|
|
39
|
+
## Errores silenciosos
|
|
275
40
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
- [ ] Todo middleware custom llama `await next()` (excepto cuando corta intencionalmente)
|
|
282
|
-
- [ ] No hay lógica de negocio en middlewares — solo cross-cutting concerns
|
|
41
|
+
| Error | Señal | Fix |
|
|
42
|
+
|-------|-------|-----|
|
|
43
|
+
| Body parser después de auth | Auth lee raw body | Body parser ANTES |
|
|
44
|
+
| Error handler ausente | Error crashea server | `app.use(errorHandlerMw)` al final |
|
|
45
|
+
| Rate limit en todas las rutas | GET /health también limitado | Skip en health endpoints |
|