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.
Files changed (65) 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/claude-md-stub.ts +21 -8
  19. package/cli/stubs/module/core.ts +162 -0
  20. package/cli/stubs/module/data.ts +171 -0
  21. package/cli/stubs/module/index.ts +5 -0
  22. package/cli/stubs/module/service.ts +198 -0
  23. package/cli/stubs/module/types.ts +12 -0
  24. package/cli/stubs/module-stub.ts +2 -552
  25. package/kernel/auth.ts +114 -0
  26. package/kernel/cache.ts +37 -0
  27. package/kernel/config.ts +129 -0
  28. package/kernel/container.ts +64 -0
  29. package/kernel/db/orm-migrate.ts +136 -0
  30. package/kernel/db/orm-repository.ts +45 -0
  31. package/kernel/db/orm-utils.ts +93 -0
  32. package/kernel/db/orm.ts +254 -0
  33. package/kernel/db/transactor.ts +17 -0
  34. package/kernel/db/types.ts +72 -0
  35. package/kernel/errors.ts +102 -0
  36. package/kernel/framework.default.ts +41 -0
  37. package/kernel/framework.ts +8 -2144
  38. package/kernel/http/router.ts +131 -0
  39. package/kernel/http/server.ts +303 -0
  40. package/kernel/http/types.ts +56 -0
  41. package/kernel/index.ts +25 -0
  42. package/kernel/logger.ts +50 -0
  43. package/kernel/middlewares.ts +38 -21
  44. package/kernel/modules/create-module.ts +5 -0
  45. package/kernel/modules/system.ts +149 -0
  46. package/kernel/modules/types.ts +46 -0
  47. package/kernel/seeds.ts +48 -0
  48. package/kernel/static.ts +11 -2
  49. package/kernel/testing.ts +8 -3
  50. package/kernel/validator.ts +116 -0
  51. package/modules/events/index.ts +19 -3
  52. package/modules/mail/index.ts +14 -2
  53. package/modules/storage/local-adapter.ts +19 -5
  54. package/modules/ws/index.ts +123 -18
  55. package/package.json +8 -11
  56. package/skills/auth/SKILL.md +36 -220
  57. package/skills/cli/SKILL.md +32 -251
  58. package/skills/config/SKILL.md +30 -239
  59. package/skills/connectors/SKILL.md +32 -295
  60. package/skills/helpers/SKILL.md +26 -195
  61. package/skills/middlewares/SKILL.md +30 -267
  62. package/skills/orm/SKILL.md +42 -349
  63. package/skills/realtime/SKILL.md +22 -297
  64. package/skills/services/SKILL.md +40 -183
  65. package/skills/testing/SKILL.md +34 -266
@@ -1,282 +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
- ---
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
- Los middlewares globales corren ANTES que los de ruta. El error handler del router atrapa excepciones de toda la cadena.
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
- - [ ] `timeout()` es el PRIMERO en los globales
277
- - [ ] `cors()` tiene `origins` específicos en producción (no `'*'`)
278
- - [ ] `bodyLimit()` registrado antes de endpoints que reciben JSON
279
- - [ ] Rate limiter en endpoints de login/registro (máximo 5/15min)
280
- - [ ] Middlewares de ruta van en array: `[auth.authenticate('admin')]`
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 |