jerkjs 2.5.6 → 2.5.8

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.
@@ -13,6 +13,8 @@ const { Logger } = require('../utils/logger');
13
13
  const { ErrorHandler } = require('../utils/errorHandler');
14
14
  const { getMimeType } = require('../utils/mimeType');
15
15
  const RouteMatcher = require('../router/RouteMatcher');
16
+ const AdminExtension = require('../admin/AdminExtension');
17
+ const { globalStats } = require('../utils/globalStats');
16
18
 
17
19
  // Carga anticipada de módulos comunes para evitar cargas repetidas
18
20
  let hooksInstance = null;
@@ -59,6 +61,42 @@ class APIServer {
59
61
 
60
62
  // Inicializar el componente de enrutamiento
61
63
  this.routeMatcher = new RouteMatcher();
64
+
65
+ // Inicializar la extensión de administración
66
+ this.adminExtension = null;
67
+
68
+ // Inicializar el sistema de hooks si no existe
69
+ if (!options.hooks) {
70
+ const { hooks } = require('../../index.js'); // Ruta corregida
71
+ this.hooks = hooks;
72
+ } else {
73
+ this.hooks = options.hooks;
74
+ }
75
+
76
+ // Conectar el sistema de hooks al routeMatcher
77
+ this.routeMatcher.hooks = this.hooks;
78
+
79
+ // Propiedad para almacenar el viewEngine
80
+ this._viewEngine = null;
81
+ }
82
+
83
+ /**
84
+ * Setter para el viewEngine que también conecta los hooks
85
+ */
86
+ set viewEngine(engine) {
87
+ this._viewEngine = engine;
88
+
89
+ // Conectar el sistema de hooks al viewEngine si está disponible
90
+ if (this._viewEngine && this.hooks) {
91
+ this._viewEngine.hooks = this.hooks;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Getter para el viewEngine
97
+ */
98
+ get viewEngine() {
99
+ return this._viewEngine;
62
100
  }
63
101
 
64
102
  /**
@@ -353,12 +391,18 @@ class APIServer {
353
391
  // Crear handler para archivos estáticos
354
392
  const staticHandler = this.createStaticFileHandler(routeConfig.static, routeConfig.path);
355
393
 
356
- this.routes.push({
394
+ const route = {
357
395
  method: routeConfig.method.toUpperCase(),
358
396
  path: routeConfig.path,
359
397
  handler: staticHandler,
360
398
  isStatic: true // Marcar como ruta estática para posible procesamiento especial
361
- });
399
+ };
400
+ this.routes.push(route);
401
+
402
+ // Disparar hook cuando se registra una ruta
403
+ if (this.hooks) {
404
+ this.hooks.doAction('route_registered', route);
405
+ }
362
406
 
363
407
  return; // Salir después de procesar ruta estática
364
408
  }
@@ -437,11 +481,17 @@ class APIServer {
437
481
  };
438
482
 
439
483
  // Agregar la ruta con el handler autenticado
440
- this.routes.push({
484
+ const route = {
441
485
  method: routeConfig.method.toUpperCase(),
442
486
  path: routeConfig.path,
443
487
  handler: authenticatedHandler
444
- });
488
+ };
489
+ this.routes.push(route);
490
+
491
+ // Disparar hook cuando se registra una ruta
492
+ if (this.hooks) {
493
+ this.hooks.doAction('route_registered', route);
494
+ }
445
495
  } else {
446
496
  // Si no hay sessionManager en el servidor, agregar la ruta normalmente
447
497
  this.routes.push({
@@ -488,27 +538,45 @@ class APIServer {
488
538
  };
489
539
 
490
540
  // Agregar la ruta con el handler autenticado
491
- this.routes.push({
541
+ const route = {
492
542
  method: routeConfig.method.toUpperCase(),
493
543
  path: routeConfig.path,
494
544
  handler: authenticatedHandler
495
- });
545
+ };
546
+ this.routes.push(route);
547
+
548
+ // Disparar hook cuando se registra una ruta
549
+ if (this.hooks) {
550
+ this.hooks.doAction('route_registered', route);
551
+ }
496
552
  } else {
497
553
  // Si no hay authenticator en el servidor, agregar la ruta normalmente
498
- this.routes.push({
554
+ const route = {
499
555
  method: routeConfig.method.toUpperCase(),
500
556
  path: routeConfig.path,
501
557
  handler: finalHandler
502
- });
558
+ };
559
+ this.routes.push(route);
560
+
561
+ // Disparar hook cuando se registra una ruta
562
+ if (this.hooks) {
563
+ this.hooks.doAction('route_registered', route);
564
+ }
503
565
  }
504
566
  }
505
567
  } else {
506
568
  // Si no hay autenticación requerida, agregar la ruta normalmente
507
- this.routes.push({
569
+ const route = {
508
570
  method: routeConfig.method.toUpperCase(),
509
571
  path: routeConfig.path,
510
572
  handler: finalHandler
511
- });
573
+ };
574
+ this.routes.push(route);
575
+
576
+ // Disparar hook cuando se registra una ruta
577
+ if (this.hooks) {
578
+ this.hooks.doAction('route_registered', route);
579
+ }
512
580
  }
513
581
  }
514
582
 
@@ -579,12 +647,28 @@ class APIServer {
579
647
  // Disparar hook después de iniciar el servidor
580
648
  if (hooks) {
581
649
  hooks.doAction('post_server_start', this);
650
+
651
+ // Disparar hook para inicializar extensiones después de que el servidor esté completamente iniciado
652
+ hooks.doAction('admin_extensions_initialize', this);
582
653
  }
583
654
  });
584
655
 
585
656
  return this.server;
586
657
  }
587
658
 
659
+ /**
660
+ * Inicializa la extensión de administración
661
+ * @param {Object} options - Opciones para la extensión de administración
662
+ */
663
+ initializeAdminExtension(options = {}) {
664
+ if (!this.adminExtension) {
665
+ this.adminExtension = new AdminExtension(options);
666
+ this.adminExtension.initialize(this);
667
+ }
668
+
669
+ return this.adminExtension;
670
+ }
671
+
588
672
  /**
589
673
  * Detiene el servidor
590
674
  */
@@ -594,6 +678,11 @@ class APIServer {
594
678
  this.logger.info('Servidor detenido');
595
679
  });
596
680
  }
681
+
682
+ // Cerrar la extensión de administración si está activa
683
+ if (this.adminExtension) {
684
+ this.adminExtension.close();
685
+ }
597
686
  }
598
687
 
599
688
  /**
@@ -643,6 +732,9 @@ class APIServer {
643
732
  // Concatenar todos los chunks una sola vez
644
733
  req.body = Buffer.concat(bodyChunks).toString();
645
734
 
735
+ // Actualizar estadísticas globales de bytes recibidos
736
+ globalStats.requestBytes += bodySize;
737
+
646
738
  // Parsear body si es JSON
647
739
  if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
648
740
  try {
@@ -802,9 +894,60 @@ class APIServer {
802
894
  }
803
895
  }
804
896
 
897
+ // Capturar el tamaño de la respuesta antes de enviarla
898
+ const originalWrite = res.write;
899
+ const originalEnd = res.end;
900
+ let responseData = [];
901
+
902
+ res.write = function(chunk) {
903
+ if (chunk) {
904
+ responseData.push(Buffer.from(chunk));
905
+ }
906
+ return originalWrite.apply(this, arguments);
907
+ };
908
+
909
+ res.end = function(chunk) {
910
+ if (chunk) {
911
+ responseData.push(Buffer.from(chunk));
912
+ }
913
+
914
+ // Almacenar los datos de la respuesta para estadísticas
915
+ res._data = Buffer.concat(responseData);
916
+
917
+ // Actualizar estadísticas globales de bytes enviados
918
+ if (res._data) {
919
+ const responseSize = Buffer.byteLength(res._data, 'utf8');
920
+ globalStats.responseBytes += responseSize;
921
+ }
922
+
923
+ return originalEnd.apply(this, arguments);
924
+ };
925
+
926
+ // Actualizar estadísticas globales de solicitudes procesadas
927
+ globalStats.requestsProcessed++;
928
+
805
929
  // Ejecutar handler de la ruta
806
930
  await matchedRoute.route.handler(req, res);
807
931
 
932
+ // Actualizar estadísticas globales de respuestas enviadas
933
+ globalStats.responsesSent++;
934
+
935
+ // Registrar acceso a ruta
936
+ if (req.originalUrl) {
937
+ const routeKey = `${req.method} ${req.originalUrl}`;
938
+ if (!globalStats.routeAccesses.has(routeKey)) {
939
+ globalStats.routeAccesses.set(routeKey, 0);
940
+ }
941
+ globalStats.routeAccesses.set(routeKey, globalStats.routeAccesses.get(routeKey) + 1);
942
+ }
943
+
944
+ // Registrar hit al endpoint
945
+ const endpointKey = `${req.method} ${matchedRoute.route.path}`;
946
+ if (!globalStats.endpointHits.has(endpointKey)) {
947
+ globalStats.endpointHits.set(endpointKey, 0);
948
+ }
949
+ globalStats.endpointHits.set(endpointKey, globalStats.endpointHits.get(endpointKey) + 1);
950
+
808
951
  if (hooks) {
809
952
  hooks.doAction('route_handler_executed', matchedRoute, req, res);
810
953
  }
@@ -274,7 +274,22 @@ class ViewEngine {
274
274
 
275
275
  // Si el cache está habilitado, guardar la vista compilada (sin variables)
276
276
  if (this.cacheEnabled) {
277
- this.viewCache.set(viewPath, viewContent);
277
+ // Aplicar filter antes de cachear la vista
278
+ const contentToCache = this.hooks ?
279
+ this.hooks.applyFilters('before_view_cache', viewContent, viewPath) : viewContent;
280
+
281
+ // Verificar con hook si se debe cachear esta vista
282
+ const shouldCache = this.hooks ?
283
+ this.hooks.applyFilters('should_cache_view', true, viewPath, contentToCache) : true;
284
+
285
+ if (shouldCache) {
286
+ this.viewCache.set(viewPath, contentToCache);
287
+
288
+ // Disparar hook después de cachear
289
+ if (this.hooks) {
290
+ this.hooks.doAction('view_cached', viewPath, contentToCache);
291
+ }
292
+ }
278
293
  }
279
294
 
280
295
  // Procesar el template con las variables
@@ -817,7 +832,17 @@ class ViewEngine {
817
832
  * Limpia el cache de vistas
818
833
  */
819
834
  clearCache() {
835
+ // Disparar hook antes de limpiar el caché
836
+ if (this.hooks) {
837
+ this.hooks.doAction('before_view_cache_clear', this);
838
+ }
839
+
820
840
  this.viewCache.clear();
841
+
842
+ // Disparar hook después de limpiar el caché
843
+ if (this.hooks) {
844
+ this.hooks.doAction('view_cache_cleared');
845
+ }
821
846
  }
822
847
  }
823
848
 
@@ -1,31 +1,79 @@
1
1
  /**
2
- * Componente especializado para la lógica de enrutado
2
+ * Componente especializado para la lógica de enrutado optimizado
3
3
  * Implementación del componente router/RouteMatcher.js
4
- * JERK Framework v2.1 - Separación de responsabilidades para enrutamiento
4
+ * JERK Framework v2.5.7 - Optimización de rendimiento con índices y pre-filtrado
5
5
  */
6
6
 
7
7
  class RouteMatcher {
8
8
  /**
9
- * Constructor del matcher de rutas
9
+ * Constructor del matcher de rutas optimizado
10
10
  */
11
11
  constructor() {
12
12
  // Cache de expresiones regulares para rutas parametrizadas
13
13
  this.routeRegexCache = new Map();
14
+
15
+ // Índices para optimización
16
+ this.exactRoutes = new Map(); // Rutas exactas indexadas por método y path
17
+ this.routeBuckets = {}; // Rutas organizadas por método, número de segmentos y primer segmento
18
+ this.indexesValid = false;
19
+
20
+ // Referencia al sistema de hooks (se establecerá externamente)
21
+ this.hooks = null;
22
+ }
23
+
24
+ /**
25
+ * Actualiza los índices basados en el conjunto actual de rutas
26
+ * @param {Array} routes - Array de rutas registradas
27
+ */
28
+ updateIndexes(routes) {
29
+ // Limpiar índices existentes
30
+ this.exactRoutes.clear();
31
+ this.routeBuckets = {};
32
+
33
+ // Reconstruir índices
34
+ for (const route of routes) {
35
+ // Indexar rutas exactas
36
+ const methodPathKey = `${route.method}:${route.path}`;
37
+ this.exactRoutes.set(methodPathKey, route);
38
+
39
+ // Organizar rutas en buckets por método, número de segmentos y primer segmento
40
+ const segments = route.path.split('/').filter(s => s !== '');
41
+ const segmentCount = segments.length;
42
+ const firstSegment = segments.length > 0 ? segments[0] : '';
43
+
44
+ // Inicializar estructura de buckets si no existe
45
+ if (!this.routeBuckets[route.method]) {
46
+ this.routeBuckets[route.method] = {};
47
+ }
48
+ if (!this.routeBuckets[route.method][segmentCount]) {
49
+ this.routeBuckets[route.method][segmentCount] = {};
50
+ }
51
+ if (!this.routeBuckets[route.method][segmentCount][firstSegment]) {
52
+ this.routeBuckets[route.method][segmentCount][firstSegment] = [];
53
+ }
54
+
55
+ // Agregar ruta al bucket correspondiente
56
+ this.routeBuckets[route.method][segmentCount][firstSegment].push(route);
57
+ }
58
+
59
+ this.indexesValid = true;
14
60
  }
15
61
 
16
62
  /**
17
- * Método para encontrar una ruta coincidente
63
+ * Método para encontrar una ruta coincidente con optimizaciones
18
64
  * @param {Array} routes - Array de rutas registradas
19
65
  * @param {string} method - Método HTTP
20
66
  * @param {string} pathname - Ruta a buscar
21
67
  * @returns {Object|null} - Objeto de ruta encontrado o null
22
68
  */
23
69
  findRoute(routes, method, pathname) {
24
- // Buscar ruta exacta primero
25
- const exactMatch = routes.find(route =>
26
- route.method === method && route.path === pathname
27
- );
70
+ // Actualizar índices si es necesario
71
+ if (!this.indexesValid || routes.length !== this.getStoredRoutesCount()) {
72
+ this.updateIndexes(routes);
73
+ }
28
74
 
75
+ // Búsqueda por ruta exacta (más rápida)
76
+ const exactMatch = this.findExactMatch(method, pathname);
29
77
  if (exactMatch) {
30
78
  return {
31
79
  route: exactMatch,
@@ -33,45 +81,143 @@ class RouteMatcher {
33
81
  };
34
82
  }
35
83
 
36
- // Buscar rutas estáticas que coincidan exactamente antes que rutas parametrizadas
37
- // Esto permite que rutas estáticas como /css/style.css tengan prioridad sobre rutas parametrizadas
38
- for (const route of routes) {
39
- if (route.method !== method) continue;
40
-
41
- // Verificar si es una ruta estática que coincide exactamente
84
+ // Pre-filtrar rutas candidatas usando índices
85
+ const pathnameSegments = pathname.split('/').filter(s => s !== '');
86
+ const segmentCount = pathnameSegments.length;
87
+ const firstSegment = pathnameSegments.length > 0 ? pathnameSegments[0] : '';
88
+
89
+ // Obtener rutas candidatas basadas en número de segmentos y primer segmento
90
+ const candidateRoutes = this.getCandidateRoutes(method, segmentCount, firstSegment, pathname);
91
+
92
+ // Buscar rutas estáticas que coincidan exactamente
93
+ const staticExactMatch = this.findStaticExactMatch(candidateRoutes, pathname);
94
+ if (staticExactMatch) {
95
+ return {
96
+ route: staticExactMatch,
97
+ params: {}
98
+ };
99
+ }
100
+
101
+ // Buscar rutas parametrizadas entre los candidatos
102
+ const parametrizedMatch = this.findParametrizedMatch(candidateRoutes, pathname);
103
+ if (parametrizedMatch) {
104
+ return parametrizedMatch;
105
+ }
106
+
107
+ // Buscar rutas estáticas (prefijos) entre los candidatos
108
+ const staticPrefixMatch = this.findStaticPrefixMatch(candidateRoutes, pathname);
109
+ if (staticPrefixMatch) {
110
+ return staticPrefixMatch;
111
+ }
112
+
113
+ return null;
114
+ }
115
+
116
+ /**
117
+ * Busca rutas exactas usando el índice
118
+ * @param {string} method - Método HTTP
119
+ * @param {string} pathname - Ruta a buscar
120
+ * @returns {Object|null} - Ruta exacta encontrada o null
121
+ */
122
+ findExactMatch(method, pathname) {
123
+ const methodPathKey = `${method}:${pathname}`;
124
+ return this.exactRoutes.get(methodPathKey) || null;
125
+ }
126
+
127
+ /**
128
+ * Obtiene rutas candidatas basadas en número de segmentos y primer segmento
129
+ * @param {string} method - Método HTTP
130
+ * @param {number} segmentCount - Número de segmentos de la ruta buscada
131
+ * @param {string} firstSegment - Primer segmento de la ruta buscada
132
+ * @param {string} pathname - Ruta completa buscada
133
+ * @returns {Array} - Array de rutas candidatas
134
+ */
135
+ getCandidateRoutes(method, segmentCount, firstSegment, pathname) {
136
+ const candidates = [];
137
+
138
+ // Si existe un bucket para este método, número de segmentos y primer segmento
139
+ if (this.routeBuckets[method] &&
140
+ this.routeBuckets[method][segmentCount] &&
141
+ this.routeBuckets[method][segmentCount][firstSegment]) {
142
+ candidates.push(...this.routeBuckets[method][segmentCount][firstSegment]);
143
+ }
144
+
145
+ // También agregar rutas de otros primeros segmentos para este método y número de segmentos
146
+ // (por si hay rutas parametrizadas que también podrían coincidir)
147
+ if (this.routeBuckets[method] && this.routeBuckets[method][segmentCount]) {
148
+ for (const segment in this.routeBuckets[method][segmentCount]) {
149
+ if (segment !== firstSegment) {
150
+ candidates.push(...this.routeBuckets[method][segmentCount][segment]);
151
+ }
152
+ }
153
+ }
154
+
155
+ // Agregar rutas estáticas que podrían coincidir por prefijo
156
+ if (this.routeBuckets[method]) {
157
+ for (const count in this.routeBuckets[method]) {
158
+ for (const segment in this.routeBuckets[method][count]) {
159
+ const routes = this.routeBuckets[method][count][segment];
160
+ for (const route of routes) {
161
+ if (route.isStatic && pathname.startsWith(route.path)) {
162
+ candidates.push(route);
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ return candidates;
170
+ }
171
+
172
+ /**
173
+ * Busca rutas estáticas con coincidencia exacta entre candidatos
174
+ * @param {Array} candidateRoutes - Rutas candidatas
175
+ * @param {string} pathname - Ruta a buscar
176
+ * @returns {Object|null} - Ruta estática exacta encontrada o null
177
+ */
178
+ findStaticExactMatch(candidateRoutes, pathname) {
179
+ for (const route of candidateRoutes) {
42
180
  if (route.isStatic && route.path === pathname) {
43
- return {
44
- route: route,
45
- params: {}
46
- };
181
+ return route;
47
182
  }
48
183
  }
184
+ return null;
185
+ }
49
186
 
50
- // Buscar rutas parametrizadas
51
- // Pero antes de devolver una ruta parametrizada, verificar si hay una ruta estática más específica
52
- const parametrizedMatches = [];
53
- for (const route of routes) {
54
- if (route.method !== method) continue;
55
-
56
- // Convertir ruta parametrizada a expresión regular
57
- const routeRegex = this.pathToRegex(route.path);
58
- const match = pathname.match(routeRegex);
59
-
60
- if (match) {
61
- const params = this.extractParams(route.path, pathname);
62
- parametrizedMatches.push({
63
- route: route,
64
- params: params
65
- });
187
+ /**
188
+ * Busca rutas parametrizadas entre candidatos
189
+ * @param {Array} candidateRoutes - Rutas candidatas
190
+ * @param {string} pathname - Ruta a buscar
191
+ * @returns {Object|null} - Ruta parametrizada encontrada o null
192
+ */
193
+ findParametrizedMatch(candidateRoutes, pathname) {
194
+ for (const route of candidateRoutes) {
195
+ if (this.isParametrizedRoute(route.path)) {
196
+ // Convertir ruta parametrizada a expresión regular
197
+ const routeRegex = this.pathToRegex(route.path);
198
+ const match = pathname.match(routeRegex);
199
+
200
+ if (match) {
201
+ const params = this.extractParams(route.path, pathname);
202
+ return {
203
+ route: route,
204
+ params: params
205
+ };
206
+ }
66
207
  }
67
208
  }
209
+ return null;
210
+ }
68
211
 
69
- // Buscar rutas estáticas (prefijos)
212
+ /**
213
+ * Busca rutas estáticas por prefijo entre candidatos
214
+ * @param {Array} candidateRoutes - Rutas candidatas
215
+ * @param {string} pathname - Ruta a buscar
216
+ * @returns {Object|null} - Ruta estática por prefijo encontrada o null
217
+ */
218
+ findStaticPrefixMatch(candidateRoutes, pathname) {
70
219
  const staticMatches = [];
71
- for (const route of routes) {
72
- if (route.method !== method) continue;
73
-
74
- // Para rutas estáticas, verificar si la ruta solicitada comienza con el prefijo de la ruta estática
220
+ for (const route of candidateRoutes) {
75
221
  if (route.isStatic && pathname.startsWith(route.path)) {
76
222
  // Verificar que sea exactamente el prefijo o que haya una barra después del prefijo
77
223
  const remainingPath = pathname.substring(route.path.length);
@@ -85,27 +231,40 @@ class RouteMatcher {
85
231
  }
86
232
  }
87
233
 
88
- // Prioridad: si hay rutas estáticas que coinciden, y la solicitud parece ser para un archivo
89
- // (tiene extensión), dar prioridad a la ruta estática más específica
90
- const hasFileExtension = /\.[^.]+$/.test(pathname);
91
-
92
- if (hasFileExtension && staticMatches.length > 0) {
234
+ if (staticMatches.length > 0) {
93
235
  // Ordenar por especificidad (longitud del prefijo, descendente) y tomar la más específica
94
236
  staticMatches.sort((a, b) => b.specificity - a.specificity);
95
- // Para solicitudes de archivos, dar prioridad a rutas estáticas más específicas
96
- return staticMatches[0]; // Devolver la coincidencia estática más específica
97
- } else if (parametrizedMatches.length > 0) {
98
- // Para solicitudes sin extensión o rutas API, dar prioridad a rutas parametrizadas
99
- return parametrizedMatches[0]; // Devolver la primera coincidencia parametrizada
100
- } else if (staticMatches.length > 0) {
101
- // Si no hay coincidencias parametrizadas, usar la estática más específica
102
- staticMatches.sort((a, b) => b.specificity - a.specificity);
103
237
  return staticMatches[0];
104
238
  }
105
239
 
106
240
  return null;
107
241
  }
108
242
 
243
+ /**
244
+ * Determina si una ruta es parametrizada
245
+ * @param {string} path - Ruta a verificar
246
+ * @returns {boolean} - True si la ruta contiene parámetros
247
+ */
248
+ isParametrizedRoute(path) {
249
+ return path.includes(':');
250
+ }
251
+
252
+ /**
253
+ * Obtiene el conteo total de rutas almacenadas en índices
254
+ * @returns {number} - Número total de rutas indexadas
255
+ */
256
+ getStoredRoutesCount() {
257
+ let count = 0;
258
+ for (const method in this.routeBuckets) {
259
+ for (const segmentCount in this.routeBuckets[method]) {
260
+ for (const firstSegment in this.routeBuckets[method][segmentCount]) {
261
+ count += this.routeBuckets[method][segmentCount][firstSegment].length;
262
+ }
263
+ }
264
+ }
265
+ return count;
266
+ }
267
+
109
268
  /**
110
269
  * Convierte una ruta con parámetros a expresión regular
111
270
  * @param {string} path - Ruta con posibles parámetros
@@ -131,8 +290,20 @@ class RouteMatcher {
131
290
  const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
132
291
  const regex = new RegExp(`^${regexPath}$`);
133
292
 
134
- // Almacenar en caché
135
- this.routeRegexCache.set(path, regex);
293
+ // Verificar con hook si se debe cachear esta expresión regular
294
+ const shouldCache = this.hooks ?
295
+ this.hooks.applyFilters('should_cache_route_regex', true, path, regex) : true;
296
+
297
+ if (shouldCache) {
298
+ // Almacenar en caché
299
+ this.routeRegexCache.set(path, regex);
300
+
301
+ // Disparar hook después de cachear
302
+ if (this.hooks) {
303
+ this.hooks.doAction('route_regex_cached', path, regex);
304
+ }
305
+ }
306
+
136
307
  return regex;
137
308
  }
138
309
 
@@ -169,6 +340,23 @@ class RouteMatcher {
169
340
 
170
341
  return params;
171
342
  }
343
+
344
+ /**
345
+ * Limpia el caché de expresiones regulares
346
+ */
347
+ clearCache() {
348
+ // Disparar hook antes de limpiar el caché
349
+ if (this.hooks) {
350
+ this.hooks.doAction('before_route_regex_cache_clear', this);
351
+ }
352
+
353
+ this.routeRegexCache.clear();
354
+
355
+ // Disparar hook después de limpiar el caché
356
+ if (this.hooks) {
357
+ this.hooks.doAction('route_regex_cache_cleared');
358
+ }
359
+ }
172
360
  }
173
361
 
174
362
  module.exports = RouteMatcher;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Almacenamiento global de estadísticas para el framework JERK
3
+ */
4
+
5
+ // Crear un objeto global para almacenar estadísticas
6
+ const globalStats = {
7
+ requestsProcessed: 0,
8
+ responsesSent: 0,
9
+ requestBytes: 0,
10
+ responseBytes: 0,
11
+ routeAccesses: new Map(), // Contador de accesos por ruta
12
+ endpointHits: new Map(), // Contador de hits por endpoint
13
+ lastRequests: [] // Últimas solicitudes para análisis
14
+ };
15
+
16
+ module.exports = { globalStats };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jerkjs",
3
- "version": "2.5.6",
4
- "description": "JERK Framework v2.5.6 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, template engine, integration with qbuilderjs, complete MVC architecture with models, enhanced route loading from directory, improved model loading system, and fixed routing issues with static and parametrized routes",
3
+ "version": "2.5.8",
4
+ "description": "JERK Framework v2.5.8 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, template engine, integration with qbuilderjs, complete MVC architecture with models, enhanced route loading from directory, improved model loading system, administration extension, and fixed routing issues with static and parametrized routes",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1",