ultimate-express 1.4.9 → 1.4.10

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/README.md CHANGED
@@ -35,6 +35,7 @@ Tested using [wrk](https://github.com/wg/wrk) (`-d 60 -t 1 -c 200`). Tested on U
35
35
  | middlewares/express-static (/static/index.js) | 6.58k | 32.45k | 10.15 MB/sec | 49.43 MB/sec | **4.87X** |
36
36
  | engines/ejs (/test) | 5.50k | 40.82k | 2.45 MB/sec | 18.38 MB/sec | **7.42X** |
37
37
  | middlewares/body-urlencoded (/abc) | 8.07k | 50.52k | 1.68 MB/sec | 10.78 MB/sec | **6.26X** |
38
+ | middlewares/compression-file (/small-file) | 4.81k | 14.92k | 386 MB/sec | 1.17 GB/sec | **3.10X** |
38
39
 
39
40
  ### Performance against other frameworks
40
41
 
@@ -327,6 +328,8 @@ Almost all middlewares that are compatible with Express are compatible with µEx
327
328
  - ✅ [passport](https://www.npmjs.com/package/passport)
328
329
  - ✅ [morgan](https://www.npmjs.com/package/morgan)
329
330
  - ✅ [swagger-ui-express](https://www.npmjs.com/package/swagger-ui-express)
331
+ - ✅ [graphql-http](https://www.npmjs.com/package/graphql-http)
332
+ - ✅ [better-sse](https://www.npmjs.com/package/better-sse)
330
333
 
331
334
  Middlewares and modules that are confirmed to not work:
332
335
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-express",
3
- "version": "1.4.9",
3
+ "version": "1.4.10",
4
4
  "description": "The Ultimate Express. Fastest http server with full Express compatibility, based on uWebSockets.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -31,7 +31,7 @@ module.exports = function compileDeclarative(cb, app) {
31
31
 
32
32
  const tokens = [...acorn.tokenizer(code, { ecmaVersion: "latest" })];
33
33
 
34
- if(tokens.some(token => ['throw', 'new', 'await'].includes(token.value))) {
34
+ if(tokens.some(token => ['throw', 'new', 'await', 'return'].includes(token.value))) {
35
35
  return false;
36
36
  }
37
37
 
@@ -186,7 +186,7 @@ module.exports = function compileDeclarative(cb, app) {
186
186
  if(call.arguments[0].type !== 'Literal' || call.arguments[1].type !== 'Literal') {
187
187
  return false;
188
188
  }
189
- const sameHeader = headers.find(header => header[0] === call.arguments[0].value);
189
+ const sameHeader = headers.find(header => header[0].toLowerCase() === call.arguments[0].value.toLowerCase());
190
190
  if(sameHeader) {
191
191
  sameHeader[1] = call.arguments[1].value;
192
192
  } else {
@@ -207,6 +207,12 @@ module.exports = function compileDeclarative(cb, app) {
207
207
  if(sendUsed) {
208
208
  return false;
209
209
  }
210
+ if(call.obj.propertyName === 'send') {
211
+ const index = headers.findIndex(header => header[0].toLowerCase() === 'content-type');
212
+ if(index === -1) {
213
+ headers.push(['content-type', 'text/html; charset=utf-8']);
214
+ }
215
+ }
210
216
  const arg = call.arguments[0];
211
217
  if(arg) {
212
218
  if(arg.type === 'Literal') {
@@ -216,6 +222,17 @@ module.exports = function compileDeclarative(cb, app) {
216
222
  let val = arg.value;
217
223
  if(val === null) {
218
224
  val = '';
225
+ const index = headers.findIndex(header => header[0].toLowerCase() === 'content-type');
226
+ if(index !== -1) {
227
+ headers.splice(index, 1);
228
+ }
229
+ }
230
+ if(typeof val === 'boolean') {
231
+ if(!headers.some(header => header[0].toLowerCase() === 'content-type')) {
232
+ headers.push(['content-type', 'application/json; charset=utf-8']);
233
+ } else {
234
+ headers.find(header => header[0].toLowerCase() === 'content-type')[1] = 'application/json; charset=utf-8';
235
+ }
219
236
  }
220
237
  body.push({type: 'text', value: val});
221
238
  } else if(arg.type === 'TemplateLiteral') {
@@ -304,7 +321,11 @@ module.exports = function compileDeclarative(cb, app) {
304
321
  return false;
305
322
  }
306
323
 
307
- headers.push(['content-type', 'application/json; charset=utf-8']);
324
+ if(!headers.some(header => header[0].toLowerCase() === 'content-type')) {
325
+ headers.push(['content-type', 'application/json; charset=utf-8']);
326
+ } else {
327
+ headers.find(header => header[0].toLowerCase() === 'content-type')[1] = 'application/json; charset=utf-8';
328
+ }
308
329
  body.push({
309
330
  type: 'text',
310
331
  value:
@@ -334,6 +355,9 @@ module.exports = function compileDeclarative(cb, app) {
334
355
  if(header[0].toLowerCase() === 'content-length') {
335
356
  return false;
336
357
  }
358
+ if(header[0].toLowerCase() === 'content-type' && header[1].includes('text/') && !header[1].includes(';')) {
359
+ header[1] += '; charset=utf-8';
360
+ }
337
361
  decRes = decRes.writeHeader(header[0], header[1]);
338
362
  }
339
363
 
@@ -169,7 +169,7 @@ function createBodyParser(defaultType, beforeReturn) {
169
169
 
170
170
  req.body = new NullObject();
171
171
 
172
- // skip reading body for non-json content type
172
+ // skip reading body for no content type
173
173
  if(!type) {
174
174
  return next();
175
175
  }
@@ -281,6 +281,10 @@ const json = createBodyParser('application/json', function(req, res, next, optio
281
281
  return next(new Error('Invalid body'));
282
282
  }
283
283
  }
284
+ if(buf.length === 0) {
285
+ req.body = {};
286
+ return next();
287
+ }
284
288
  try {
285
289
  req.body = JSON.parse(buf.toString(), options.reviver);
286
290
  } catch(e) {
package/src/response.js CHANGED
@@ -177,6 +177,7 @@ module.exports = class Response extends Writable {
177
177
  }
178
178
  });
179
179
  }, 50);
180
+ this.#writeTimeout.unref();
180
181
  }
181
182
  this.writingChunk = false;
182
183
  callback(null);
@@ -243,9 +244,6 @@ module.exports = class Response extends Writable {
243
244
  this._res.writeHeader(header, value);
244
245
  }
245
246
  }
246
- if(!this.headers['content-type']) {
247
- this._res.writeHeader('content-type', 'text/html' + (utf8 ? `; charset=utf-8` : ''));
248
- }
249
247
  this.headersSent = true;
250
248
  }
251
249
  _implicitHeader() {
@@ -285,6 +283,8 @@ module.exports = class Response extends Writable {
285
283
  this._res.end();
286
284
  this.finished = true;
287
285
  if(this.socketExists) this.socket.emit('close');
286
+ this.emit('finish');
287
+ this.emit('close');
288
288
  return;
289
289
  }
290
290
  }
@@ -323,6 +323,7 @@ module.exports = class Response extends Writable {
323
323
  const isBuffer = Buffer.isBuffer(body);
324
324
  if(body === null || body === undefined) {
325
325
  body = '';
326
+ return this.end(body);
326
327
  } else if(typeof body === 'object' && !isBuffer) {
327
328
  return this.json(body);
328
329
  } else if(typeof body === 'number') {
@@ -331,18 +332,25 @@ module.exports = class Response extends Writable {
331
332
  return this.status(body).send(arguments[1]);
332
333
  } else {
333
334
  deprecated('res.send(status)', 'res.sendStatus(status)');
335
+ if(!this.headers['content-type']) {
336
+ this.headers['content-type'] = 'text/plain; charset=utf-8';
337
+ }
334
338
  return this.sendStatus(body);
335
339
  }
340
+ } else if(typeof body === 'boolean') {
341
+ return this.json(body);
336
342
  } else if(!isBuffer) {
337
343
  body = String(body);
338
344
  }
339
- if(typeof body === 'string') {
345
+ if(typeof body === 'string' && !isBuffer) {
340
346
  const contentType = this.headers['content-type'];
341
347
  if(!contentType) {
342
348
  this.headers['content-type'] = 'text/html; charset=utf-8';
343
349
  } else if(!contentType.includes(';')) {
344
350
  this.headers['content-type'] += '; charset=utf-8';
345
351
  }
352
+ } else {
353
+ this.headers['content-type'] = 'application/octet-stream';
346
354
  }
347
355
  return this.end(body);
348
356
  }
@@ -611,6 +619,9 @@ module.exports = class Response extends Writable {
611
619
  getHeader(field) {
612
620
  return this.get(field);
613
621
  }
622
+ getHeaders(){
623
+ return this.headers;
624
+ }
614
625
  removeHeader(field) {
615
626
  delete this.headers[field.toLowerCase()];
616
627
  return this;
@@ -714,11 +725,7 @@ module.exports = class Response extends Writable {
714
725
  jsonp(object) {
715
726
  let callback = this.req.query[this.app.get('jsonp callback name')];
716
727
  let body = stringify(object, this.app.get('json replacer'), this.app.get('json spaces'), this.app.get('json escape'));
717
-
718
- if(!this.headers['content-type']) {
719
- this.headers['content-type'] = 'application/javascript; charset=utf-8';
720
- this.headers['X-Content-Type-Options'] = 'nosniff';
721
- }
728
+ let js = false;
722
729
 
723
730
  if(Array.isArray(callback)) {
724
731
  callback = callback[0];
@@ -736,6 +743,13 @@ module.exports = class Response extends Writable {
736
743
  .replace(/\u2029/g, '\\u2029')
737
744
  }
738
745
  body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
746
+ js = true;
747
+ }
748
+
749
+
750
+ if(!this.headers['content-type']) {
751
+ this.headers['content-type'] = `${js ? 'text/javascript' : 'application/json'}; charset=utf-8`;
752
+ if(js) this.headers['X-Content-Type-Options'] = 'nosniff';
739
753
  }
740
754
 
741
755
  return this.send(body);
@@ -760,7 +774,25 @@ module.exports = class Response extends Writable {
760
774
  this.location(url);
761
775
  this.status(status);
762
776
  this.headers['content-type'] = 'text/plain; charset=utf-8';
763
- return this.send(`${statuses.message[status] ?? status}. Redirecting to ${url}`);
777
+ let body;
778
+ // Support text/{plain,html} by default
779
+ this.format({
780
+ text: function() {
781
+ body = statuses.message[status] + '. Redirecting to ' + url
782
+ },
783
+ html: function() {
784
+ let u = escapeHtml(url);
785
+ body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>'
786
+ },
787
+ default: function() {
788
+ body = '';
789
+ }
790
+ });
791
+ if (this.req.method === 'HEAD') {
792
+ this.end();
793
+ } else {
794
+ this.end(body);
795
+ }
764
796
  }
765
797
 
766
798
  type(type) {
@@ -773,6 +805,11 @@ module.exports = class Response extends Writable {
773
805
  contentType = this.type;
774
806
 
775
807
  vary(field) {
808
+ // checks for back-compat
809
+ if (!field || (Array.isArray(field) && !field.length)) {
810
+ deprecate('res.vary(): Provide a field name');
811
+ return this;
812
+ }
776
813
  vary(this, field);
777
814
  return this;
778
815
  }
package/src/router.js CHANGED
@@ -180,7 +180,7 @@ module.exports = class Router extends EventEmitter {
180
180
  optimizedPathToRouter = optimizedPathToRouter.slice(0, -1); // remove last element, which is the router itself
181
181
  if(optimizedPathToRouter) {
182
182
  // wait for routes in router to be registered
183
- setTimeout(() => {
183
+ const t = setTimeout(() => {
184
184
  if(!this.listenCalled) {
185
185
  return; // can only optimize router whos parent is listening
186
186
  }
@@ -208,6 +208,7 @@ module.exports = class Router extends EventEmitter {
208
208
  }
209
209
  }
210
210
  }, 100);
211
+ t.unref();
211
212
  }
212
213
  // only 1 router can be optimized per route
213
214
  break;
@@ -354,7 +355,10 @@ module.exports = class Router extends EventEmitter {
354
355
  path = path.slice(0, -1);
355
356
  }
356
357
  let match = pattern.exec(path);
357
- const obj = match?.groups ?? new NullObject();
358
+ if( match?.groups ){
359
+ return match.groups;
360
+ }
361
+ const obj = new NullObject();
358
362
  for(let i = 1; i < match.length; i++) {
359
363
  obj[i - 1] = match[i];
360
364
  }