ultimate-express 1.3.0 → 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
@@ -289,6 +289,7 @@ Almost all middlewares that are compatible with Express are compatible with µEx
289
289
  - ✅ [body-parser](https://npmjs.com/package/body-parser) (use `express.text()` etc instead for better performance)
290
290
  - ✅ [cookie-parser](https://npmjs.com/package/cookie-parser)
291
291
  - ✅ [cookie-session](https://npmjs.com/package/cookie-session)
292
+ - ✅ [compression](https://npmjs.com/package/compression)
292
293
  - ✅ [serve-static](https://npmjs.com/package/serve-static) (use `express.static()` instead for better performance)
293
294
  - ✅ [serve-index](https://npmjs.com/package/serve-index)
294
295
  - ✅ [cors](https://npmjs.com/package/cors)
@@ -301,10 +302,10 @@ Almost all middlewares that are compatible with Express are compatible with µEx
301
302
  - ✅ [express-rate-limit](https://npmjs.com/package/express-rate-limit)
302
303
  - ✅ [express-subdomain](https://npmjs.com/package/express-subdomain)
303
304
  - ✅ [vhost](https://npmjs.com/package/vhost)
305
+ - ✅ [tsoa](https://github.com/lukeautry/tsoa)
304
306
 
305
307
  Middlewares and modules that are confirmed to not work:
306
308
 
307
- - ❌ [compression](https://npmjs.com/package/compression) - doesn't error, but doesn't compress
308
309
  - ❌ [express-async-errors](https://npmjs.com/package/express-async-errors) - doesn't work, use `app.set('catch async errors', true)` instead.
309
310
 
310
311
  ## Tested view engines
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "ultimate-express",
3
- "version": "1.3.0",
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"
@@ -42,7 +43,7 @@
42
43
  "@types/express": "^4.0.0",
43
44
  "accepts": "^1.3.8",
44
45
  "bytes": "^3.1.2",
45
- "cookie": "^0.6.0",
46
+ "cookie": "^1.0.1",
46
47
  "cookie-signature": "^1.2.1",
47
48
  "encodeurl": "^2.0.0",
48
49
  "etag": "^1.8.1",
@@ -62,6 +63,7 @@
62
63
  },
63
64
  "devDependencies": {
64
65
  "body-parser": "^1.20.3",
66
+ "compression": "^1.7.4",
65
67
  "cookie-parser": "^1.4.6",
66
68
  "cookie-session": "^2.1.0",
67
69
  "cors": "^2.8.5",
@@ -81,6 +83,7 @@
81
83
  "multer": "^1.4.5-lts.1",
82
84
  "mustache-express": "^1.3.2",
83
85
  "pako": "^2.1.0",
86
+ "pkg-pr-new": "^0.0.29",
84
87
  "pug": "^3.0.3",
85
88
  "response-time": "^2.3.2",
86
89
  "serve-index": "^1.9.1",
@@ -16,7 +16,7 @@ limitations under the License.
16
16
 
17
17
  const uWS = require("uWebSockets.js");
18
18
  const Router = require("./router.js");
19
- const { removeDuplicateSlashes, defaultSettings, compileTrust, createETagGenerator, fastQueryParse } = require("./utils.js");
19
+ const { removeDuplicateSlashes, defaultSettings, compileTrust, createETagGenerator, fastQueryParse, NullObject } = require("./utils.js");
20
20
  const querystring = require("fast-querystring");
21
21
  const ViewClass = require("./view.js");
22
22
  const path = require("path");
@@ -27,7 +27,7 @@ const cpuCount = os.cpus().length;
27
27
 
28
28
  let workers = [];
29
29
  let taskKey = 0;
30
- const workerTasks = {};
30
+ const workerTasks = new NullObject();
31
31
 
32
32
  function createWorker() {
33
33
  const worker = new Worker(path.join(__dirname, 'worker.js'));
@@ -47,7 +47,7 @@ function createWorker() {
47
47
  }
48
48
 
49
49
  class Application extends Router {
50
- constructor(settings = {}) {
50
+ constructor(settings = new NullObject()) {
51
51
  super(settings);
52
52
  if(!settings?.uwsOptions) {
53
53
  settings.uwsOptions = {};
@@ -62,8 +62,8 @@ class Application extends Router {
62
62
  this.uwsApp = uWS.App(settings.uwsOptions);
63
63
  this.ssl = false;
64
64
  }
65
- this.cache = {};
66
- this.engines = {};
65
+ this.cache = new NullObject();
66
+ this.engines = new NullObject();
67
67
  this.locals = {
68
68
  settings: this.settings
69
69
  };
@@ -206,7 +206,7 @@ class Application extends Router {
206
206
  throw err;
207
207
  }
208
208
  this.port = uWS.us_socket_local_port(socket);
209
- callback(this.port);
209
+ if(callback) callback(this.port);
210
210
  };
211
211
  let fn = 'listen';
212
212
  let args = [];
@@ -229,6 +229,7 @@ class Application extends Router {
229
229
  }
230
230
  this.listenCalled = true;
231
231
  this.uwsApp[fn](...args);
232
+ return this.uwsApp;
232
233
  }
233
234
 
234
235
  address() {
@@ -260,10 +261,10 @@ class Application extends Router {
260
261
  render(name, options, callback) {
261
262
  if(typeof options === 'function') {
262
263
  callback = options;
263
- options = {};
264
+ options = new NullObject();
264
265
  }
265
266
  if(!options) {
266
- options = {};
267
+ options = new NullObject();
267
268
  } else {
268
269
  options = Object.assign({}, options);
269
270
  }
@@ -318,4 +319,4 @@ class Application extends Router {
318
319
 
319
320
  module.exports = function(options) {
320
321
  return new Application(options);
321
- }
322
+ }
@@ -20,10 +20,10 @@ const bytes = require('bytes');
20
20
  const zlib = require('fast-zlib');
21
21
  const typeis = require('type-is');
22
22
  const querystring = require('fast-querystring');
23
- const { fastQueryParse } = require('./utils.js');
23
+ const { fastQueryParse, NullObject } = require('./utils.js');
24
24
 
25
25
  function static(root, options) {
26
- if(!options) options = {};
26
+ if(!options) options = new NullObject();
27
27
  if(typeof options.index === 'undefined') options.index = 'index.html';
28
28
  if(typeof options.redirect === 'undefined') options.redirect = true;
29
29
  if(typeof options.fallthrough === 'undefined') options.fallthrough = true;
@@ -132,7 +132,7 @@ function createInflate(contentEncoding) {
132
132
  function createBodyParser(defaultType, beforeReturn) {
133
133
  return function(options) {
134
134
  if(typeof options !== 'object') {
135
- options = {};
135
+ options = new NullObject();
136
136
  }
137
137
  if(typeof options.limit === 'undefined') options.limit = bytes('100kb');
138
138
  else options.limit = bytes(options.limit);
@@ -160,7 +160,7 @@ function createBodyParser(defaultType, beforeReturn) {
160
160
  return next();
161
161
  }
162
162
 
163
- req.body = {};
163
+ req.body = new NullObject();
164
164
 
165
165
  // skip reading body for non-json content type
166
166
  if(!type) {
package/src/request.js CHANGED
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- const { patternToRegex, deprecated } = require("./utils.js");
17
+ const { patternToRegex, deprecated, NullObject } = require("./utils.js");
18
18
  const accepts = require("accepts");
19
19
  const typeis = require("type-is");
20
20
  const parseRange = require("range-parser");
@@ -64,7 +64,7 @@ module.exports = class Request extends Readable {
64
64
  this._opPath = this._opPath.slice(0, -1);
65
65
  }
66
66
  this.method = req.getCaseSensitiveMethod().toUpperCase();
67
- this.params = {};
67
+ this.params = new NullObject();
68
68
 
69
69
  this._gotParams = new Set();
70
70
  this._stack = [];
@@ -206,7 +206,7 @@ module.exports = class Request extends Readable {
206
206
  if(qp) {
207
207
  this.#cachedQuery = qp(this.urlQuery.slice(1));
208
208
  } else {
209
- this.#cachedQuery = {};
209
+ this.#cachedQuery = new NullObject();
210
210
  }
211
211
  return this.#cachedQuery;
212
212
  }
@@ -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 = {};
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 = {};
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
@@ -20,7 +20,7 @@ const vary = require("vary");
20
20
  const encodeUrl = require("encodeurl");
21
21
  const {
22
22
  normalizeType, stringify, deprecated, UP_PATH_REGEXP, decode,
23
- containsDotFile, isPreconditionFailure, isRangeFresh, parseHttpDate
23
+ containsDotFile, isPreconditionFailure, isRangeFresh, NullObject
24
24
  } = require("./utils.js");
25
25
  const { Writable } = require("stream");
26
26
  const { isAbsolute } = require("path");
@@ -70,7 +70,7 @@ module.exports = class Response extends Writable {
70
70
  this._res = res;
71
71
  this.headersSent = false;
72
72
  this.app = app;
73
- this.locals = {};
73
+ this.locals = new NullObject();
74
74
  this.finished = false;
75
75
  this.aborted = false;
76
76
  this.statusCode = 200;
@@ -208,12 +208,9 @@ module.exports = class Response extends Writable {
208
208
  _implicitHeader() {
209
209
  // compatibility function
210
210
  // usually should send headers but this is useless for us
211
- return;
211
+ this.writeHead(this.statusCode);
212
212
  }
213
213
  status(code) {
214
- if(this.headersSent) {
215
- throw new Error('Can\'t set status: Response was already sent');
216
- }
217
214
  this.statusCode = parseInt(code);
218
215
  return this;
219
216
  }
@@ -228,7 +225,7 @@ module.exports = class Response extends Writable {
228
225
  this._res.cork(() => {
229
226
  if(!this.headersSent) {
230
227
  const etagFn = this.app.get('etag fn');
231
- if(data && !this.headers['etag'] && etagFn && !this.req.noEtag) {
228
+ if(etagFn && data && !this.headers['etag'] && !this.req.noEtag) {
232
229
  this.headers['etag'] = etagFn(data);
233
230
  }
234
231
  const fresh = this.req.fresh;
@@ -241,8 +238,9 @@ module.exports = class Response extends Writable {
241
238
  return;
242
239
  }
243
240
  }
244
- if(!data && this.headers['content-length']) {
245
- 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());
246
244
  } else {
247
245
  if(data instanceof Buffer) {
248
246
  data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
@@ -282,21 +280,23 @@ module.exports = class Response extends Writable {
282
280
  }
283
281
  if(typeof body === 'string') {
284
282
  const contentType = this.headers['content-type'];
285
- if(contentType && !contentType.includes(';')) {
283
+ if(!contentType) {
284
+ this.headers['content-type'] = 'text/html; charset=utf-8';
285
+ } else if(!contentType.includes(';')) {
286
286
  this.headers['content-type'] += '; charset=utf-8';
287
287
  }
288
288
  }
289
289
  return this.end(body);
290
290
  }
291
- sendFile(path, options = {}, callback) {
291
+ sendFile(path, options = new NullObject(), callback) {
292
292
  if(typeof path !== 'string') {
293
293
  throw new TypeError('path argument is required to res.sendFile');
294
294
  }
295
295
  if(typeof options === 'function') {
296
296
  callback = options;
297
- options = {};
297
+ options = new NullObject();
298
298
  }
299
- if(!options) options = {};
299
+ if(!options) options = new NullObject();
300
300
  let done = callback;
301
301
  if(!done) done = this.req.next;
302
302
  // default options
@@ -362,7 +362,8 @@ module.exports = class Response extends Writable {
362
362
  this.status(403);
363
363
  return done(new Error('Forbidden'));
364
364
  case 'ignore_files':
365
- if(parts.length > 1 && parts[parts.length - 1].startsWith('.')) {
365
+ const len = parts.length;
366
+ if(len > 1 && parts[len - 1].startsWith('.')) {
366
367
  this.status(404);
367
368
  return done(new Error('Not found'));
368
369
  }
@@ -409,7 +410,7 @@ module.exports = class Response extends Writable {
409
410
  }
410
411
 
411
412
  // etag
412
- if(options.etag && !this.headers['etag'] && etagFn) {
413
+ if(options.etag && etagFn && !this.headers['etag']) {
413
414
  this.headers['etag'] = etagFn(stat);
414
415
  }
415
416
  if(!options.etag) {
@@ -443,9 +444,10 @@ module.exports = class Response extends Writable {
443
444
  }
444
445
  if(ranges !== -2 && ranges.length === 1) {
445
446
  this.status(206);
446
- this.headers['content-range'] = `bytes ${ranges[0].start}-${ranges[0].end}/${stat.size}`;
447
- offset = ranges[0].start;
448
- 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;
449
451
  ranged = true;
450
452
  }
451
453
  }
@@ -488,16 +490,16 @@ module.exports = class Response extends Writable {
488
490
  download(path, filename, options, callback) {
489
491
  let done = callback;
490
492
  let name = filename;
491
- let opts = options || {};
493
+ let opts = options || new NullObject();
492
494
 
493
495
  // support function as second or third arg
494
496
  if (typeof filename === 'function') {
495
497
  done = filename;
496
498
  name = null;
497
- opts = {};
499
+ opts = new NullObject();
498
500
  } else if (typeof options === 'function') {
499
501
  done = options;
500
- opts = {};
502
+ opts = new NullObject();
501
503
  }
502
504
 
503
505
  // support optional filename, where options may be in it's place
@@ -518,7 +520,7 @@ module.exports = class Response extends Writable {
518
520
  }
519
521
  set(field, value) {
520
522
  if(this.headersSent) {
521
- throw new Error('Can\'t write headers: Response was already sent');
523
+ throw new Error('Cannot set headers after they are sent to the client');
522
524
  }
523
525
  if(typeof field === 'object') {
524
526
  for(const header in field) {
@@ -537,18 +539,12 @@ module.exports = class Response extends Writable {
537
539
  }
538
540
  return this;
539
541
  }
540
- header(field, value) {
541
- return this.set(field, value);
542
- }
543
- setHeader(field, value) {
544
- return this.set(field, value);
545
- }
542
+ header = this.set;
543
+ setHeader = this.set;
546
544
  get(field) {
547
545
  return this.headers[field.toLowerCase()];
548
546
  }
549
- getHeader(field) {
550
- return this.get(field);
551
- }
547
+ getHeader = this.get;
552
548
  removeHeader(field) {
553
549
  delete this.headers[field.toLowerCase()];
554
550
  return this;
@@ -577,10 +573,10 @@ module.exports = class Response extends Writable {
577
573
  render(view, options, callback) {
578
574
  if(typeof options === 'function') {
579
575
  callback = options;
580
- options = {};
576
+ options = new NullObject();
581
577
  }
582
578
  if(!options) {
583
- options = {};
579
+ options = new NullObject();
584
580
  } else {
585
581
  options = Object.assign({}, options);
586
582
  }
@@ -594,7 +590,7 @@ module.exports = class Response extends Writable {
594
590
  }
595
591
  cookie(name, value, options) {
596
592
  if(!options) {
597
- options = {};
593
+ options = new NullObject();
598
594
  }
599
595
  let val = typeof value === 'object' ? "j:"+JSON.stringify(value) : String(value);
600
596
  if(options.maxAge != null) {
@@ -707,14 +703,10 @@ module.exports = class Response extends Writable {
707
703
  let ct = type.indexOf('/') === -1
708
704
  ? (mime.contentType(type) || 'application/octet-stream')
709
705
  : type;
710
- if(ct.startsWith('text/') || ct === 'application/json' || ct === 'application/javascript') {
711
- ct += '; charset=UTF-8';
712
- }
706
+
713
707
  return this.set('content-type', ct);
714
708
  }
715
- contentType(type) {
716
- return this.type(type);
717
- }
709
+ contentType = this.type;
718
710
 
719
711
  vary(field) {
720
712
  vary(this, field);
package/src/router.js CHANGED
@@ -18,6 +18,7 @@ const { patternToRegex, needsConversionToRegex, deprecated, findIndexStartingFro
18
18
  const Response = require("./response.js");
19
19
  const Request = require("./request.js");
20
20
  const { EventEmitter } = require("tseep");
21
+ const { NullObject } = require("./utils.js");
21
22
 
22
23
  let routeKey = 0;
23
24
 
@@ -30,6 +31,8 @@ const methods = [
30
31
  ];
31
32
  const supportedUwsMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE'];
32
33
 
34
+ const regExParam = /:(\w+)/g;
35
+
33
36
  module.exports = class Router extends EventEmitter {
34
37
  constructor(settings = {}) {
35
38
  super();
@@ -263,12 +266,12 @@ module.exports = class Router extends EventEmitter {
263
266
  method = 'del';
264
267
  }
265
268
  if(!route.optimizedRouter && route.path.includes(":")) {
266
- route.optimizedParams = route.path.match(/:(\w+)/g).map(p => p.slice(1));
269
+ route.optimizedParams = route.path.match(regExParam).map(p => p.slice(1));
267
270
  }
268
271
  const fn = async (res, req) => {
269
272
  const { request, response } = this.handleRequest(res, req);
270
273
  if(route.optimizedParams) {
271
- request.optimizedParams = {};
274
+ request.optimizedParams = new NullObject();
272
275
  for(let i = 0; i < route.optimizedParams.length; i++) {
273
276
  request.optimizedParams[route.optimizedParams[i]] = req.getParameter(i);
274
277
  }
@@ -281,7 +284,7 @@ module.exports = class Router extends EventEmitter {
281
284
  }
282
285
  };
283
286
  route.optimizedPath = optimizedPath;
284
- let replacedPath = route.path.replace(/:(\w+)/g, ':x');
287
+ let replacedPath = route.path.replace(regExParam, ':x');
285
288
  this.uwsApp[method](replacedPath, fn);
286
289
  if(!this.get('strict routing') && route.path[route.path.length - 1] !== '/') {
287
290
  this.uwsApp[method](replacedPath + '/', fn);
@@ -319,7 +322,7 @@ module.exports = class Router extends EventEmitter {
319
322
 
320
323
  _extractParams(pattern, path) {
321
324
  let match = pattern.exec(path);
322
- const obj = match?.groups ?? {};
325
+ const obj = match?.groups ?? new NullObject();
323
326
  for(let i = 1; i < match.length; i++) {
324
327
  obj[i - 1] = match[i];
325
328
  }
@@ -342,7 +345,7 @@ module.exports = class Router extends EventEmitter {
342
345
  }
343
346
  }
344
347
  } else {
345
- req.params = {};
348
+ req.params = new NullObject();
346
349
  if(req._paramStack.length > 0) {
347
350
  for(let params of req._paramStack) {
348
351
  req.params = {...params, ...req.params};
@@ -406,33 +409,33 @@ module.exports = class Router extends EventEmitter {
406
409
  }
407
410
 
408
411
  async _routeRequest(req, res, startIndex = 0, routes = this._routes, skipCheck = false, skipUntil) {
409
- return new Promise(async (resolve) => {
410
- let routeIndex = skipCheck ? startIndex : findIndexStartingFrom(routes, r => (r.all || r.method === req.method || (r.gettable && req.method === 'HEAD')) && this._pathMatches(r, req), startIndex);
411
- const route = routes[routeIndex];
412
- if(!route) {
413
- if(!skipCheck) {
414
- // on normal unoptimized routes, if theres no match then there is no route
415
- return resolve(false);
416
- }
417
- // on optimized routes, there can be more routes, so we have to use unoptimized routing and skip until we find route we stopped at
418
- 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;
419
418
  }
420
- let callbackindex = 0;
421
- const continueRoute = await this._preprocessRequest(req, res, route);
422
- if(route.use) {
423
- const strictRouting = this.get('strict routing');
424
- req._stack.push(route.path);
425
- req._opPath = req.path.replace(this.getFullMountpath(req), '');
426
- if(strictRouting) {
427
- if(req.endsWithSlash && req.path !== '/') {
428
- req._opPath += '/';
429
- }
430
- } else if(req.endsWithSlash && req.path !== '/') {
431
- 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 += '/';
432
431
  }
433
- req.url = req._opPath + req.urlQuery;
434
- if(req.url === '') req.url = '/';
432
+ } else if(req.endsWithSlash && req.path !== '/') {
433
+ req._opPath = req._opPath.slice(0, -1);
435
434
  }
435
+ req.url = req._opPath + req.urlQuery;
436
+ if(req.url === '') req.url = '/';
437
+ }
438
+ return new Promise((resolve) => {
436
439
  const next = async (thingamabob) => {
437
440
  if(thingamabob) {
438
441
  if(thingamabob === 'route' || thingamabob === 'skipPop') {
@@ -533,7 +536,7 @@ module.exports = class Router extends EventEmitter {
533
536
  }
534
537
 
535
538
  route(path) {
536
- let fns = {};
539
+ let fns = new NullObject();
537
540
  for(let method of methods) {
538
541
  fns[method] = (...callbacks) => {
539
542
  return this.createRoute(method.toUpperCase(), path, fns, ...callbacks);
@@ -564,4 +567,4 @@ module.exports = class Router extends EventEmitter {
564
567
  `</body>\n` +
565
568
  `</html>\n`);
566
569
  }
567
- }
570
+ }
package/src/types.d.ts CHANGED
@@ -1,4 +1,49 @@
1
- declare module 'ultimate-express' {
2
- import express from '@types/express';
3
- export = express;
4
- }
1
+ declare module "ultimate-express" {
2
+ import e from "@types/express";
3
+ import { AppOptions } from "uWebSockets.js";
4
+
5
+ type Settings = {
6
+ uwsOptions?: AppOptions;
7
+ threads?: number;
8
+ };
9
+
10
+ namespace express {
11
+ export import json = e.json;
12
+ export import raw = e.raw;
13
+ export import text = e.text;
14
+
15
+ // export import application = e.application;
16
+ export import request = e.request;
17
+ export import response = e.response;
18
+
19
+ export import static = e.static;
20
+ // export import query = e.query;
21
+
22
+ export import urlencoded = e.urlencoded;
23
+
24
+ export import RouterOptions = e.RouterOptions;
25
+ export import Application = e.Application;
26
+ export import CookieOptions = e.CookieOptions;
27
+ export import Errback = e.Errback;
28
+ export import ErrorRequestHandler = e.ErrorRequestHandler;
29
+ export import Express = e.Express;
30
+ export import Handler = e.Handler;
31
+ export import IRoute = e.IRoute;
32
+ export import IRouter = e.IRouter;
33
+ export import IRouterHandler = e.IRouterHandler;
34
+ export import IRouterMatcher = e.IRouterMatcher;
35
+ export import MediaType = e.MediaType;
36
+ export import NextFunction = e.NextFunction;
37
+ export import Locals = e.Locals;
38
+ export import Request = e.Request;
39
+ export import RequestHandler = e.RequestHandler;
40
+ export import RequestParamHandler = e.RequestParamHandler;
41
+ export import Response = e.Response;
42
+ export import Router = e.Router;
43
+ export import Send = e.Send;
44
+ }
45
+
46
+ function express(settings?: Settings): e.Express;
47
+
48
+ export = express;
49
+ }
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;
@@ -315,6 +320,10 @@ function isRangeFresh(req, res) {
315
320
  return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
316
321
  }
317
322
 
323
+ // fast null object
324
+ const NullObject = function() {};
325
+ NullObject.prototype = Object.create(null);
326
+
318
327
  module.exports = {
319
328
  removeDuplicateSlashes,
320
329
  patternToRegex,
@@ -326,6 +335,7 @@ module.exports = {
326
335
  compileTrust,
327
336
  deprecated,
328
337
  UP_PATH_REGEXP,
338
+ NullObject,
329
339
  decode,
330
340
  containsDotFile,
331
341
  parseTokenList,
@@ -336,4 +346,4 @@ module.exports = {
336
346
  findIndexStartingFrom,
337
347
  fastQueryParse,
338
348
  canBeOptimized
339
- };
349
+ };
package/src/view.js CHANGED
@@ -16,11 +16,12 @@ limitations under the License.
16
16
 
17
17
  const path = require("path");
18
18
  const fs = require("fs");
19
+ const { NullObject } = require("./utils.js");
19
20
 
20
21
  module.exports = class View {
21
22
  constructor(name, options) {
22
23
  this.name = name;
23
- this.options = options ? Object.assign({}, options) : {};
24
+ this.options = options ? Object.assign({}, options) : new NullObject();
24
25
  this.defaultEngine = options.defaultEngine;
25
26
  this.ext = path.extname(name);
26
27
  this.root = options.root;