fulmine.js 5.21.2 → 5.21.4

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
@@ -1,6 +1,6 @@
1
1
  <img src="./assets/logo-mark.svg" alt="" width="88" align="right">
2
2
 
3
- # Fulmine.js: the drop-in Express 5 replacement, up to 20x faster
3
+ # Fulmine.js: the drop-in Express 5 replacement, up to 22x faster
4
4
 
5
5
  **Fulmine** (lightning in Italian ⚡) is an Express 5 compatible web framework for Node.js, built on
6
6
  [µWebSockets.js](https://github.com/uNetworking/uWebSockets.js) instead of `node:http`. Same API, same
@@ -24,8 +24,8 @@ const express = require("fulmine.js"); // instead of require("express")
24
24
 
25
25
  ## Why Fulmine
26
26
 
27
- - **Faster than Express, measured.** 1.3x to 4.9x on plain routing, 2x to 5x on a request with a body,
28
- 7x to 20x on a large route table. Routes are matched in C++ by µWS's own router,
27
+ - **Faster than Express, measured.** 1.2x to 4.5x on plain routing, 1.7x to 5x on a request with a body,
28
+ 7x to 22x on a large route table. Routes are matched in C++ by µWS's own router,
29
29
  and a simple enough handler is answered without running any JavaScript at all.
30
30
  - **Zero rewrite.** `helmet`, `cors`, `passport`, `morgan`, `multer`, `express-session` and the rest of
31
31
  the Express ecosystem keep working. Not "mostly": every test runs against real Express first and the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fulmine.js",
3
- "version": "5.21.2",
3
+ "version": "5.21.4",
4
4
  "description": "Drop-in Express 5 replacement on uWebSockets.js, up to 20x faster. Same API, your middleware and framework keep working.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "scripts": {
22
22
  "test": "node tests/index.js",
23
+ "gates": "node tools/gates.js",
23
24
  "test:unit": "node --expose-gc --test --test-timeout=120000 \"tests/unit/*.test.js\"",
24
25
  "test:types": "tsd --files tests/types/*.test-d.ts",
25
26
  "test:express": "node tools/express-suite.js",
@@ -137,10 +138,13 @@
137
138
  "@release-it/conventional-changelog": "^12.0.0",
138
139
  "@types/accepts": "^1.3.7",
139
140
  "@types/bytes": "^3.1.5",
141
+ "@types/compressible": "^2.0.3",
140
142
  "@types/content-disposition": "^0.5.9",
143
+ "@types/cookie": "^0.6.0",
141
144
  "@types/cookie-signature": "^1.1.2",
142
145
  "@types/encodeurl": "^1.0.3",
143
146
  "@types/etag": "^1.8.4",
147
+ "@types/fast-decode-uri-component": "^1.0.0",
144
148
  "@types/fresh": "^0.5.3",
145
149
  "@types/mime-types": "^3.0.1",
146
150
  "@types/ms": "^2.1.0",
package/src/adopt.js CHANGED
@@ -36,7 +36,10 @@ const UWS_SPEC = /** @type {string} */ (require("../package.json").dependencies[
36
36
  /** The pnpm override that drops this package's own copy, so the project's direct one is the only one. */
37
37
  const UWS_OVERRIDE = `${SELF}>${UWS}`;
38
38
 
39
- /** Where each manager keeps its substitutions, and what to call it when telling someone. */
39
+ /**
40
+ * Where each manager keeps its substitutions, and what to call it when telling someone.
41
+ * @type {Record<string, {keys: string[], reinstall: string}>}
42
+ */
40
43
  const MANAGERS = {
41
44
  npm: { keys: ["overrides"], reinstall: "npm install" },
42
45
  pnpm: { keys: ["pnpm", "overrides"], reinstall: "pnpm install" },
@@ -41,6 +41,9 @@ const { registerWebSocketRoutes } = require("./websocket.js");
41
41
  const { addServerMembers } = require("./server-shape.js");
42
42
  const { workerCount, forkWorkers, isSupervising, becomeSupervisor } = require("./cluster.js");
43
43
 
44
+ /** @typedef {import("uWebSockets.js").HttpRequest} UwsRequest */
45
+ /** @typedef {import("uWebSockets.js").HttpResponse} UwsResponse */
46
+
44
47
  const cpuCount = os.cpus().length;
45
48
 
46
49
  // marks a "trust proxy" the application never set, under express's key, so a sub-app may inherit
@@ -95,12 +98,14 @@ class Application extends Router {
95
98
  /**
96
99
  * A mounted app's settings chain onto its parent's, as in express.
97
100
  *
101
+ * @override
98
102
  * @type {boolean}
99
103
  */
100
104
  _inheritsSettings = true;
101
105
 
102
106
  /**
103
107
  * See Router#_isApplication.
108
+ * @override
104
109
  * @type {boolean}
105
110
  */
106
111
  _isApplication = true;
@@ -141,7 +146,7 @@ class Application extends Router {
141
146
  /** @type {Response} */
142
147
  response;
143
148
 
144
- /** @type {boolean} */
149
+ /** @override @type {boolean} */
145
150
  listenCalled = false;
146
151
 
147
152
  /** @type {FSWorker[]} */
@@ -487,6 +492,7 @@ class Application extends Router {
487
492
  * @param {import("./router-utils.js").NativePreset} [preset] see nativePreset
488
493
  * @param {import("./router-utils.js").SkipHolder} [skipHolder] forwarded whole, dropping it
489
494
  * silently turned every skip off
495
+ * @override
490
496
  * @returns {Request} the request, with the response as request.res
491
497
  */
492
498
  handleRequest(res, req, preset, skipHolder) {
@@ -509,6 +515,7 @@ class Application extends Router {
509
515
  * The µWS app, for what µWS offers that this does not (socket.io attaches to it). Made on
510
516
  * first ask, so an app served through node's http never loads the binary, see src/uws.js.
511
517
  *
518
+ * @override
512
519
  * @returns {any}
513
520
  */
514
521
  get uwsApp() {
@@ -521,7 +528,9 @@ class Application extends Router {
521
528
 
522
529
  /** The catch-all uWS handler, for every request no native route took. */
523
530
  _createRequestHandler() {
524
- this.uwsApp.any("/*", (res, req) => this._serveGeneric(res, req));
531
+ this.uwsApp.any("/*", (/** @type {UwsResponse} */ res, /** @type {UwsRequest} */ req) =>
532
+ this._serveGeneric(res, req)
533
+ );
525
534
  }
526
535
 
527
536
  /**
package/src/cli.js CHANGED
@@ -41,6 +41,7 @@ const TO = "fulmine.js";
41
41
 
42
42
  // modules this has a faster version of, reported and not rewritten: the replacement lives on the
43
43
  // express import, which may not be in scope where these are required
44
+ /** @type {Record<string, string>} */
44
45
  const BUILT_IN_INSTEAD = {
45
46
  compression: "express.compression(), which takes the same options",
46
47
  "serve-static": "express.static()",
@@ -20,6 +20,12 @@ limitations under the License.
20
20
  * What res.on was given, parked until there is a compressor to hang it on.
21
21
  * @typedef {Parameters<import("stream").Writable["on"]>} OnArgs
22
22
  */
23
+ /**
24
+ * What the hooks below take: writeHead's arguments in node's two shapes, see applyWriteHead, and
25
+ * the callback write() and end() may carry in place of the encoding.
26
+ * @typedef {Parameters<Response["writeHead"]>} WriteHeadArgs
27
+ * @typedef {(err?: Error|null) => void} WriteCallback
28
+ */
23
29
 
24
30
  // express.compression(): the compression module's options, defaults and decision order, with three
25
31
  // differences: a whole body (res.send, res.json) is compressed in one call and goes out with a
@@ -229,8 +235,10 @@ function toBuffer(chunk, encoding) {
229
235
  * compressed at all. The default says yes to any compressible content type.
230
236
  * @param {string} [options.enforceEncoding] what to use when the request carries no
231
237
  * Accept-Encoding at all. Default "identity", which is to say nothing is compressed.
232
- * @param {object} [options.brotli] brotli options, `params` included. The default quality is 4.
233
- * @param {object} [options.zstd] zstd options, `params` included, node's own defaults otherwise.
238
+ * @param {import("zlib").BrotliOptions} [options.brotli] brotli options, `params` included. The
239
+ * default quality is 4.
240
+ * @param {import("zlib").ZstdOptions} [options.zstd] zstd options, `params` included, node's own
241
+ * defaults otherwise.
234
242
  * @param {string[]} [options.encodings] the encodings this middleware may answer with, out of
235
243
  * "br", "zstd", "gzip" and "deflate". What is not named is never used, however the client ranks
236
244
  * it. An uncompressed answer is always on offer, and enforceEncoding is outside this list. This
@@ -251,7 +259,7 @@ function compression(options) {
251
259
  const brotliOptions = { ...opts.brotli };
252
260
  brotliOptions.params = {
253
261
  [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
254
- ...(opts.brotli && /** @type {import("zlib").BrotliOptions} */ (opts.brotli).params)
262
+ ...(opts.brotli && opts.brotli.params)
255
263
  };
256
264
  // zstd at node's default level is already in the band this middleware wants
257
265
  const zstdOptions = { ...opts.zstd };
@@ -380,11 +388,21 @@ function compression(options) {
380
388
  }
381
389
  };
382
390
  // at writeHead too, its headers applied first as on-headers orders it
391
+ /**
392
+ * @param {WriteHeadArgs[0]} statusCode
393
+ * @param {WriteHeadArgs[1]} [statusMessage]
394
+ * @param {WriteHeadArgs[2]} [headers]
395
+ */
383
396
  res.writeHead = function writeHead(statusCode, statusMessage, headers) {
384
397
  const reason = applyWriteHead(this, statusMessage, headers);
385
398
  vary();
386
399
  return _plainWriteHead.call(this, statusCode, reason);
387
400
  };
401
+ /**
402
+ * @param {any} [chunk]
403
+ * @param {BufferEncoding|WriteCallback} [encoding]
404
+ * @param {WriteCallback} [callback]
405
+ */
388
406
  res.end = function end(chunk, encoding, callback) {
389
407
  vary();
390
408
  return _plainEnd.call(this, chunk, encoding, callback);
@@ -492,6 +510,11 @@ function compression(options) {
492
510
  }
493
511
 
494
512
  // decided at writeHead too, its headers applied first as on-headers orders it
513
+ /**
514
+ * @param {WriteHeadArgs[0]} statusCode
515
+ * @param {WriteHeadArgs[1]} [statusMessage]
516
+ * @param {WriteHeadArgs[2]} [headers]
517
+ */
495
518
  res.writeHead = function writeHead(statusCode, statusMessage, headers) {
496
519
  const reason = applyWriteHead(this, statusMessage, headers);
497
520
  if (!decided) {
@@ -500,6 +523,11 @@ function compression(options) {
500
523
  return _writeHead.call(this, statusCode, reason);
501
524
  };
502
525
 
526
+ /**
527
+ * @param {any} chunk
528
+ * @param {BufferEncoding|WriteCallback} [encoding]
529
+ * @param {WriteCallback} [callback]
530
+ */
503
531
  res.write = function write(chunk, encoding, callback) {
504
532
  if (typeof encoding === "function") {
505
533
  callback = encoding;
@@ -520,6 +548,11 @@ function compression(options) {
520
548
  return _write.call(this, chunk, encoding, callback);
521
549
  };
522
550
 
551
+ /**
552
+ * @param {any} [chunk]
553
+ * @param {BufferEncoding|WriteCallback} [encoding]
554
+ * @param {WriteCallback} [callback]
555
+ */
523
556
  res.end = function end(chunk, encoding, callback) {
524
557
  if (typeof chunk === "function") {
525
558
  callback = chunk;
@@ -575,6 +608,10 @@ function compression(options) {
575
608
  return _end.call(this, chunk, callback);
576
609
  };
577
610
 
611
+ /**
612
+ * @param {OnArgs[0]} type
613
+ * @param {OnArgs[1]} listener
614
+ */
578
615
  res.on = function on(type, listener) {
579
616
  if (!listeners || type !== "drain") {
580
617
  return _on.call(this, type, listener);
@@ -144,6 +144,7 @@ function literalValue(node) {
144
144
  return literalValue(element);
145
145
  });
146
146
  case "ObjectExpression": {
147
+ /** @type {Record<string, unknown>} */
147
148
  const out = {};
148
149
  for (const property of node.properties) {
149
150
  if (property.type !== "Property" || property.computed || property.kind !== "init") {
@@ -236,6 +237,11 @@ function readStatusAndHeaders(callExprs, headers) {
236
237
 
237
238
  for (let [header, value] of pairs) {
238
239
  const name = String(header).toLowerCase();
240
+ // a chunked framing the handler asked for: a compiled response is one end() with a
241
+ // length, the ordinary path frames it through write(), see Response#writeHeaders
242
+ if (name === "transfer-encoding") {
243
+ return null;
244
+ }
239
245
  // res.set resolves a content-type through the mime database, setHeader does not
240
246
  if (call.obj.propertyName !== "setHeader" && name === "content-type") {
241
247
  const resolved = contentTypeSet(String(value));
@@ -266,6 +272,9 @@ function readStatusAndHeaders(callExprs, headers) {
266
272
  if (call.arguments[0].type !== "Literal" || call.arguments[1].type !== "Literal") {
267
273
  return null;
268
274
  }
275
+ if (String(call.arguments[0].value).toLowerCase() === "transfer-encoding") {
276
+ return null;
277
+ }
269
278
  if (!headerIsWritable(String(call.arguments[0].value), String(call.arguments[1].value))) {
270
279
  return null;
271
280
  }
@@ -289,8 +298,8 @@ function readStatusAndHeaders(callExprs, headers) {
289
298
  * @param {[string, string][]} headers written to
290
299
  * @param {any[]} body written to, a literal's value kept as it is
291
300
  * @param {Application|Router} app for the json settings
292
- * @param {string[]} queries names bound by a destructured req.query
293
- * @param {string[]} params names bound by a destructured req.params
301
+ * @param {Binding[]} queries what a destructured req.query bound
302
+ * @param {Binding[]} params what a destructured req.params bound
294
303
  * @returns {{sendUsed: boolean, bodyFromSend: boolean}|null}
295
304
  */
296
305
  function readBody(callExprs, headers, body, app, queries, params) {
@@ -383,10 +392,14 @@ function readBody(callExprs, headers, body, app, queries, params) {
383
392
  }
384
393
  body.push({ type, value: expr.property.name });
385
394
  } else if (expr.type === "Identifier") {
386
- if (queries.includes(expr.name)) {
387
- body.push({ type: "query", value: expr.name });
388
- } else if (params.includes(expr.name)) {
389
- body.push({ type: "params", value: expr.name });
395
+ // the key, not the local name: a minifier renames the local and uWS
396
+ // would be asked for a parameter that does not exist
397
+ const query = queries.find((binding) => binding.local === expr.name);
398
+ const param = params.find((binding) => binding.local === expr.name);
399
+ if (query) {
400
+ body.push({ type: "query", value: query.key });
401
+ } else if (param) {
402
+ body.push({ type: "params", value: param.key });
390
403
  } else {
391
404
  return null;
392
405
  }
@@ -525,7 +538,8 @@ function readHandler(cb) {
525
538
  }
526
539
  }
527
540
 
528
- const args = fn.params.map((param) => param.name);
541
+ // undefined for a destructured one, which readParamNames reads by hand
542
+ const args = fn.params.map((/** @type {any} */ param) => param.name);
529
543
 
530
544
  if (args.length < 2) {
531
545
  return null;
@@ -550,9 +564,40 @@ function readHandler(cb) {
550
564
  * @property {string} res
551
565
  * @property {string|undefined} queryName
552
566
  * @property {string|undefined} paramsName
553
- * @property {string[]} queries
554
- * @property {string[]} params
567
+ * @property {Binding[]} queries
568
+ * @property {Binding[]} params
569
+ */
570
+
571
+ /**
572
+ * One name a destructured req.query or req.params bound, with the key it stands for: the same
573
+ * word in `{ id }`, two after a minifier has been through (`{ id: c }`).
574
+ * @typedef {object} Binding
575
+ * @property {string} local
576
+ * @property {string} key
577
+ */
578
+
579
+ /**
580
+ * The bindings of one destructuring pattern, `{ id, name: n }`. False for a shape this cannot
581
+ * read: a computed key, a rest element, a default value or a nested pattern.
582
+ *
583
+ * @param {any} pattern an ObjectPattern node
584
+ * @param {Binding[]} into
585
+ * @returns {boolean}
555
586
  */
587
+ function readBindings(pattern, into) {
588
+ for (const prop of pattern.properties) {
589
+ if (
590
+ prop.type !== "Property" ||
591
+ prop.computed ||
592
+ prop.key.type !== "Identifier" ||
593
+ prop.value.type !== "Identifier"
594
+ ) {
595
+ return false;
596
+ }
597
+ into.push({ local: prop.value.name, key: prop.key.name });
598
+ }
599
+ return true;
600
+ }
556
601
 
557
602
  /**
558
603
  * The names a destructured `req` binds for query and params; null for a pattern this cannot read.
@@ -564,21 +609,20 @@ function readHandler(cb) {
564
609
  function readParamNames(fn, args) {
565
610
  const [req, res] = args;
566
611
  let queryName, paramsName;
567
- const queries = [],
568
- params = [];
612
+ /** @type {Binding[]} */
613
+ const queries = [];
614
+ /** @type {Binding[]} */
615
+ const params = [];
569
616
 
570
617
  if (fn.params[0].type === "ObjectPattern") {
571
- const query = fn.params[0].properties.find((prop) => prop.key.name === "query");
572
- const param = fn.params[0].properties.find((prop) => prop.key.name === "params");
618
+ const query = fn.params[0].properties.find((/** @type {any} */ prop) => prop.key.name === "query");
619
+ const param = fn.params[0].properties.find((/** @type {any} */ prop) => prop.key.name === "params");
573
620
 
574
621
  if (query?.value?.type === "Identifier") {
575
622
  queryName = query.value.name;
576
623
  } else if (query?.value?.type === "ObjectPattern") {
577
- for (const prop of query.value.properties) {
578
- if (prop.value.type !== "Identifier") {
579
- return null;
580
- }
581
- queries.push(prop.value.name);
624
+ if (!readBindings(query.value, queries)) {
625
+ return null;
582
626
  }
583
627
  } else {
584
628
  return null;
@@ -587,11 +631,8 @@ function readParamNames(fn, args) {
587
631
  if (param?.value?.type === "Identifier") {
588
632
  paramsName = param.value.name;
589
633
  } else if (param?.value?.type === "ObjectPattern") {
590
- for (const prop of param.value.properties) {
591
- if (prop.value.type !== "Identifier") {
592
- return null;
593
- }
594
- params.push(prop.value.name);
634
+ if (!readBindings(param.value, params)) {
635
+ return null;
595
636
  }
596
637
  } else {
597
638
  return null;
@@ -698,8 +739,8 @@ function identifiersAllowed(fn, args, names) {
698
739
  (identifiers[i - 2] === req && identifiers[i - 1] === "query") ||
699
740
  id === queryName ||
700
741
  id === paramsName ||
701
- queries.includes(id) ||
702
- params.includes(id)
742
+ queries.some((binding) => binding.local === id) ||
743
+ params.some((binding) => binding.local === id)
703
744
  );
704
745
  }
705
746
  // A uWS declarative response never enters node, so it is very fast. Only a handler that is simple
@@ -71,21 +71,21 @@ for (const member of [
71
71
  } else if (descriptor.get || descriptor.set) {
72
72
  const innerGet = descriptor.get;
73
73
  const innerSet = descriptor.set;
74
- Object.defineProperty(LazyReadableBase.prototype, member, {
75
- ...descriptor,
76
- get: innerGet
77
- ? /** @this {import("stream").Readable} */ function () {
78
- materialise(this);
79
- return innerGet.call(this);
80
- }
81
- : undefined,
82
- set: innerSet
83
- ? /** @this {import("stream").Readable} @param {unknown} value */ function (value) {
84
- materialise(this);
85
- innerSet.call(this, value);
86
- }
87
- : undefined
88
- });
74
+ // the original's accessor pair, each half wrapped where there is one
75
+ const wrapped = { ...descriptor };
76
+ if (innerGet) {
77
+ wrapped.get = /** @this {import("stream").Readable} */ function () {
78
+ materialise(this);
79
+ return innerGet.call(this);
80
+ };
81
+ }
82
+ if (innerSet) {
83
+ wrapped.set = /** @this {import("stream").Readable} @param {unknown} value */ function (value) {
84
+ materialise(this);
85
+ innerSet.call(this, value);
86
+ };
87
+ }
88
+ Object.defineProperty(LazyReadableBase.prototype, member, wrapped);
89
89
  }
90
90
  }
91
91
 
@@ -65,21 +65,21 @@ for (const member of [
65
65
  } else if (descriptor.get || descriptor.set) {
66
66
  const innerGet = descriptor.get;
67
67
  const innerSet = descriptor.set;
68
- Object.defineProperty(LazyWritableBase.prototype, member, {
69
- ...descriptor,
70
- get: innerGet
71
- ? /** @this {import("stream").Writable} */ function () {
72
- materialiseWritable(this);
73
- return innerGet.call(this);
74
- }
75
- : undefined,
76
- set: innerSet
77
- ? /** @this {import("stream").Writable} @param {unknown} value */ function (value) {
78
- materialiseWritable(this);
79
- innerSet.call(this, value);
80
- }
81
- : undefined
82
- });
68
+ // the original's accessor pair, each half wrapped where there is one
69
+ const wrapped = { ...descriptor };
70
+ if (innerGet) {
71
+ wrapped.get = /** @this {import("stream").Writable} */ function () {
72
+ materialiseWritable(this);
73
+ return innerGet.call(this);
74
+ };
75
+ }
76
+ if (innerSet) {
77
+ wrapped.set = /** @this {import("stream").Writable} @param {unknown} value */ function (value) {
78
+ materialiseWritable(this);
79
+ innerSet.call(this, value);
80
+ };
81
+ }
82
+ Object.defineProperty(LazyWritableBase.prototype, member, wrapped);
83
83
  }
84
84
  }
85
85
 
@@ -720,7 +720,7 @@ function inflateError(inflate, err) {
720
720
  *
721
721
  * @param {string|undefined} rawContentEncoding
722
722
  * @param {any} options the parser's options, read loosely: only inflate is looked at
723
- * @returns {{inflate?: Inflater, error?: HttpError}}
723
+ * @returns {{inflate?: Inflater|undefined, error?: HttpError}}
724
724
  */
725
725
  function encodingFor(rawContentEncoding, options) {
726
726
  if (!options.inflate) {
@@ -799,11 +799,16 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
799
799
  checkOptions(options);
800
800
  }
801
801
  // bytes.parse only on a string: bytes(1024) formats it to "1KB" and no comparison held
802
- if (typeof options.limit === "undefined") {
803
- options.limit = /** @type {number} */ (bytes.parse("100kb"));
802
+ if (options.limit === undefined || options.limit === null) {
803
+ options.limit = 100 * 1024;
804
804
  } else if (typeof options.limit !== "number") {
805
- // null for a size it cannot read, as body-parser passes it along
806
- options.limit = /** @type {number} */ (bytes.parse(options.limit));
805
+ // a size it cannot read is refused here, as body-parser 2.3 does: passed along as
806
+ // null it disabled the limit (CVE-2026-12590)
807
+ const parsed = bytes.parse(options.limit);
808
+ if (parsed === null) {
809
+ throw new TypeError(`option limit "${String(options.limit)}" is invalid`);
810
+ }
811
+ options.limit = parsed;
807
812
  }
808
813
 
809
814
  const limit = /** @type {number} */ (options.limit);
@@ -836,6 +841,11 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
836
841
  /** @type {string[]|null|undefined} the "body methods" setting, read on the first request */
837
842
  let additionalMethods;
838
843
 
844
+ /**
845
+ * @param {Request & {body?: unknown}} req
846
+ * @param {Response} res
847
+ * @param {(err?: unknown) => void} next
848
+ */
839
849
  const parserMiddleware = (req, res, next) => {
840
850
  // the prologue is synchronous, bindContext waits for the read (1.4us of nothing here)
841
851
 
@@ -867,8 +877,11 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
867
877
  }
868
878
 
869
879
  if (options.simpleType) {
870
- const semicolonIndex = type.indexOf(";");
871
- const clearType = semicolonIndex !== -1 ? type.substring(0, semicolonIndex) : type;
880
+ // only a type function lets a request without a content-type past the check above,
881
+ // and simpleType is never set beside one
882
+ const header = /** @type {string} */ (type);
883
+ const semicolonIndex = header.indexOf(";");
884
+ const clearType = semicolonIndex !== -1 ? header.substring(0, semicolonIndex) : header;
872
885
  // the trim and lowercase only when the exact compare fails
873
886
  if (clearType !== options.simpleType && clearType.trim().toLowerCase() !== options.simpleType) {
874
887
  return next();
@@ -879,7 +892,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
879
892
  return next();
880
893
  }
881
894
  } else {
882
- if (!claimsType(type)) {
895
+ if (!claimsType(/** @type {string} */ (type))) {
883
896
  return next();
884
897
  }
885
898
  }
@@ -1102,11 +1115,11 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1102
1115
  }
1103
1116
  }
1104
1117
  // fewer bytes than content-length promised; not when inflating, it counts the compressed ones
1105
- if (!inflate && length !== undefined && !isNaN(length) && totalSize !== Number(length)) {
1118
+ if (!inflate && !Number.isNaN(lengthNumber) && totalSize !== lengthNumber) {
1106
1119
  return next(
1107
1120
  bodyError("request size did not match content length", 400, "request.size.invalid", {
1108
- expected: Number(length),
1109
- length: Number(length),
1121
+ expected: lengthNumber,
1122
+ length: lengthNumber,
1110
1123
  received: totalSize
1111
1124
  })
1112
1125
  );
package/src/nest.js CHANGED
@@ -56,6 +56,7 @@ class FulmineExpressAdapter extends ExpressAdapter {
56
56
  /**
57
57
  * The app is the server. Nest calls this once, from NestApplication's constructor.
58
58
  *
59
+ * @override
59
60
  * @param {import("@nestjs/common").NestApplicationOptions} [options] the options NestFactory.create was given
60
61
  * @returns {void}
61
62
  */
@@ -84,6 +85,7 @@ class FulmineExpressAdapter extends ExpressAdapter {
84
85
  * for them in `app.router.stack`; there is no such array here, so a second call was putting a
85
86
  * second pair in front of every request.
86
87
  *
88
+ * @override
87
89
  * @param {string} [prefix]
88
90
  * @param {boolean} [rawBody]
89
91
  * @returns {void}