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.
- package/package.json +1 -1
- package/src/application.js +25 -36
- package/src/cli.js +22 -16
- package/src/cluster.js +2 -2
- package/src/compression.js +102 -61
- package/src/declarative.js +47 -24
- package/src/hot-settings.js +5 -5
- package/src/lazy-readable.js +8 -6
- package/src/lazy-writable.js +3 -3
- package/src/middlewares.js +94 -55
- package/src/nest.js +3 -2
- package/src/node-shim.js +10 -5
- package/src/optimizer.js +9 -7
- package/src/options.d.ts +9 -4
- package/src/request-utils.js +3 -2
- package/src/request.js +49 -38
- package/src/response.js +145 -109
- package/src/route.js +3 -3
- package/src/router-utils.js +62 -14
- package/src/router.js +46 -28
- package/src/server-shape.js +14 -10
- package/src/server-timing.js +16 -4
- package/src/socket.js +3 -3
- package/src/testing.js +4 -3
- package/src/usage.js +10 -5
- package/src/utils.js +120 -23
- package/src/verify.js +4 -3
- package/src/view.js +1 -1
- package/src/walk.js +8 -7
- package/src/websocket.js +17 -8
- package/src/work.js +3 -3
package/src/utils.js
CHANGED
|
@@ -30,6 +30,18 @@ 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
|
+
* }} HttpError
|
|
44
|
+
*/
|
|
33
45
|
|
|
34
46
|
const EMPTY_REGEX = new RegExp(``);
|
|
35
47
|
|
|
@@ -765,8 +777,9 @@ const MEMO_LIMIT = 512;
|
|
|
765
777
|
* The wrapped function must never answer undefined, since that is what the cache reads as a miss.
|
|
766
778
|
* The mime lookups here answer false for something they do not know, which caches correctly.
|
|
767
779
|
*
|
|
768
|
-
* @
|
|
769
|
-
* @
|
|
780
|
+
* @template T
|
|
781
|
+
* @param {(key: string) => T} fn
|
|
782
|
+
* @returns {(key: string) => T}
|
|
770
783
|
*/
|
|
771
784
|
function memoizeByString(fn) {
|
|
772
785
|
const cache = new Map();
|
|
@@ -811,7 +824,7 @@ function normalizeType(type) {
|
|
|
811
824
|
* unicode escapes, so a string in the body cannot close a script tag in an HTML page that embeds
|
|
812
825
|
* the response.
|
|
813
826
|
*
|
|
814
|
-
* @param {
|
|
827
|
+
* @param {unknown} value whatever the handler passed to res.json
|
|
815
828
|
* @param {any} [replacer] the "json replacer" setting
|
|
816
829
|
* @param {string|number} [spaces] the "json spaces" setting
|
|
817
830
|
* @param {boolean} [escape] the "json escape" setting
|
|
@@ -1042,13 +1055,17 @@ function cachedStat(file, ttl) {
|
|
|
1042
1055
|
/**
|
|
1043
1056
|
* A duration setting as milliseconds: false is off, a string is read by ms, a number is itself.
|
|
1044
1057
|
*
|
|
1045
|
-
* @param {
|
|
1058
|
+
* @param {string|number|boolean|undefined} value the setting as the application wrote it
|
|
1046
1059
|
* @param {string} name for the error, which names the setting the application wrote
|
|
1047
1060
|
* @returns {number}
|
|
1048
1061
|
*/
|
|
1049
1062
|
function durationSetting(value, name) {
|
|
1050
1063
|
const parsed =
|
|
1051
|
-
value === false || value === undefined
|
|
1064
|
+
value === false || value === undefined
|
|
1065
|
+
? 0
|
|
1066
|
+
: typeof value === "string"
|
|
1067
|
+
? ms(/** @type {import("ms").StringValue} */ (value))
|
|
1068
|
+
: value;
|
|
1052
1069
|
if (typeof parsed !== "number" || !(parsed >= 0)) {
|
|
1053
1070
|
throw new TypeError(`${name} must be a duration`);
|
|
1054
1071
|
}
|
|
@@ -1129,8 +1146,9 @@ function deprecated(oldMethod, newMethod, full = false) {
|
|
|
1129
1146
|
* request, each time picking up after the route it just ran, and Array.findIndex has no way to
|
|
1130
1147
|
* start anywhere but the beginning.
|
|
1131
1148
|
*
|
|
1132
|
-
* @
|
|
1133
|
-
* @param {
|
|
1149
|
+
* @template T
|
|
1150
|
+
* @param {T[]} arr
|
|
1151
|
+
* @param {(item: T, index: number, arr: T[]) => boolean} fn
|
|
1134
1152
|
* @param {number} [index] where to start
|
|
1135
1153
|
* @returns {number} the index, or -1
|
|
1136
1154
|
*/
|
|
@@ -1165,7 +1183,7 @@ function decode(path) {
|
|
|
1165
1183
|
*
|
|
1166
1184
|
* @param {string} value
|
|
1167
1185
|
* @returns {string}
|
|
1168
|
-
* @throws {
|
|
1186
|
+
* @throws {HttpError} carrying status 400 when the value cannot be decoded
|
|
1169
1187
|
*/
|
|
1170
1188
|
function decodeParam(value) {
|
|
1171
1189
|
// the common case, and worth the check: a parameter is usually a number or a word, and
|
|
@@ -1178,7 +1196,8 @@ function decodeParam(value) {
|
|
|
1178
1196
|
} catch {
|
|
1179
1197
|
// a URIError, not an Error: express throws what decodeURIComponent threw, so an error
|
|
1180
1198
|
// handler written as `err instanceof URIError` has to keep working here
|
|
1181
|
-
|
|
1199
|
+
/** @type {HttpError} */
|
|
1200
|
+
const err = new URIError(`Failed to decode param '${value}'`);
|
|
1182
1201
|
err.status = 400;
|
|
1183
1202
|
err.statusCode = 400;
|
|
1184
1203
|
err.expose = true;
|
|
@@ -1335,7 +1354,7 @@ function statTag(stat, weak) {
|
|
|
1335
1354
|
* ETag comes from its size and mtime while a body's comes from its contents.
|
|
1336
1355
|
*
|
|
1337
1356
|
* @param {{weak: boolean}} options
|
|
1338
|
-
* @returns {(body:
|
|
1357
|
+
* @returns {(body: string|Buffer|import("fs").Stats, encoding?: BufferEncoding) => string}
|
|
1339
1358
|
*/
|
|
1340
1359
|
function createETagGenerator(options) {
|
|
1341
1360
|
return function generateETag(body, encoding) {
|
|
@@ -1362,14 +1381,15 @@ function createETagGenerator(options) {
|
|
|
1362
1381
|
* @returns {boolean}
|
|
1363
1382
|
*/
|
|
1364
1383
|
function isRangeFresh(req, res) {
|
|
1365
|
-
|
|
1384
|
+
// folded to one string, as every header but set-cookie is
|
|
1385
|
+
const ifRange = /** @type {string|undefined} */ (req.headers["if-range"]);
|
|
1366
1386
|
if (!ifRange) {
|
|
1367
1387
|
return true;
|
|
1368
1388
|
}
|
|
1369
1389
|
|
|
1370
1390
|
// if-range as etag
|
|
1371
1391
|
if (ifRange.indexOf('"') !== -1) {
|
|
1372
|
-
const etag = res.get("etag");
|
|
1392
|
+
const etag = /** @type {string|undefined} */ (res.get("etag"));
|
|
1373
1393
|
return Boolean(etag && ifRange.indexOf(etag) !== -1);
|
|
1374
1394
|
}
|
|
1375
1395
|
|
|
@@ -1496,16 +1516,73 @@ function headerError(message, code) {
|
|
|
1496
1516
|
void err.stack;
|
|
1497
1517
|
// back to the prototype's "TypeError", which is what node leaves behind. Cast because Error
|
|
1498
1518
|
// declares name as always present, and this deletes the own property to uncover it again
|
|
1499
|
-
delete (/** @type {
|
|
1519
|
+
delete (/** @type {{name?: string}} */ (err).name);
|
|
1500
1520
|
err.code = code;
|
|
1501
1521
|
return err;
|
|
1502
1522
|
}
|
|
1503
1523
|
|
|
1524
|
+
/**
|
|
1525
|
+
* node's ERR_HTTP_HEADERS_SENT, for a head that can no longer change: "set" from setHeader,
|
|
1526
|
+
* "remove" from removeHeader, "write" from writeHead, which is how node words each one.
|
|
1527
|
+
*
|
|
1528
|
+
* @param {string} verb
|
|
1529
|
+
* @returns {NodeJS.ErrnoException}
|
|
1530
|
+
*/
|
|
1531
|
+
function headersSentError(verb) {
|
|
1532
|
+
/** @type {NodeJS.ErrnoException} */
|
|
1533
|
+
const err = new Error(`Cannot ${verb} headers after they are sent to the client`);
|
|
1534
|
+
// the first line of the stack reads as node's, see headerError
|
|
1535
|
+
err.name = "Error [ERR_HTTP_HEADERS_SENT]";
|
|
1536
|
+
void err.stack;
|
|
1537
|
+
delete (/** @type {{name?: string}} */ (err).name);
|
|
1538
|
+
err.code = "ERR_HTTP_HEADERS_SENT";
|
|
1539
|
+
return err;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* Applies the headers a writeHead call carries, in either of node's shapes, (status, headers) or
|
|
1544
|
+
* (status, reason, headers), and answers the reason phrase when there was one. Shared with the
|
|
1545
|
+
* middleware that hooks writeHead: what it decides at the head has to see these first, the way
|
|
1546
|
+
* on-headers applies them before its listeners run.
|
|
1547
|
+
*
|
|
1548
|
+
* @param {{setHeader(name: string, value: import("http").OutgoingHttpHeader|undefined): unknown}} res
|
|
1549
|
+
* @param {string|import("http").OutgoingHttpHeaders|import("http").OutgoingHttpHeader[]} [statusMessage]
|
|
1550
|
+
* @param {import("http").OutgoingHttpHeaders|import("http").OutgoingHttpHeader[]} [headers]
|
|
1551
|
+
* @returns {string|undefined} the reason phrase
|
|
1552
|
+
*/
|
|
1553
|
+
function applyWriteHead(res, statusMessage, headers) {
|
|
1554
|
+
let reason;
|
|
1555
|
+
if (typeof statusMessage === "string") {
|
|
1556
|
+
reason = statusMessage;
|
|
1557
|
+
} else if (!headers) {
|
|
1558
|
+
// the two-argument shape, where what looked like a reason phrase is the headers
|
|
1559
|
+
headers = statusMessage;
|
|
1560
|
+
}
|
|
1561
|
+
if (Array.isArray(headers)) {
|
|
1562
|
+
// node takes a flat list here, name then value, and not a list of pairs. An odd length is
|
|
1563
|
+
// the caller's mistake and node names the argument in what it throws
|
|
1564
|
+
if (headers.length % 2 !== 0) {
|
|
1565
|
+
/** @type {NodeJS.ErrnoException} */
|
|
1566
|
+
const err = new TypeError(`The argument 'headers' is invalid. Received ${JSON.stringify(headers)}`);
|
|
1567
|
+
err.code = "ERR_INVALID_ARG_VALUE";
|
|
1568
|
+
throw err;
|
|
1569
|
+
}
|
|
1570
|
+
for (let i = 0; i < headers.length; i += 2) {
|
|
1571
|
+
res.setHeader(/** @type {string} */ (headers[i]), headers[i + 1]);
|
|
1572
|
+
}
|
|
1573
|
+
} else if (headers) {
|
|
1574
|
+
for (const header in headers) {
|
|
1575
|
+
res.setHeader(header, headers[header]);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return reason;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1504
1581
|
/**
|
|
1505
1582
|
* Refuses a header name that is not an HTTP token, the way node's setHeader does and with its
|
|
1506
1583
|
* error, so an application catching ERR_INVALID_HTTP_TOKEN behind Express catches it here.
|
|
1507
1584
|
*
|
|
1508
|
-
* @param {
|
|
1585
|
+
* @param {unknown} name whatever a caller passed as a header name, which is what is being checked
|
|
1509
1586
|
* @returns {void}
|
|
1510
1587
|
* @throws {TypeError} if the name is not a token, which includes not being a string
|
|
1511
1588
|
*/
|
|
@@ -1579,21 +1656,39 @@ const STAT_ERROR_STATUS = { ENAMETOOLONG: 404, ENOTDIR: 404, ENOENT: 404 };
|
|
|
1579
1656
|
* of that handler as 500. The message is the status's own name, as http-errors writes it.
|
|
1580
1657
|
*
|
|
1581
1658
|
* @param {number} status
|
|
1582
|
-
* @
|
|
1659
|
+
* @param {string} [message] the status text unless given, as http-errors has it
|
|
1660
|
+
* @returns {HttpError}
|
|
1583
1661
|
*/
|
|
1584
|
-
function httpError(status) {
|
|
1585
|
-
|
|
1586
|
-
const err =
|
|
1662
|
+
function httpError(status, message = statuses.message[status] ?? "Error") {
|
|
1663
|
+
/** @type {HttpError} */
|
|
1664
|
+
const err = new Error(message);
|
|
1587
1665
|
// http-errors names these BadRequestError, ForbiddenError and so on, and the name is what the
|
|
1588
1666
|
// error page shows: an application looking at a 400 sees the same word Express shows it. Set
|
|
1589
1667
|
// before anything reads the stack, which V8 formats on first read
|
|
1590
|
-
err.name =
|
|
1668
|
+
err.name = httpErrorName(status);
|
|
1591
1669
|
err.expose = status < 500;
|
|
1592
1670
|
err.statusCode = status;
|
|
1593
1671
|
err.status = status;
|
|
1594
1672
|
return err;
|
|
1595
1673
|
}
|
|
1596
1674
|
|
|
1675
|
+
/**
|
|
1676
|
+
* The name http-errors gives an error for a status, NotFoundError for 404: each word of the status
|
|
1677
|
+
* text capitalised and run together, plus Error unless it already ends in it, which is how
|
|
1678
|
+
* "Internal Server Error" stays InternalServerError.
|
|
1679
|
+
*
|
|
1680
|
+
* @param {number} status
|
|
1681
|
+
* @returns {string}
|
|
1682
|
+
*/
|
|
1683
|
+
function httpErrorName(status) {
|
|
1684
|
+
const name = (statuses.message[status] ?? "Error")
|
|
1685
|
+
.split(" ")
|
|
1686
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
1687
|
+
.join("")
|
|
1688
|
+
.replace(/[^ _0-9a-z]/gi, "");
|
|
1689
|
+
return name.endsWith("Error") ? name : name + "Error";
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1597
1692
|
/**
|
|
1598
1693
|
* Marks an fs error the way send does before it is handed on, so an error handler reading
|
|
1599
1694
|
* err.status or err.statusCode finds what it would find behind Express. The three properties are
|
|
@@ -1602,8 +1697,8 @@ function httpError(status) {
|
|
|
1602
1697
|
*
|
|
1603
1698
|
* The error itself is returned rather than a new one, so its errno, code, syscall and path survive.
|
|
1604
1699
|
*
|
|
1605
|
-
* @param {
|
|
1606
|
-
* @returns {
|
|
1700
|
+
* @param {HttpError} err the fs error, which carries its errno and path
|
|
1701
|
+
* @returns {HttpError} the same error
|
|
1607
1702
|
*/
|
|
1608
1703
|
function asStatError(err) {
|
|
1609
1704
|
err.expose = false;
|
|
@@ -1616,8 +1711,7 @@ function asStatError(err) {
|
|
|
1616
1711
|
// A constructor whose instances have no prototype, so a key from a request body or a query string
|
|
1617
1712
|
// cannot reach Object.prototype. Typed as returning a plain record: without that, assigning one
|
|
1618
1713
|
// 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 () {});
|
|
1714
|
+
const NullObject = /** @type {new () => Record<string, any>} */ (/** @type {unknown} */ (function () {}));
|
|
1621
1715
|
NullObject.prototype = Object.create(null);
|
|
1622
1716
|
|
|
1623
1717
|
module.exports = {
|
|
@@ -1669,6 +1763,9 @@ module.exports = {
|
|
|
1669
1763
|
withUtf8Charset,
|
|
1670
1764
|
asStatError,
|
|
1671
1765
|
httpError,
|
|
1766
|
+
httpErrorName,
|
|
1767
|
+
headersSentError,
|
|
1768
|
+
applyWriteHead,
|
|
1672
1769
|
EMPTY_REGEX,
|
|
1673
1770
|
settingsEpoch
|
|
1674
1771
|
};
|
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 {
|
|
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
|
|
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`,
|
|
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
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 {
|
|
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 {
|
|
53
|
-
* @param {(value:
|
|
54
|
-
* @param {(err:
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
59
|
-
* @param {Set<
|
|
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 {
|
|
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 {
|
|
108
|
-
* @returns {(res:
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 (
|
|
89
|
+
if (done[key]) {
|
|
90
90
|
listed.push(name);
|
|
91
91
|
}
|
|
92
92
|
}
|