ultimate-express 1.3.1 → 1.3.2

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
@@ -302,6 +302,7 @@ Almost all middlewares that are compatible with Express are compatible with µEx
302
302
  - ✅ [express-rate-limit](https://npmjs.com/package/express-rate-limit)
303
303
  - ✅ [express-subdomain](https://npmjs.com/package/express-subdomain)
304
304
  - ✅ [vhost](https://npmjs.com/package/vhost)
305
+ - ✅ [tsoa](https://github.com/lukeautry/tsoa)
305
306
 
306
307
  Middlewares and modules that are confirmed to not work:
307
308
 
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "ultimate-express",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
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": {
7
- "test": "node tests/index.js"
7
+ "test": "node tests/index.js",
8
+ "dev": "node --inspect=9229 demo/index.js"
8
9
  },
9
10
  "engines": {
10
11
  "node": ">=16"
package/src/request.js CHANGED
@@ -351,53 +351,54 @@ module.exports = class Request extends Readable {
351
351
  if(this.#cachedHeaders) {
352
352
  return this.#cachedHeaders;
353
353
  }
354
- let headers = new NullObject();
355
- this.#rawHeadersEntries.forEach((val) => {
356
- const key = val[0].toLowerCase();
357
- const value = val[1];
358
- if(headers[key]) {
354
+ this.#cachedHeaders = new NullObject();
355
+ for (let index = 0, len = this.#rawHeadersEntries.length; index < len; index++) {
356
+ let [key, value] = this.#rawHeadersEntries[index];
357
+ key = key.toLowerCase();
358
+ if(this.#cachedHeaders[key]) {
359
359
  if(discardedDuplicates.includes(key)) {
360
- return;
360
+ continue;
361
361
  }
362
362
  if(key === 'cookie') {
363
- headers[key] += '; ' + value;
363
+ this.#cachedHeaders[key] += '; ' + value;
364
364
  } else if(key === 'set-cookie') {
365
- headers[key].push(value);
365
+ this.#cachedHeaders[key].push(value);
366
366
  } else {
367
- headers[key] += ', ' + value;
367
+ this.#cachedHeaders[key] += ', ' + value;
368
368
  }
369
- return;
369
+ continue;
370
370
  }
371
371
  if(key === 'set-cookie') {
372
- headers[key] = [value];
372
+ this.#cachedHeaders[key] = [value];
373
373
  } else {
374
- headers[key] = value;
374
+ this.#cachedHeaders[key] = value;
375
375
  }
376
- });
377
- this.#cachedHeaders = headers;
378
- return headers;
376
+ }
377
+ return this.#cachedHeaders;
379
378
  }
380
379
 
381
380
  get headersDistinct() {
382
381
  if(this.#cachedDistinctHeaders) {
383
382
  return this.#cachedDistinctHeaders;
384
383
  }
385
- let headers = new NullObject();
384
+ this.#cachedDistinctHeaders = new NullObject();
386
385
  this.#rawHeadersEntries.forEach((val) => {
387
- if(!headers[val[0]]) {
388
- headers[val[0]] = [];
386
+ const [key, value] = val;
387
+ if(!this.#cachedDistinctHeaders[key]) {
388
+ this.#cachedDistinctHeaders[key] = [value];
389
+ return;
389
390
  }
390
- headers[val[0]].push(val[1]);
391
+ this.#cachedDistinctHeaders[key].push(value);
391
392
  });
392
- this.#cachedDistinctHeaders = headers;
393
- return headers;
393
+ return this.#cachedDistinctHeaders;
394
394
  }
395
395
 
396
396
  get rawHeaders() {
397
397
  const res = [];
398
- this.#rawHeadersEntries.forEach((val) => {
398
+ for (let index = 0, len = this.#rawHeadersEntries.length; index < len; index++) {
399
+ const val = this.#rawHeadersEntries[index];
399
400
  res.push(val[0], val[1]);
400
- });
401
+ }
401
402
  return res;
402
403
  }
403
404
  }
package/src/response.js CHANGED
@@ -225,7 +225,7 @@ module.exports = class Response extends Writable {
225
225
  this._res.cork(() => {
226
226
  if(!this.headersSent) {
227
227
  const etagFn = this.app.get('etag fn');
228
- if(data && !this.headers['etag'] && etagFn && !this.req.noEtag) {
228
+ if(etagFn && data && !this.headers['etag'] && !this.req.noEtag) {
229
229
  this.headers['etag'] = etagFn(data);
230
230
  }
231
231
  const fresh = this.req.fresh;
@@ -238,8 +238,9 @@ module.exports = class Response extends Writable {
238
238
  return;
239
239
  }
240
240
  }
241
- if(!data && this.headers['content-length']) {
242
- this._res.endWithoutBody(this.headers['content-length'].toString());
241
+ const contentLength = this.headers['content-length'];
242
+ if(!data && contentLength) {
243
+ this._res.endWithoutBody(contentLength.toString());
243
244
  } else {
244
245
  if(data instanceof Buffer) {
245
246
  data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
@@ -279,8 +280,8 @@ module.exports = class Response extends Writable {
279
280
  }
280
281
  if(typeof body === 'string') {
281
282
  const contentType = this.headers['content-type'];
282
- if(!contentType){
283
- this.type('html'); // string defaulting to html
283
+ if(!contentType) {
284
+ this.headers['content-type'] = 'text/html; charset=utf-8';
284
285
  } else if(!contentType.includes(';')) {
285
286
  this.headers['content-type'] += '; charset=utf-8';
286
287
  }
@@ -361,7 +362,8 @@ module.exports = class Response extends Writable {
361
362
  this.status(403);
362
363
  return done(new Error('Forbidden'));
363
364
  case 'ignore_files':
364
- if(parts.length > 1 && parts[parts.length - 1].startsWith('.')) {
365
+ const len = parts.length;
366
+ if(len > 1 && parts[len - 1].startsWith('.')) {
365
367
  this.status(404);
366
368
  return done(new Error('Not found'));
367
369
  }
@@ -408,7 +410,7 @@ module.exports = class Response extends Writable {
408
410
  }
409
411
 
410
412
  // etag
411
- if(options.etag && !this.headers['etag'] && etagFn) {
413
+ if(options.etag && etagFn && !this.headers['etag']) {
412
414
  this.headers['etag'] = etagFn(stat);
413
415
  }
414
416
  if(!options.etag) {
@@ -442,9 +444,10 @@ module.exports = class Response extends Writable {
442
444
  }
443
445
  if(ranges !== -2 && ranges.length === 1) {
444
446
  this.status(206);
445
- this.headers['content-range'] = `bytes ${ranges[0].start}-${ranges[0].end}/${stat.size}`;
446
- offset = ranges[0].start;
447
- len = ranges[0].end - ranges[0].start + 1;
447
+ const range = ranges[0];
448
+ this.headers['content-range'] = `bytes ${range.start}-${range.end}/${stat.size}`;
449
+ offset = range.start;
450
+ len = range.end - range.start + 1;
448
451
  ranged = true;
449
452
  }
450
453
  }
@@ -536,18 +539,12 @@ module.exports = class Response extends Writable {
536
539
  }
537
540
  return this;
538
541
  }
539
- header(field, value) {
540
- return this.set(field, value);
541
- }
542
- setHeader(field, value) {
543
- return this.set(field, value);
544
- }
542
+ header = this.set;
543
+ setHeader = this.set;
545
544
  get(field) {
546
545
  return this.headers[field.toLowerCase()];
547
546
  }
548
- getHeader(field) {
549
- return this.get(field);
550
- }
547
+ getHeader = this.get;
551
548
  removeHeader(field) {
552
549
  delete this.headers[field.toLowerCase()];
553
550
  return this;
@@ -709,9 +706,7 @@ module.exports = class Response extends Writable {
709
706
 
710
707
  return this.set('content-type', ct);
711
708
  }
712
- contentType(type) {
713
- return this.type(type);
714
- }
709
+ contentType = this.type;
715
710
 
716
711
  vary(field) {
717
712
  vary(this, field);
package/src/router.js CHANGED
@@ -31,6 +31,8 @@ const methods = [
31
31
  ];
32
32
  const supportedUwsMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE'];
33
33
 
34
+ const regExParam = /:(\w+)/g;
35
+
34
36
  module.exports = class Router extends EventEmitter {
35
37
  constructor(settings = {}) {
36
38
  super();
@@ -264,7 +266,7 @@ module.exports = class Router extends EventEmitter {
264
266
  method = 'del';
265
267
  }
266
268
  if(!route.optimizedRouter && route.path.includes(":")) {
267
- route.optimizedParams = route.path.match(/:(\w+)/g).map(p => p.slice(1));
269
+ route.optimizedParams = route.path.match(regExParam).map(p => p.slice(1));
268
270
  }
269
271
  const fn = async (res, req) => {
270
272
  const { request, response } = this.handleRequest(res, req);
@@ -282,7 +284,7 @@ module.exports = class Router extends EventEmitter {
282
284
  }
283
285
  };
284
286
  route.optimizedPath = optimizedPath;
285
- let replacedPath = route.path.replace(/:(\w+)/g, ':x');
287
+ let replacedPath = route.path.replace(regExParam, ':x');
286
288
  this.uwsApp[method](replacedPath, fn);
287
289
  if(!this.get('strict routing') && route.path[route.path.length - 1] !== '/') {
288
290
  this.uwsApp[method](replacedPath + '/', fn);
@@ -407,33 +409,33 @@ module.exports = class Router extends EventEmitter {
407
409
  }
408
410
 
409
411
  async _routeRequest(req, res, startIndex = 0, routes = this._routes, skipCheck = false, skipUntil) {
410
- return new Promise(async (resolve) => {
411
- let routeIndex = skipCheck ? startIndex : findIndexStartingFrom(routes, r => (r.all || r.method === req.method || (r.gettable && req.method === 'HEAD')) && this._pathMatches(r, req), startIndex);
412
- const route = routes[routeIndex];
413
- if(!route) {
414
- if(!skipCheck) {
415
- // on normal unoptimized routes, if theres no match then there is no route
416
- return resolve(false);
417
- }
418
- // on optimized routes, there can be more routes, so we have to use unoptimized routing and skip until we find route we stopped at
419
- return resolve(this._routeRequest(req, res, 0, this._routes, false, skipUntil));
412
+ let routeIndex = skipCheck ? startIndex : findIndexStartingFrom(routes, r => (r.all || r.method === req.method || (r.gettable && req.method === 'HEAD')) && this._pathMatches(r, req), startIndex);
413
+ const route = routes[routeIndex];
414
+ if(!route) {
415
+ if(!skipCheck) {
416
+ // on normal unoptimized routes, if theres no match then there is no route
417
+ return false;
420
418
  }
421
- let callbackindex = 0;
422
- const continueRoute = await this._preprocessRequest(req, res, route);
423
- if(route.use) {
424
- const strictRouting = this.get('strict routing');
425
- req._stack.push(route.path);
426
- req._opPath = req.path.replace(this.getFullMountpath(req), '');
427
- if(strictRouting) {
428
- if(req.endsWithSlash && req.path !== '/') {
429
- req._opPath += '/';
430
- }
431
- } else if(req.endsWithSlash && req.path !== '/') {
432
- req._opPath = req._opPath.slice(0, -1);
419
+ // on optimized routes, there can be more routes, so we have to use unoptimized routing and skip until we find route we stopped at
420
+ return this._routeRequest(req, res, 0, this._routes, false, skipUntil);
421
+ }
422
+ let callbackindex = 0;
423
+ const continueRoute = await this._preprocessRequest(req, res, route);
424
+ if(route.use) {
425
+ const strictRouting = this.get('strict routing');
426
+ req._stack.push(route.path);
427
+ req._opPath = req.path.replace(this.getFullMountpath(req), '');
428
+ if(strictRouting) {
429
+ if(req.endsWithSlash && req.path !== '/') {
430
+ req._opPath += '/';
433
431
  }
434
- req.url = req._opPath + req.urlQuery;
435
- if(req.url === '') req.url = '/';
432
+ } else if(req.endsWithSlash && req.path !== '/') {
433
+ req._opPath = req._opPath.slice(0, -1);
436
434
  }
435
+ req.url = req._opPath + req.urlQuery;
436
+ if(req.url === '') req.url = '/';
437
+ }
438
+ return new Promise((resolve) => {
437
439
  const next = async (thingamabob) => {
438
440
  if(thingamabob) {
439
441
  if(thingamabob === 'route' || thingamabob === 'skipPop') {
@@ -565,4 +567,4 @@ module.exports = class Router extends EventEmitter {
565
567
  `</body>\n` +
566
568
  `</html>\n`);
567
569
  }
568
- }
570
+ }
package/src/utils.js CHANGED
@@ -23,7 +23,11 @@ const etag = require("etag");
23
23
  const { Stats } = require("fs");
24
24
 
25
25
  function fastQueryParse(query, options) {
26
- if(query.length <= 128) {
26
+ const len = query.length;
27
+ if(len === 0){
28
+ return new NullObject();
29
+ }
30
+ if(len <= 128) {
27
31
  if(!query.includes('[') && !query.includes('%5B') && !query.includes('.') && !query.includes('%2E')) {
28
32
  return querystring.parse(query);
29
33
  }
@@ -44,9 +48,9 @@ function patternToRegex(pattern, isPrefix = false) {
44
48
  }
45
49
 
46
50
  let regexPattern = pattern
47
- .replace(/\./g, '\\.')
48
- .replace(/\-/g, '\\-')
49
- .replace(/\*/g, '(.*)') // Convert * to .*
51
+ .replaceAll('.', '\\.')
52
+ .replaceAll('-', '\\-')
53
+ .replaceAll('*', '(.*)') // Convert * to .*
50
54
  .replace(/:(\w+)(\(.+?\))?/g, (match, param, regex) => {
51
55
  return `(?<${param}>${regex ? regex + '($|\\/)' : '[^/]+'})`;
52
56
  }); // Convert :param to capture group
@@ -101,12 +105,13 @@ function acceptParams(str) {
101
105
  const parts = str.split(/ *; */);
102
106
  const ret = { value: parts[0], quality: 1, params: {} }
103
107
 
104
- for (let i = 1; i < parts.length; ++i) {
108
+ for (let i = 1, len = parts.length; i < len; ++i) {
105
109
  const pms = parts[i].split(/ *= */);
106
- if ('q' === pms[0]) {
107
- ret.quality = parseFloat(pms[1]);
110
+ const [pms_0, pms_1] = pms;
111
+ if ('q' === pms_0) {
112
+ ret.quality = parseFloat(pms_1);
108
113
  } else {
109
- ret.params[pms[0]] = pms[1];
114
+ ret.params[pms_0] = pms_1;
110
115
  }
111
116
  }
112
117
 
@@ -218,7 +223,7 @@ function decode (path) {
218
223
  const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
219
224
 
220
225
  function containsDotFile(parts) {
221
- for(let i = 0; i < parts.length; i++) {
226
+ for(let i = 0, len = parts.length; i < len; i++) {
222
227
  const part = parts[i];
223
228
  if(part.length > 1 && part[0] === '.') {
224
229
  return true;
@@ -341,4 +346,4 @@ module.exports = {
341
346
  findIndexStartingFrom,
342
347
  fastQueryParse,
343
348
  canBeOptimized
344
- };
349
+ };