fulmine.js 5.19.3 → 5.19.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.
@@ -26,6 +26,7 @@ const mime = require("mime-types");
26
26
  const compressible = require("compressible");
27
27
  const ms = require("ms");
28
28
  const qs = require("qs");
29
+ const statuses = require("statuses");
29
30
  const parseQuery = require("./parse-query.js");
30
31
  const { kGetSafe } = require("./usage.js");
31
32
  const { AsyncResource } = require("async_hooks");
@@ -48,6 +49,7 @@ const {
48
49
  NullObject,
49
50
  asStatError,
50
51
  httpError,
52
+ httpErrorName,
51
53
  memoizeByString,
52
54
  containsDotFile,
53
55
  negotiateEncoding,
@@ -74,6 +76,26 @@ const PRECOMPRESSED = [
74
76
 
75
77
  /** @typedef {import("./request.js")} Request */
76
78
  /** @typedef {import("./response.js")} Response */
79
+ /** @typedef {import("./utils.js").HttpError} HttpError */
80
+ /** @typedef {import("./options").BodyParserOptions} BodyParserOptions */
81
+ /**
82
+ * A decompressor from fast-zlib, carrying the flag its final flush passes, see createInflate.
83
+ * @typedef {(import("fast-zlib").Inflate|import("fast-zlib").Gunzip|import("fast-zlib").BrotliDecompress)
84
+ * & {_finishFlag?: number}} Inflater
85
+ */
86
+ /**
87
+ * What a parser does with the collected bytes: turns them into req.body and carries on. `body` is
88
+ * deliberately not a field of Request, see the comment there, so it is added here. The charset is
89
+ * undefined only for raw, which never decodes: the others settle it before a byte is read.
90
+ * @typedef {(
91
+ * req: Request & {body?: unknown},
92
+ * res: Response,
93
+ * next: (err?: unknown) => void,
94
+ * options: BodyParserOptions,
95
+ * buf: Buffer,
96
+ * encoding: string|undefined
97
+ * ) => void} BodyHandler
98
+ */
77
99
 
78
100
  // The failures express.static answers by moving on to the next handler rather than by reporting
79
101
  // them, when fallthrough is on. They all mean the same thing: the request is not a file here.
@@ -123,7 +145,7 @@ let iconv;
123
145
  * iconv-lite, loaded only when a request names a charset the Buffer cannot decode, so the common
124
146
  * utf-8 request never pays for it.
125
147
  *
126
- * @returns {any}
148
+ * @returns {typeof import("iconv-lite")}
127
149
  */
128
150
  function loadIconv() {
129
151
  if (!iconv) iconv = require("iconv-lite");
@@ -200,7 +222,7 @@ function decodeBody(buf, encoding) {
200
222
  case "iso-8859-1":
201
223
  return buf.toString("latin1");
202
224
  default:
203
- return loadIconv().decode(buf, encoding);
225
+ return loadIconv().decode(buf, /** @type {import("iconv-lite").Encoding} */ (encoding));
204
226
  }
205
227
  }
206
228
 
@@ -210,29 +232,51 @@ function decodeBody(buf, encoding) {
210
232
  *
211
233
  * @param {Request} req
212
234
  * @param {Response} res
213
- * @param {(err?: any) => void} next
214
- * @param {any} options the parser options, settled by createBodyParser
235
+ * @param {(err?: unknown) => void} next
236
+ * @param {BodyParserOptions} options the parser options, settled by createBodyParser
215
237
  * @param {Buffer} buf
238
+ * @param {string|undefined} encoding the charset the body is about to be decoded with, undefined
239
+ * for raw, which never decodes
216
240
  * @returns {boolean}
217
241
  */
218
- function runVerify(req, res, next, options, buf) {
242
+ function runVerify(req, res, next, options, buf, encoding) {
219
243
  if (!options.verify) {
220
244
  return true;
221
245
  }
222
246
  try {
223
- options.verify(req, res, buf);
247
+ // the charset goes too, as body-parser hands it over: a hook checking a signature over
248
+ // the decoded text needs it. raw gets null, which is what body-parser gives it there
249
+ options.verify(req, res, buf, encoding ?? null);
224
250
  return true;
225
251
  } catch (e) {
226
- const err = /** @type {any} */ (e);
227
- next(
228
- asBodyError(err, err.status ?? err.statusCode ?? 403, err.type ?? "entity.verify.failed", {
229
- body: buf
230
- })
231
- );
252
+ next(verifyError(e, buf));
232
253
  return false;
233
254
  }
234
255
  }
235
256
 
257
+ /**
258
+ * The error a verify hook's throw becomes, as http-errors shapes it for body-parser. An Error is
259
+ * kept, with its own status when that is one a client can be answered with; a thrown string
260
+ * becomes a 403 with that message, and anything else a plain 403.
261
+ *
262
+ * @param {unknown} thrown
263
+ * @param {Buffer} buf the body, which rides on the error as body-parser puts it there
264
+ * @returns {HttpError}
265
+ */
266
+ function verifyError(thrown, buf) {
267
+ const own = thrown instanceof Error ? /** @type {HttpError} */ (thrown) : undefined;
268
+ let status = own ? own.status || own.statusCode || 403 : 403;
269
+ // http-errors answers 500 for a status it cannot answer with: not a number, or outside 4xx
270
+ // and 5xx with no message of its own
271
+ if (typeof status !== "number" || (!statuses.message[status] && (status < 400 || status >= 600))) {
272
+ status = 500;
273
+ }
274
+ const err = own ?? httpError(status, typeof thrown === "string" ? thrown : undefined);
275
+ // read off whatever was thrown, as body-parser reads it
276
+ const type = /** @type {{type?: string}|null|undefined} */ (thrown)?.type || "entity.verify.failed";
277
+ return asBodyError(err, status, type, { body: buf });
278
+ }
279
+
236
280
  /**
237
281
  * The message a strict violation gets, which is the one V8 would have produced had the body been
238
282
  * invalid JSON rather than merely not an object.
@@ -255,22 +299,13 @@ function strictSyntaxMessage(text, char) {
255
299
  JSON.parse(partial);
256
300
  } catch (e) {
257
301
  // put the real characters back where the placeholders were named
258
- return /** @type {any} */ (e).message.replace(/#+/g, (/** @type {string} */ placeholder) =>
302
+ return /** @type {SyntaxError} */ (e).message.replace(/#+/g, (/** @type {string} */ placeholder) =>
259
303
  text.substring(index, index + placeholder.length)
260
304
  );
261
305
  }
262
306
  return "strict violation";
263
307
  }
264
308
 
265
- // The name http-errors gives each status body-parser answers with. An application reading
266
- // err.name, or a logger printing it, sees "PayloadTooLargeError" from Express and would have
267
- // seen a bare "Error" here.
268
- const BODY_ERROR_NAMES = {
269
- 400: "BadRequestError",
270
- 413: "PayloadTooLargeError",
271
- 415: "UnsupportedMediaTypeError"
272
- };
273
-
274
309
  /**
275
310
  * The error a body parser hands to next(), shaped as body-parser shapes it: with a status, since
276
311
  * `res.status(err.status || 500)` would otherwise answer 500 to a request that was merely too
@@ -283,10 +318,11 @@ const BODY_ERROR_NAMES = {
283
318
  * @returns {Error}
284
319
  */
285
320
  function bodyError(message, status, type, extra) {
286
- const err = /** @type {any} */ (new Error(message));
287
- if (BODY_ERROR_NAMES[status]) {
288
- err.name = BODY_ERROR_NAMES[status];
289
- }
321
+ /** @type {HttpError} */
322
+ const err = new Error(message);
323
+ // the name http-errors gives it: an application reading err.name, or a logger printing it,
324
+ // sees "PayloadTooLargeError" from Express and would have seen a bare "Error" here
325
+ err.name = httpErrorName(status);
290
326
  return asBodyError(err, status, type, extra);
291
327
  }
292
328
 
@@ -296,7 +332,7 @@ function bodyError(message, status, type, extra) {
296
332
  * its stack and any property the thrower put on it are all still there when the application
297
333
  * reads it.
298
334
  *
299
- * @param {any} err whatever was thrown, which need not be an Error
335
+ * @param {HttpError} err the error the fields go on: JSON.parse's SyntaxError, or a verify hook's own
300
336
  * @param {number} status
301
337
  * @param {string} type body-parser's own name for the kind of failure
302
338
  * @param {object} [extra] anything else body-parser puts on that particular error
@@ -411,7 +447,7 @@ function pickPrecompressed(filePath, accept, ttl, statTtl) {
411
447
  *
412
448
  * @param {string} dir the directory to look in
413
449
  * @param {string[]} indexList the index names, in order
414
- * @returns {{stat: any, name: string, candidate: string}|null} the file to serve, or null
450
+ * @returns {{stat: import("fs").Stats, name: string, candidate: string}|null} the file to serve, or null
415
451
  */
416
452
  function findIndexFile(dir, indexList) {
417
453
  let lastError;
@@ -445,7 +481,7 @@ function findIndexFile(dir, indexList) {
445
481
  *
446
482
  * @param {string} root directory to serve from
447
483
  * @param {import("./options").StaticOptions} [options]
448
- * @returns {(req: any, res: any, next: (err?: any) => void) => any}
484
+ * @returns {(req: Request, res: Response, next: (err?: unknown) => void) => void}
449
485
  */
450
486
  function serveStatic(root, options) {
451
487
  // serve-static's own messages, thrown where the middleware is written rather than where a
@@ -478,21 +514,22 @@ function serveStatic(root, options) {
478
514
  if (options.setHeaders !== undefined && typeof options.setHeaders !== "function") {
479
515
  throw new TypeError("option setHeaders must be function");
480
516
  }
517
+ // serve-static's own option, which res.sendFile does not take, so it goes down under a name
518
+ // only this middleware writes, see sendFile
519
+ options._setHeaders = options.setHeaders;
481
520
  // How long express.static remembers which twins a path has. A second is short enough that a
482
521
  // deploy is picked up while it is still going out, and long enough that the lookup costs
483
522
  // nothing under any traffic at all. { cache: false } asks the disk on every request.
484
523
  let twinTtl = 0;
485
524
  if (options.preCompressed) {
486
- const cache = /** @type {any} */ (
487
- typeof options.preCompressed === "object" ? options.preCompressed.cache : undefined
488
- );
525
+ const cache = typeof options.preCompressed === "object" ? options.preCompressed.cache : undefined;
489
526
  twinTtl =
490
527
  cache === undefined
491
528
  ? 1000
492
529
  : cache === false
493
530
  ? 0
494
531
  : typeof cache === "string"
495
- ? ms(/** @type {any} */ (cache))
532
+ ? ms(/** @type {import("ms").StringValue} */ (cache))
496
533
  : cache;
497
534
  if (typeof twinTtl !== "number" || !(twinTtl >= 0)) {
498
535
  throw new TypeError("option preCompressed.cache must be a duration");
@@ -662,7 +699,7 @@ function serveStatic(root, options) {
662
699
  // error handler with its errno, code, syscall and path still on it, and an
663
700
  // error handler doing res.send(err) sends those as JSON. Passing the string
664
701
  // sent an HTML page instead.
665
- return next(asStatError(statError));
702
+ return next(asStatError(/** @type {HttpError} */ (statError)));
666
703
  } else return next();
667
704
  }
668
705
  }
@@ -707,7 +744,7 @@ function serveStatic(root, options) {
707
744
  res.status(404);
708
745
  // the fs error, as above: the index file is missing and the error handler
709
746
  // is told which one and where
710
- return next(asStatError(err));
747
+ return next(asStatError(/** @type {HttpError} */ (err)));
711
748
  } else return next();
712
749
  }
713
750
  if (found === null) {
@@ -778,7 +815,7 @@ function serveStatic(root, options) {
778
815
  * encoding nobody knows throws, since decoding it wrong is worse than refusing.
779
816
  *
780
817
  * @param {string|undefined} contentEncoding
781
- * @returns {any|undefined}
818
+ * @returns {Inflater|false|undefined}
782
819
  */
783
820
  function createInflate(contentEncoding) {
784
821
  const encoding = (contentEncoding || "identity").toLowerCase();
@@ -799,7 +836,7 @@ function createInflate(contentEncoding) {
799
836
  return false;
800
837
  }
801
838
  // the flag the final flush passes, so a truncated stream errors instead of resolving empty
802
- /** @type {any} */ (stream)._finishFlag =
839
+ /** @type {Inflater} */ (stream)._finishFlag =
803
840
  encoding === "br" ? zlib.constants.BROTLI_OPERATION_FINISH : zlib.constants.Z_FINISH;
804
841
  return stream;
805
842
  }
@@ -810,9 +847,9 @@ function createInflate(contentEncoding) {
810
847
  * bytes over. They differ in the content type they claim and in what they turn the bytes into.
811
848
  *
812
849
  * @param {string} defaultType the type matched when the caller names none
813
- * @param {(...args: any[]) => any} beforeReturn turns the collected bytes into req.body. Called
814
- * with the request, the response, next, the options, the body and its charset
815
- * @param {(options: any) => void} [checkOptions] whatever this parser alone has to check
850
+ * @param {BodyHandler} beforeReturn turns the collected bytes into req.body. Called with the
851
+ * request, the response, next, the options, the body and its charset
852
+ * @param {(options: BodyParserOptions) => void} [checkOptions] whatever this parser alone has to check
816
853
  * @param {string} [charsetPolicy] which charsets this parser accepts, as body-parser draws the
817
854
  * lines: "utf" (json, utf-* only), "urlencoded" (utf-8 and iso-8859-1), "any" (anything iconv
818
855
  * knows), or undefined for a parser that never decodes (raw)
@@ -972,7 +1009,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
972
1009
  if (lengthNumber === 0) {
973
1010
  req.bodyRead = true;
974
1011
  const empty = Buffer.alloc(0);
975
- if (!runVerify(req, res, next, options, empty)) {
1012
+ if (!runVerify(req, res, next, options, empty, encoding)) {
976
1013
  return;
977
1014
  }
978
1015
  return beforeReturn(req, res, next, options, empty, encoding);
@@ -1002,6 +1039,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1002
1039
  return next();
1003
1040
  }
1004
1041
 
1042
+ /** @type {Inflater|false|undefined} */
1005
1043
  let inflate;
1006
1044
  let totalSize = 0;
1007
1045
  const rawContentEncoding = req._rawHeader("content-encoding");
@@ -1070,7 +1108,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1070
1108
  if (copyBody) {
1071
1109
  buf = Buffer.from(buf);
1072
1110
  }
1073
- if (!runVerify(req, res, next, options, buf)) {
1111
+ if (!runVerify(req, res, next, options, buf, encoding)) {
1074
1112
  return;
1075
1113
  }
1076
1114
  beforeReturn(req, res, next, options, buf, encoding);
@@ -1106,10 +1144,10 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1106
1144
  * nothing, and an unhandled 'error' event ends the process. The listener goes on after
1107
1145
  * the throw, since process() would have removed it.
1108
1146
  *
1109
- * @param {any} err what inflate.process threw
1147
+ * @param {HttpError} err what inflate.process threw
1110
1148
  */
1111
1149
  function failInflate(err) {
1112
- /** @type {any} */ (inflate).instance?.on?.("error", () => {});
1150
+ /** @type {Inflater} */ (inflate).instance?.on?.("error", () => {});
1113
1151
  finished = true;
1114
1152
  abs.length = 0;
1115
1153
  target = null;
@@ -1162,7 +1200,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1162
1200
  * finished flag matters: uWS goes on delivering chunks after an oversized body has
1163
1201
  * been refused, and without it every further chunk would answer the request again.
1164
1202
  *
1165
- * @param {any} buf a Buffer, or an ArrayBuffer straight from uWS
1203
+ * @param {Buffer|ArrayBuffer} buf a Buffer, or an ArrayBuffer straight from uWS
1166
1204
  */
1167
1205
  function onData(buf) {
1168
1206
  if (finished) {
@@ -1177,7 +1215,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1177
1215
  } catch (e) {
1178
1216
  // a body that does not decompress is the client's mistake, and zlib
1179
1217
  // throwing here used to escape into whatever called us
1180
- return failInflate(e);
1218
+ return failInflate(/** @type {HttpError} */ (e));
1181
1219
  }
1182
1220
  }
1183
1221
 
@@ -1198,7 +1236,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1198
1236
  try {
1199
1237
  tail = inflate.process(EMPTY_BUFFER, inflate._finishFlag);
1200
1238
  } catch (e) {
1201
- return failInflate(e);
1239
+ return failInflate(/** @type {HttpError} */ (e));
1202
1240
  }
1203
1241
  if (tail.length && !keepChunk(tail)) {
1204
1242
  return;
@@ -1226,7 +1264,7 @@ function createBodyParser(defaultType, beforeReturn, checkOptions, charsetPolicy
1226
1264
  : abs.length === 1
1227
1265
  ? abs[0]
1228
1266
  : Buffer.concat(abs);
1229
- if (!runVerify(req, res, next, options, buf)) {
1267
+ if (!runVerify(req, res, next, options, buf, encoding)) {
1230
1268
  return;
1231
1269
  }
1232
1270
  beforeReturn(req, res, next, options, buf, encoding);
@@ -1274,7 +1312,7 @@ const json = createBodyParser(
1274
1312
  // either: it decodes through iconv, which strips it, so by the time the first character is
1275
1313
  // looked at the mark is gone. JSON.parse would refuse it, so without this a body saved by
1276
1314
  // an editor that writes a BOM is answered 400 here and 200 by Express.
1277
- const text = stripBom(decodeBody(buf, encoding));
1315
+ const text = stripBom(decodeBody(buf, /** @type {string} */ (encoding)));
1278
1316
 
1279
1317
  // "strict" means only an object or an array is a body, which is body-parser's default and
1280
1318
  // was not honoured here at all: the check read req.body before this function had parsed
@@ -1302,7 +1340,7 @@ const json = createBodyParser(
1302
1340
  } catch (e) {
1303
1341
  // V8's own error, which is what body-parser hands on: its message says where the parse
1304
1342
  // gave up, and it is still the SyntaxError an application may be testing for
1305
- const err = /** @type {any} */ (e);
1343
+ const err = /** @type {SyntaxError} */ (e);
1306
1344
  return next(asBodyError(err, 400, "entity.parse.failed", { body: text }));
1307
1345
  }
1308
1346
 
@@ -1329,7 +1367,7 @@ const text = createBodyParser(
1329
1367
  "text/plain",
1330
1368
  function (req, res, next, options, buf, encoding) {
1331
1369
  try {
1332
- req.body = decodeBody(buf, encoding);
1370
+ req.body = decodeBody(buf, /** @type {string} */ (encoding));
1333
1371
  } catch (e) {
1334
1372
  return next(e);
1335
1373
  }
@@ -1371,7 +1409,7 @@ const urlencoded = createBodyParser(
1371
1409
  "application/x-www-form-urlencoded",
1372
1410
  function (req, res, next, options, buf, encoding) {
1373
1411
  try {
1374
- const body = decodeBody(buf, encoding);
1412
+ const body = decodeBody(buf, /** @type {string} */ (encoding));
1375
1413
  // Express 5 defaults extended to false, so nested keys need opting in
1376
1414
  const extended = typeof options.extended !== "undefined" ? options.extended : false;
1377
1415
  // qs has to know the charset itself for anything but utf-8, and the sentinel options
@@ -1387,7 +1425,8 @@ const urlencoded = createBodyParser(
1387
1425
  }
1388
1426
  req.body = parsed;
1389
1427
  } else {
1390
- const count = parameterCount(body, options.parameterLimit);
1428
+ // settled by the check below the parser, so it is a number by the time a body arrives
1429
+ const count = parameterCount(body, /** @type {number} */ (options.parameterLimit));
1391
1430
  if (count === undefined) {
1392
1431
  return next(bodyError("too many parameters", 413, "parameters.too.many"));
1393
1432
  }
@@ -1401,7 +1440,7 @@ const urlencoded = createBodyParser(
1401
1440
  arrayLimit: Math.max(100, count + 1),
1402
1441
  charsetSentinel: options.charsetSentinel,
1403
1442
  interpretNumericEntities: options.interpretNumericEntities,
1404
- charset: encoding,
1443
+ charset: /** @type {"utf-8"|"iso-8859-1"} */ (encoding),
1405
1444
  parameterLimit: options.parameterLimit
1406
1445
  };
1407
1446
  req.body = needsQs
@@ -1419,7 +1458,7 @@ const urlencoded = createBodyParser(
1419
1458
  strictDepth: true,
1420
1459
  charsetSentinel: options.charsetSentinel,
1421
1460
  interpretNumericEntities: options.interpretNumericEntities,
1422
- charset: encoding,
1461
+ charset: /** @type {"utf-8"|"iso-8859-1"} */ (encoding),
1423
1462
  parameterLimit: options.parameterLimit
1424
1463
  })
1425
1464
  );
package/src/nest.js CHANGED
@@ -47,7 +47,8 @@ const fulmine = require("./index.js");
47
47
  */
48
48
  class FulmineExpressAdapter extends ExpressAdapter {
49
49
  /**
50
- * @param {any} [instance] an application from `fulmine()`; one is created when omitted
50
+ * @param {import("fulmine.js").FulmineApplication} [instance] an application from `fulmine()`; one is
51
+ * created when omitted
51
52
  */
52
53
  constructor(instance) {
53
54
  super(instance || fulmine());
@@ -61,7 +62,7 @@ class FulmineExpressAdapter extends ExpressAdapter {
61
62
  /**
62
63
  * The app is the server. Nest calls this once, from NestApplication's constructor.
63
64
  *
64
- * @param {any} [options] the options NestFactory.create was given
65
+ * @param {import("@nestjs/common").NestApplicationOptions} [options] the options NestFactory.create was given
65
66
  * @returns {void}
66
67
  */
67
68
  initHttpServer(options) {
package/src/node-shim.js CHANGED
@@ -389,7 +389,7 @@ class NodeHttpResponse {
389
389
 
390
390
  /**
391
391
  * Whether these are node's own request and response rather than this project's.
392
- * @param {any} req anything a caller handed the router, which is the point of the check
392
+ * @param {unknown} req anything a caller handed the router, which is the point of the check
393
393
  */
394
394
  function isNodeRequest(req) {
395
395
  return req instanceof IncomingMessage;
@@ -398,14 +398,19 @@ function isNodeRequest(req) {
398
398
  /**
399
399
  * Serves a request that arrived through node's HTTP server with the given router or app.
400
400
  *
401
- * @param {any} router the router or application serving this request
401
+ * @param {import("./router.js")} router the router or application serving this request
402
402
  * @param {import("http").IncomingMessage} nodeReq
403
403
  * @param {import("http").ServerResponse} nodeRes
404
- * @param {(err?: any) => void} [next] called when nothing in the router answered
404
+ * @param {(err?: unknown) => void} [next] called when nothing in the router answered
405
405
  */
406
406
  function serveNodeRequest(router, nodeReq, nodeRes, next) {
407
- const shimRes = new NodeHttpResponse(nodeReq, nodeRes);
408
- const shimReq = new NodeHttpRequest(nodeReq);
407
+ // the shims stand in for uWS's pair, and the checker is told so once, here
408
+ const shimRes = /** @type {import("uWebSockets.js").HttpResponse} */ (
409
+ /** @type {unknown} */ (new NodeHttpResponse(nodeReq, nodeRes))
410
+ );
411
+ const shimReq = /** @type {import("uWebSockets.js").HttpRequest} */ (
412
+ /** @type {unknown} */ (new NodeHttpRequest(nodeReq))
413
+ );
409
414
  const request = router.handleRequest(shimRes, shimReq);
410
415
  const response = request.res;
411
416
  // the shim's onAborted rides node's own close event, needed on every request here
package/src/optimizer.js CHANGED
@@ -19,6 +19,7 @@ limitations under the License.
19
19
 
20
20
  /** @typedef {import("./router.js")} Router */
21
21
  /** @typedef {import("./router-utils.js").RouteEntry} RouteEntry */
22
+ /** @typedef {import("./application.js").Application} Application */
22
23
 
23
24
  const {
24
25
  patternToRegex,
@@ -53,7 +54,7 @@ const {
53
54
  let Router;
54
55
 
55
56
  /**
56
- * @param {any} cls the Router class, passed in to keep this module out of its require cycle
57
+ * @param {typeof import("./router.js")} cls the Router class, passed in to keep this module out of its require cycle
57
58
  */
58
59
  function useRouterClass(cls) {
59
60
  Router = cls;
@@ -66,8 +67,8 @@ function useRouterClass(cls) {
66
67
  *
67
68
  * @param {Router} router
68
69
  * @param {RouteEntry} route
69
- * @param {any[]} routes every route of this router, in registration order
70
- * @returns {any[]|false} the chain, ending in the route itself
70
+ * @param {RouteEntry[]} routes every route of this router, in registration order
71
+ * @returns {RouteEntry[]|false} the chain, ending in the route itself
71
72
  */
72
73
  function optimizeRoute(router, route, routes) {
73
74
  const optimizedPath = [];
@@ -216,7 +217,8 @@ function optimizeRoute(router, route, routes) {
216
217
  * routers and carrying their prefix down. Runs once, when the app starts listening, since it
217
218
  * needs every route to have been registered first.
218
219
  *
219
- * @param {any} root the application whose routes are being compiled
220
+ * @param {Router} root the application whose routes are being compiled. A plain router, which has
221
+ * no uwsApp, is left alone
220
222
  */
221
223
  function compileOptimizedRoutes(root) {
222
224
  if (!root.uwsApp) {
@@ -376,7 +378,7 @@ function compileOptimizedRoutes(root) {
376
378
  *
377
379
  * @param {Router} router
378
380
  * @param {RouteEntry} route
379
- * @param {any[]} optimizedPath the routes to run, in order, ending with this one
381
+ * @param {RouteEntry[]} optimizedPath the routes to run, in order, ending with this one
380
382
  */
381
383
  function registerUwsRoute(router, route, optimizedPath) {
382
384
  let method = route.method.toLowerCase();
@@ -428,7 +430,7 @@ function registerUwsRoute(router, route, optimizedPath) {
428
430
  // this one
429
431
  if (caseGuards !== null && anyGuardHits(caseGuards, req.getUrl())) {
430
432
  // an application is what registers native routes, and only it serves
431
- return /** @type {any} */ (router)._serveGeneric(res, req);
433
+ return /** @type {Application} */ (router)._serveGeneric(res, req);
432
434
  }
433
435
  const request = router.handleRequest(res, req, preset, skipHolder);
434
436
  const response = request.res;
@@ -526,7 +528,7 @@ function registerUwsRoute(router, route, optimizedPath) {
526
528
 
527
529
  // the response prototype the route will really run under: its own app's, which sees a
528
530
  // method patched there or inherited from a parent app, falling back to the registering app
529
- const responseProto = /** @type {any} */ (route.owner)?.response ?? /** @type {any} */ (router).response;
531
+ const responseProto = route.owner?.response ?? /** @type {Application} */ (router).response;
530
532
  // check if route is declarative
531
533
  if (
532
534
  optimizedPath.length === 1 && // must not have middlewares
package/src/options.d.ts CHANGED
@@ -42,8 +42,8 @@ export interface SendFileOptions {
42
42
  dotfiles?: "allow" | "deny" | "ignore" | "ignore_files";
43
43
  /** Extra headers for the response. */
44
44
  headers?: Record<string, string>;
45
- /** Called before the file goes out, to set headers from the path or its stat. */
46
- setHeaders?: (res: any, path: string, stat: any) => void;
45
+ /** Internal: express.static's setHeaders, which res.sendFile itself does not take. */
46
+ _setHeaders?: (res: any, path: string, stat: any) => void;
47
47
  /** First byte of the window to send. */
48
48
  start?: number;
49
49
  /** Last byte of the window to send. */
@@ -68,6 +68,8 @@ export interface StaticOptions extends SendFileOptions {
68
68
  fallthrough?: boolean;
69
69
  /** Extensions tried when the path names no file, or false to try none. */
70
70
  extensions?: string[] | false;
71
+ /** Called before the file goes out, to set headers from the path or its stat. */
72
+ setHeaders?: (res: any, path: string, stat: any) => void;
71
73
  /**
72
74
  * Serve `file.br` or `file.gz` in place of `file` when one is on disk and the client takes it.
73
75
  * Off by default. Vary: Accept-Encoding is sent whether or not a variant is found, and the
@@ -95,8 +97,11 @@ export interface BodyParserOptions {
95
97
  limit?: number | string;
96
98
  /** Which content types this parser claims. */
97
99
  type?: string | string[] | ((req: any) => boolean);
98
- /** Runs on the raw bytes before parsing, which is where a signature check belongs. */
99
- verify?: false | ((req: any, res: any, buf: Buffer, encoding: string) => void);
100
+ /**
101
+ * Runs on the raw bytes before parsing, which is where a signature check belongs. The charset
102
+ * is the one the body is decoded with, null for raw, as body-parser hands it over.
103
+ */
104
+ verify?: false | ((req: any, res: any, buf: Buffer, encoding: string | null) => void);
100
105
  /** Whether a compressed body is decompressed rather than refused. */
101
106
  inflate?: boolean;
102
107
  /** The charset assumed when the request names none. */
@@ -23,7 +23,8 @@ const { isIP } = require("node:net");
23
23
 
24
24
  // accepts, type-is, proxy-addr and fresh declare a node IncomingMessage but read only .headers off
25
25
  // it. This request is not one, so it is passed as itself and the declared type is stepped around.
26
- const asMessage = (req) => /** @type {any} */ (req);
26
+ /** @param {Request} req @returns {import("http").IncomingMessage} */
27
+ const asMessage = (req) => /** @type {import("http").IncomingMessage} */ (/** @type {unknown} */ (req));
27
28
 
28
29
  /**
29
30
  * Writes an address like node's socket.remoteAddress, which is inet_ntop and so RFC 5952: leading
@@ -106,7 +107,7 @@ function isMappedIPv4(bytes) {
106
107
  * whenever the listener is dual stack, which is every listen() without an IPv4 address. uWS already
107
108
  * gives mapped peers as sixteen bytes, four bytes come only from a v4 listener or the node shim.
108
109
  *
109
- * @param {any} app the application the request arrived at
110
+ * @param {import("./application.js").Application} app the application the request arrived at
110
111
  * @returns {boolean}
111
112
  */
112
113
  function mapsIPv4Peer(app) {