fulmine.js 5.19.3 → 5.19.5

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/src/usage.js CHANGED
@@ -18,6 +18,8 @@ limitations under the License.
18
18
 
19
19
  const acorn = require("acorn");
20
20
 
21
+ /** @typedef {import("./router-utils.js").RouteEntry} RouteEntry */
22
+
21
23
  // Marks a middleware the analysis may trust on a GET request without reading its source: the
22
24
  // body parsers set it, whose prologue only reads body-framing headers and leaves a bodyless
23
25
  // GET alone (and a GET that declares a body falls back to the full header copy).
@@ -107,7 +109,7 @@ function analyze(fn) {
107
109
  // class methods and native functions do not parse alone, and unread code is unknown code
108
110
  return UNKNOWN;
109
111
  }
110
- let root = /** @type {any} */ (tree.body[0]);
112
+ let root = /** @type {import("acorn").AnyNode} */ (tree.body[0]);
111
113
  if (!root) {
112
114
  return UNKNOWN;
113
115
  }
@@ -122,7 +124,8 @@ function analyze(fn) {
122
124
  return UNKNOWN;
123
125
  }
124
126
 
125
- const params = /** @type {any[]} */ (root.params);
127
+ // checked by the loop below, which answers UNKNOWN for anything else
128
+ const params = /** @type {import("acorn").Identifier[]} */ (root.params);
126
129
  // rest or destructured parameters alias the objects somewhere the walk cannot follow
127
130
  for (const p of params) {
128
131
  if (p.type !== "Identifier") {
@@ -231,9 +234,11 @@ function analyze(fn) {
231
234
  * Walks every node, handing each its parent. Arrays and nested objects are entered, nothing
232
235
  * is interpreted: the judging happens in the visitor.
233
236
  *
234
- * @param {any} node an acorn AST node. acorn ships no useful node types, and every shape here is checked by hand
237
+ * @param {any} node an acorn node, or an array or a scalar under one: walked by key, so no shape
238
+ * is assumed
235
239
  * @param {any} parent its parent node, or null at the root
236
- * @param {(node: any, parent: any) => void} visit
240
+ * @param {(node: any, parent: any) => void} visit handed every node, loose because the visitor
241
+ * reads edges of its own off each
237
242
  */
238
243
  function walk(node, parent, visit) {
239
244
  if (!node || typeof node.type !== "string") {
@@ -265,7 +270,7 @@ function walk(node, parent, visit) {
265
270
  * it passes only when no later route could catch the fall-through. The framework's own 404 answers
266
271
  * from the path alone.
267
272
  *
268
- * @param {any[]} chain the routes the native handler runs, in order, this route last
273
+ * @param {RouteEntry[]} chain the routes the native handler runs, in order, this route last
269
274
  * @param {boolean} allowTerminalNext whether a fall-through past the chain lands only in the
270
275
  * framework's own final answer
271
276
  * @returns {{skipHeaders: boolean, skipQuery: boolean}}
package/src/utils.js CHANGED
@@ -30,6 +30,19 @@ const { Stats } = require("fs");
30
30
 
31
31
  /** @typedef {import("./request.js")} Request */
32
32
  /** @typedef {import("./response.js")} Response */
33
+ /**
34
+ * An error with the http-errors fields on it, which are what `res.status(err.status || 500)`, the
35
+ * error page and the body parsers read. None is required: a plain throw carries none of them.
36
+ * @typedef {Error & {
37
+ * status?: number,
38
+ * statusCode?: number,
39
+ * expose?: boolean,
40
+ * code?: string,
41
+ * type?: string,
42
+ * types?: string[],
43
+ * headers?: Record<string, any>
44
+ * }} HttpError
45
+ */
33
46
 
34
47
  const EMPTY_REGEX = new RegExp(``);
35
48
 
@@ -765,8 +778,9 @@ const MEMO_LIMIT = 512;
765
778
  * The wrapped function must never answer undefined, since that is what the cache reads as a miss.
766
779
  * The mime lookups here answer false for something they do not know, which caches correctly.
767
780
  *
768
- * @param {(key: string) => any} fn
769
- * @returns {(key: string) => any}
781
+ * @template T
782
+ * @param {(key: string) => T} fn
783
+ * @returns {(key: string) => T}
770
784
  */
771
785
  function memoizeByString(fn) {
772
786
  const cache = new Map();
@@ -794,6 +808,16 @@ const lookupType = memoizeByString((type) => mime.lookup(type) || "application/o
794
808
  */
795
809
  const contentTypeFor = memoizeByString((type) => mime.contentType(type) || "application/octet-stream");
796
810
 
811
+ /**
812
+ * The content-type res.set stores for a value, which is what express stores: an extension resolved
813
+ * to its media type with the charset the database gives it, and false when it resolves to nothing.
814
+ * A false there is falsy for every default below it, so send() and json() write their own.
815
+ *
816
+ * @param {string} value
817
+ * @returns {string|false}
818
+ */
819
+ const contentTypeSet = memoizeByString((value) => mime.contentType(value));
820
+
797
821
  /**
798
822
  * A media type from either spelling: "html" is looked up in the mime database, while anything
799
823
  * containing a slash is already one and is parsed for its parameters.
@@ -811,7 +835,7 @@ function normalizeType(type) {
811
835
  * unicode escapes, so a string in the body cannot close a script tag in an HTML page that embeds
812
836
  * the response.
813
837
  *
814
- * @param {any} value whatever the handler passed to res.json
838
+ * @param {unknown} value whatever the handler passed to res.json
815
839
  * @param {any} [replacer] the "json replacer" setting
816
840
  * @param {string|number} [spaces] the "json spaces" setting
817
841
  * @param {boolean} [escape] the "json escape" setting
@@ -1042,13 +1066,17 @@ function cachedStat(file, ttl) {
1042
1066
  /**
1043
1067
  * A duration setting as milliseconds: false is off, a string is read by ms, a number is itself.
1044
1068
  *
1045
- * @param {any} value the setting as the application wrote it
1069
+ * @param {string|number|boolean|undefined} value the setting as the application wrote it
1046
1070
  * @param {string} name for the error, which names the setting the application wrote
1047
1071
  * @returns {number}
1048
1072
  */
1049
1073
  function durationSetting(value, name) {
1050
1074
  const parsed =
1051
- value === false || value === undefined ? 0 : typeof value === "string" ? ms(/** @type {any} */ (value)) : value;
1075
+ value === false || value === undefined
1076
+ ? 0
1077
+ : typeof value === "string"
1078
+ ? ms(/** @type {import("ms").StringValue} */ (value))
1079
+ : value;
1052
1080
  if (typeof parsed !== "number" || !(parsed >= 0)) {
1053
1081
  throw new TypeError(`${name} must be a duration`);
1054
1082
  }
@@ -1129,8 +1157,9 @@ function deprecated(oldMethod, newMethod, full = false) {
1129
1157
  * request, each time picking up after the route it just ran, and Array.findIndex has no way to
1130
1158
  * start anywhere but the beginning.
1131
1159
  *
1132
- * @param {any[]} arr
1133
- * @param {(item: any, index: number, arr: any[]) => boolean} fn
1160
+ * @template T
1161
+ * @param {T[]} arr
1162
+ * @param {(item: T, index: number, arr: T[]) => boolean} fn
1134
1163
  * @param {number} [index] where to start
1135
1164
  * @returns {number} the index, or -1
1136
1165
  */
@@ -1165,7 +1194,7 @@ function decode(path) {
1165
1194
  *
1166
1195
  * @param {string} value
1167
1196
  * @returns {string}
1168
- * @throws {any} carrying status 400 when the value cannot be decoded
1197
+ * @throws {HttpError} carrying status 400 when the value cannot be decoded
1169
1198
  */
1170
1199
  function decodeParam(value) {
1171
1200
  // the common case, and worth the check: a parameter is usually a number or a word, and
@@ -1178,7 +1207,8 @@ function decodeParam(value) {
1178
1207
  } catch {
1179
1208
  // a URIError, not an Error: express throws what decodeURIComponent threw, so an error
1180
1209
  // handler written as `err instanceof URIError` has to keep working here
1181
- const err = /** @type {any} */ (new URIError(`Failed to decode param '${value}'`));
1210
+ /** @type {HttpError} */
1211
+ const err = new URIError(`Failed to decode param '${value}'`);
1182
1212
  err.status = 400;
1183
1213
  err.statusCode = 400;
1184
1214
  err.expose = true;
@@ -1335,7 +1365,7 @@ function statTag(stat, weak) {
1335
1365
  * ETag comes from its size and mtime while a body's comes from its contents.
1336
1366
  *
1337
1367
  * @param {{weak: boolean}} options
1338
- * @returns {(body: any, encoding?: BufferEncoding) => string}
1368
+ * @returns {(body: string|Buffer|import("fs").Stats, encoding?: BufferEncoding) => string}
1339
1369
  */
1340
1370
  function createETagGenerator(options) {
1341
1371
  return function generateETag(body, encoding) {
@@ -1362,14 +1392,15 @@ function createETagGenerator(options) {
1362
1392
  * @returns {boolean}
1363
1393
  */
1364
1394
  function isRangeFresh(req, res) {
1365
- const ifRange = req.headers["if-range"];
1395
+ // folded to one string, as every header but set-cookie is
1396
+ const ifRange = /** @type {string|undefined} */ (req.headers["if-range"]);
1366
1397
  if (!ifRange) {
1367
1398
  return true;
1368
1399
  }
1369
1400
 
1370
1401
  // if-range as etag
1371
1402
  if (ifRange.indexOf('"') !== -1) {
1372
- const etag = res.get("etag");
1403
+ const etag = /** @type {string|undefined} */ (res.get("etag"));
1373
1404
  return Boolean(etag && ifRange.indexOf(etag) !== -1);
1374
1405
  }
1375
1406
 
@@ -1496,16 +1527,73 @@ function headerError(message, code) {
1496
1527
  void err.stack;
1497
1528
  // back to the prototype's "TypeError", which is what node leaves behind. Cast because Error
1498
1529
  // declares name as always present, and this deletes the own property to uncover it again
1499
- delete (/** @type {any} */ (err).name);
1530
+ delete (/** @type {{name?: string}} */ (err).name);
1500
1531
  err.code = code;
1501
1532
  return err;
1502
1533
  }
1503
1534
 
1535
+ /**
1536
+ * node's ERR_HTTP_HEADERS_SENT, for a head that can no longer change: "set" from setHeader,
1537
+ * "remove" from removeHeader, "write" from writeHead, which is how node words each one.
1538
+ *
1539
+ * @param {string} verb
1540
+ * @returns {NodeJS.ErrnoException}
1541
+ */
1542
+ function headersSentError(verb) {
1543
+ /** @type {NodeJS.ErrnoException} */
1544
+ const err = new Error(`Cannot ${verb} headers after they are sent to the client`);
1545
+ // the first line of the stack reads as node's, see headerError
1546
+ err.name = "Error [ERR_HTTP_HEADERS_SENT]";
1547
+ void err.stack;
1548
+ delete (/** @type {{name?: string}} */ (err).name);
1549
+ err.code = "ERR_HTTP_HEADERS_SENT";
1550
+ return err;
1551
+ }
1552
+
1553
+ /**
1554
+ * Applies the headers a writeHead call carries, in either of node's shapes, (status, headers) or
1555
+ * (status, reason, headers), and answers the reason phrase when there was one. Shared with the
1556
+ * middleware that hooks writeHead: what it decides at the head has to see these first, the way
1557
+ * on-headers applies them before its listeners run.
1558
+ *
1559
+ * @param {{setHeader(name: string, value: import("http").OutgoingHttpHeader|undefined): unknown}} res
1560
+ * @param {string|import("http").OutgoingHttpHeaders|import("http").OutgoingHttpHeader[]} [statusMessage]
1561
+ * @param {import("http").OutgoingHttpHeaders|import("http").OutgoingHttpHeader[]} [headers]
1562
+ * @returns {string|undefined} the reason phrase
1563
+ */
1564
+ function applyWriteHead(res, statusMessage, headers) {
1565
+ let reason;
1566
+ if (typeof statusMessage === "string") {
1567
+ reason = statusMessage;
1568
+ } else if (!headers) {
1569
+ // the two-argument shape, where what looked like a reason phrase is the headers
1570
+ headers = statusMessage;
1571
+ }
1572
+ if (Array.isArray(headers)) {
1573
+ // node takes a flat list here, name then value, and not a list of pairs. An odd length is
1574
+ // the caller's mistake and node names the argument in what it throws
1575
+ if (headers.length % 2 !== 0) {
1576
+ /** @type {NodeJS.ErrnoException} */
1577
+ const err = new TypeError(`The argument 'headers' is invalid. Received ${JSON.stringify(headers)}`);
1578
+ err.code = "ERR_INVALID_ARG_VALUE";
1579
+ throw err;
1580
+ }
1581
+ for (let i = 0; i < headers.length; i += 2) {
1582
+ res.setHeader(/** @type {string} */ (headers[i]), headers[i + 1]);
1583
+ }
1584
+ } else if (headers) {
1585
+ for (const header in headers) {
1586
+ res.setHeader(header, headers[header]);
1587
+ }
1588
+ }
1589
+ return reason;
1590
+ }
1591
+
1504
1592
  /**
1505
1593
  * Refuses a header name that is not an HTTP token, the way node's setHeader does and with its
1506
1594
  * error, so an application catching ERR_INVALID_HTTP_TOKEN behind Express catches it here.
1507
1595
  *
1508
- * @param {any} name whatever a caller passed as a header name, which is what is being checked
1596
+ * @param {unknown} name whatever a caller passed as a header name, which is what is being checked
1509
1597
  * @returns {void}
1510
1598
  * @throws {TypeError} if the name is not a token, which includes not being a string
1511
1599
  */
@@ -1579,21 +1667,39 @@ const STAT_ERROR_STATUS = { ENAMETOOLONG: 404, ENOTDIR: 404, ENOENT: 404 };
1579
1667
  * of that handler as 500. The message is the status's own name, as http-errors writes it.
1580
1668
  *
1581
1669
  * @param {number} status
1582
- * @returns {any}
1670
+ * @param {string} [message] the status text unless given, as http-errors has it
1671
+ * @returns {HttpError}
1583
1672
  */
1584
- function httpError(status) {
1585
- const message = statuses.message[status] ?? "Error";
1586
- const err = /** @type {any} */ (new Error(message));
1673
+ function httpError(status, message = statuses.message[status] ?? "Error") {
1674
+ /** @type {HttpError} */
1675
+ const err = new Error(message);
1587
1676
  // http-errors names these BadRequestError, ForbiddenError and so on, and the name is what the
1588
1677
  // error page shows: an application looking at a 400 sees the same word Express shows it. Set
1589
1678
  // before anything reads the stack, which V8 formats on first read
1590
- err.name = `${message.replace(/\W/g, "")}Error`;
1679
+ err.name = httpErrorName(status);
1591
1680
  err.expose = status < 500;
1592
1681
  err.statusCode = status;
1593
1682
  err.status = status;
1594
1683
  return err;
1595
1684
  }
1596
1685
 
1686
+ /**
1687
+ * The name http-errors gives an error for a status, NotFoundError for 404: each word of the status
1688
+ * text capitalised and run together, plus Error unless it already ends in it, which is how
1689
+ * "Internal Server Error" stays InternalServerError.
1690
+ *
1691
+ * @param {number} status
1692
+ * @returns {string}
1693
+ */
1694
+ function httpErrorName(status) {
1695
+ const name = (statuses.message[status] ?? "Error")
1696
+ .split(" ")
1697
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
1698
+ .join("")
1699
+ .replace(/[^ _0-9a-z]/gi, "");
1700
+ return name.endsWith("Error") ? name : name + "Error";
1701
+ }
1702
+
1597
1703
  /**
1598
1704
  * Marks an fs error the way send does before it is handed on, so an error handler reading
1599
1705
  * err.status or err.statusCode finds what it would find behind Express. The three properties are
@@ -1602,8 +1708,8 @@ function httpError(status) {
1602
1708
  *
1603
1709
  * The error itself is returned rather than a new one, so its errno, code, syscall and path survive.
1604
1710
  *
1605
- * @param {any} err the fs error, which carries its errno and path
1606
- * @returns {any} the same error
1711
+ * @param {HttpError} err the fs error, which carries its errno and path
1712
+ * @returns {HttpError} the same error
1607
1713
  */
1608
1714
  function asStatError(err) {
1609
1715
  err.expose = false;
@@ -1616,8 +1722,7 @@ function asStatError(err) {
1616
1722
  // A constructor whose instances have no prototype, so a key from a request body or a query string
1617
1723
  // cannot reach Object.prototype. Typed as returning a plain record: without that, assigning one
1618
1724
  // reads as assigning `any`, which resets narrowing instead of removing undefined from it.
1619
- /** @type {new () => Record<string, any>} */
1620
- const NullObject = /** @type {any} */ (function () {});
1725
+ const NullObject = /** @type {new () => Record<string, any>} */ (/** @type {unknown} */ (function () {}));
1621
1726
  NullObject.prototype = Object.create(null);
1622
1727
 
1623
1728
  module.exports = {
@@ -1646,6 +1751,7 @@ module.exports = {
1646
1751
  entityTag,
1647
1752
  statTag,
1648
1753
  contentTypeFor,
1754
+ contentTypeSet,
1649
1755
  negotiateEncoding,
1650
1756
  ENCODING_BR,
1651
1757
  ENCODING_GZIP,
@@ -1669,6 +1775,9 @@ module.exports = {
1669
1775
  withUtf8Charset,
1670
1776
  asStatError,
1671
1777
  httpError,
1778
+ httpErrorName,
1779
+ headersSentError,
1780
+ applyWriteHead,
1672
1781
  EMPTY_REGEX,
1673
1782
  settingsEpoch
1674
1783
  };
package/src/verify.js CHANGED
@@ -109,7 +109,8 @@ function checkNode(running = process.versions.node, required = require("../packa
109
109
  * @returns {string|undefined}
110
110
  */
111
111
  function currentGlibc() {
112
- return /** @type {any} */ (process.report.getReport()).header.glibcVersionRuntime;
112
+ return /** @type {{header: {glibcVersionRuntime?: string}}} */ (process.report.getReport()).header
113
+ .glibcVersionRuntime;
113
114
  }
114
115
 
115
116
  /**
@@ -206,7 +207,7 @@ function checkBinary(platform = process.platform, arch = process.arch, abi = pro
206
207
  */
207
208
  function abiToNode(abi) {
208
209
  const known = { 108: "18", 115: "20", 127: "22", 131: "23", 137: "24", 147: "26" };
209
- return /** @type {any} */ (known)[abi] ?? `ABI ${abi}`;
210
+ return known[abi] ?? `ABI ${abi}`;
210
211
  }
211
212
 
212
213
  /**
@@ -269,7 +270,7 @@ function checkDependencies(dir) {
269
270
  const installed = { ...pkg.dependencies, ...pkg.devDependencies };
270
271
  for (const name of Object.keys(NEEDS_A_LOOK)) {
271
272
  if (installed[name]) {
272
- results.push(result("note", `${name} needs a different API here`, /** @type {any} */ (NEEDS_A_LOOK)[name]));
273
+ results.push(result("note", `${name} needs a different API here`, NEEDS_A_LOOK[name]));
273
274
  }
274
275
  }
275
276
  return results;
package/src/view.js CHANGED
@@ -113,7 +113,7 @@ module.exports = class View {
113
113
  this.engine(
114
114
  this.path,
115
115
  options,
116
- /** @this {any} */ function onRender() {
116
+ /** @this {unknown} */ function onRender() {
117
117
  if (!sync) {
118
118
  return callback.apply(this, arguments);
119
119
  }
package/src/walk.js CHANGED
@@ -31,6 +31,7 @@ const {
31
31
  /** @typedef {import("./request.js")} Request */
32
32
  /** @typedef {import("./response.js")} Response */
33
33
  /** @typedef {import("./router.js")} Router */
34
+ /** @typedef {import("./router-utils.js").RouteEntry} RouteEntry */
34
35
 
35
36
  /**
36
37
  * One walk of one router's routes, for one request.
@@ -46,12 +47,12 @@ class Walk {
46
47
  * @param {Router} router
47
48
  * @param {Request} req
48
49
  * @param {Response} res
49
- * @param {any[]} routes the route table being walked, see createRoute in router.js
50
+ * @param {RouteEntry[]} routes the route table being walked, see createRoute in router.js
50
51
  * @param {boolean} skipCheck take the route at the index without matching it, which is how an
51
52
  * already-decided chain is walked
52
- * @param {any} skipUntil route to resume after when this chain runs out, or undefined
53
- * @param {(value: any) => void} resolve
54
- * @param {(err: any) => void} reject
53
+ * @param {RouteEntry|undefined} skipUntil route to resume after when this chain runs out, or undefined
54
+ * @param {(value: RouteEntry|false) => void} resolve
55
+ * @param {(err: unknown) => void} reject
55
56
  */
56
57
  constructor(router, req, res, routes, skipCheck, skipUntil, resolve, reject) {
57
58
  this.router = router;
@@ -85,7 +86,7 @@ class Walk {
85
86
  * Leaves the rest of this route, with the error if there is one, and carries on with the route
86
87
  * after it.
87
88
  *
88
- * @param {any} [err] whatever was thrown, which need not be an Error
89
+ * @param {unknown} [err] whatever was thrown, which need not be an Error
89
90
  */
90
91
  stepOutOfRoute(err) {
91
92
  if (err) {
@@ -289,7 +290,7 @@ class Walk {
289
290
  * Enters the route the walk is on: a mount adjusts req.url, req.path and the mount stack on the
290
291
  * way in, and then the route's callbacks run one after another through next().
291
292
  *
292
- * @param {any} continueRoute what _preprocessRequest decided: true to run, "route" to skip
293
+ * @param {true|"route"} continueRoute what _preprocessRequest decided: true to run, "route" to skip
293
294
  */
294
295
  runRoute(continueRoute) {
295
296
  const req = this.req;
@@ -439,7 +440,7 @@ class Walk {
439
440
  * One hop, which is what next() does: with nothing, run the route's next callback; with "route",
440
441
  * leave the route; with anything else, remember it as the error and carry on.
441
442
  *
442
- * @param {any} thingamabob what next() was called with: nothing, "route", or an error
443
+ * @param {unknown} thingamabob what next() was called with: nothing, "route", or an error
443
444
  */
444
445
  step(thingamabob) {
445
446
  const req = this.req;
package/src/websocket.js CHANGED
@@ -20,6 +20,12 @@ const { canBeOptimizedWithParams, decodeParam, NullObject } = require("./utils.j
20
20
 
21
21
  /** @typedef {import("./router.js")} Router */
22
22
  /** @typedef {import("./application.js").Application} Application */
23
+ /** @typedef {import("./request.js")} Request */
24
+ /** @typedef {import("./response.js")} Response */
25
+ /** @typedef {import("./router-utils.js").WsRoute} WsRoute */
26
+ /** @typedef {import("uWebSockets.js").HttpRequest} UwsRequest */
27
+ /** @typedef {import("uWebSockets.js").HttpResponse} UwsResponse */
28
+ /** @typedef {import("uWebSockets.js").us_socket_context_t} UwsContext */
23
29
 
24
30
  // the parameter names in a path, in the order µWS numbers them
25
31
  const PARAM = /:(\w+)/g;
@@ -55,8 +61,8 @@ function joinPaths(prefix, path) {
55
61
  * @param {Router} router
56
62
  * @param {string|null} prefix the mount path accumulated so far, or null once a mount was a
57
63
  * shape µWS cannot match, which makes everything below it unreachable
58
- * @param {any[]} out
59
- * @param {Set<any>} seen routers already walked, since a router may be mounted twice
64
+ * @param {WsRoute[]} out
65
+ * @param {Set<Router>} seen routers already walked, since a router may be mounted twice
60
66
  */
61
67
  function collectRoutes(router, prefix, out, seen) {
62
68
  if (seen.has(router)) {
@@ -102,14 +108,17 @@ function collectRoutes(router, prefix, out, seen) {
102
108
  * The uWS upgrade handler for one route: builds this project's request and response, offers them
103
109
  * to the application's own `upgrade` hook, and completes the handshake unless that hook answered.
104
110
  *
105
- * @param {Application} app the application whose request and response classes serve this route
111
+ * @param {Router} app the router whose request and response classes serve this route
106
112
  * @param {string} path the composed path, whose parameters are read back by index
107
- * @param {any} behavior what the caller registered
108
- * @returns {(res: any, req: any, context: any) => void}
113
+ * @param {Record<string, unknown>} behavior what the caller registered
114
+ * @returns {(res: UwsResponse, req: UwsRequest, context: UwsContext) => void}
109
115
  */
110
116
  function makeUpgradeHandler(app, path, behavior) {
111
117
  const paramNames = [...path.matchAll(PARAM)].map((match) => match[1]);
112
- const userUpgrade = behavior.upgrade;
118
+ // a function, checked by checkBehavior where it was registered
119
+ const userUpgrade = /** @type {((req: Request, res: Response) => void|Promise<void>)|undefined} */ (
120
+ behavior.upgrade
121
+ );
113
122
 
114
123
  return (res, req, context) => {
115
124
  // read off the uWS request before anything can await: it is neutered on return, and the
@@ -122,7 +131,7 @@ function makeUpgradeHandler(app, path, behavior) {
122
131
  if (paramNames.length) {
123
132
  const params = new NullObject();
124
133
  for (let i = 0; i < paramNames.length; i++) {
125
- params[paramNames[i]] = decodeParam(req.getParameter(i));
134
+ params[paramNames[i]] = decodeParam(/** @type {string} */ (req.getParameter(i)));
126
135
  }
127
136
  request.params = params;
128
137
  }
@@ -216,7 +225,7 @@ function registerWebSocketRoutes(app) {
216
225
  * used: a handler under a misspelled name would otherwise never run and never say why.
217
226
  *
218
227
  * @param {string} path
219
- * @param {any} behavior uWS's WebSocketBehavior, whose shipped typings do not describe it
228
+ * @param {unknown} behavior whatever was passed as one, which is what is being checked
220
229
  */
221
230
  function checkBehavior(path, behavior) {
222
231
  if (typeof path !== "string") {
package/src/work.js CHANGED
@@ -54,7 +54,7 @@ function work(req, res) {
54
54
  const native = req.route?._native;
55
55
  // cast for the three the classes do not declare: `body` is deliberately not a field of
56
56
  // Request, and the two stream states are node's own, written when a lazy stream is built
57
- const loose = /** @type {any} */ (req);
57
+ const loose = /** @type {{body?: unknown, _readableState?: unknown}} */ (req);
58
58
  return {
59
59
  native: Boolean(native),
60
60
  declarative: Boolean(native?.declarative),
@@ -62,7 +62,7 @@ function work(req, res) {
62
62
  query: req._queryParsed,
63
63
  body: loose.body !== undefined,
64
64
  requestStream: loose._readableState !== undefined,
65
- responseStream: /** @type {any} */ (res)._writableState !== undefined,
65
+ responseStream: /** @type {{_writableState?: unknown}} */ (res)._writableState !== undefined,
66
66
  socket: req._socketBuilt || res._socketBuilt
67
67
  };
68
68
  }
@@ -86,7 +86,7 @@ const NAMES = [
86
86
  function names(done) {
87
87
  const listed = [];
88
88
  for (const [key, name] of NAMES) {
89
- if (/** @type {any} */ (done)[key]) {
89
+ if (done[key]) {
90
90
  listed.push(name);
91
91
  }
92
92
  }