arckode-framework 1.3.2 → 1.4.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.
Files changed (64) hide show
  1. package/adapters/jwt.ts +6 -4
  2. package/adapters/mysql.ts +7 -2
  3. package/adapters/postgres.ts +37 -0
  4. package/adapters/sqlite.ts +7 -1
  5. package/adapters/vendor.d.ts +48 -0
  6. package/cli/analyze/checks.ts +333 -0
  7. package/cli/analyze/index.ts +44 -0
  8. package/cli/analyze/report.ts +107 -0
  9. package/cli/analyze/types.ts +46 -0
  10. package/cli/analyze/utils.ts +36 -0
  11. package/cli/analyze.ts +2 -647
  12. package/cli/commands/db-migrate.ts +213 -89
  13. package/cli/commands/db-seed.ts +97 -32
  14. package/cli/commands/db-utils.ts +192 -0
  15. package/cli/commands/new.ts +175 -0
  16. package/cli/commands/routes.ts +94 -0
  17. package/cli/index.ts +57 -404
  18. package/cli/stubs/module/core.ts +162 -0
  19. package/cli/stubs/module/data.ts +171 -0
  20. package/cli/stubs/module/index.ts +5 -0
  21. package/cli/stubs/module/service.ts +198 -0
  22. package/cli/stubs/module/types.ts +12 -0
  23. package/cli/stubs/module-stub.ts +2 -552
  24. package/kernel/auth.ts +114 -0
  25. package/kernel/cache.ts +37 -0
  26. package/kernel/config.ts +129 -0
  27. package/kernel/container.ts +64 -0
  28. package/kernel/db/orm-migrate.ts +136 -0
  29. package/kernel/db/orm-repository.ts +45 -0
  30. package/kernel/db/orm-utils.ts +93 -0
  31. package/kernel/db/orm.ts +254 -0
  32. package/kernel/db/transactor.ts +17 -0
  33. package/kernel/db/types.ts +72 -0
  34. package/kernel/errors.ts +102 -0
  35. package/kernel/framework.default.ts +41 -0
  36. package/kernel/framework.ts +8 -2144
  37. package/kernel/http/router.ts +131 -0
  38. package/kernel/http/server.ts +303 -0
  39. package/kernel/http/types.ts +56 -0
  40. package/kernel/index.ts +25 -0
  41. package/kernel/logger.ts +50 -0
  42. package/kernel/middlewares.ts +19 -7
  43. package/kernel/modules/create-module.ts +5 -0
  44. package/kernel/modules/system.ts +149 -0
  45. package/kernel/modules/types.ts +46 -0
  46. package/kernel/seeds.ts +48 -0
  47. package/kernel/static.ts +11 -2
  48. package/kernel/testing.ts +8 -3
  49. package/kernel/validator.ts +116 -0
  50. package/modules/events/index.ts +19 -3
  51. package/modules/mail/index.ts +14 -2
  52. package/modules/storage/local-adapter.ts +19 -5
  53. package/modules/ws/index.ts +123 -18
  54. package/package.json +8 -11
  55. package/skills/auth/SKILL.md +36 -220
  56. package/skills/cli/SKILL.md +32 -251
  57. package/skills/config/SKILL.md +30 -239
  58. package/skills/connectors/SKILL.md +32 -295
  59. package/skills/helpers/SKILL.md +26 -195
  60. package/skills/middlewares/SKILL.md +30 -280
  61. package/skills/orm/SKILL.md +42 -349
  62. package/skills/realtime/SKILL.md +22 -297
  63. package/skills/services/SKILL.md +40 -183
  64. package/skills/testing/SKILL.md +34 -266
@@ -1,295 +1,45 @@
1
- # SKILL: Arckode Middlewares — Globales y por Ruta
1
+ # Middlewares — HTTP Pipeline
2
2
 
3
- > Activar cuando: agregar CORS, rate limit, timeout, logging, compresión, body limit, auth en rutas, o crear un middleware propio.
3
+ ## Stack
4
4
 
5
- ---
5
+ Express 4.x (request → middleware chain → controller)
6
6
 
7
- ## 1. IMPORT
7
+ ## Orden canónico
8
8
 
9
9
  ```ts
10
- import {
11
- cors,
12
- rateLimit,
13
- requestLogger,
14
- bodyLimit,
15
- timeout,
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
- ## 2. MIDDLEWARES GLOBALES (composition-root.ts)
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
- Los globales se aplican a TODAS las rutas. Registrar en este orden:
29
+ ## Middleware personalizado
26
30
 
27
31
  ```ts
28
- import { cors, rateLimit, requestLogger, bodyLimit, timeout, compression } from 'arckode-framework/middlewares'
29
-
30
- // Orden obligatorio — cada uno depende del anterior
31
- router.use(timeout(10_000)) // 1. Cortar requests colgados antes que todo
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
- **Múltiples orígenes:** `Access-Control-Allow-Origin` solo acepta UN valor por request
61
- (no una lista separada por comas — los browsers lo rechazan). El middleware compara el
62
- header `Origin` del request contra la lista y emite solo el origen que hizo match.
63
- Si el origin no está en la lista, el header se omite (el browser bloquea el request).
64
-
65
- ```
66
- Request: Origin: https://mi-app.com
67
- Response: Access-Control-Allow-Origin: https://mi-app.com ✅
68
-
69
- Request: Origin: https://evil.com
70
- Response: (sin Access-Control-Allow-Origin) → blocked ✅
71
- ```
72
-
73
- ---
74
-
75
- ## 4. RATE LIMIT
76
-
77
- ```ts
78
- // Global: 100 requests por minuto por IP
79
- const limiter = rateLimit({ windowMs: 60_000, max: 100 })
80
- router.use(limiter)
81
-
82
- // Por ruta específica (más restrictivo para endpoints sensibles)
83
- const loginLimiter = rateLimit({
84
- windowMs: 15 * 60_000, // 15 minutos
85
- max: 5, // máximo 5 intentos
86
- keyBy: (req) => req.headers['x-forwarded-for'] as string ?? 'anon',
87
- })
88
- router.post('/auth/login', [loginLimiter], req => controller.login(req))
89
-
90
- // Rate limit por usuario autenticado (después de que auth middleware corra)
91
- const userLimiter = rateLimit({
92
- windowMs: 60_000,
93
- max: 200,
94
- keyBy: (req) => req.user?.id ?? req.headers['x-forwarded-for'] as string ?? 'anon',
95
- })
96
-
97
- // Resetear límite de un usuario (ej: tras verificación de captcha)
98
- limiter.reset('192.168.1.1')
99
- ```
100
-
101
- **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.
102
-
103
- ---
104
-
105
- ## 5. REQUEST LOGGER
106
-
107
- ```ts
108
- // Loguea: "POST /auth/login", "POST /auth/login 200 43ms"
109
- router.use(requestLogger(logger))
110
-
111
- // El logger hijo del módulo también puede usarse
112
- router.use(requestLogger(logger.child('http')))
113
- ```
114
-
115
- Output en logs:
116
- ```
117
- [app.http] POST /auth/login { requestId: 'abc-123' }
118
- [app.http] POST /auth/login 200 43ms { requestId: 'abc-123', status: 200, duration: 43 }
119
- ```
120
-
121
- ---
122
-
123
- ## 6. BODY LIMIT
124
-
125
- ```ts
126
- // Default: 1MB
127
- router.use(bodyLimit())
128
-
129
- // Custom: 10MB (para endpoints de upload)
130
- router.use(bodyLimit(10 * 1024 * 1024))
131
-
132
- // Por ruta (más permisivo solo en upload)
133
- router.post('/archivos/upload', [bodyLimit(50 * 1024 * 1024)], req => controller.upload(req))
134
- ```
135
-
136
- Retorna **413** si el body supera el límite.
137
-
138
- ---
139
-
140
- ## 7. TIMEOUT
141
-
142
- ```ts
143
- // Default: 5 segundos
144
- router.use(timeout())
145
-
146
- // Custom por aplicación
147
- router.use(timeout(10_000)) // 10s
148
-
149
- // Más permisivo para endpoints lentos (reportes, exports)
150
- router.get('/reportes/export', [timeout(60_000)], req => controller.export(req))
151
- ```
152
-
153
- Lanza `InternalError` si el handler no responde en tiempo.
154
-
155
- ---
156
-
157
- ## 8. COMPRESSION (gzip)
158
-
159
- ```ts
160
- // Comprime responses JSON > 1KB si el cliente acepta gzip
161
- router.use(compression())
162
-
163
- // Custom threshold
164
- router.use(compression({ threshold: 512 })) // comprimir si > 512 bytes
165
- ```
166
-
167
- **No aplica a:** SSE streams, responses `null`, responses que ya tienen `Content-Encoding`.
168
-
169
- ---
170
-
171
- ## 9. MIDDLEWARES POR RUTA
172
-
173
- ```ts
174
- // Un middleware
175
- router.get('/admin', [auth.authenticate('admin')], req => controller.dashboard(req))
176
-
177
- // Múltiples middlewares (se ejecutan en orden)
178
- router.post(
179
- '/auth/login',
180
- [loginLimiter, bodyLimit(10_000)],
181
- req => controller.login(req)
182
- )
183
-
184
- // Auth + role específico
185
- router.delete(
186
- '/usuarios/:id',
187
- [auth.authenticate('admin', 'superadmin')],
188
- req => controller.eliminar(req)
189
- )
190
- ```
191
-
192
- ---
193
-
194
- ## 10. CREAR UN MIDDLEWARE PROPIO
195
-
196
- ```ts
197
- import type { MiddlewareHandler } from 'arckode-framework'
198
-
199
- // Middleware que verifica un API key en el header
200
- export function apiKeyAuth(validKeys: string[]): MiddlewareHandler {
201
- return async (req, next) => {
202
- const key = req.headers['x-api-key'] as string
203
- if (!key || !validKeys.includes(key)) {
204
- return { status: 401, body: { error: 'API key inválida' } }
205
- }
206
- return next() // siempre llamar next() para continuar la cadena
207
- }
208
- }
209
-
210
- // Middleware de auditoría
211
- export function auditLog(repo: RepositoryAdapter<AuditoriaDTO>): MiddlewareHandler {
212
- return async (req, next) => {
213
- const res = await next()
214
- if (req.method !== 'GET' && res.status < 400) {
215
- await repo.create({
216
- userId: req.user?.id ?? 'anon',
217
- action: `${req.method} ${req.path}`,
218
- status: res.status,
219
- } as any)
220
- }
221
- return res
222
- }
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()
223
36
  }
224
-
225
- // Registrar
226
- router.use(apiKeyAuth(['key-secreta-1', 'key-secreta-2']))
227
- ```
228
-
229
- **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).
230
-
231
- ---
232
-
233
- ## 11. CADENA DE EJECUCIÓN
234
-
235
- ```
236
- Request entrante
237
- → timeout (global)
238
- → cors (global)
239
- → bodyLimit (global)
240
- → requestLogger (global)
241
- → compression (global)
242
- → loginLimiter (por ruta, si aplica)
243
- → auth.authenticate (por ruta, si aplica)
244
- → handler (controller)
245
- ← Response saliente
246
37
  ```
247
38
 
248
- Los middlewares globales corren ANTES que los de ruta. El error handler del router atrapa excepciones de toda la cadena.
249
-
250
- ---
251
-
252
- ## 12. ANTI-PATRONES
253
-
254
- ```ts
255
- // ❌ Middleware global que modifica req.body con lógica de negocio
256
- router.use(async (req, next) => {
257
- req.body.precio = req.body.precio * 1.21 // impuesto — NO acá, va en el service
258
- return next()
259
- })
260
-
261
- // ❌ Olvidar llamar next() — la request queda colgada
262
- router.use(async (req, next) => {
263
- if (req.headers['x-tenant']) {
264
- // ... pero nunca llama next()
265
- }
266
- // cuelga para requests sin x-tenant
267
- })
268
-
269
- // ❌ Lógica de autenticación duplicada en cada handler
270
- router.get('/datos', async (req) => {
271
- const token = req.headers['authorization']
272
- if (!token) return { status: 401, body: { error: '...' } } // NO
273
- // usar auth.authenticate() como middleware por ruta
274
- })
275
-
276
- // ✅ Cortar la cadena cuando es correcto (no llamar next)
277
- router.use(async (req, next) => {
278
- if (req.method === 'OPTIONS') {
279
- return { status: 204, body: null } // cortar la cadena intencionalmente
280
- }
281
- return next()
282
- })
283
- ```
284
-
285
- ---
286
-
287
- ## 13. CHECKLIST MIDDLEWARES
39
+ ## Errores silenciosos
288
40
 
289
- - [ ] `timeout()` es el PRIMERO en los globales
290
- - [ ] `cors()` tiene `origins` específicos en producción (no `'*'`)
291
- - [ ] `bodyLimit()` registrado antes de endpoints que reciben JSON
292
- - [ ] Rate limiter en endpoints de login/registro (máximo 5/15min)
293
- - [ ] Middlewares de ruta van en array: `[auth.authenticate('admin')]`
294
- - [ ] Todo middleware custom llama `await next()` (excepto cuando corta intencionalmente)
295
- - [ ] 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 |