fulmine.js 5.19.2 → 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 +2 -1
- package/src/adopt.js +20 -26
- package/src/application.js +63 -73
- package/src/cli.js +48 -48
- package/src/cluster.js +18 -27
- package/src/compression.js +141 -112
- package/src/declarative.js +611 -540
- package/src/hot-settings.js +80 -0
- package/src/index.js +11 -17
- package/src/lazy-readable.js +131 -0
- package/src/lazy-writable.js +97 -0
- package/src/middlewares.js +153 -130
- package/src/nest.js +22 -36
- package/src/node-shim.js +19 -16
- package/src/optimizer.js +600 -0
- package/src/options.d.ts +9 -4
- package/src/parse-query.js +3 -3
- package/src/request-utils.js +307 -0
- package/src/request.js +147 -548
- package/src/response-utils.js +88 -0
- package/src/response.js +228 -535
- package/src/route.js +7 -8
- package/src/router-utils.js +998 -0
- package/src/router.js +166 -2170
- package/src/server-shape.js +40 -51
- package/src/server-timing.js +32 -33
- package/src/socket.js +208 -0
- package/src/testing.js +43 -45
- package/src/usage.js +25 -25
- package/src/utils.js +165 -78
- package/src/verify.js +22 -31
- package/src/view.js +6 -8
- package/src/walk.js +581 -0
- package/src/websocket.js +34 -26
- package/src/work.js +22 -28
package/src/websocket.js
CHANGED
|
@@ -18,10 +18,19 @@ limitations under the License.
|
|
|
18
18
|
|
|
19
19
|
const { canBeOptimizedWithParams, decodeParam, NullObject } = require("./utils.js");
|
|
20
20
|
|
|
21
|
+
/** @typedef {import("./router.js")} Router */
|
|
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 */
|
|
29
|
+
|
|
21
30
|
// the parameter names in a path, in the order µWS numbers them
|
|
22
31
|
const PARAM = /:(\w+)/g;
|
|
23
32
|
|
|
24
|
-
// Handlers
|
|
33
|
+
// Handlers uWS calls with the socket. Everything else in a behavior object is a uWS setting
|
|
25
34
|
// (maxPayloadLength, idleTimeout, compression, ...) and rides through untouched.
|
|
26
35
|
const SOCKET_HANDLERS = ["open", "message", "dropped", "drain", "close", "ping", "pong", "subscription"];
|
|
27
36
|
|
|
@@ -46,15 +55,14 @@ function joinPaths(prefix, path) {
|
|
|
46
55
|
/**
|
|
47
56
|
* Every websocket route reachable from this router, with the mount paths already applied.
|
|
48
57
|
*
|
|
49
|
-
* Walked separately from the HTTP routes: those fall back to ordinary routing when
|
|
50
|
-
* match them,
|
|
51
|
-
* refused out loud instead.
|
|
58
|
+
* Walked separately from the HTTP routes: those fall back to ordinary routing when uWS cannot
|
|
59
|
+
* match them, a websocket has no fallback, so an unmountable one is refused out loud.
|
|
52
60
|
*
|
|
53
|
-
* @param {
|
|
61
|
+
* @param {Router} router
|
|
54
62
|
* @param {string|null} prefix the mount path accumulated so far, or null once a mount was a
|
|
55
63
|
* shape µWS cannot match, which makes everything below it unreachable
|
|
56
|
-
* @param {
|
|
57
|
-
* @param {Set<
|
|
64
|
+
* @param {WsRoute[]} out
|
|
65
|
+
* @param {Set<Router>} seen routers already walked, since a router may be mounted twice
|
|
58
66
|
*/
|
|
59
67
|
function collectRoutes(router, prefix, out, seen) {
|
|
60
68
|
if (seen.has(router)) {
|
|
@@ -97,21 +105,23 @@ function collectRoutes(router, prefix, out, seen) {
|
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/**
|
|
100
|
-
* The
|
|
101
|
-
*
|
|
102
|
-
* answered the request itself.
|
|
108
|
+
* The uWS upgrade handler for one route: builds this project's request and response, offers them
|
|
109
|
+
* to the application's own `upgrade` hook, and completes the handshake unless that hook answered.
|
|
103
110
|
*
|
|
104
|
-
* @param {
|
|
111
|
+
* @param {Router} app the router whose request and response classes serve this route
|
|
105
112
|
* @param {string} path the composed path, whose parameters are read back by index
|
|
106
|
-
* @param {
|
|
107
|
-
* @returns {(res:
|
|
113
|
+
* @param {Record<string, unknown>} behavior what the caller registered
|
|
114
|
+
* @returns {(res: UwsResponse, req: UwsRequest, context: UwsContext) => void}
|
|
108
115
|
*/
|
|
109
116
|
function makeUpgradeHandler(app, path, behavior) {
|
|
110
117
|
const paramNames = [...path.matchAll(PARAM)].map((match) => match[1]);
|
|
111
|
-
|
|
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
|
+
);
|
|
112
122
|
|
|
113
123
|
return (res, req, context) => {
|
|
114
|
-
// read off the
|
|
124
|
+
// read off the uWS request before anything can await: it is neutered on return, and the
|
|
115
125
|
// handshake needs these three even when the upgrade is decided asynchronously
|
|
116
126
|
const key = req.getHeader("sec-websocket-key");
|
|
117
127
|
const protocol = req.getHeader("sec-websocket-protocol");
|
|
@@ -121,7 +131,7 @@ function makeUpgradeHandler(app, path, behavior) {
|
|
|
121
131
|
if (paramNames.length) {
|
|
122
132
|
const params = new NullObject();
|
|
123
133
|
for (let i = 0; i < paramNames.length; i++) {
|
|
124
|
-
params[paramNames[i]] = decodeParam(req.getParameter(i));
|
|
134
|
+
params[paramNames[i]] = decodeParam(/** @type {string} */ (req.getParameter(i)));
|
|
125
135
|
}
|
|
126
136
|
request.params = params;
|
|
127
137
|
}
|
|
@@ -169,17 +179,16 @@ function makeUpgradeHandler(app, path, behavior) {
|
|
|
169
179
|
return;
|
|
170
180
|
}
|
|
171
181
|
|
|
172
|
-
// an async hook
|
|
173
|
-
//
|
|
174
|
-
// handler, which is the only place µWS accepts it
|
|
182
|
+
// an async hook outlives this callback, so uWS has to be told who to call if the client
|
|
183
|
+
// leaves first. Registered now, inside the handler, the only place uWS accepts it
|
|
175
184
|
res.onAborted(() => {
|
|
176
185
|
aborted = true;
|
|
177
186
|
// and on the response too, so a hook that is still awaiting can see the client left
|
|
178
187
|
// rather than working on towards a handshake nobody is waiting for
|
|
179
188
|
response.aborted = true;
|
|
180
189
|
});
|
|
181
|
-
//
|
|
182
|
-
//
|
|
190
|
+
// whatever the hook writes now lands outside the cork uWS holds for this callback, so the
|
|
191
|
+
// response opens its own, exactly as a route handler answering late does
|
|
183
192
|
response._corkNeeded = true;
|
|
184
193
|
decision.then(accept, (err) => {
|
|
185
194
|
if (!aborted && !response.finished) {
|
|
@@ -193,11 +202,10 @@ function makeUpgradeHandler(app, path, behavior) {
|
|
|
193
202
|
}
|
|
194
203
|
|
|
195
204
|
/**
|
|
196
|
-
* Hands every websocket route
|
|
197
|
-
*
|
|
198
|
-
* covers the same path, so the two live side by side.
|
|
205
|
+
* Hands every websocket route to uWS. Called from listen(), before the catch-all: uWS routes an
|
|
206
|
+
* upgrade to the websocket route even when a catch-all covers the same path.
|
|
199
207
|
*
|
|
200
|
-
* @param {
|
|
208
|
+
* @param {Application} app
|
|
201
209
|
*/
|
|
202
210
|
function registerWebSocketRoutes(app) {
|
|
203
211
|
const routes = [];
|
|
@@ -217,7 +225,7 @@ function registerWebSocketRoutes(app) {
|
|
|
217
225
|
* used: a handler under a misspelled name would otherwise never run and never say why.
|
|
218
226
|
*
|
|
219
227
|
* @param {string} path
|
|
220
|
-
* @param {
|
|
228
|
+
* @param {unknown} behavior whatever was passed as one, which is what is being checked
|
|
221
229
|
*/
|
|
222
230
|
function checkBehavior(path, behavior) {
|
|
223
231
|
if (typeof path !== "string") {
|
package/src/work.js
CHANGED
|
@@ -16,27 +16,21 @@ limitations under the License.
|
|
|
16
16
|
|
|
17
17
|
// What one request actually made this framework do, read from state it already keeps.
|
|
18
18
|
//
|
|
19
|
-
// Most of
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// puts the folded object back on every request, and the answer stays correct, so nothing fails.
|
|
19
|
+
// Most of the speed here is work that does not happen: no Readable, no Writable, no folded headers
|
|
20
|
+
// object, no parsed query, no socket stand-in. One careless middleware brings it back, and the
|
|
21
|
+
// answer stays correct, so nothing fails. Every field below is already kept for other reasons, so
|
|
22
|
+
// asking costs a load and nothing is counted or wrapped for the sake of being asked.
|
|
24
23
|
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// is the whole design rule here: a probe that charges the requests nobody is probing would be
|
|
28
|
-
// paid for by everyone, forever, to be read once.
|
|
24
|
+
// Not here: whether the constructor copied the headers out of uWS. That is about the chain and
|
|
25
|
+
// `routeReport().skipHeaders` reports it already.
|
|
29
26
|
//
|
|
30
|
-
//
|
|
31
|
-
// a decision about the chain rather than about the request, `routeReport().skipHeaders` reports it
|
|
32
|
-
// already, and the one case where the two differ, a granted route whose request declares a body,
|
|
33
|
-
// would cost a flag written on every request to be read on almost none.
|
|
34
|
-
//
|
|
35
|
-
// The two readers are `express.testing.expectLazy`, which fails a build that lost one of these,
|
|
36
|
-
// and `express.serverTiming()`, which writes them into the header for a browser to show.
|
|
27
|
+
// Read by `express.testing.expectLazy` and by `express.serverTiming()`.
|
|
37
28
|
|
|
38
29
|
"use strict";
|
|
39
30
|
|
|
31
|
+
/** @typedef {import("./request.js")} Request */
|
|
32
|
+
/** @typedef {import("./response.js")} Response */
|
|
33
|
+
|
|
40
34
|
/**
|
|
41
35
|
* @typedef {object} Work
|
|
42
36
|
* @property {boolean} native whether µWS matched this route itself
|
|
@@ -50,29 +44,30 @@ limitations under the License.
|
|
|
50
44
|
*/
|
|
51
45
|
|
|
52
46
|
/**
|
|
53
|
-
* What this request did
|
|
54
|
-
* reader that wants the whole picture asks at the end of it.
|
|
47
|
+
* What this request did so far. The answer changes while the chain runs, so ask at the end of it.
|
|
55
48
|
*
|
|
56
|
-
* @param {
|
|
57
|
-
* @param {
|
|
49
|
+
* @param {Request} req
|
|
50
|
+
* @param {Response} res the response, since half of this is about the response
|
|
58
51
|
* @returns {Work}
|
|
59
52
|
*/
|
|
60
53
|
function work(req, res) {
|
|
61
54
|
const native = req.route?._native;
|
|
55
|
+
// cast for the three the classes do not declare: `body` is deliberately not a field of
|
|
56
|
+
// Request, and the two stream states are node's own, written when a lazy stream is built
|
|
57
|
+
const loose = /** @type {{body?: unknown, _readableState?: unknown}} */ (req);
|
|
62
58
|
return {
|
|
63
59
|
native: Boolean(native),
|
|
64
60
|
declarative: Boolean(native?.declarative),
|
|
65
61
|
headers: req._headersBuilt,
|
|
66
62
|
query: req._queryParsed,
|
|
67
|
-
body:
|
|
68
|
-
requestStream:
|
|
69
|
-
responseStream: res._writableState !== undefined,
|
|
63
|
+
body: loose.body !== undefined,
|
|
64
|
+
requestStream: loose._readableState !== undefined,
|
|
65
|
+
responseStream: /** @type {{_writableState?: unknown}} */ (res)._writableState !== undefined,
|
|
70
66
|
socket: req._socketBuilt || res._socketBuilt
|
|
71
67
|
};
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
// The order
|
|
75
|
-
// header and a failure message read the same way.
|
|
70
|
+
// The order both readers list them in, cheapest first, so a header and a failure message agree.
|
|
76
71
|
const NAMES = [
|
|
77
72
|
["headers", "headers"],
|
|
78
73
|
["query", "query"],
|
|
@@ -83,8 +78,7 @@ const NAMES = [
|
|
|
83
78
|
];
|
|
84
79
|
|
|
85
80
|
/**
|
|
86
|
-
* The names of everything that did happen, for a message or a header. Empty
|
|
87
|
-
* did none of it, which is the one this framework is built to serve.
|
|
81
|
+
* The names of everything that did happen, for a message or a header. Empty is the good case.
|
|
88
82
|
*
|
|
89
83
|
* @param {Work} done
|
|
90
84
|
* @returns {string[]}
|
|
@@ -92,7 +86,7 @@ const NAMES = [
|
|
|
92
86
|
function names(done) {
|
|
93
87
|
const listed = [];
|
|
94
88
|
for (const [key, name] of NAMES) {
|
|
95
|
-
if (
|
|
89
|
+
if (done[key]) {
|
|
96
90
|
listed.push(name);
|
|
97
91
|
}
|
|
98
92
|
}
|