fulmine.js 5.19.8 → 5.19.9
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/package.json +1 -1
- package/src/application.js +5 -4
- package/src/cli.js +4 -1
- package/src/compression.js +1 -0
- package/src/declarative.js +15 -3
- package/src/hot-settings.js +7 -0
- package/src/index.js +1 -0
- package/src/middlewares.js +6 -2
- package/src/node-shim.js +34 -10
- package/src/optimizer.js +24 -3
- package/src/request-utils.js +3 -2
- package/src/request.js +8 -15
- package/src/response-utils.js +1 -1
- package/src/response.js +15 -1
- package/src/route.js +12 -6
- package/src/router-utils.js +11 -2
- package/src/router.js +27 -4
- package/src/testing.js +1 -1
- package/src/utils.js +28 -9
- package/src/walk.js +8 -3
- package/src/websocket.js +1 -1
package/package.json
CHANGED
package/src/application.js
CHANGED
|
@@ -47,7 +47,7 @@ const cpuCount = os.cpus().length;
|
|
|
47
47
|
// mounted sub-app knows it may inherit the parent's
|
|
48
48
|
const trustProxyDefaultSymbol = "@@symbol:trust_proxy_default";
|
|
49
49
|
|
|
50
|
-
const workers = [];
|
|
50
|
+
const workers = /** @type {FSWorker[]} */ ([]);
|
|
51
51
|
let taskKey = 0;
|
|
52
52
|
const workerTasks = new NullObject();
|
|
53
53
|
|
|
@@ -133,7 +133,7 @@ class Application extends Router {
|
|
|
133
133
|
becomeSupervisor();
|
|
134
134
|
}
|
|
135
135
|
if (settings.uwsApp) {
|
|
136
|
-
this.uwsApp = settings.uwsApp;
|
|
136
|
+
this.uwsApp = /** @type {import("uWebSockets.js").TemplatedApp} */ (settings.uwsApp);
|
|
137
137
|
} else if (settings.http3) {
|
|
138
138
|
// uWS.H3App exists in the pinned build but its QUIC stack does not: the constructor
|
|
139
139
|
// segfaults on Linux and hangs forever on Windows before serving a single request,
|
|
@@ -401,7 +401,7 @@ class Application extends Router {
|
|
|
401
401
|
if (value != null && (!Array.isArray(value) || value.some((m) => typeof m !== "string"))) {
|
|
402
402
|
throw new TypeError('"etag methods" wants an array of method names, or null for all of them');
|
|
403
403
|
}
|
|
404
|
-
value = value == null ? undefined : value.map((m) => m.toUpperCase());
|
|
404
|
+
value = value == null ? undefined : value.map((/** @type {string} */ m) => m.toUpperCase());
|
|
405
405
|
} else if (key === "etag") {
|
|
406
406
|
// The skips are not taken back here. They used to be, because send consults freshness,
|
|
407
407
|
// but that branch reads if-none-match, if-modified-since and cache-control by name
|
|
@@ -594,7 +594,7 @@ class Application extends Router {
|
|
|
594
594
|
// uWS runs this handler from inside its own listen(), so everything it hands back to the
|
|
595
595
|
// caller is deferred a tick. Express binds synchronously too but reports through events,
|
|
596
596
|
// and node emits both 'listening' and 'error' from a process.nextTick.
|
|
597
|
-
const onListen = (socket) => {
|
|
597
|
+
const onListen = (/** @type {import("uWebSockets.js").us_listen_socket|false} */ socket) => {
|
|
598
598
|
if (!socket) {
|
|
599
599
|
/** @type {NodeJS.ErrnoException} */
|
|
600
600
|
const err = new Error("listen EADDRINUSE: address already in use :::" + port);
|
|
@@ -926,6 +926,7 @@ class Application extends Router {
|
|
|
926
926
|
// Tried once before and reverted the same day, because a callable app broke supertest: `request(app)`
|
|
927
927
|
// reads `typeof app === "function"` and wraps what it finds in http.createServer, and there was
|
|
928
928
|
// nothing underneath that could serve node's IncomingMessage. src/node-shim.js closes that hole.
|
|
929
|
+
/** @param {object} [options] the settings express() takes, see the Application constructor */
|
|
929
930
|
module.exports = function (options) {
|
|
930
931
|
return new Application(options)._asCallable();
|
|
931
932
|
};
|
package/src/cli.js
CHANGED
|
@@ -214,6 +214,7 @@ function findSpecifiersTypeScript(source, fileName, ts, seen) {
|
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
+
/** @param {import("typescript").Node} node */
|
|
217
218
|
const visit = (node) => {
|
|
218
219
|
// import express from "express", import type { Request } from "express", export * from it.
|
|
219
220
|
// A type-only import is rewritten too: the types come from the new package as well.
|
|
@@ -465,7 +466,7 @@ function findEntry(given) {
|
|
|
465
466
|
* owns listen.
|
|
466
467
|
*
|
|
467
468
|
* @param {string} entry
|
|
468
|
-
* @returns {
|
|
469
|
+
* @returns {Application[]} the prototypes to stub, this command's copy first
|
|
469
470
|
*/
|
|
470
471
|
function listenOwners(entry) {
|
|
471
472
|
const builds = new Set([require("./index.js")]);
|
|
@@ -477,6 +478,7 @@ function listenOwners(entry) {
|
|
|
477
478
|
}
|
|
478
479
|
}
|
|
479
480
|
|
|
481
|
+
/** @type {Application[]} */
|
|
480
482
|
const owners = [];
|
|
481
483
|
for (const build of builds) {
|
|
482
484
|
if (typeof build !== "function") {
|
|
@@ -531,6 +533,7 @@ function loadApps(argv, command) {
|
|
|
531
533
|
return null;
|
|
532
534
|
}
|
|
533
535
|
|
|
536
|
+
/** @type {Application[]} */
|
|
534
537
|
const listened = [];
|
|
535
538
|
const real = owners.map((proto) => proto.listen);
|
|
536
539
|
for (const proto of owners) {
|
package/src/compression.js
CHANGED
package/src/declarative.js
CHANGED
|
@@ -26,6 +26,7 @@ const uWSAny = /** @type {any} */ (uWS);
|
|
|
26
26
|
const statuses = require("statuses");
|
|
27
27
|
|
|
28
28
|
/** @typedef {import("./application.js").Application} Application */
|
|
29
|
+
/** @typedef {import("./router.js")} Router */
|
|
29
30
|
|
|
30
31
|
const parser = acorn.Parser;
|
|
31
32
|
|
|
@@ -45,7 +46,11 @@ const allowedResMethods = [
|
|
|
45
46
|
|
|
46
47
|
const allowedIdentifiers = ["query", "params", ...allowedResMethods];
|
|
47
48
|
|
|
48
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* What res.type(x) sets the content type to. A lookup on a literal.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} type
|
|
53
|
+
*/
|
|
49
54
|
const typeValueOf = (type) => (type.indexOf("/") === -1 ? contentTypeFor(type) : type);
|
|
50
55
|
|
|
51
56
|
// what one instruction of a declarative response can carry, since uWS writes its length as a u16
|
|
@@ -307,7 +312,7 @@ function readStatusAndHeaders(callExprs, headers) {
|
|
|
307
312
|
* @param {any[]} callExprs the res calls, in run order, as readStatusAndHeaders takes them
|
|
308
313
|
* @param {[string, string][]} headers the headers read so far, written to
|
|
309
314
|
* @param {any[]} body the body parts, written to; loose because a literal's value is kept as it is
|
|
310
|
-
* @param {Application} app the application, for the json settings
|
|
315
|
+
* @param {Application|Router} app the application or router the route hangs on, for the json settings
|
|
311
316
|
* @param {string[]} queries names bound by a destructured req.query
|
|
312
317
|
* @param {string[]} params names bound by a destructured req.params
|
|
313
318
|
* @returns {{sendUsed: boolean, bodyFromSend: boolean}|null}
|
|
@@ -438,6 +443,7 @@ function readBody(callExprs, headers, body, app, queries, params) {
|
|
|
438
443
|
}
|
|
439
444
|
body.push({ type: arg.object.property.name, value: arg.property.name });
|
|
440
445
|
} else if (arg.type === "BinaryExpression") {
|
|
446
|
+
/** @type {any[]} the parts, in the same loose shape as body */
|
|
441
447
|
const stuff = [];
|
|
442
448
|
/**
|
|
443
449
|
* Reads a chain of string concatenations right to left. Each side must be a literal or a
|
|
@@ -768,6 +774,10 @@ function identifiersAllowed(fn, args, names) {
|
|
|
768
774
|
// - doesnt create variables
|
|
769
775
|
// - only uses req.query and req.params
|
|
770
776
|
// basically, its only simple, static responses
|
|
777
|
+
/**
|
|
778
|
+
* @param {Function} cb the handler
|
|
779
|
+
* @param {Application|Router} app the application or router the route hangs on, for the json settings
|
|
780
|
+
*/
|
|
771
781
|
module.exports = function compileDeclarative(cb, app) {
|
|
772
782
|
try {
|
|
773
783
|
const handler = readHandler(cb);
|
|
@@ -791,8 +801,8 @@ module.exports = function compileDeclarative(cb, app) {
|
|
|
791
801
|
return false;
|
|
792
802
|
}
|
|
793
803
|
|
|
804
|
+
/** @type {[string, string][]} */
|
|
794
805
|
const headers = [];
|
|
795
|
-
const body = [];
|
|
796
806
|
|
|
797
807
|
const status = readStatusAndHeaders(callExprs, headers);
|
|
798
808
|
if (status === null) {
|
|
@@ -800,6 +810,8 @@ module.exports = function compileDeclarative(cb, app) {
|
|
|
800
810
|
}
|
|
801
811
|
const { statusCode, sendStatusUsed } = status;
|
|
802
812
|
|
|
813
|
+
/** @type {any[]} loose because a literal's value is kept as it is, see readBody */
|
|
814
|
+
const body = [];
|
|
803
815
|
const read = readBody(callExprs, headers, body, app, queries, params);
|
|
804
816
|
if (read === null) {
|
|
805
817
|
return false;
|
package/src/hot-settings.js
CHANGED
|
@@ -27,14 +27,21 @@ class HotSettings {
|
|
|
27
27
|
constructor() {
|
|
28
28
|
this.epoch = 0;
|
|
29
29
|
this.xPoweredBy = false;
|
|
30
|
+
/** @type {((body: string|Buffer|import("fs").Stats, encoding?: BufferEncoding) => string)|undefined} */
|
|
30
31
|
this.etagFn = undefined;
|
|
31
32
|
// null means every method, which is express's behaviour and the default
|
|
33
|
+
/** @type {Set<string>|null} */
|
|
32
34
|
this.etagMethods = null;
|
|
35
|
+
/** @type {((query: string|null) => Record<string, any>)|undefined} */
|
|
33
36
|
this.queryParserFn = undefined;
|
|
37
|
+
/** @type {import("./utils.js").TrustFn|undefined} */
|
|
34
38
|
this.trustProxyFn = undefined;
|
|
35
39
|
this.trustProxyProtocol = false;
|
|
40
|
+
/** @type {boolean|undefined} */
|
|
36
41
|
this.jsonEscape = undefined;
|
|
42
|
+
/** @type {any} the "json replacer" setting, as stringify takes it */
|
|
37
43
|
this.jsonReplacer = undefined;
|
|
44
|
+
/** @type {string|number|undefined} */
|
|
38
45
|
this.jsonSpaces = undefined;
|
|
39
46
|
}
|
|
40
47
|
}
|
package/src/index.js
CHANGED
|
@@ -68,6 +68,7 @@ try {
|
|
|
68
68
|
module.exports = /** @type {any} */ (Application);
|
|
69
69
|
|
|
70
70
|
// a router is a function too: it has to be callable to be used as middleware
|
|
71
|
+
/** @param {object} [options] the options express.Router() takes */
|
|
71
72
|
module.exports.Router = function (options) {
|
|
72
73
|
return new Router(options)._asCallable();
|
|
73
74
|
};
|
package/src/middlewares.js
CHANGED
|
@@ -139,6 +139,7 @@ function stripBom(text) {
|
|
|
139
139
|
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/** @type {typeof import("iconv-lite")|undefined} */
|
|
142
143
|
let iconv;
|
|
143
144
|
|
|
144
145
|
/**
|
|
@@ -966,6 +967,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
966
967
|
(contentType) => !!typeis.is(contentType, /** @type {string[]} */ (options.type))
|
|
967
968
|
);
|
|
968
969
|
|
|
970
|
+
/** @type {string[]|null|undefined} the "body methods" setting, read on the first request */
|
|
969
971
|
let additionalMethods;
|
|
970
972
|
|
|
971
973
|
const parserMiddleware = (req, res, next) => {
|
|
@@ -1038,6 +1040,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1038
1040
|
// halves sit on either side of the encoding, which is the order body-parser reads
|
|
1039
1041
|
// them in: the charset this parser accepts at all, then the Content-Encoding, then
|
|
1040
1042
|
// whether iconv knows the charset.
|
|
1043
|
+
/** @type {string|undefined} */
|
|
1041
1044
|
let encoding;
|
|
1042
1045
|
if (charsetPolicy) {
|
|
1043
1046
|
encoding = charsetOf(type) ?? defaultCharset;
|
|
@@ -1129,7 +1132,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1129
1132
|
// not us, and would wait for an end that is never coming
|
|
1130
1133
|
req.complete = true;
|
|
1131
1134
|
req.readable = false;
|
|
1132
|
-
req._res.collectBody(limit, (body) => {
|
|
1135
|
+
req._res.collectBody(limit, (/** @type {ArrayBuffer|null} */ body) => {
|
|
1133
1136
|
if (body === null) {
|
|
1134
1137
|
// over maxSize: uWS refused it natively
|
|
1135
1138
|
return next(
|
|
@@ -1165,6 +1168,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1165
1168
|
// known and we are not inflating, the final size is known up front, so chunks go
|
|
1166
1169
|
// straight into one buffer and the body is copied once. The cap means a client that
|
|
1167
1170
|
// declares a body and never sends it costs no more than one that sends it
|
|
1171
|
+
/** @type {Buffer[]} */
|
|
1168
1172
|
const abs = [];
|
|
1169
1173
|
const declaredLength = inflate ? -1 : Number(length);
|
|
1170
1174
|
let target =
|
|
@@ -1310,7 +1314,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
|
|
|
1310
1314
|
// if we are fast enough (not async), we can do it
|
|
1311
1315
|
// otherwise we need to use a stream since it already started streaming it
|
|
1312
1316
|
if (!req.receivedData) {
|
|
1313
|
-
req._res.onData((ab, isLast) => {
|
|
1317
|
+
req._res.onData((/** @type {ArrayBuffer} */ ab, /** @type {boolean} */ isLast) => {
|
|
1314
1318
|
onData(ab);
|
|
1315
1319
|
if (isLast) {
|
|
1316
1320
|
// this subscription replaced the Readable's own, so the stream will
|
package/src/node-shim.js
CHANGED
|
@@ -39,21 +39,30 @@ function addressToBytes(address) {
|
|
|
39
39
|
if (!address) {
|
|
40
40
|
return new ArrayBuffer(0);
|
|
41
41
|
}
|
|
42
|
-
// node reports an IPv4 client on a dual stack socket as ::ffff:127.0.0.1
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
// node reports an IPv4 client on a dual stack socket as ::ffff:127.0.0.1 and one on an IPv4
|
|
43
|
+
// socket as 127.0.0.1, which is the difference req.ip reads back out of the width: uWS hands
|
|
44
|
+
// the mapped peer over as the sixteen bytes, the plain one as four. Keeping node's own form
|
|
45
|
+
// rather than flattening both to four is what makes the shim answer what node answers,
|
|
46
|
+
// whichever socket accepted the connection
|
|
47
|
+
const mapped = address.startsWith("::ffff:") && address.includes(".");
|
|
48
|
+
const dotted = mapped ? address.slice(7) : address;
|
|
49
|
+
if (dotted.includes(".")) {
|
|
50
|
+
const parts = dotted.split(".");
|
|
47
51
|
if (parts.length !== 4) {
|
|
48
52
|
return new ArrayBuffer(0);
|
|
49
53
|
}
|
|
50
|
-
const bytes = new Uint8Array(4);
|
|
54
|
+
const bytes = new Uint8Array(mapped ? 16 : 4);
|
|
55
|
+
const offset = mapped ? 12 : 0;
|
|
56
|
+
if (mapped) {
|
|
57
|
+
bytes[10] = 0xff;
|
|
58
|
+
bytes[11] = 0xff;
|
|
59
|
+
}
|
|
51
60
|
for (let i = 0; i < 4; i++) {
|
|
52
61
|
const value = Number(parts[i]);
|
|
53
62
|
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
54
63
|
return new ArrayBuffer(0);
|
|
55
64
|
}
|
|
56
|
-
bytes[i] = value;
|
|
65
|
+
bytes[offset + i] = value;
|
|
57
66
|
}
|
|
58
67
|
return bytes.buffer;
|
|
59
68
|
}
|
|
@@ -80,13 +89,19 @@ function addressToBytes(address) {
|
|
|
80
89
|
return view.buffer;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* A chunk as the ArrayBuffer uWS deals in, copied rather than viewed so nothing aliases node's.
|
|
94
|
+
*
|
|
95
|
+
* @param {ArrayBuffer|Buffer|string} chunk
|
|
96
|
+
* @returns {ArrayBuffer}
|
|
97
|
+
*/
|
|
84
98
|
function toArrayBuffer(chunk) {
|
|
85
99
|
if (chunk instanceof ArrayBuffer) {
|
|
86
100
|
return chunk;
|
|
87
101
|
}
|
|
88
102
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
89
|
-
|
|
103
|
+
// node types the store as possibly shared, but a Buffer it made or was given here never is
|
|
104
|
+
return /** @type {ArrayBuffer} */ (buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
/**
|
|
@@ -175,6 +190,7 @@ class NodeHttpResponse {
|
|
|
175
190
|
// how much of the body has gone out, which is what uWS reports through getWriteOffset and
|
|
176
191
|
// hands back to an onWritable callback
|
|
177
192
|
this._offset = 0;
|
|
193
|
+
/** @type {((offset: number) => boolean)|null} */
|
|
178
194
|
this._onWritable = null;
|
|
179
195
|
this._aborted = false;
|
|
180
196
|
// the current body handler: uWS keeps one and a second onData replaces it, so this does too
|
|
@@ -195,6 +211,8 @@ class NodeHttpResponse {
|
|
|
195
211
|
* uWS batches everything written inside this into one syscall. node has no equivalent that
|
|
196
212
|
* means the same thing, and its own cork would hold the write until the callback returned
|
|
197
213
|
* without changing what is sent, so this only runs it.
|
|
214
|
+
*
|
|
215
|
+
* @param {() => void} cb
|
|
198
216
|
*/
|
|
199
217
|
cork(cb) {
|
|
200
218
|
cb();
|
|
@@ -300,13 +318,19 @@ class NodeHttpResponse {
|
|
|
300
318
|
* Called when there is room to write again. uWS asks the handler to answer whether it managed
|
|
301
319
|
* to write everything, and calls it again if not; node's drain says nothing, so the handler is
|
|
302
320
|
* kept until the next drain and its answer ignored.
|
|
321
|
+
*
|
|
322
|
+
* @param {(offset: number) => boolean} handler
|
|
303
323
|
*/
|
|
304
324
|
onWritable(handler) {
|
|
305
325
|
this._onWritable = handler;
|
|
306
326
|
return this;
|
|
307
327
|
}
|
|
308
328
|
|
|
309
|
-
/**
|
|
329
|
+
/**
|
|
330
|
+
* Called when the connection goes before the response is finished.
|
|
331
|
+
*
|
|
332
|
+
* @param {() => void} handler
|
|
333
|
+
*/
|
|
310
334
|
onAborted(handler) {
|
|
311
335
|
this._nodeRes.on("close", () => {
|
|
312
336
|
if (!this._nodeRes.writableFinished) {
|
package/src/optimizer.js
CHANGED
|
@@ -51,6 +51,7 @@ const {
|
|
|
51
51
|
|
|
52
52
|
// router.js requires this file while it is still being evaluated, so its class cannot be required
|
|
53
53
|
// from here: it hands it over on the line under its own export instead.
|
|
54
|
+
/** @type {typeof import("./router.js")} */
|
|
54
55
|
let Router;
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -233,6 +234,12 @@ function compileOptimizedRoutes(root) {
|
|
|
233
234
|
|
|
234
235
|
// pathPrefix/chainPrefix accumulate across nested sole-callback mounts, and outerGuards
|
|
235
236
|
// carries what was written before them and answers only part of what is under them
|
|
237
|
+
/**
|
|
238
|
+
* @param {Router} router
|
|
239
|
+
* @param {string} pathPrefix
|
|
240
|
+
* @param {RouteEntry[]} chainPrefix
|
|
241
|
+
* @param {import("./router-utils.js").MountGuard[]} outerGuards
|
|
242
|
+
*/
|
|
236
243
|
const walk = (router, pathPrefix, chainPrefix, outerGuards) => {
|
|
237
244
|
for (const route of router._routes) {
|
|
238
245
|
if (route.use) {
|
|
@@ -344,7 +351,7 @@ function compileOptimizedRoutes(root) {
|
|
|
344
351
|
if (route._caseGuards) {
|
|
345
352
|
// compared against the whole path µWS matched, so they carry the mount
|
|
346
353
|
// prefix, folded along with the rest of it
|
|
347
|
-
registered._caseGuards = route._caseGuards.map((p) => pathPrefix + p);
|
|
354
|
+
registered._caseGuards = route._caseGuards.map((/** @type {string} */ p) => pathPrefix + p);
|
|
348
355
|
}
|
|
349
356
|
root._registerUwsRoute(registered, chain);
|
|
350
357
|
// the chain holds the original object, so the request-time guard has to find
|
|
@@ -388,18 +395,27 @@ function registerUwsRoute(router, route, optimizedPath) {
|
|
|
388
395
|
method = "del";
|
|
389
396
|
}
|
|
390
397
|
if (route.path.includes(":")) {
|
|
391
|
-
route.optimizedParams = route.path.match(regExParam).map((p) => p.slice(1));
|
|
398
|
+
route.optimizedParams = route.path.match(regExParam).map((/** @type {string} */ p) => p.slice(1));
|
|
392
399
|
}
|
|
393
400
|
// null for almost every route: only a parameter route with an earlier literal that a case
|
|
394
401
|
// variant could slip past carries one, see _optimizeRoute. Built once here, and matched
|
|
395
402
|
// insensitively, since that is the folding the guard exists for
|
|
396
403
|
const caseGuards = route._caseGuards
|
|
397
|
-
? route._caseGuards.map((
|
|
404
|
+
? route._caseGuards.map((/** @type {string} */ p) =>
|
|
405
|
+
needsConversionToRegex(p) ? patternToRegex(p, false, false) : p.toLowerCase()
|
|
406
|
+
)
|
|
398
407
|
: null;
|
|
408
|
+
/**
|
|
409
|
+
* @param {RouteEntry[]} chain
|
|
410
|
+
* @param {import("./router-utils.js").NativePreset|undefined} preset
|
|
411
|
+
* @param {{skipHeaders: boolean, skipQuery: boolean}} skips
|
|
412
|
+
* @param {string|null} wireMethod
|
|
413
|
+
*/
|
|
399
414
|
const makeHandler = (chain, preset, skips, wireMethod) => {
|
|
400
415
|
// the mutable object a granted skip lives on, so a middleware arriving after listen can
|
|
401
416
|
// take it back: a literal registration's preset doubles as it, a parameterised one gets a
|
|
402
417
|
// holder of its own. It carries the method too, so the constructor settles it in one compare
|
|
418
|
+
/** @type {import("./router-utils.js").SkipHolder|undefined} */
|
|
403
419
|
let skipHolder = preset;
|
|
404
420
|
if (skipHolder === undefined && (skips.skipHeaders || skips.skipQuery || wireMethod !== null)) {
|
|
405
421
|
skipHolder = {
|
|
@@ -504,6 +520,11 @@ function registerUwsRoute(router, route, optimizedPath) {
|
|
|
504
520
|
}
|
|
505
521
|
}
|
|
506
522
|
// remembered so a middleware or setting arriving after listen can take the skips back
|
|
523
|
+
/**
|
|
524
|
+
* @param {string} path
|
|
525
|
+
* @param {string} method
|
|
526
|
+
* @param {{skipHeaders: boolean, skipQuery: boolean}} skips
|
|
527
|
+
*/
|
|
507
528
|
const makePreset = (path, method, skips) => {
|
|
508
529
|
const preset = nativePreset(path, method);
|
|
509
530
|
if (skips.skipHeaders || skips.skipQuery) {
|
package/src/request-utils.js
CHANGED
|
@@ -104,8 +104,9 @@ function isMappedIPv4(bytes) {
|
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Whether node would report an IPv4 peer of this app in mapped form, "::ffff:a.b.c.d". Node maps it
|
|
107
|
-
* whenever the listener is dual stack, which is every listen() without an IPv4 address.
|
|
108
|
-
*
|
|
107
|
+
* whenever the listener is dual stack, which is every listen() without an IPv4 address. Only the
|
|
108
|
+
* address invented after the response has ended asks: every address actually read says which form
|
|
109
|
+
* it is in by its width, uWS handing a mapped peer over as sixteen bytes and a plain one as four.
|
|
109
110
|
*
|
|
110
111
|
* @param {import("./application.js").Application} app the application the request arrived at
|
|
111
112
|
* @returns {boolean}
|
package/src/request.js
CHANGED
|
@@ -204,13 +204,6 @@ module.exports = class Request extends LazyReadable {
|
|
|
204
204
|
*/
|
|
205
205
|
rawIp;
|
|
206
206
|
|
|
207
|
-
/**
|
|
208
|
-
* Whether rawIp came from a PROXY protocol preamble rather than from the socket. Only the
|
|
209
|
-
* IPv4 mapping reads it, see parsedIp. Declared for the same reason as rawIp.
|
|
210
|
-
* @type {boolean}
|
|
211
|
-
*/
|
|
212
|
-
_ipFromProxy = false;
|
|
213
|
-
|
|
214
207
|
/**
|
|
215
208
|
* Whether the request declared a body, content-length or transfer-encoding, spotted during
|
|
216
209
|
* the header copy. Declared for the same reason as rawIp.
|
|
@@ -457,7 +450,9 @@ module.exports = class Request extends LazyReadable {
|
|
|
457
450
|
this._lastMethod = this.method;
|
|
458
451
|
// the folded _opPath and the percent scan of _originalPath, built on the hop that first
|
|
459
452
|
// wants them and dropped by every rewrite, see _pathMatches and Walk#dispatch
|
|
453
|
+
/** @type {string|null} */
|
|
460
454
|
this._opPathLower = null;
|
|
455
|
+
/** @type {boolean|null} */
|
|
461
456
|
this._mayFailDecode = null;
|
|
462
457
|
this.params = {};
|
|
463
458
|
|
|
@@ -478,6 +473,7 @@ module.exports = class Request extends LazyReadable {
|
|
|
478
473
|
this._paramStack = null;
|
|
479
474
|
// route and application in pairs, one pair per mounted application entered from another
|
|
480
475
|
// application, so handing back puts the one that was current back, see rememberApp
|
|
476
|
+
/** @type {any[]|undefined} route and app alternating, loose because the pairs share one array */
|
|
481
477
|
this._appStack = undefined;
|
|
482
478
|
this.receivedData = false;
|
|
483
479
|
// node's IncomingMessage flag: false until the whole body has arrived. on-finished
|
|
@@ -1048,7 +1044,6 @@ module.exports = class Request extends LazyReadable {
|
|
|
1048
1044
|
const proxied = uwsRes.getProxiedRemoteAddress();
|
|
1049
1045
|
// empty unless a preamble arrived, which is the only thing that tells the two apart
|
|
1050
1046
|
if (proxied.byteLength !== 0) {
|
|
1051
|
-
this._ipFromProxy = true;
|
|
1052
1047
|
return proxied;
|
|
1053
1048
|
}
|
|
1054
1049
|
}
|
|
@@ -1084,14 +1079,12 @@ module.exports = class Request extends LazyReadable {
|
|
|
1084
1079
|
/** @type {string|undefined} */
|
|
1085
1080
|
let ip;
|
|
1086
1081
|
if (rawIp.byteLength === 4) {
|
|
1087
|
-
// ipv4
|
|
1082
|
+
// ipv4, and plain: four bytes mean the peer arrived over IPv4 on an IPv4 socket, which
|
|
1083
|
+
// is what node writes plain as well. A dual stack listener hands an IPv4 peer over as
|
|
1084
|
+
// the mapped sixteen below, the node shim passes node's own form through, and an
|
|
1085
|
+
// address a proxy declared is the four numbers the proxy sent, so none of them wants
|
|
1086
|
+
// a prefix invented here
|
|
1088
1087
|
ip = new Uint8Array(rawIp).join(".");
|
|
1089
|
-
// the mapped form belongs to a dual stack listener, which is what makes an IPv4 peer
|
|
1090
|
-
// arrive as ::ffff:a.b.c.d. An address a proxy declared never came through that socket,
|
|
1091
|
-
// so it is left as the four numbers the proxy sent
|
|
1092
|
-
if (!this._ipFromProxy && mapsIPv4Peer(this.app)) {
|
|
1093
|
-
ip = "::ffff:" + ip;
|
|
1094
|
-
}
|
|
1095
1088
|
} else if (rawIp.byteLength === 16) {
|
|
1096
1089
|
const bytes = new Uint8Array(rawIp);
|
|
1097
1090
|
if (isMappedIPv4(bytes)) {
|
package/src/response-utils.js
CHANGED
|
@@ -68,7 +68,7 @@ for (const s of [
|
|
|
68
68
|
// One status line per code, built on first use: the default path, with no custom reason phrase,
|
|
69
69
|
// paid a template string and a trim per request for a line that never changes. Bounded to real
|
|
70
70
|
// HTTP codes so a wild writeHead value cannot grow the array or flip it into dictionary mode.
|
|
71
|
-
const STATUS_LINES = [];
|
|
71
|
+
const STATUS_LINES = /** @type {string[]} */ ([]);
|
|
72
72
|
/**
|
|
73
73
|
* @param {number} code
|
|
74
74
|
* @param {string|undefined} text an explicit reason phrase, which bypasses the cache
|
package/src/response.js
CHANGED
|
@@ -208,6 +208,10 @@ module.exports = class Response extends LazyWritable {
|
|
|
208
208
|
this.req = req;
|
|
209
209
|
this._res = res;
|
|
210
210
|
this.headersSent = false;
|
|
211
|
+
// whether a body goes out, decided from the method that arrived, as node decides it when
|
|
212
|
+
// it builds its ServerResponse: a middleware that rewrites req.method afterwards, which
|
|
213
|
+
// method-override does, changes what the router matches and not what the wire carries
|
|
214
|
+
this._hasBody = req._isHead !== true;
|
|
211
215
|
this.app = app;
|
|
212
216
|
this.locals = new NullObject();
|
|
213
217
|
this.finished = false;
|
|
@@ -238,6 +242,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
238
242
|
this.headers["x-powered-by"] = "Fulmine";
|
|
239
243
|
}
|
|
240
244
|
|
|
245
|
+
/** @type {unknown} a slot for whatever a middleware puts on res.body, never read here */
|
|
241
246
|
this.body = undefined;
|
|
242
247
|
// what was handed to uWS, kept so a caller asking for content-length after the fact can be
|
|
243
248
|
// answered, see get(). Undefined until the response ends, and for one that sends no body
|
|
@@ -811,7 +816,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
811
816
|
} else {
|
|
812
817
|
// a Buffer goes to uWS as the view it is: copying it into a fresh ArrayBuffer was
|
|
813
818
|
// an allocation per body, and uWS reads the view's own offset and length
|
|
814
|
-
if (this.
|
|
819
|
+
if (!this._hasBody) {
|
|
815
820
|
const length = Buffer.byteLength(data ?? "");
|
|
816
821
|
this.headers["content-length"] = String(length);
|
|
817
822
|
this._res.endWithoutBody(length, closeConnection);
|
|
@@ -961,6 +966,15 @@ module.exports = class Response extends LazyWritable {
|
|
|
961
966
|
delete this.headers["transfer-encoding"];
|
|
962
967
|
body = "";
|
|
963
968
|
}
|
|
969
|
+
// express's send reads req.method here and hands node's end() nothing for a HEAD, the
|
|
970
|
+
// length already set, and node frames what it is handed: a GET a middleware made a HEAD
|
|
971
|
+
// answers its length and no body. end() alone decides by the wire, see _hasBody
|
|
972
|
+
if (this.req.method === "HEAD") {
|
|
973
|
+
if (this.statusCode !== 204 && this.statusCode !== 304) {
|
|
974
|
+
this.headers["content-length"] = String(Buffer.byteLength(body));
|
|
975
|
+
}
|
|
976
|
+
return this.end();
|
|
977
|
+
}
|
|
964
978
|
return this.end(body);
|
|
965
979
|
}
|
|
966
980
|
|
package/src/route.js
CHANGED
|
@@ -48,6 +48,10 @@ class Route {
|
|
|
48
48
|
* @returns {boolean}
|
|
49
49
|
*/
|
|
50
50
|
handlesMethod(method) {
|
|
51
|
+
// all() answers every verb, registered or not, which is what express reads _all for
|
|
52
|
+
if (this.methods._all) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
51
55
|
const lowered = method.toLowerCase();
|
|
52
56
|
return Boolean(this.methods[lowered] || (lowered === "head" && this.methods.get));
|
|
53
57
|
}
|
|
@@ -87,6 +91,7 @@ class Route {
|
|
|
87
91
|
}
|
|
88
92
|
req.route = this;
|
|
89
93
|
|
|
94
|
+
/** @param {unknown} [err] */
|
|
90
95
|
const next = (err) => {
|
|
91
96
|
// next("route") leaves this route, and next("router") leaves whoever is running it
|
|
92
97
|
if (err === "route") {
|
|
@@ -129,7 +134,7 @@ class Route {
|
|
|
129
134
|
// does it. A bare rejection carries none and gets the one express invents. Thenable
|
|
130
135
|
// too, which express deprecates but still waits for
|
|
131
136
|
if (out && typeof out.then === "function") {
|
|
132
|
-
out.then(null, (thrown) => next(thrown || new Error("Rejected promise")));
|
|
137
|
+
out.then(null, (/** @type {unknown} */ thrown) => next(thrown || new Error("Rejected promise")));
|
|
133
138
|
}
|
|
134
139
|
} catch (thrown) {
|
|
135
140
|
next(thrown);
|
|
@@ -162,14 +167,15 @@ for (const method of ["all", ...METHODS.map((verb) => verb.toLowerCase())]) {
|
|
|
162
167
|
if (method !== "all" && typeof Route.prototype[method] === "function") {
|
|
163
168
|
continue;
|
|
164
169
|
}
|
|
165
|
-
Route.prototype[method] = function (...handlers) {
|
|
170
|
+
Route.prototype[method] = function (/** @type {unknown[]} */ ...handlers) {
|
|
166
171
|
const flattened = handlers.flat(Infinity);
|
|
167
172
|
checkRouteHandlers(method, flattened);
|
|
168
|
-
|
|
173
|
+
// the check above threw on anything else
|
|
174
|
+
for (const handle of /** @type {Function[]} */ (flattened)) {
|
|
169
175
|
this.stack.push({ method: method === "all" ? undefined : method, handle });
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
// express marks the route _all rather than naming every verb, and whoever reads
|
|
177
|
+
// req.route.methods reads that key, so it is written the same way here
|
|
178
|
+
this.methods[method === "all" ? "_all" : method] = true;
|
|
173
179
|
}
|
|
174
180
|
return this;
|
|
175
181
|
};
|
package/src/router-utils.js
CHANGED
|
@@ -175,8 +175,14 @@ function layerFor(route, callback) {
|
|
|
175
175
|
* @returns {Layer} the layer object, which is express's shape and not one of ours
|
|
176
176
|
*/
|
|
177
177
|
function routeLayer(route) {
|
|
178
|
+
/**
|
|
179
|
+
* @param {Request} req
|
|
180
|
+
* @param {Response} res
|
|
181
|
+
* @param {(err?: unknown) => void} next
|
|
182
|
+
*/
|
|
178
183
|
const handle = function handle(req, res, next) {
|
|
179
184
|
let index = 0;
|
|
185
|
+
/** @param {unknown} [err] */
|
|
180
186
|
const step = (err) => {
|
|
181
187
|
const callback = route.callbacks[index++];
|
|
182
188
|
if (callback === undefined) {
|
|
@@ -206,6 +212,7 @@ function routeLayer(route) {
|
|
|
206
212
|
* The 404 epilogue stays on a microtask, where the await used to resume: a middleware that writes
|
|
207
213
|
* after calling next() must still win the headersSent check, as it does in express.
|
|
208
214
|
* @this {Walk}
|
|
215
|
+
* @param {RouteEntry|false} matched what the walk ended on, as the resolve receives it
|
|
209
216
|
*/
|
|
210
217
|
function nativeDone(matched) {
|
|
211
218
|
if (this.settled) {
|
|
@@ -238,6 +245,7 @@ function nativeDone(matched) {
|
|
|
238
245
|
* an unhandled rejection. Deferred like the resolve, since every rejection used to reach the
|
|
239
246
|
* handler's catch through an await.
|
|
240
247
|
* @this {Walk}
|
|
248
|
+
* @param {unknown} err
|
|
241
249
|
*/
|
|
242
250
|
function nativeFail(err) {
|
|
243
251
|
if (this.settled) {
|
|
@@ -313,7 +321,7 @@ const PATH_PROPERTY = {
|
|
|
313
321
|
const ABSORB_URL = Request.prototype._absorbUrlRewrite;
|
|
314
322
|
const ABSORB_METHOD = Request.prototype._absorbMethodRewrite;
|
|
315
323
|
|
|
316
|
-
const NO_PARAM_NAMES = [];
|
|
324
|
+
const NO_PARAM_NAMES = /** @type {string[]} */ ([]);
|
|
317
325
|
|
|
318
326
|
/**
|
|
319
327
|
* The parameter names a route captures with its own pattern.
|
|
@@ -636,7 +644,8 @@ function hasErrorMiddleware(router) {
|
|
|
636
644
|
}
|
|
637
645
|
|
|
638
646
|
/**
|
|
639
|
-
*
|
|
647
|
+
* @param {unknown[]} handlers what a registration was given, flattened
|
|
648
|
+
* @param {string} [emptyMessage]
|
|
640
649
|
*/
|
|
641
650
|
function checkHandlers(handlers, emptyMessage = "argument handler is required") {
|
|
642
651
|
if (handlers.length === 0) {
|
package/src/router.js
CHANGED
|
@@ -169,6 +169,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
169
169
|
|
|
170
170
|
this._paramCallbacks = new Map();
|
|
171
171
|
this._mountpathCache = new Map();
|
|
172
|
+
/** @type {RouteEntry[]} */
|
|
172
173
|
this._routes = [];
|
|
173
174
|
// websocket routes, kept apart from the HTTP ones: µWS serves them itself and listen()
|
|
174
175
|
// hands them over whole, mount paths and all
|
|
@@ -908,6 +909,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
908
909
|
*/
|
|
909
910
|
_handleError(err, handler, request, response) {
|
|
910
911
|
if (handler) {
|
|
912
|
+
/** @param {unknown} [pass] */
|
|
911
913
|
const next = (pass) => {
|
|
912
914
|
delete request._error;
|
|
913
915
|
delete request._errorKey;
|
|
@@ -1154,15 +1156,20 @@ module.exports = class Router extends EventEmitter {
|
|
|
1154
1156
|
return new Promise((resolve) => {
|
|
1155
1157
|
let index = 0;
|
|
1156
1158
|
let name = "";
|
|
1159
|
+
/** @type {unknown} */
|
|
1157
1160
|
let value;
|
|
1158
1161
|
let entry;
|
|
1162
|
+
/** @type {Function[]} */
|
|
1159
1163
|
let fns = [];
|
|
1160
1164
|
let fnIndex = 0;
|
|
1161
1165
|
|
|
1162
1166
|
// one parameter after the other, err being what the last one's callbacks ended with
|
|
1167
|
+
/** @param {unknown} [err] */
|
|
1163
1168
|
const nextParam = (err) => {
|
|
1164
1169
|
if (err) {
|
|
1165
|
-
|
|
1170
|
+
// an error already in flight stays the one in flight: express reaches a mount's
|
|
1171
|
+
// param callbacks with it pending and goes on with next(layerError || err)
|
|
1172
|
+
if (err !== "route" && !req._error) {
|
|
1166
1173
|
req._error = err;
|
|
1167
1174
|
req._errorKey = route.routeKey;
|
|
1168
1175
|
req._errorGroup = route.group;
|
|
@@ -1188,6 +1195,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
1188
1195
|
};
|
|
1189
1196
|
|
|
1190
1197
|
// and one callback of the current parameter after the other
|
|
1198
|
+
/** @param {unknown} [err] */
|
|
1191
1199
|
const nextCallback = (err) => {
|
|
1192
1200
|
const fn = fns[fnIndex++];
|
|
1193
1201
|
// read before the callback runs and again after it: one that rewrites
|
|
@@ -1248,6 +1256,13 @@ module.exports = class Router extends EventEmitter {
|
|
|
1248
1256
|
|
|
1249
1257
|
/**
|
|
1250
1258
|
* Resolves with the route that answered, or false when nothing matched.
|
|
1259
|
+
*
|
|
1260
|
+
* @param {Request} req
|
|
1261
|
+
* @param {Response} res
|
|
1262
|
+
* @param {number} [startIndex]
|
|
1263
|
+
* @param {RouteEntry[]} [routes]
|
|
1264
|
+
* @param {boolean} [skipCheck] take the route at the index without matching it, see Walk
|
|
1265
|
+
* @param {RouteEntry} [skipUntil] route to resume after when this chain runs out, see Walk
|
|
1251
1266
|
* @returns {Promise<RouteEntry|false>}
|
|
1252
1267
|
*/
|
|
1253
1268
|
_routeRequest(req, res, startIndex = 0, routes = this._routes, skipCheck = false, skipUntil) {
|
|
@@ -1366,11 +1381,16 @@ module.exports = class Router extends EventEmitter {
|
|
|
1366
1381
|
// one map for the whole chain, because express builds one Route for it: a request answered
|
|
1367
1382
|
// by the get() of an app.route() reads post() in its req.route.methods too
|
|
1368
1383
|
const groupMethods = new NullObject();
|
|
1384
|
+
/** @type {(Omit<Layer, "route"> & {method: string|undefined})[]} express's Route#stack, see createRoute */
|
|
1369
1385
|
const groupStack = [];
|
|
1370
1386
|
// express hands back a Route, which carries these three beside the verb methods
|
|
1371
1387
|
fns.path = path;
|
|
1372
1388
|
fns.methods = groupMethods;
|
|
1373
1389
|
fns.stack = groupStack;
|
|
1390
|
+
/**
|
|
1391
|
+
* @param {string} method
|
|
1392
|
+
* @param {unknown[]} callbacks
|
|
1393
|
+
*/
|
|
1374
1394
|
const inGroup = (method, callbacks) => {
|
|
1375
1395
|
this._pendingGroup = group;
|
|
1376
1396
|
this._pendingGroupMethods = groupMethods;
|
|
@@ -1384,9 +1404,9 @@ module.exports = class Router extends EventEmitter {
|
|
|
1384
1404
|
}
|
|
1385
1405
|
};
|
|
1386
1406
|
for (const method of methods) {
|
|
1387
|
-
fns[method] = (...callbacks) => inGroup(method, callbacks);
|
|
1407
|
+
fns[method] = (/** @type {unknown[]} */ ...callbacks) => inGroup(method, callbacks);
|
|
1388
1408
|
}
|
|
1389
|
-
fns.get = (...callbacks) => inGroup("GET", callbacks);
|
|
1409
|
+
fns.get = (/** @type {unknown[]} */ ...callbacks) => inGroup("GET", callbacks);
|
|
1390
1410
|
return fns;
|
|
1391
1411
|
}
|
|
1392
1412
|
|
|
@@ -1482,7 +1502,10 @@ module.exports = class Router extends EventEmitter {
|
|
|
1482
1502
|
useRouterClass(module.exports);
|
|
1483
1503
|
|
|
1484
1504
|
for (const method of methods) {
|
|
1485
|
-
module.exports.prototype[method] = function (
|
|
1505
|
+
module.exports.prototype[method] = function (
|
|
1506
|
+
/** @type {string|RegExp|(string|RegExp)[]} */ path,
|
|
1507
|
+
/** @type {unknown[]} */ ...callbacks
|
|
1508
|
+
) {
|
|
1486
1509
|
return this.createRoute(method, path, this, ...callbacks);
|
|
1487
1510
|
};
|
|
1488
1511
|
}
|
package/src/testing.js
CHANGED
|
@@ -134,7 +134,7 @@ function select(app, patterns, caller) {
|
|
|
134
134
|
throw new TypeError(`${caller} needs a path, or a list of them, to check`);
|
|
135
135
|
}
|
|
136
136
|
const report = routeReport(app);
|
|
137
|
-
const selected = [];
|
|
137
|
+
const selected = /** @type {ReturnType<typeof routeReport>} */ ([]);
|
|
138
138
|
for (const pattern of wanted) {
|
|
139
139
|
const matched = report.filter((entry) => names(entry, pattern));
|
|
140
140
|
if (matched.length === 0) {
|
package/src/utils.js
CHANGED
|
@@ -208,6 +208,9 @@ function getPatternMeta(pattern) {
|
|
|
208
208
|
* A bare `*`, an unnamed parameter, an inline regex like :id(\\d+) and the `+`, `?`, `()` operators
|
|
209
209
|
* throw: a route that quietly stops matching is worse than one that fails at startup. The names it
|
|
210
210
|
* captures go in a WeakMap beside the regex, see PatternMeta.
|
|
211
|
+
*
|
|
212
|
+
* @param {string|RegExp} pattern
|
|
213
|
+
* @returns {RegExp}
|
|
211
214
|
*/
|
|
212
215
|
function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict = false) {
|
|
213
216
|
if (pattern instanceof RegExp) {
|
|
@@ -222,11 +225,12 @@ function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict
|
|
|
222
225
|
let regexPattern = "";
|
|
223
226
|
let i = 0;
|
|
224
227
|
const len = pattern.length;
|
|
225
|
-
const wildcardNames = [];
|
|
228
|
+
const wildcardNames = /** @type {string[]} */ ([]);
|
|
226
229
|
// express takes /:a/:a, and two capture groups cannot share a name, so a repeat is compiled
|
|
227
230
|
// under a spelling of its own and mapped back when the parameters are read out. Reading them
|
|
228
231
|
// in order then leaves the last occurrence in place, which is the value express reports
|
|
229
|
-
const groupOutputName = new Map();
|
|
232
|
+
const groupOutputName = /** @type {Map<string, string>} */ (new Map());
|
|
233
|
+
/** @param {string} name */
|
|
230
234
|
const uniqueGroupName = (name) => {
|
|
231
235
|
if (!groupOutputName.has(name)) {
|
|
232
236
|
groupOutputName.set(name, name);
|
|
@@ -255,7 +259,11 @@ function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict
|
|
|
255
259
|
let lastCaptureWasWildcard = false;
|
|
256
260
|
let wildcardInSegment = false;
|
|
257
261
|
let paramInSegment = false;
|
|
258
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* Records literal text as it is emitted, which is what the rules above are written against.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} text
|
|
266
|
+
*/
|
|
259
267
|
const literal = (text) => {
|
|
260
268
|
backtrack += text;
|
|
261
269
|
if (lastCaptureWasWildcard) {
|
|
@@ -750,7 +758,8 @@ function acceptParams(str) {
|
|
|
750
758
|
const length = str.length;
|
|
751
759
|
const colonIndex = str.indexOf(";");
|
|
752
760
|
let index = colonIndex === -1 ? length : colonIndex;
|
|
753
|
-
const
|
|
761
|
+
const params = /** @type {Record<string, string>} */ ({});
|
|
762
|
+
const ret = { value: str.slice(0, index).trim(), quality: 1, params };
|
|
754
763
|
|
|
755
764
|
while (index < length) {
|
|
756
765
|
const splitIndex = str.indexOf("=", index);
|
|
@@ -1098,13 +1107,20 @@ function durationSetting(value, name) {
|
|
|
1098
1107
|
return parsed;
|
|
1099
1108
|
}
|
|
1100
1109
|
|
|
1110
|
+
/**
|
|
1111
|
+
* The predicate "trust proxy" compiles to: whether the address at hop i is trusted. The address is
|
|
1112
|
+
* undefined over a unix socket, see Request#parsedIp.
|
|
1113
|
+
*
|
|
1114
|
+
* @typedef {(addr: string|undefined, i: number) => boolean} TrustFn
|
|
1115
|
+
*/
|
|
1116
|
+
|
|
1101
1117
|
/**
|
|
1102
1118
|
* Turns whatever "trust proxy" was set to into the function proxy-addr wants: a predicate saying
|
|
1103
1119
|
* whether the address at hop i is trusted. true trusts everything, a number trusts that many hops,
|
|
1104
1120
|
* and a string or a list is read as addresses and subnet names.
|
|
1105
1121
|
*
|
|
1106
|
-
* @param {boolean|number|string|string[]|
|
|
1107
|
-
* @returns {
|
|
1122
|
+
* @param {boolean|number|string|string[]|TrustFn} val
|
|
1123
|
+
* @returns {TrustFn}
|
|
1108
1124
|
*/
|
|
1109
1125
|
function compileTrust(val) {
|
|
1110
1126
|
if (typeof val === "function") return val;
|
|
@@ -1118,8 +1134,9 @@ function compileTrust(val) {
|
|
|
1118
1134
|
|
|
1119
1135
|
if (typeof val === "number") {
|
|
1120
1136
|
// Support trusting hop count
|
|
1121
|
-
|
|
1122
|
-
|
|
1137
|
+
const hops = val;
|
|
1138
|
+
return function (/** @type {string|undefined} */ a, /** @type {number} */ i) {
|
|
1139
|
+
return i < hops;
|
|
1123
1140
|
};
|
|
1124
1141
|
}
|
|
1125
1142
|
|
|
@@ -1130,7 +1147,9 @@ function compileTrust(val) {
|
|
|
1130
1147
|
});
|
|
1131
1148
|
}
|
|
1132
1149
|
|
|
1133
|
-
|
|
1150
|
+
// proxy-addr answers false to an address it cannot parse, undefined included, though its
|
|
1151
|
+
// typing does not admit one
|
|
1152
|
+
return /** @type {TrustFn} */ (proxyaddr.compile(val || []));
|
|
1134
1153
|
}
|
|
1135
1154
|
|
|
1136
1155
|
const shownWarnings = new Set();
|
package/src/walk.js
CHANGED
|
@@ -79,6 +79,7 @@ class Walk {
|
|
|
79
79
|
//
|
|
80
80
|
// Null here and bound on the first route with more than one callback, the only shape that
|
|
81
81
|
// reads it: a request that never meets one paid a bind for nothing
|
|
82
|
+
/** @type {((err?: unknown) => void)|null} */
|
|
82
83
|
this.leaveRoute = null;
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -103,6 +104,7 @@ class Walk {
|
|
|
103
104
|
* a chain of N middlewares costs one promise instead of N nested ones.
|
|
104
105
|
*
|
|
105
106
|
* @param {number} startIndex where to resume the scan
|
|
107
|
+
* @returns {void}
|
|
106
108
|
*/
|
|
107
109
|
dispatch(startIndex) {
|
|
108
110
|
const req = this.req;
|
|
@@ -226,7 +228,7 @@ class Walk {
|
|
|
226
228
|
.then((resumed) => this.runRoute(resumed))
|
|
227
229
|
// wrapped so the native pair keeps the walk as receiver; a promise's reject
|
|
228
230
|
// would not have cared
|
|
229
|
-
.catch((err) => this.reject(err));
|
|
231
|
+
.catch((/** @type {unknown} */ err) => this.reject(err));
|
|
230
232
|
return;
|
|
231
233
|
}
|
|
232
234
|
return this.runRoute(continueRoute);
|
|
@@ -291,6 +293,7 @@ class Walk {
|
|
|
291
293
|
* way in, and then the route's callbacks run one after another through next().
|
|
292
294
|
*
|
|
293
295
|
* @param {true|"route"} continueRoute what _preprocessRequest decided: true to run, "route" to skip
|
|
296
|
+
* @returns {void}
|
|
294
297
|
*/
|
|
295
298
|
runRoute(continueRoute) {
|
|
296
299
|
const req = this.req;
|
|
@@ -347,6 +350,7 @@ class Walk {
|
|
|
347
350
|
*
|
|
348
351
|
* @param {number} kind what the callback is, one of the CALLBACK_ constants
|
|
349
352
|
* @param {Function} callback
|
|
353
|
+
* @returns {void}
|
|
350
354
|
*/
|
|
351
355
|
errorHop(kind, callback) {
|
|
352
356
|
const req = this.req;
|
|
@@ -441,6 +445,7 @@ class Walk {
|
|
|
441
445
|
* leave the route; with anything else, remember it as the error and carry on.
|
|
442
446
|
*
|
|
443
447
|
* @param {unknown} thingamabob what next() was called with: nothing, "route", or an error
|
|
448
|
+
* @returns {void}
|
|
444
449
|
*/
|
|
445
450
|
step(thingamabob) {
|
|
446
451
|
const req = this.req;
|
|
@@ -492,7 +497,7 @@ class Walk {
|
|
|
492
497
|
}
|
|
493
498
|
callback
|
|
494
499
|
._routeRequest(req, res, 0)
|
|
495
|
-
.then((routed) => {
|
|
500
|
+
.then((/** @type {RouteEntry|false} */ routed) => {
|
|
496
501
|
// the child's params are scoped to it, and must not leak into the routes after
|
|
497
502
|
if (pushedParams) {
|
|
498
503
|
req._paramStack.pop();
|
|
@@ -531,7 +536,7 @@ class Walk {
|
|
|
531
536
|
// a rejection out of the nested walk, or a throw above, must reject this one
|
|
532
537
|
// instead of dying as an unhandled rejection; wrapped for the native pair's
|
|
533
538
|
// receiver
|
|
534
|
-
.catch((err) => this.reject(err));
|
|
539
|
+
.catch((/** @type {unknown} */ err) => this.reject(err));
|
|
535
540
|
} else {
|
|
536
541
|
// errors and error handlers live out of line: this is the cold path, and its size
|
|
537
542
|
// was pushing step past the inlining threshold
|
package/src/websocket.js
CHANGED
|
@@ -208,7 +208,7 @@ function makeUpgradeHandler(app, path, behavior) {
|
|
|
208
208
|
* @param {Application} app
|
|
209
209
|
*/
|
|
210
210
|
function registerWebSocketRoutes(app) {
|
|
211
|
-
const routes = [];
|
|
211
|
+
const routes = /** @type {WsRoute[]} */ ([]);
|
|
212
212
|
collectRoutes(app, "", routes, new Set());
|
|
213
213
|
for (const route of routes) {
|
|
214
214
|
const uwsBehavior = { ...route.behavior };
|