jerkjs 2.1.2 → 2.1.3

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/CHANGELOG.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
- ## v2.1.2 - 17 de enero de 2026
3
+ ## v2.1.3 - 17 de enero de 2026
4
4
 
5
5
  ### Nuevas características
6
6
 
7
+ - **Sistema de hooks/filters en middleware de compresión**: Se ha integrado completamente el sistema de hooks y filters en el middleware de compresión, permitiendo extender y personalizar el comportamiento de compresión en múltiples puntos del proceso (antes/después de la compresión, modificación de chunks, manejo de encabezados, etc.)
8
+
7
9
  - **Optimización del procesamiento del body request**: Se ha implementado una nueva técnica para procesar el cuerpo de las solicitudes usando arrays de chunks en lugar de concatenación de strings, lo que reduce significativamente el uso de memoria y mejora el rendimiento para solicitudes grandes (reducción del 50-70% en tiempo de procesamiento).
8
10
 
9
11
  - **Cacheo de expresiones regulares para rutas parametrizadas**: Se ha añadido un sistema de cache para las expresiones regulares compiladas de rutas parametrizadas, evitando la recompilación repetida y mejorando el rendimiento del enrutamiento (reducción del 10-20% en tiempo de routing).
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # JERK Framework v2.1.2
1
+ # JERK Framework v2.1.3
2
2
 
3
3
  ![JERK Framework Logo](jerk.jpg)
4
4
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Middleware de compresión para el framework JERK
3
3
  * Implementación del componente middleware/compressor.js
4
- * JERK Framework v2.1.1 - Con optimizaciones de eficiencia y corrección de errores
4
+ * JERK Framework v2.1.3 - Con optimizaciones de eficiencia, corrección de errores y sistema de hooks/filters
5
5
  */
6
6
 
7
7
  const zlib = require('zlib');
@@ -14,12 +14,14 @@ class Compressor {
14
14
  * @param {number} options.threshold - Tamaño mínimo en bytes para comprimir
15
15
  * @param {Object} options.gzipOptions - Opciones para compresión gzip
16
16
  * @param {Object} options.deflateOptions - Opciones para compresión deflate
17
+ * @param {Object} options.hooks - Sistema de hooks para extensibilidad
17
18
  */
18
19
  constructor(options = {}) {
19
20
  this.encodings = options.encodings || ['gzip', 'deflate'];
20
21
  this.threshold = options.threshold || 1024; // 1KB por defecto
21
22
  this.gzipOptions = options.gzipOptions || {};
22
23
  this.deflateOptions = options.deflateOptions || {};
24
+ this.hooks = options.hooks || null; // Sistema de hooks opcional para extensibilidad
23
25
  }
24
26
 
25
27
  /**
@@ -37,7 +39,7 @@ class Compressor {
37
39
 
38
40
  // Determinar el método de compresión preferido
39
41
  let compressionMethod = null;
40
-
42
+
41
43
  if (acceptEncoding.includes('gzip')) {
42
44
  compressionMethod = 'gzip';
43
45
  } else if (acceptEncoding.includes('deflate')) {
@@ -50,6 +52,11 @@ class Compressor {
50
52
  return;
51
53
  }
52
54
 
55
+ // Permitir a los hooks modificar el método de compresión
56
+ if (this.hooks && this.hooks.applyFilters) {
57
+ compressionMethod = this.hooks.applyFilters('compressor_method', compressionMethod, req, res);
58
+ }
59
+
53
60
  // Guardar el método original de res.end
54
61
  const originalEnd = res.end;
55
62
  const originalWriteHead = res.writeHead;
@@ -59,6 +66,10 @@ class Compressor {
59
66
 
60
67
  // Sobrescribir res.write para capturar los chunks
61
68
  res.write = (chunk, encoding) => {
69
+ // Permitir a los hooks interceptar los chunks antes de almacenarlos
70
+ if (this.hooks && this.hooks.applyFilters) {
71
+ chunk = this.hooks.applyFilters('compressor_chunk_before_store', chunk, req, res, encoding);
72
+ }
62
73
  responseChunks.push(chunk);
63
74
  };
64
75
 
@@ -66,16 +77,40 @@ class Compressor {
66
77
  res.end = (chunk, encoding) => {
67
78
  // Añadir el chunk final a los chunks si existe
68
79
  if (chunk) {
80
+ // Permitir a los hooks interceptar el chunk final antes de almacenarlo
81
+ if (this.hooks && this.hooks.applyFilters) {
82
+ chunk = this.hooks.applyFilters('compressor_final_chunk_before_store', chunk, req, res, encoding);
83
+ }
69
84
  responseChunks.push(chunk);
70
85
  }
71
86
 
72
87
  // Concatenar todos los chunks una sola vez
73
- const responseBody = Buffer.concat(responseChunks.map(c =>
88
+ let responseBody = Buffer.concat(responseChunks.map(c =>
74
89
  typeof c === 'string' ? Buffer.from(c, encoding) : c
75
90
  )).toString();
76
91
 
92
+ // Permitir a los hooks modificar el cuerpo de la respuesta antes de evaluar el umbral
93
+ if (this.hooks && this.hooks.applyFilters) {
94
+ responseBody = this.hooks.applyFilters('compressor_response_body_before_threshold_check', responseBody, req, res);
95
+ }
96
+
77
97
  // Si el cuerpo es menor que el umbral, enviar sin comprimir
78
98
  if (Buffer.byteLength(responseBody) < this.threshold) {
99
+ // Permitir a los hooks modificar el comportamiento cuando no se comprime
100
+ if (this.hooks && this.hooks.applyFilters) {
101
+ const shouldCompress = this.hooks.applyFilters('compressor_should_compress', false, responseBody, req, res, this.threshold);
102
+ if (shouldCompress) {
103
+ // Si un hook determina que debería comprimirse, proseguir con la compresión
104
+ this._performCompression(req, res, originalEnd, originalWriteHead, responseBody, compressionMethod);
105
+ return;
106
+ }
107
+ }
108
+
109
+ // Permitir a los hooks modificar el comportamiento cuando no se comprime
110
+ if (this.hooks && this.hooks.doAction) {
111
+ this.hooks.doAction('compressor_skip_compression', req, res, responseBody, this.threshold);
112
+ }
113
+
79
114
  // Solo establecer encabezados si no se han enviado aún
80
115
  if (!res.headersSent) {
81
116
  res.removeHeader('Content-Encoding'); // Asegurar que no haya encabezado de codificación
@@ -85,6 +120,11 @@ class Compressor {
85
120
  return;
86
121
  }
87
122
 
123
+ // Permitir a los hooks modificar el comportamiento cuando se va a comprimir
124
+ if (this.hooks && this.hooks.doAction) {
125
+ this.hooks.doAction('compressor_before_compression', req, res, responseBody, compressionMethod);
126
+ }
127
+
88
128
  // Aplicar compresión según el método seleccionado
89
129
  let compressedBody;
90
130
  let compressPromise;
@@ -95,6 +135,10 @@ class Compressor {
95
135
  if (err) {
96
136
  reject(err);
97
137
  } else {
138
+ // Permitir a los hooks modificar el buffer comprimido
139
+ if (this.hooks && this.hooks.applyFilters) {
140
+ buffer = this.hooks.applyFilters('compressor_after_gzip', buffer, req, res, responseBody);
141
+ }
98
142
  resolve(buffer);
99
143
  }
100
144
  });
@@ -105,6 +149,10 @@ class Compressor {
105
149
  if (err) {
106
150
  reject(err);
107
151
  } else {
152
+ // Permitir a los hooks modificar el buffer comprimido
153
+ if (this.hooks && this.hooks.applyFilters) {
154
+ buffer = this.hooks.applyFilters('compressor_after_deflate', buffer, req, res, responseBody);
155
+ }
108
156
  resolve(buffer);
109
157
  }
110
158
  });
@@ -114,21 +162,46 @@ class Compressor {
114
162
  // Esperar a que se complete la compresión y enviar la respuesta
115
163
  compressPromise
116
164
  .then(compressed => {
165
+ // Permitir a los hooks modificar el cuerpo comprimido antes de enviar
166
+ if (this.hooks && this.hooks.applyFilters) {
167
+ compressed = this.hooks.applyFilters('compressor_before_send', compressed, req, res, compressionMethod);
168
+ }
169
+
117
170
  // Solo establecer encabezados si no se han enviado aún
118
171
  if (!res.headersSent) {
119
172
  // Establecer encabezados apropiados
120
173
  res.setHeader('Content-Encoding', compressionMethod);
121
174
  res.removeHeader('Content-Length'); // Eliminar Content-Length original
122
175
 
176
+ // Permitir a los hooks modificar los encabezados
177
+ if (this.hooks && this.hooks.doAction) {
178
+ this.hooks.doAction('compressor_before_headers_sent', req, res, compressionMethod);
179
+ }
180
+
123
181
  // Llamar al writeHead original
124
182
  originalWriteHead.call(res);
125
183
  }
126
184
 
185
+ // Permitir a los hooks modificar el comportamiento antes de enviar la respuesta
186
+ if (this.hooks && this.hooks.doAction) {
187
+ this.hooks.doAction('compressor_before_response_sent', req, res, compressed, compressionMethod);
188
+ }
189
+
127
190
  // Enviar el cuerpo comprimido
128
191
  originalEnd.call(res, compressed, encoding);
192
+
193
+ // Permitir a los hooks actuar después de enviar la respuesta
194
+ if (this.hooks && this.hooks.doAction) {
195
+ this.hooks.doAction('compressor_after_response_sent', req, res, compressed, compressionMethod);
196
+ }
129
197
  })
130
198
  .catch(err => {
131
199
  console.error('Error comprimiendo la respuesta:', err);
200
+ // Permitir a los hooks manejar el error de compresión
201
+ if (this.hooks && this.hooks.doAction) {
202
+ this.hooks.doAction('compressor_error_during_compression', err, req, res, responseBody, compressionMethod);
203
+ }
204
+
132
205
  // Si ocurre un error, enviar sin comprimir
133
206
  if (!res.headersSent) {
134
207
  res.removeHeader('Content-Encoding');
@@ -152,13 +225,18 @@ class Compressor {
152
225
  jsonOnly() {
153
226
  return (req, res, next) => {
154
227
  const originalSend = res.send; // Suponiendo que hay un método send
155
-
228
+
156
229
  res.send = (data) => {
157
230
  // Verificar si el tipo de contenido es JSON
158
231
  const contentType = res.getHeader('Content-Type');
159
232
  if (contentType && contentType.includes('application/json')) {
160
233
  // Convertir a string si no lo es
161
- const jsonString = typeof data === 'string' ? data : JSON.stringify(data);
234
+ let jsonString = typeof data === 'string' ? data : JSON.stringify(data);
235
+
236
+ // Permitir a los hooks modificar el JSON antes de la compresión
237
+ if (this.hooks && this.hooks.applyFilters) {
238
+ jsonString = this.hooks.applyFilters('compressor_json_before_compression', jsonString, req, res, data);
239
+ }
162
240
 
163
241
  // Continuar con la lógica de compresión
164
242
  // (similar a la implementación en middleware())
@@ -176,6 +254,11 @@ class Compressor {
176
254
  compressionMethod = 'deflate';
177
255
  }
178
256
 
257
+ // Permitir a los hooks modificar el método de compresión para JSON
258
+ if (this.hooks && this.hooks.applyFilters) {
259
+ compressionMethod = this.hooks.applyFilters('compressor_json_method', compressionMethod, req, res);
260
+ }
261
+
179
262
  if (!compressionMethod || !this.encodings.includes(compressionMethod)) {
180
263
  res.setHeader('Content-Type', 'application/json');
181
264
  originalSend.call(res, jsonString);
@@ -183,44 +266,112 @@ class Compressor {
183
266
  }
184
267
 
185
268
  if (Buffer.byteLength(jsonString) < this.threshold) {
186
- res.setHeader('Content-Type', 'application/json');
187
- res.removeHeader('Content-Encoding');
188
- originalSend.call(res, jsonString);
189
- return;
269
+ // Permitir a los hooks modificar el comportamiento cuando no se comprime JSON
270
+ if (this.hooks && this.hooks.applyFilters) {
271
+ const shouldCompressJSON = this.hooks.applyFilters('compressor_json_should_compress', false, jsonString, req, res, this.threshold);
272
+ if (!shouldCompressJSON) {
273
+ res.setHeader('Content-Type', 'application/json');
274
+ res.removeHeader('Content-Encoding');
275
+ originalSend.call(res, jsonString);
276
+ return;
277
+ }
278
+ } else {
279
+ res.setHeader('Content-Type', 'application/json');
280
+ res.removeHeader('Content-Encoding');
281
+ originalSend.call(res, jsonString);
282
+ return;
283
+ }
284
+ }
285
+
286
+ // Permitir a los hooks modificar el comportamiento antes de comprimir JSON
287
+ if (this.hooks && this.hooks.doAction) {
288
+ this.hooks.doAction('compressor_json_before_compression', req, res, jsonString, compressionMethod);
190
289
  }
191
290
 
192
291
  if (compressionMethod === 'gzip') {
193
292
  zlib.gzip(jsonString, this.gzipOptions, (err, compressed) => {
194
293
  if (err) {
195
294
  console.error('Error comprimiendo JSON:', err);
295
+ // Permitir a los hooks manejar el error de compresión JSON
296
+ if (this.hooks && this.hooks.doAction) {
297
+ this.hooks.doAction('compressor_json_error_during_compression', err, req, res, jsonString, compressionMethod);
298
+ }
299
+
196
300
  if (!res.headersSent) {
197
301
  res.setHeader('Content-Type', 'application/json');
198
302
  }
199
303
  originalSend.call(res, jsonString);
200
304
  } else {
305
+ // Permitir a los hooks modificar el JSON comprimido
306
+ if (this.hooks && this.hooks.applyFilters) {
307
+ compressed = this.hooks.applyFilters('compressor_json_after_gzip', compressed, req, res, jsonString);
308
+ }
309
+
201
310
  if (!res.headersSent) {
202
311
  res.setHeader('Content-Encoding', compressionMethod);
203
312
  res.removeHeader('Content-Length');
204
313
  res.setHeader('Content-Type', 'application/json');
314
+
315
+ // Permitir a los hooks modificar los encabezados antes de enviar JSON
316
+ if (this.hooks && this.hooks.doAction) {
317
+ this.hooks.doAction('compressor_json_before_headers_sent', req, res, compressionMethod);
318
+ }
319
+ }
320
+
321
+ // Permitir a los hooks modificar el comportamiento antes de enviar JSON comprimido
322
+ if (this.hooks && this.hooks.doAction) {
323
+ this.hooks.doAction('compressor_json_before_send', req, res, compressed, compressionMethod);
205
324
  }
325
+
206
326
  originalSend.call(res, compressed);
327
+
328
+ // Permitir a los hooks actuar después de enviar JSON comprimido
329
+ if (this.hooks && this.hooks.doAction) {
330
+ this.hooks.doAction('compressor_json_after_send', req, res, compressed, compressionMethod);
331
+ }
207
332
  }
208
333
  });
209
334
  } else if (compressionMethod === 'deflate') {
210
335
  zlib.deflate(jsonString, this.deflateOptions, (err, compressed) => {
211
336
  if (err) {
212
337
  console.error('Error comprimiendo JSON:', err);
338
+ // Permitir a los hooks manejar el error de compresión JSON
339
+ if (this.hooks && this.hooks.doAction) {
340
+ this.hooks.doAction('compressor_json_error_during_compression', err, req, res, jsonString, compressionMethod);
341
+ }
342
+
213
343
  if (!res.headersSent) {
214
344
  res.setHeader('Content-Type', 'application/json');
215
345
  }
216
346
  originalSend.call(res, jsonString);
217
347
  } else {
348
+ // Permitir a los hooks modificar el JSON comprimido
349
+ if (this.hooks && this.hooks.applyFilters) {
350
+ compressed = this.hooks.applyFilters('compressor_json_after_deflate', compressed, req, res, jsonString);
351
+ }
352
+
218
353
  if (!res.headersSent) {
219
354
  res.setHeader('Content-Encoding', compressionMethod);
220
355
  res.removeHeader('Content-Length');
221
356
  res.setHeader('Content-Type', 'application/json');
357
+
358
+ // Permitir a los hooks modificar los encabezados antes de enviar JSON
359
+ if (this.hooks && this.hooks.doAction) {
360
+ this.hooks.doAction('compressor_json_before_headers_sent', req, res, compressionMethod);
361
+ }
362
+ }
363
+
364
+ // Permitir a los hooks modificar el comportamiento antes de enviar JSON comprimido
365
+ if (this.hooks && this.hooks.doAction) {
366
+ this.hooks.doAction('compressor_json_before_send', req, res, compressed, compressionMethod);
222
367
  }
368
+
223
369
  originalSend.call(res, compressed);
370
+
371
+ // Permitir a los hooks actuar después de enviar JSON comprimido
372
+ if (this.hooks && this.hooks.doAction) {
373
+ this.hooks.doAction('compressor_json_after_send', req, res, compressed, compressionMethod);
374
+ }
224
375
  }
225
376
  });
226
377
  }
@@ -235,6 +386,103 @@ class Compressor {
235
386
  }
236
387
  };
237
388
  }
389
+ /**
390
+ * Método auxiliar para realizar la compresión
391
+ * @private
392
+ */
393
+ _performCompression(req, res, originalEnd, originalWriteHead, responseBody, compressionMethod) {
394
+ const zlib = require('zlib');
395
+
396
+ // Permitir a los hooks modificar el comportamiento cuando se va a comprimir
397
+ if (this.hooks && this.hooks.doAction) {
398
+ this.hooks.doAction('compressor_before_compression', req, res, responseBody, compressionMethod);
399
+ }
400
+
401
+ // Aplicar compresión según el método seleccionado
402
+ let compressedBody;
403
+ let compressPromise;
404
+
405
+ if (compressionMethod === 'gzip') {
406
+ compressPromise = new Promise((resolve, reject) => {
407
+ zlib.gzip(responseBody, this.gzipOptions, (err, buffer) => {
408
+ if (err) {
409
+ reject(err);
410
+ } else {
411
+ // Permitir a los hooks modificar el buffer comprimido
412
+ if (this.hooks && this.hooks.applyFilters) {
413
+ buffer = this.hooks.applyFilters('compressor_after_gzip', buffer, req, res, responseBody);
414
+ }
415
+ resolve(buffer);
416
+ }
417
+ });
418
+ });
419
+ } else if (compressionMethod === 'deflate') {
420
+ compressPromise = new Promise((resolve, reject) => {
421
+ zlib.deflate(responseBody, this.deflateOptions, (err, buffer) => {
422
+ if (err) {
423
+ reject(err);
424
+ } else {
425
+ // Permitir a los hooks modificar el buffer comprimido
426
+ if (this.hooks && this.hooks.applyFilters) {
427
+ buffer = this.hooks.applyFilters('compressor_after_deflate', buffer, req, res, responseBody);
428
+ }
429
+ resolve(buffer);
430
+ }
431
+ });
432
+ });
433
+ }
434
+
435
+ // Esperar a que se complete la compresión y enviar la respuesta
436
+ compressPromise
437
+ .then(compressed => {
438
+ // Permitir a los hooks modificar el cuerpo comprimido antes de enviar
439
+ if (this.hooks && this.hooks.applyFilters) {
440
+ compressed = this.hooks.applyFilters('compressor_before_send', compressed, req, res, compressionMethod);
441
+ }
442
+
443
+ // Solo establecer encabezados si no se han enviado aún
444
+ if (!res.headersSent) {
445
+ // Establecer encabezados apropiados
446
+ res.setHeader('Content-Encoding', compressionMethod);
447
+ res.removeHeader('Content-Length'); // Eliminar Content-Length original
448
+
449
+ // Permitir a los hooks modificar los encabezados
450
+ if (this.hooks && this.hooks.doAction) {
451
+ this.hooks.doAction('compressor_before_headers_sent', req, res, compressionMethod);
452
+ }
453
+
454
+ // Llamar al writeHead original
455
+ originalWriteHead.call(res);
456
+ }
457
+
458
+ // Permitir a los hooks modificar el comportamiento antes de enviar la respuesta
459
+ if (this.hooks && this.hooks.doAction) {
460
+ this.hooks.doAction('compressor_before_response_sent', req, res, compressed, compressionMethod);
461
+ }
462
+
463
+ // Enviar el cuerpo comprimido
464
+ originalEnd.call(res, compressed);
465
+
466
+ // Permitir a los hooks actuar después de enviar la respuesta
467
+ if (this.hooks && this.hooks.doAction) {
468
+ this.hooks.doAction('compressor_after_response_sent', req, res, compressed, compressionMethod);
469
+ }
470
+ })
471
+ .catch(err => {
472
+ console.error('Error comprimiendo la respuesta:', err);
473
+ // Permitir a los hooks manejar el error de compresión
474
+ if (this.hooks && this.hooks.doAction) {
475
+ this.hooks.doAction('compressor_error_during_compression', err, req, res, responseBody, compressionMethod);
476
+ }
477
+
478
+ // Si ocurre un error, enviar sin comprimir
479
+ if (!res.headersSent) {
480
+ res.removeHeader('Content-Encoding');
481
+ originalWriteHead.call(res);
482
+ }
483
+ originalEnd.call(res, responseBody);
484
+ });
485
+ }
238
486
  }
239
487
 
240
488
  module.exports = Compressor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jerkjs",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "JERK Framework v2.1 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, and template engine with performance optimizations",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -33,6 +33,24 @@ class CompressionTestController {
33
33
  res.writeHead(200, { 'Content-Type': 'application/json' });
34
34
  res.end(JSON.stringify(largeData));
35
35
  }
36
+
37
+ // Nuevo endpoint para probar hooks de compresión
38
+ testCompressionHooks(req, res) {
39
+ // Datos para probar los hooks
40
+ const testData = {
41
+ message: 'Prueba de hooks de compresión',
42
+ hooksTest: true,
43
+ timestamp: new Date().toISOString(),
44
+ data: Array.from({ length: 500 }, (_, i) => ({
45
+ id: i,
46
+ testValue: `Valor de prueba ${i}`,
47
+ description: `Este es un elemento de prueba para verificar que los hooks de compresión funcionan correctamente`
48
+ }))
49
+ };
50
+
51
+ res.writeHead(200, { 'Content-Type': 'application/json' });
52
+ res.end(JSON.stringify(testData));
53
+ }
36
54
  }
37
55
 
38
56
  module.exports = new CompressionTestController();
@@ -54,5 +54,12 @@
54
54
  "controller": "CompressionTestController",
55
55
  "handler": "getLargeData",
56
56
  "contentType": "application/json"
57
+ },
58
+ {
59
+ "path": "/test-compression-hooks",
60
+ "method": "GET",
61
+ "controller": "CompressionTestController",
62
+ "handler": "testCompressionHooks",
63
+ "contentType": "application/json"
57
64
  }
58
65
  ]
@@ -44,7 +44,7 @@ class StandardServer {
44
44
  this.authenticator = new Authenticator({ logger: this.logger });
45
45
  this.cors = new Cors();
46
46
  this.rateLimiter = new RateLimiter();
47
- this.compressor = new Compressor();
47
+ this.compressor = new Compressor({ hooks: hooks });
48
48
  this.firewall = new Firewall({ logger: this.logger });
49
49
  this.sessionManager = new SessionManager();
50
50