jerkjs 2.0.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 (177) hide show
  1. package/LICENSE +200 -0
  2. package/README.md +171 -0
  3. package/doc/EXTENSION_MANUAL.md +958 -0
  4. package/doc/FIREWALL_MANUAL.md +419 -0
  5. package/doc/HOOKS_REFERENCE_IMPROVED.md +599 -0
  6. package/doc/MANUAL_API_SDK.md +539 -0
  7. package/doc/MANUAL_MVC.md +397 -0
  8. package/doc/MARIADB_TOKENS_IMPLEMENTATION.md +113 -0
  9. package/doc/MIDDLEWARE_MANUAL.md +521 -0
  10. package/doc/OAUTH2_GOOGLE_MANUAL.md +408 -0
  11. package/doc/frontend-and-sessions.md +356 -0
  12. package/examples/advanced/controllers/productController.js +64 -0
  13. package/examples/advanced/controllers/userController.js +85 -0
  14. package/examples/advanced/routes.json +51 -0
  15. package/examples/advanced_example.js +93 -0
  16. package/examples/basic/controllers/userController.js +85 -0
  17. package/examples/basic_example.js +72 -0
  18. package/examples/frontend/README.md +71 -0
  19. package/examples/frontend/app.js +71 -0
  20. package/examples/frontend/controllers/apiController.js +39 -0
  21. package/examples/frontend/controllers/authController.js +220 -0
  22. package/examples/frontend/controllers/formController.js +47 -0
  23. package/examples/frontend/controllers/messageController.js +96 -0
  24. package/examples/frontend/controllers/pageController.js +178 -0
  25. package/examples/frontend/controllers/staticController.js +167 -0
  26. package/examples/frontend/routes.json +90 -0
  27. package/examples/mvc_example/app.js +138 -0
  28. package/examples/mvc_example/views/home/index.html +26 -0
  29. package/examples/mvc_example/views/home/simple.html +3 -0
  30. package/examples/mvc_example/views/layout.html +23 -0
  31. package/examples/mvc_example/views/test.html +3 -0
  32. package/examples/mvc_example/views/user/invalid.html +6 -0
  33. package/examples/mvc_example/views/user/list.html +36 -0
  34. package/examples/mvc_example/views/user/notfound.html +6 -0
  35. package/examples/mvc_example/views/user/profile.html +11 -0
  36. package/examples/mvc_routes_example/app.js +34 -0
  37. package/examples/mvc_routes_example/controllers/mainController.js +27 -0
  38. package/examples/mvc_routes_example/controllers/productController.js +47 -0
  39. package/examples/mvc_routes_example/controllers/userController.js +76 -0
  40. package/examples/mvc_routes_example/routes.json +30 -0
  41. package/examples/mvc_routes_example/views/layout.html +31 -0
  42. package/examples/mvc_routes_example/views/main/index.html +11 -0
  43. package/examples/mvc_routes_example/views/product/catalog.html +24 -0
  44. package/examples/mvc_routes_example/views/user/invalid.html +6 -0
  45. package/examples/mvc_routes_example/views/user/list.html +40 -0
  46. package/examples/mvc_routes_example/views/user/notfound.html +6 -0
  47. package/examples/mvc_routes_example/views/user/profile.html +18 -0
  48. package/examples/public/README.md +92 -0
  49. package/examples/public/app.js +72 -0
  50. package/examples/public/controllers/healthController.js +20 -0
  51. package/examples/public/controllers/mainController.js +22 -0
  52. package/examples/public/controllers/userController.js +139 -0
  53. package/examples/public/routes.json +51 -0
  54. package/examples/v2/README.md +72 -0
  55. package/examples/v2/app.js +74 -0
  56. package/examples/v2/app_fixed.js +74 -0
  57. package/examples/v2/controllers/authController.js +64 -0
  58. package/examples/v2/controllers/mainController.js +24 -0
  59. package/examples/v2/controllers/protectedController.js +12 -0
  60. package/examples/v2/controllers/userController.js +16 -0
  61. package/examples/v2/package.json +27 -0
  62. package/examples/v2/routes.json +30 -0
  63. package/examples/v2/test_api.sh +47 -0
  64. package/examples/v2/tokens_example.sqlite +0 -0
  65. package/examples/v2.1_firewall_demo/README.md +113 -0
  66. package/examples/v2.1_firewall_demo/app.js +182 -0
  67. package/examples/v2.1_firewall_demo/package.json +27 -0
  68. package/examples/v2.1_hooks_demo/README.md +85 -0
  69. package/examples/v2.1_hooks_demo/app.js +101 -0
  70. package/examples/v2.1_hooks_demo/controllers/hooksController.js +29 -0
  71. package/examples/v2.1_hooks_demo/controllers/mainController.js +18 -0
  72. package/examples/v2.1_hooks_demo/package.json +27 -0
  73. package/examples/v2.1_hooks_demo/routes.json +16 -0
  74. package/examples/v2.1_openapi_demo/README.md +82 -0
  75. package/examples/v2.1_openapi_demo/app.js +296 -0
  76. package/examples/v2.1_openapi_demo/package.json +26 -0
  77. package/examples/v2_cors/README.md +82 -0
  78. package/examples/v2_cors/app.js +108 -0
  79. package/examples/v2_cors/package.json +23 -0
  80. package/examples/v2_json_auth/README.md +83 -0
  81. package/examples/v2_json_auth/app.js +72 -0
  82. package/examples/v2_json_auth/controllers/authController.js +67 -0
  83. package/examples/v2_json_auth/controllers/mainController.js +16 -0
  84. package/examples/v2_json_auth/controllers/protectedController.js +12 -0
  85. package/examples/v2_json_auth/controllers/tokenController.js +28 -0
  86. package/examples/v2_json_auth/controllers/userController.js +15 -0
  87. package/examples/v2_json_auth/package.json +26 -0
  88. package/examples/v2_json_auth/routes.json +37 -0
  89. package/examples/v2_json_auth/tokens.json +20 -0
  90. package/examples/v2_mariadb_auth/README.md +94 -0
  91. package/examples/v2_mariadb_auth/app.js +81 -0
  92. package/examples/v2_mariadb_auth/controllers/authController.js +95 -0
  93. package/examples/v2_mariadb_auth/controllers/mainController.js +31 -0
  94. package/examples/v2_mariadb_auth/controllers/protectedController.js +12 -0
  95. package/examples/v2_mariadb_auth/controllers/userController.js +17 -0
  96. package/examples/v2_mariadb_auth/package.json +27 -0
  97. package/examples/v2_mariadb_auth/routes.json +37 -0
  98. package/examples/v2_no_auth/README.md +75 -0
  99. package/examples/v2_no_auth/app.js +72 -0
  100. package/examples/v2_no_auth/controllers/healthController.js +14 -0
  101. package/examples/v2_no_auth/controllers/mainController.js +19 -0
  102. package/examples/v2_no_auth/controllers/productController.js +31 -0
  103. package/examples/v2_no_auth/controllers/publicController.js +16 -0
  104. package/examples/v2_no_auth/package.json +22 -0
  105. package/examples/v2_no_auth/routes.json +37 -0
  106. package/examples/v2_oauth/README.md +70 -0
  107. package/examples/v2_oauth/app.js +90 -0
  108. package/examples/v2_oauth/controllers/mainController.js +45 -0
  109. package/examples/v2_oauth/controllers/oauthController.js +247 -0
  110. package/examples/v2_oauth/controllers/protectedController.js +13 -0
  111. package/examples/v2_oauth/controllers/userController.js +17 -0
  112. package/examples/v2_oauth/package.json +26 -0
  113. package/examples/v2_oauth/routes.json +44 -0
  114. package/examples/v2_openapi/README.md +77 -0
  115. package/examples/v2_openapi/app.js +222 -0
  116. package/examples/v2_openapi/controllers/authController.js +52 -0
  117. package/examples/v2_openapi/controllers/mainController.js +26 -0
  118. package/examples/v2_openapi/controllers/productController.js +17 -0
  119. package/examples/v2_openapi/controllers/userController.js +27 -0
  120. package/examples/v2_openapi/package.json +26 -0
  121. package/examples/v2_openapi/routes.json +37 -0
  122. package/generate_token.js +10 -0
  123. package/index.js +85 -0
  124. package/jerk.jpg +0 -0
  125. package/lib/core/handler.js +86 -0
  126. package/lib/core/hooks.js +224 -0
  127. package/lib/core/router.js +204 -0
  128. package/lib/core/securityEnhancedServer.js +752 -0
  129. package/lib/core/server.js +369 -0
  130. package/lib/loader/controllerLoader.js +175 -0
  131. package/lib/loader/routeLoader.js +341 -0
  132. package/lib/middleware/auditLogger.js +208 -0
  133. package/lib/middleware/authenticator.js +565 -0
  134. package/lib/middleware/compressor.js +218 -0
  135. package/lib/middleware/cors.js +135 -0
  136. package/lib/middleware/firewall.js +443 -0
  137. package/lib/middleware/rateLimiter.js +210 -0
  138. package/lib/middleware/session.js +301 -0
  139. package/lib/middleware/validator.js +193 -0
  140. package/lib/mvc/controllerBase.js +207 -0
  141. package/lib/mvc/viewEngine.js +752 -0
  142. package/lib/utils/configParser.js +223 -0
  143. package/lib/utils/logger.js +145 -0
  144. package/lib/utils/mariadbTokenAdapter.js +226 -0
  145. package/lib/utils/openapiGenerator.js +140 -0
  146. package/lib/utils/sqliteTokenAdapter.js +224 -0
  147. package/lib/utils/tokenManager.js +254 -0
  148. package/package.json +47 -0
  149. package/v2examplle/v2_json_auth/README.md +83 -0
  150. package/v2examplle/v2_json_auth/app.js +72 -0
  151. package/v2examplle/v2_json_auth/controllers/authController.js +67 -0
  152. package/v2examplle/v2_json_auth/controllers/mainController.js +16 -0
  153. package/v2examplle/v2_json_auth/controllers/protectedController.js +12 -0
  154. package/v2examplle/v2_json_auth/controllers/tokenController.js +28 -0
  155. package/v2examplle/v2_json_auth/controllers/userController.js +15 -0
  156. package/v2examplle/v2_json_auth/package.json +26 -0
  157. package/v2examplle/v2_json_auth/routes.json +37 -0
  158. package/v2examplle/v2_json_auth/tokens.json +20 -0
  159. package/v2examplle/v2_mariadb_auth/README.md +94 -0
  160. package/v2examplle/v2_mariadb_auth/app.js +81 -0
  161. package/v2examplle/v2_mariadb_auth/controllers/authController.js +95 -0
  162. package/v2examplle/v2_mariadb_auth/controllers/mainController.js +31 -0
  163. package/v2examplle/v2_mariadb_auth/controllers/protectedController.js +12 -0
  164. package/v2examplle/v2_mariadb_auth/controllers/userController.js +17 -0
  165. package/v2examplle/v2_mariadb_auth/package.json +27 -0
  166. package/v2examplle/v2_mariadb_auth/routes.json +37 -0
  167. package/v2examplle/v2_sqlite_auth/README.md +72 -0
  168. package/v2examplle/v2_sqlite_auth/app.js +74 -0
  169. package/v2examplle/v2_sqlite_auth/app_fixed.js +74 -0
  170. package/v2examplle/v2_sqlite_auth/controllers/authController.js +64 -0
  171. package/v2examplle/v2_sqlite_auth/controllers/mainController.js +24 -0
  172. package/v2examplle/v2_sqlite_auth/controllers/protectedController.js +12 -0
  173. package/v2examplle/v2_sqlite_auth/controllers/userController.js +16 -0
  174. package/v2examplle/v2_sqlite_auth/package.json +27 -0
  175. package/v2examplle/v2_sqlite_auth/routes.json +30 -0
  176. package/v2examplle/v2_sqlite_auth/test_api.sh +47 -0
  177. package/v2examplle/v2_sqlite_auth/tokens_example.sqlite +0 -0
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Servidor HTTP básico para el framework API SDK
3
+ * Implementación del componente core/server.js
4
+ */
5
+
6
+ const http = require('http');
7
+ const https = require('https');
8
+ const url = require('url');
9
+ const fs = require('fs');
10
+ const { Logger } = require('../utils/logger');
11
+
12
+ class APIServer {
13
+ /**
14
+ * Constructor del servidor
15
+ * @param {Object} options - Opciones de configuración del servidor
16
+ * @param {number} options.port - Puerto donde escuchará el servidor
17
+ * @param {string} options.host - Host donde escuchará el servidor
18
+ * @param {boolean} options.https - Habilitar servidor HTTPS
19
+ * @param {string} options.key - Ruta al archivo de clave privada para HTTPS
20
+ * @param {string} options.cert - Ruta al archivo de certificado para HTTPS
21
+ * @param {number} options.requestTimeout - Timeout para solicitudes en milisegundos
22
+ * @param {number} options.connectionTimeout - Timeout para conexiones en milisegundos
23
+ */
24
+ constructor(options = {}) {
25
+ this.port = options.port || 3000;
26
+ this.host = options.host || 'localhost';
27
+ this.https = options.https || false;
28
+ this.httpsOptions = {};
29
+
30
+ if (options.key && options.cert) {
31
+ this.httpsOptions = {
32
+ key: fs.readFileSync(options.key),
33
+ cert: fs.readFileSync(options.cert)
34
+ };
35
+ }
36
+
37
+ this.requestTimeout = options.requestTimeout || 120000; // 2 minutos por defecto
38
+ this.connectionTimeout = options.connectionTimeout || 120000; // 2 minutos por defecto
39
+ this.maxBodySize = options.maxBodySize || 10 * 1024 * 1024; // 10MB por defecto
40
+
41
+ this.routes = [];
42
+ this.middlewares = [];
43
+ this.logger = new Logger();
44
+ this.server = null;
45
+ }
46
+
47
+ /**
48
+ * Método para agregar una ruta al servidor
49
+ * @param {string} method - Método HTTP (GET, POST, PUT, DELETE, etc.)
50
+ * @param {string} path - Ruta del endpoint
51
+ * @param {Function} handler - Función manejadora de la ruta
52
+ */
53
+ addRoute(method, path, handler) {
54
+ this.routes.push({
55
+ method: method.toUpperCase(),
56
+ path,
57
+ handler
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Método para agregar middleware
63
+ * @param {Function} middleware - Función de middleware
64
+ */
65
+ use(middleware) {
66
+ this.middlewares.push(middleware);
67
+ }
68
+
69
+ /**
70
+ * Método para encontrar una ruta coincidente
71
+ * @param {string} method - Método HTTP
72
+ * @param {string} pathname - Ruta a buscar
73
+ * @returns {Object|null} - Objeto de ruta encontrado o null
74
+ */
75
+ findRoute(method, pathname) {
76
+ // Buscar ruta exacta primero
77
+ const exactMatch = this.routes.find(route =>
78
+ route.method === method && route.path === pathname
79
+ );
80
+
81
+ if (exactMatch) {
82
+ return {
83
+ route: exactMatch,
84
+ params: {}
85
+ };
86
+ }
87
+
88
+ // Buscar rutas parametrizadas
89
+ for (const route of this.routes) {
90
+ if (route.method !== method) continue;
91
+
92
+ // Convertir ruta parametrizada a expresión regular
93
+ const routeRegex = this.pathToRegex(route.path);
94
+ const match = pathname.match(routeRegex);
95
+
96
+ if (match) {
97
+ const params = this.extractParams(route.path, pathname);
98
+ return {
99
+ route: route,
100
+ params
101
+ };
102
+ }
103
+ }
104
+
105
+ return null;
106
+ }
107
+
108
+ /**
109
+ * Convierte una ruta con parámetros a expresión regular
110
+ * @param {string} path - Ruta con posibles parámetros
111
+ * @returns {RegExp} - Expresión regular para la ruta
112
+ */
113
+ pathToRegex(path) {
114
+ // Escapar caracteres especiales de la ruta, excepto los parámetros
115
+ // Pero dejar : sin escapar ya que lo usaremos para identificar parámetros
116
+ let escapedPath = '';
117
+ for (let i = 0; i < path.length; i++) {
118
+ const char = path[i];
119
+ if (char.match(/[.+?^${}()|[\]\\]/) && !(i > 0 && path[i-1] === ':')) {
120
+ escapedPath += '\\' + char;
121
+ } else {
122
+ escapedPath += char;
123
+ }
124
+ }
125
+ // Reemplazar parámetros :param con grupos de captura no greedy para evitar problemas de matching
126
+ const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
127
+ return new RegExp(`^${regexPath}$`);
128
+ }
129
+
130
+ /**
131
+ * Extrae los parámetros de una ruta parametrizada
132
+ * @param {string} routePath - Ruta con parámetros (ej. /users/:id)
133
+ * @param {string} actualPath - Ruta real solicitada
134
+ * @returns {Object} - Objeto con los parámetros extraídos
135
+ */
136
+ extractParams(routePath, actualPath) {
137
+ const params = {};
138
+
139
+ // Expresión regular para encontrar parámetros en la ruta
140
+ const paramNames = [];
141
+ const paramNameRegex = /:([a-zA-Z0-9_]+)/g;
142
+ let match;
143
+
144
+ while ((match = paramNameRegex.exec(routePath)) !== null) {
145
+ paramNames.push(match[1]);
146
+ }
147
+
148
+ // Crear expresión regular para extraer valores
149
+ const routeRegex = this.pathToRegex(routePath);
150
+ const values = actualPath.match(routeRegex);
151
+
152
+ if (values) {
153
+ // El primer elemento es la cadena completa, los demás son los valores capturados
154
+ for (let i = 0; i < paramNames.length; i++) {
155
+ if (values[i + 1]) {
156
+ params[paramNames[i]] = values[i + 1];
157
+ }
158
+ }
159
+ }
160
+
161
+ return params;
162
+ }
163
+
164
+ /**
165
+ * Inicia el servidor
166
+ */
167
+ start() {
168
+ // Disparar hook antes de iniciar el servidor
169
+ const hooks = require('../../index.js').hooks;
170
+ if (hooks) {
171
+ hooks.doAction('pre_server_start', this);
172
+ }
173
+
174
+ if (this.https && Object.keys(this.httpsOptions).length > 0) {
175
+ this.server = https.createServer(this.httpsOptions, this.handleRequest.bind(this));
176
+ } else {
177
+ this.server = http.createServer(this.handleRequest.bind(this));
178
+ }
179
+
180
+ // Configurar timeouts
181
+ this.server.setTimeout(this.requestTimeout);
182
+ this.server.on('timeout', (socket) => {
183
+ this.logger.warn('Conexión expirada por timeout');
184
+ socket.end();
185
+ });
186
+
187
+ this.server.on('connection', (socket) => {
188
+ // Configurar timeout de conexión
189
+ socket.setTimeout(this.connectionTimeout);
190
+ socket.on('timeout', () => {
191
+ this.logger.warn('Socket expirado por timeout');
192
+ socket.destroy();
193
+ });
194
+ });
195
+
196
+ this.server.listen(this.port, this.host, () => {
197
+ if (this.https && Object.keys(this.httpsOptions).length > 0) {
198
+ this.logger.info(`Servidor iniciado en https://${this.host}:${this.port}`);
199
+ } else {
200
+ this.logger.info(`Servidor iniciado en http://${this.host}:${this.port}`);
201
+ }
202
+
203
+ // Disparar hook después de iniciar el servidor
204
+ if (hooks) {
205
+ hooks.doAction('post_server_start', this);
206
+ }
207
+ });
208
+
209
+ return this.server;
210
+ }
211
+
212
+ /**
213
+ * Detiene el servidor
214
+ */
215
+ stop() {
216
+ if (this.server) {
217
+ this.server.close(() => {
218
+ this.logger.info('Servidor detenido');
219
+ });
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Maneja las solicitudes entrantes
225
+ * @param {Object} req - Objeto de solicitud HTTP
226
+ * @param {Object} res - Objeto de respuesta HTTP
227
+ */
228
+ async handleRequest(req, res) {
229
+ const parsedUrl = url.parse(req.url, true);
230
+ const { pathname, query } = parsedUrl;
231
+
232
+ // Agregar propiedades útiles a la solicitud
233
+ req.query = query;
234
+ req.params = {};
235
+ req.body = '';
236
+
237
+ // Configurar límite de tamaño para el cuerpo de la solicitud desde configuración
238
+ let bodySize = 0;
239
+
240
+ // Capturar cuerpo de la solicitud con límite de tamaño
241
+ req.on('data', chunk => {
242
+ bodySize += chunk.length;
243
+ if (bodySize > this.maxBodySize) {
244
+ res.writeHead(413, { 'Content-Type': 'application/json' });
245
+ res.end(JSON.stringify({ error: 'Solicitud demasiado grande', details: `El cuerpo de la solicitud excede el límite permitido de ${this.maxBodySize} bytes` }));
246
+ req.destroy(); // Terminar la conexión
247
+ return;
248
+ }
249
+ req.body += chunk.toString();
250
+ });
251
+
252
+ req.on('end', async () => {
253
+ try {
254
+ // Parsear body si es JSON
255
+ if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
256
+ try {
257
+ req.body = JSON.parse(req.body);
258
+ } catch (e) {
259
+ req.body = {};
260
+ }
261
+ }
262
+
263
+ // Verificar si es una solicitud OPTIONS preflight
264
+ // Si lo es, podríamos tener un middleware especial para manejarla
265
+ if (req.method === 'OPTIONS') {
266
+ // Ejecutar middlewares para manejar la solicitud OPTIONS (como CORS)
267
+ for (const middleware of this.middlewares) {
268
+ // Verificar si el middleware es una función antes de ejecutarla
269
+ if (typeof middleware === 'function') {
270
+ // Verificar si el middleware tiene firma (req, res, next)
271
+ if (middleware.length === 3) {
272
+ // Middleware con next
273
+ await new Promise((resolve, reject) => {
274
+ const next = (err) => {
275
+ if (err) {
276
+ reject(err);
277
+ } else {
278
+ resolve();
279
+ }
280
+ };
281
+ const result = middleware(req, res, next);
282
+ // Si el middleware devuelve una promesa, esperarla
283
+ if (result && typeof result.then === 'function') {
284
+ result.then(resolve).catch(reject);
285
+ }
286
+ });
287
+ } else {
288
+ // Middleware sin next
289
+ await middleware(req, res);
290
+ }
291
+
292
+ if (res.finished) return; // Si el middleware respondió, salir
293
+ }
294
+ }
295
+
296
+ // Si después de ejecutar los middlewares la respuesta no se ha terminado,
297
+ // buscar una ruta específica o manejar como preflight genérico
298
+ const matchedRoute = this.findRoute(req.method, pathname);
299
+ if (matchedRoute) {
300
+ // Agregar parámetros a la solicitud
301
+ req.params = matchedRoute.params;
302
+ // Ejecutar handler de la ruta
303
+ await matchedRoute.route.handler(req, res);
304
+ } else {
305
+ // Para solicitudes OPTIONS que no tienen un handler específico,
306
+ // pero ya fueron manejadas por el middleware CORS, simplemente terminar
307
+ if (!res.finished) {
308
+ // Si no hay ruta específica para OPTIONS pero el middleware no respondió,
309
+ // devolver 204 para cumplir con CORS preflight
310
+ res.writeHead(204);
311
+ res.end();
312
+ }
313
+ }
314
+ } else {
315
+ // Para otros métodos, seguir la lógica original
316
+ const matchedRoute = this.findRoute(req.method, pathname);
317
+
318
+ if (matchedRoute) {
319
+ // Agregar parámetros a la solicitud
320
+ req.params = matchedRoute.params;
321
+
322
+ // Ejecutar middlewares
323
+ for (const middleware of this.middlewares) {
324
+ // Verificar si el middleware es una función antes de ejecutarla
325
+ if (typeof middleware === 'function') {
326
+ // Verificar si el middleware tiene firma (req, res, next)
327
+ if (middleware.length === 3) {
328
+ // Middleware con next
329
+ await new Promise((resolve, reject) => {
330
+ const next = (err) => {
331
+ if (err) {
332
+ reject(err);
333
+ } else {
334
+ resolve();
335
+ }
336
+ };
337
+ const result = middleware(req, res, next);
338
+ // Si el middleware devuelve una promesa, esperarla
339
+ if (result && typeof result.then === 'function') {
340
+ result.then(resolve).catch(reject);
341
+ }
342
+ });
343
+ } else {
344
+ // Middleware sin next
345
+ await middleware(req, res);
346
+ }
347
+
348
+ if (res.finished) return; // Si el middleware respondió, salir
349
+ }
350
+ }
351
+
352
+ // Ejecutar handler de la ruta
353
+ await matchedRoute.route.handler(req, res);
354
+ } else {
355
+ // Ruta no encontrada
356
+ res.writeHead(404, { 'Content-Type': 'application/json' });
357
+ res.end(JSON.stringify({ error: 'Ruta no encontrada', path: pathname }));
358
+ }
359
+ }
360
+ } catch (error) {
361
+ this.logger.error('Error procesando la solicitud:', error.message);
362
+ res.writeHead(500, { 'Content-Type': 'application/json' });
363
+ res.end(JSON.stringify({ error: 'Error interno del servidor', details: error.message }));
364
+ }
365
+ });
366
+ }
367
+ }
368
+
369
+ module.exports = APIServer;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Carga de controladores para el framework API SDK
3
+ * Implementación del componente loader/controllerLoader.js
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ class ControllerLoader {
10
+ /**
11
+ * Constructor del cargador de controladores
12
+ */
13
+ constructor() {
14
+ this.loadedControllers = new Map();
15
+ }
16
+
17
+ /**
18
+ * Método para cargar un controlador desde un archivo
19
+ * @param {string} controllerPath - Ruta al archivo del controlador
20
+ * @returns {Object} - Módulo del controlador cargado
21
+ */
22
+ loadController(controllerPath) {
23
+ try {
24
+ // Disparar hook antes de cargar el controlador
25
+ const hooks = require('../../index.js').hooks;
26
+ if (hooks) {
27
+ hooks.doAction('pre_controller_load', controllerPath);
28
+ }
29
+
30
+ // Resolver la ruta absoluta
31
+ const absolutePath = path.resolve(process.cwd(), controllerPath);
32
+
33
+ // Verificar si el archivo existe
34
+ if (!fs.existsSync(absolutePath)) {
35
+ throw new Error(`Archivo del controlador no encontrado: ${absolutePath}`);
36
+ }
37
+
38
+ // Cargar el módulo del controlador
39
+ const controllerModule = require(absolutePath);
40
+
41
+ // Guardar en cache
42
+ this.loadedControllers.set(absolutePath, controllerModule);
43
+
44
+ // Disparar hook después de cargar el controlador
45
+ if (hooks) {
46
+ hooks.doAction('post_controller_load', controllerModule, absolutePath);
47
+ }
48
+
49
+ return controllerModule;
50
+ } catch (error) {
51
+ throw new Error(`Error cargando controlador ${controllerPath}: ${error.message}`);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Método para cargar múltiples controladores desde un directorio
57
+ * @param {string} directoryPath - Ruta al directorio de controladores
58
+ * @param {Object} options - Opciones de carga
59
+ * @returns {Object} - Objeto con todos los controladores cargados
60
+ */
61
+ loadControllersFromDirectory(directoryPath, options = {}) {
62
+ try {
63
+ // Resolver la ruta absoluta
64
+ const absoluteDirPath = path.resolve(process.cwd(), directoryPath);
65
+
66
+ // Verificar si el directorio existe
67
+ if (!fs.existsSync(absoluteDirPath)) {
68
+ throw new Error(`Directorio de controladores no encontrado: ${absoluteDirPath}`);
69
+ }
70
+
71
+ // Obtener archivos del directorio
72
+ const files = fs.readdirSync(absoluteDirPath);
73
+
74
+ const controllers = {};
75
+
76
+ for (const file of files) {
77
+ // Solo procesar archivos JavaScript
78
+ if (file.endsWith('.js')) {
79
+ const controllerName = path.basename(file, '.js');
80
+ const controllerPath = path.join(absoluteDirPath, file);
81
+
82
+ try {
83
+ const controllerModule = this.loadController(controllerPath);
84
+ controllers[controllerName] = controllerModule;
85
+ } catch (error) {
86
+ if (options.ignoreErrors) {
87
+ console.warn(`Advertencia: No se pudo cargar el controlador ${controllerPath}: ${error.message}`);
88
+ } else {
89
+ throw error;
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ return controllers;
96
+ } catch (error) {
97
+ throw new Error(`Error cargando controladores desde el directorio ${directoryPath}: ${error.message}`);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Método para obtener un controlador del cache
103
+ * @param {string} controllerPath - Ruta al archivo del controlador
104
+ * @returns {Object|null} - Módulo del controlador o null si no está cargado
105
+ */
106
+ getCachedController(controllerPath) {
107
+ const absolutePath = path.resolve(process.cwd(), controllerPath);
108
+ return this.loadedControllers.get(absolutePath) || null;
109
+ }
110
+
111
+ /**
112
+ * Método para limpiar el cache de controladores
113
+ */
114
+ clearCache() {
115
+ // Eliminar módulos del cache de require para forzar recarga
116
+ for (const [path, module] of this.loadedControllers) {
117
+ delete require.cache[require.resolve(path)];
118
+ }
119
+
120
+ this.loadedControllers.clear();
121
+ }
122
+
123
+ /**
124
+ * Método para recargar un controlador
125
+ * @param {string} controllerPath - Ruta al archivo del controlador
126
+ * @returns {Object} - Módulo del controlador recargado
127
+ */
128
+ reloadController(controllerPath) {
129
+ // Eliminar del cache de require para forzar recarga
130
+ const absolutePath = path.resolve(process.cwd(), controllerPath);
131
+ if (require.cache[require.resolve(absolutePath)]) {
132
+ delete require.cache[require.resolve(absolutePath)];
133
+ }
134
+
135
+ // Volver a cargar
136
+ return this.loadController(controllerPath);
137
+ }
138
+
139
+ /**
140
+ * Método para obtener la lista de controladores cargados
141
+ * @returns {Array} - Array con las rutas de los controladores cargados
142
+ */
143
+ getLoadedControllersList() {
144
+ return Array.from(this.loadedControllers.keys());
145
+ }
146
+
147
+ /**
148
+ * Método para verificar si un controlador está cargado
149
+ * @param {string} controllerPath - Ruta al archivo del controlador
150
+ * @returns {boolean} - True si está cargado, false en caso contrario
151
+ */
152
+ isControllerLoaded(controllerPath) {
153
+ const absolutePath = path.resolve(process.cwd(), controllerPath);
154
+ return this.loadedControllers.has(absolutePath);
155
+ }
156
+
157
+ /**
158
+ * Método para cargar un controlador y obtener un handler específico
159
+ * @param {string} controllerPath - Ruta al archivo del controlador
160
+ * @param {string} handlerName - Nombre del handler a obtener
161
+ * @returns {Function|null} - Función handler o null si no existe
162
+ */
163
+ getHandlerFromController(controllerPath, handlerName) {
164
+ const controller = this.loadController(controllerPath);
165
+ const handler = controller[handlerName];
166
+
167
+ if (typeof handler !== 'function') {
168
+ throw new Error(`El handler '${handlerName}' no es una función en el controlador: ${controllerPath}`);
169
+ }
170
+
171
+ return handler;
172
+ }
173
+ }
174
+
175
+ module.exports = ControllerLoader;