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 +3 -3
- package/package.json +5 -1
- package/src/adopt.js +4 -1
- package/src/application.js +11 -2
- package/src/cli.js +1 -0
- package/src/compression.js +40 -3
- package/src/declarative.js +66 -25
- package/src/lazy-readable.js +15 -15
- package/src/lazy-writable.js +15 -15
- package/src/middlewares.js +24 -11
- package/src/nest.js +2 -0
- package/src/optimizer.js +65 -7
- package/src/options.d.ts +1 -1
- package/src/request.js +22 -16
- package/src/response-utils.js +41 -4
- package/src/response.js +102 -62
- package/src/route.js +5 -3
- package/src/router-utils.js +40 -5
- package/src/router.js +23 -9
- package/src/server-shape.js +3 -3
- package/src/server-timing.js +21 -0
- package/src/testing.js +2 -1
- package/src/usage.js +40 -3
- package/src/utils.js +4 -1
- package/src/verify.js +2 -0
- package/src/view.js +1 -0
- package/src/websocket.js +2 -1
- package/src/work.js +1 -0
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
|
|
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.
|
|
28
|
-
7x to
|
|
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.
|
|
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
|
-
/**
|
|
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" },
|
package/src/application.js
CHANGED
|
@@ -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("/*", (
|
|
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()",
|
package/src/compression.js
CHANGED
|
@@ -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 {
|
|
233
|
-
*
|
|
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 &&
|
|
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);
|
package/src/declarative.js
CHANGED
|
@@ -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 {
|
|
293
|
-
* @param {
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
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 {
|
|
554
|
-
* @property {
|
|
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
|
-
|
|
568
|
-
|
|
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
|
-
|
|
578
|
-
|
|
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
|
-
|
|
591
|
-
|
|
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.
|
|
702
|
-
params.
|
|
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
|
package/src/lazy-readable.js
CHANGED
|
@@ -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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
package/src/lazy-writable.js
CHANGED
|
@@ -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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
package/src/middlewares.js
CHANGED
|
@@ -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 (
|
|
803
|
-
options.limit =
|
|
802
|
+
if (options.limit === undefined || options.limit === null) {
|
|
803
|
+
options.limit = 100 * 1024;
|
|
804
804
|
} else if (typeof options.limit !== "number") {
|
|
805
|
-
//
|
|
806
|
-
|
|
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
|
-
|
|
871
|
-
|
|
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 &&
|
|
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:
|
|
1109
|
-
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}
|