fulmine.js 5.19.2 → 5.19.3

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.
@@ -16,40 +16,31 @@ limitations under the License.
16
16
 
17
17
  // What makes an application answer the questions a library asks about an http.Server.
18
18
  //
19
- // `app.listen()` returns the app, and there is no node server under it: the socket belongs to µWS.
20
- // That is the one place where a drop-in stops being a drop-in, because the graceful shutdown
21
- // libraries, the connection trackers and the health check wrappers do not use a server, they
22
- // *recognise* one: `server instanceof http.Server`, then close(), address(), getConnections() and
23
- // the events around them. Everything they need can be answered honestly here.
19
+ // `app.listen()` returns the app and there is no node server under it, the socket belongs to uWS.
20
+ // Graceful shutdown libraries, connection trackers and health check wrappers recognise a server
21
+ // with `server instanceof http.Server`, then call close(), address(), getConnections().
24
22
  //
25
- // Two halves, and the second is the delicate one:
23
+ // Two halves:
26
24
  //
27
- // - the members. close(), address(), listening and the events already exist on the application,
28
- // because Express hands back an http.Server and code written for Express uses them. What was
29
- // missing is the rest of the net.Server surface, added below.
30
- // - the recognition. An application cannot inherit from http.Server: its prototype chain already
31
- // runs through Router and this project's own EventEmitter, and http.Server.prototype has a
32
- // chain of its own that cannot be spliced into it without re-parenting node's classes for the
33
- // whole process. So instanceof is taught about applications instead, through the hook the
34
- // language provides for exactly this: Symbol.hasInstance. The patch is additive. Everything
35
- // that was an http.Server before still is, and the only new answer is for an application.
25
+ // - the members. close(), address(), listening and the events are already on the application,
26
+ // because Express hands back an http.Server. The rest of net.Server is added below.
27
+ // - the recognition. An application cannot inherit from http.Server, its prototype chain runs
28
+ // through Router and this project's own EventEmitter. So instanceof is taught instead, with
29
+ // Symbol.hasInstance. The patch is additive, nothing loses the answer it had.
36
30
  //
37
- // What this deliberately does not do is pretend the plumbing is there. Nothing emits 'request',
38
- // 'connection' or 'upgrade', because those carry node sockets and there are none: a library that
39
- // counts connections through them counts zero, and socket.io still wants app.uwsApp. The shape is
40
- // honest about what is behind it, which is why getConnections answers with the requests in flight
41
- // rather than with a number nobody could stand behind.
31
+ // Nothing emits 'request', 'connection' or 'upgrade': those carry node sockets and there are none.
32
+ // A library counting connections through them counts zero, and socket.io wants app.uwsApp.
42
33
 
43
34
  const http = require("http");
44
35
  const net = require("net");
45
36
 
46
- // what marks an application, read by the instanceof hook below. A symbol rather than a property
47
- // name, so nothing can be mistaken for an application by carrying the wrong field
37
+ // what marks an application, read by the instanceof hook below. A symbol, so no plain field name
38
+ // can be mistaken for it
48
39
  const kIsApplication = Symbol.for("fulmine.application");
49
40
 
50
41
  /**
51
42
  * Teaches `instanceof` that an application is a server, once per class. The original answer is
52
- * asked first and is never overruled: this only adds an answer for objects carrying the mark.
43
+ * asked first and never overruled.
53
44
  *
54
45
  * @param {Function} klass http.Server or net.Server
55
46
  */
@@ -65,8 +56,8 @@ function acceptApplications(klass) {
65
56
  if (previous.call(this, value)) {
66
57
  return true;
67
58
  }
68
- // an application is a function, and a property read works on one; the guard is for the
69
- // primitives and the nulls that reach any instanceof
59
+ // an application is a function and a property read works on one. The guard is for the
60
+ // primitives and nulls that reach any instanceof
70
61
  return value != null && /** @type {any} */ (value)[kIsApplication] === true;
71
62
  },
72
63
  configurable: true,
@@ -79,8 +70,8 @@ acceptApplications(http.Server);
79
70
  acceptApplications(net.Server);
80
71
 
81
72
  /**
82
- * The net.Server members an application does not get from Express's side of the API, defined on
83
- * the application prototype. Each one answers for µWS rather than for a socket node does not have.
73
+ * The net.Server members Express's API does not give, on the application prototype. Each one
74
+ * answers for uWS, not for a node socket.
84
75
  *
85
76
  * @param {any} prototype Application.prototype
86
77
  */
@@ -88,11 +79,8 @@ function addServerMembers(prototype) {
88
79
  Object.defineProperty(prototype, kIsApplication, { value: true, configurable: true });
89
80
 
90
81
  /**
91
- * How many requests this application is serving right now.
92
- *
93
- * node counts sockets; there are none to count here, and the number a graceful shutdown is
94
- * waiting for is this one anyway: it reaches zero when the last answer has gone out. An idle
95
- * keep-alive connection is not counted, and closing does not wait for one either.
82
+ * How many requests this application is serving right now. node counts sockets, there are none
83
+ * here, and this is the number a graceful shutdown waits for. An idle keep-alive is not counted.
96
84
  *
97
85
  * @param {(err: Error|null, count: number) => void} callback
98
86
  */
@@ -106,9 +94,8 @@ function addServerMembers(prototype) {
106
94
  };
107
95
 
108
96
  /**
109
- * node's, for a handle this does not own: µWS's loop is what keeps the process alive, and it
110
- * is not something a caller may unref. Both are no-ops that hand the server back, so a chain
111
- * written against node's API keeps working.
97
+ * A handle this does not own: uWS's loop keeps the process alive and a caller cannot unref it.
98
+ * Both are no-ops returning the server, so a chain written against node's API keeps working.
112
99
  *
113
100
  * @returns {any}
114
101
  */
@@ -122,8 +109,8 @@ function addServerMembers(prototype) {
122
109
  };
123
110
 
124
111
  /**
125
- * Registers the callback the way node's does and remembers the value, which is all a caller
126
- * can observe. The timeout itself belongs to µWS and is set through uwsOptions.idleTimeout.
112
+ * Registers the callback like node's does and remembers the value, which is all a caller can
113
+ * observe. The timeout belongs to uWS and is set through uwsOptions.idleTimeout.
127
114
  *
128
115
  * @this {any}
129
116
  * @param {number} [msecs]
@@ -138,10 +125,8 @@ function addServerMembers(prototype) {
138
125
  return this;
139
126
  };
140
127
 
141
- // The numbers node's http.Server carries and a caller may read or write. They are inert here,
142
- // and they are declared rather than left undefined because reading one is how a library works
143
- // out what it is talking to: `server.keepAliveTimeout` undefined has been read as "not a
144
- // server" before now.
128
+ // The numbers node's http.Server carries. Inert here, but declared rather than left undefined:
129
+ // a library reads `server.keepAliveTimeout` to work out what it is talking to.
145
130
  for (const [name, value] of /** @type {[string, any][]} */ ([
146
131
  ["timeout", 0],
147
132
  ["keepAliveTimeout", 5000],
@@ -16,37 +16,27 @@ limitations under the License.
16
16
 
17
17
  // express.serverTiming(): Server-Timing, with the two things only this framework can put in it.
18
18
  //
19
- // A stopwatch middleware is nothing new, and there are several on npm. What none of them can add
20
- // is how the request was routed, because in every other framework there is only one way:
19
+ // What no other stopwatch middleware can add is how the request was routed:
21
20
  //
22
21
  // Server-Timing: route;desc="native", hdr;desc="not copied", total;dur=0.42
23
22
  //
24
- // `route;desc="native"` means µWS matched the path in C++ and handed over a chain worked out at
23
+ // `route;desc="native"` means uWS matched the path in C++ and handed over a chain worked out at
25
24
  // startup. `route;desc="router"` means this request was matched here, in javascript, layer by
26
- // layer. That is the difference between the two halves of this project, per request, in the
27
- // browser's network panel, for someone who would never run a CLI.
25
+ // layer. A handler compiled into a response never enters javascript, so there is nothing to time
26
+ // on it: `npx fulmine profile` counts those.
28
27
  //
29
- // What it cannot show is the route that is faster still: a handler compiled into a response never
30
- // enters javascript, so no middleware runs on it and there is nothing to time. `npx fulmine
31
- // profile` is where those are counted.
32
- //
33
- // The other field only this framework can write is `work`, which names what the request was made to
34
- // build: the folded headers object, the parsed query, the body, the Readable, the Writable, the
35
- // socket stand-in. A fast request builds none of them and the field is absent, so it appears
36
- // exactly when something is worth looking at. See src/work.js.
28
+ // `work` names what the request was made to build: folded headers, parsed query, body, Readable,
29
+ // Writable, socket stand-in. A fast request builds none and the field is absent. See src/work.js.
37
30
  //
38
31
  // The duration ends where the header does. Server-Timing goes out with the head, so `total` covers
39
- // everything up to the moment the answer starts leaving, and not the body after it, and `work` has
40
- // the same boundary: a stream built by the write that carries the head is built after this is
41
- // written. Every stopwatch middleware has that boundary; this one says so.
32
+ // everything up to the moment the answer starts leaving, and `work` has the same boundary.
42
33
 
43
34
  "use strict";
44
35
 
45
36
  const { work, names } = require("./work.js");
46
37
 
47
38
  /**
48
- * A duration in milliseconds, as Server-Timing writes them: two decimals, which is a hundredth of
49
- * a millisecond and finer than anything above it is worth.
39
+ * A duration in milliseconds, as Server-Timing writes them: two decimals.
50
40
  *
51
41
  * @param {bigint} nanoseconds
52
42
  * @returns {string}
@@ -88,9 +78,8 @@ function serverTiming(options) {
88
78
  const marks = [];
89
79
 
90
80
  /**
91
- * Adds a mark of the caller's own, which is what the rest of Server-Timing is for: the
92
- * query, the upstream call, the render. A duration is optional, since a mark with only a
93
- * description is a legal entry and is how a cache hit is usually reported.
81
+ * Adds a mark of the caller's own: the query, the upstream call, the render. The duration
82
+ * is optional, a mark with only a description is a legal entry.
94
83
  *
95
84
  * @param {string} name a token: letters, digits, dash and underscore
96
85
  * @param {number} [duration] milliseconds
@@ -110,8 +99,8 @@ function serverTiming(options) {
110
99
  };
111
100
 
112
101
  /**
113
- * Times a piece of work under a name, whatever it is: the value comes back, and a promise
114
- * is timed to where it settles.
102
+ * Times a piece of work under a name. The value comes back, and a promise is timed to
103
+ * where it settles.
115
104
  *
116
105
  * @param {string} name
117
106
  * @param {() => any} work
@@ -155,8 +144,7 @@ function serverTiming(options) {
155
144
  written = true;
156
145
  const entries = [];
157
146
  if (routing) {
158
- // what the router decided about the route this request ran, which is the same
159
- // verdict npx fulmine profile prints for it
147
+ // the same verdict npx fulmine profile prints for this route
160
148
  const native = req.route?._native;
161
149
  entries.push(`route;desc=${describe(native ? "native" : "router")}`);
162
150
  if (native) {
@@ -167,10 +155,9 @@ function serverTiming(options) {
167
155
  }
168
156
  }
169
157
  if (wantsWork) {
170
- // what this one request made the framework build, which the route verdict above
171
- // cannot say: a native route still folds the headers if a middleware reads them,
172
- // and that is per request, not per route. Read at the head, so it covers the
173
- // chain and not the body written after it, the same boundary as the total.
158
+ // what this request made the framework build, which the route verdict cannot say:
159
+ // a native route still folds the headers if a middleware reads them. Read at the
160
+ // head, so it covers the chain and not the body, the same boundary as the total.
174
161
  const listed = names(work(req, res));
175
162
  if (listed.length !== 0) {
176
163
  entries.push(`work;desc=${describe(listed.join(", "))}`);
package/src/socket.js ADDED
@@ -0,0 +1,208 @@
1
+ /*
2
+ Copyright 2024 dimden.dev
3
+ Copyright 2026 Nigro Simone
4
+
5
+ This file is derived from Ultimate Express and has been modified.
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+ */
19
+
20
+ /** @typedef {import("./response.js")} Response */
21
+
22
+ // events is faster at init, tseep is faster at sending events
23
+ // since we create a ton of objects and dont send a ton of events, its better to use events here
24
+ const { EventEmitter } = require("events");
25
+ const { kShapeMode } = require("./response-utils.js");
26
+
27
+ class Socket extends EventEmitter {
28
+ /**
29
+ * The Socket's error listener, shared across sockets: an error closes the stand-in, which is
30
+ * the close connection trackers wait for. EventEmitter calls it with this = the emitter.
31
+ *
32
+ * @this {any}
33
+ * @param {any} err whatever the response reported, which need not be an Error
34
+ */
35
+ static _onError(err) {
36
+ this.emit("close");
37
+ }
38
+
39
+ /**
40
+ * Enough of a node socket for the middleware that reaches for one. uWS has no socket object to
41
+ * hand over, so this stands in and forwards what it can to the response.
42
+ *
43
+ * @param {Response} response
44
+ */
45
+ constructor(response) {
46
+ super();
47
+ this.response = response;
48
+ this[kShapeMode] = true;
49
+ // middleware assigns to this one, which is why it is a field rather than a getter: express
50
+ // reads socket.encrypted for req.protocol and a proxy shim writes it
51
+ this.encrypted = response.req.app.ssl;
52
+ this.localPort = response.req.app.port;
53
+ // on-finished reads socket.readable before anything else, and a socket without one reads
54
+ // as a request that is already over
55
+ this.readable = true;
56
+
57
+ // shared, not an arrow: one per process instead of one per materialized socket
58
+ this.on("error", Socket._onError);
59
+ }
60
+
61
+ /** Whether anything more can be written, which stops being true once the response is done. */
62
+ get writable() {
63
+ return !this.response.finished;
64
+ }
65
+
66
+ /** The peer, as node reports it. Reading it out of uWS is slow, so the request caches it. */
67
+ get remoteAddress() {
68
+ return this.response.req.parsedIp;
69
+ }
70
+
71
+ /** A native µWS call almost no caller makes, so it stays behind its getter. */
72
+ get remotePort() {
73
+ return this.response.req._res.getRemotePort();
74
+ }
75
+
76
+ /**
77
+ * node's socket carries these three, usually called to take the timeout off. uWS has no per
78
+ * socket timeout reachable from javascript, so they do nothing and return the socket. n8n's
79
+ * chat trigger calls setTimeout on every webhook, and without it the workflow answered 500.
80
+ * @returns {this}
81
+ */
82
+ setTimeout() {
83
+ return this;
84
+ }
85
+
86
+ /** @returns {this} */
87
+ setKeepAlive() {
88
+ return this;
89
+ }
90
+
91
+ /** @returns {this} */
92
+ setNoDelay() {
93
+ return this;
94
+ }
95
+
96
+ /**
97
+ * Finishes the response through the socket, which is how the middleware that only knows
98
+ * about sockets ends one.
99
+ * @param {any} [body] whatever node's socket.end() would take
100
+ */
101
+ end(body) {
102
+ this.response.end(body);
103
+ }
104
+
105
+ /**
106
+ * What a server side socket answers about itself. uWS owns the connection, so these follow the
107
+ * response.
108
+ */
109
+ get destroyed() {
110
+ return this.response.finished === true;
111
+ }
112
+
113
+ /** @returns {string} "open" until the response is over, as a served socket reads. */
114
+ get readyState() {
115
+ return this.response.finished === true ? "closed" : "open";
116
+ }
117
+
118
+ /** @returns {boolean} never: this end was accepted, not dialled. */
119
+ get connecting() {
120
+ return false;
121
+ }
122
+
123
+ /** @returns {boolean} never, for the same reason. */
124
+ get pending() {
125
+ return false;
126
+ }
127
+
128
+ /**
129
+ * The end of the connection node reports here. There is no address to read back from uWS, so
130
+ * this is the port the application bound and the family the peer arrived on.
131
+ * @returns {{address: string|undefined, family: string, port: number|undefined}}
132
+ */
133
+ address() {
134
+ const remote = this.response.req.parsedIp;
135
+ return {
136
+ address: this.response.req.app._listenHost,
137
+ family: remote?.includes(":") ? "IPv6" : "IPv4",
138
+ port: this.localPort
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Drops the connection. node takes an error and re-emits it, this closes and says so through
144
+ * 'close', since there is no socket underneath to carry an error of its own.
145
+ * @returns {this}
146
+ */
147
+ destroy() {
148
+ this.close();
149
+ return this;
150
+ }
151
+
152
+ /** @returns {this} */
153
+ destroySoon() {
154
+ this.close();
155
+ return this;
156
+ }
157
+
158
+ /**
159
+ * Holds and resumes the body arriving on this connection. The other half of node's pause()
160
+ * means nothing here, the response is written when the application writes it.
161
+ * @returns {this}
162
+ */
163
+ pause() {
164
+ this.response.req.pause();
165
+ return this;
166
+ }
167
+
168
+ /** @returns {this} */
169
+ resume() {
170
+ this.response.req.resume();
171
+ return this;
172
+ }
173
+
174
+ /**
175
+ * node writes these bytes straight onto the connection. There is no way past uWS's framing
176
+ * here, so they go through the response instead.
177
+ *
178
+ * @param {any} chunk
179
+ * @param {any} [encoding]
180
+ * @param {any} [callback] node's write signature, which takes all three loosely
181
+ * @returns {boolean}
182
+ */
183
+ write(chunk, encoding, callback) {
184
+ return this.response.write(chunk, encoding, callback);
185
+ }
186
+
187
+ /** The event loop is µWS's, so there is nothing to hold open or let go. @returns {this} */
188
+ ref() {
189
+ return this;
190
+ }
191
+
192
+ /** @returns {this} */
193
+ unref() {
194
+ return this;
195
+ }
196
+
197
+ /** Closes the connection outright, without finishing a response first. */
198
+ close() {
199
+ if (this.response.finished) {
200
+ return;
201
+ }
202
+ this.response.finished = true;
203
+ this.emit("close");
204
+ this.response._res.close();
205
+ }
206
+ }
207
+
208
+ module.exports = Socket;
package/src/testing.js CHANGED
@@ -16,25 +16,27 @@ limitations under the License.
16
16
 
17
17
  // express.testing: what listen() decided about each route, as something a test can assert on.
18
18
  //
19
- // A route is answered by µWS itself only while it stays eligible, and eligibility is not a property
20
- // of the route alone: a `const` in the wrong place, a middleware that reads a header, a new route
21
- // written above an old one, and it quietly falls back to the ordinary router. The answer is still
22
- // correct, which is why nothing complains. What changes is the throughput, and by the time anyone
23
- // notices, the commit that did it is three weeks back.
19
+ // A route is answered by uWS itself only while it stays eligible: a `const` in the wrong place, a
20
+ // middleware that reads a header, a new route above an old one, and it falls back to the ordinary
21
+ // router. The answer stays correct, so nothing complains, only the throughput changes.
24
22
  //
25
- // `npx fulmine profile` prints the same verdicts for a human to read. This is the half a test can
26
- // hold on to, so a pull request that loses the fast path fails in CI with the reason written out
27
- // instead of being found in production.
23
+ // `npx fulmine profile` prints the same verdicts for a human. This is the half a test can hold on
24
+ // to, so a pull request that loses the fast path fails in CI.
28
25
 
29
26
  "use strict";
30
27
 
31
28
  const { work, names: workNames } = require("./work.js");
32
29
 
30
+ /** @typedef {import("./request.js")} Request */
31
+ /** @typedef {import("./response.js")} Response */
32
+ /** @typedef {import("./router.js")} Router */
33
+ /** @typedef {import("./application.js").Application} Application */
34
+
33
35
  /**
34
36
  * Every route of an application and of the routers mounted under it, each with the path it answers
35
37
  * from the outside.
36
38
  *
37
- * @param {any} router
39
+ * @param {Router} router
38
40
  * @param {string} prefix
39
41
  * @param {any[]} [into]
40
42
  * @returns {{route: any, full: string}[]}
@@ -55,7 +57,7 @@ function collectRoutes(router, prefix, into = []) {
55
57
  * Compiles the routes, which is what listen() does before it binds, without binding anything. Once
56
58
  * per application: a second compilation would register everything with µWS twice.
57
59
  *
58
- * @param {any} app
60
+ * @param {Application} app
59
61
  */
60
62
  function compileOnce(app) {
61
63
  if (app.listenCalled || app._testingCompiled) {
@@ -68,11 +70,10 @@ function compileOnce(app) {
68
70
  /**
69
71
  * What compiling the routes decided, one entry per route, in the order they were registered.
70
72
  *
71
- * This is the primitive the two assertions below are written on, and it is exported because an
72
- * application with rules of its own is better served asserting them itself: how many routes may
73
- * fall back, which ones may read headers, that the one route carrying the traffic is declarative.
73
+ * The primitive the two assertions below are written on. Exported so an application with rules of
74
+ * its own can assert them directly.
74
75
  *
75
- * @param {any} app an application, listening or not
76
+ * @param {Application} app an application, listening or not
76
77
  * @returns {{method: string, path: string, native: boolean, declarative: boolean, skipHeaders: boolean,
77
78
  * skipQuery: boolean, reason: string|undefined}[]}
78
79
  */
@@ -94,8 +95,8 @@ function routeReport(app) {
94
95
  /**
95
96
  * Whether one of the patterns given names this route.
96
97
  *
97
- * A pattern is a path as it was registered, not a URL: "/api/items/:id" and not "/api/items/7". It
98
- * may carry the method, "GET /health", and it may end in "*" to name everything under a prefix.
98
+ * A pattern is a path as registered, not a URL: "/api/items/:id", not "/api/items/7". It may carry
99
+ * the method, "GET /health", and may end in "*" for everything under a prefix.
99
100
  *
100
101
  * @param {{method: string, path: string}} entry
101
102
  * @param {string} pattern
@@ -118,10 +119,10 @@ function names(entry, pattern) {
118
119
  }
119
120
 
120
121
  /**
121
- * The routes the patterns name, refusing a pattern that names none: a test that asserts about a
122
- * route it misspelled has to fail rather than pass on an empty list.
122
+ * The routes the patterns name. A pattern that names none throws, so a misspelled route fails
123
+ * instead of passing on an empty list.
123
124
  *
124
- * @param {any} app
125
+ * @param {Application} app
125
126
  * @param {string|string[]} patterns
126
127
  * @param {string} caller the name in the message
127
128
  * @returns {ReturnType<typeof routeReport>}
@@ -152,12 +153,10 @@ function select(app, patterns, caller) {
152
153
  }
153
154
 
154
155
  /**
155
- * Throws unless every route named is answered by µWS itself.
156
- *
157
- * The message is the point: it names each route that fell back and why, in the same words
158
- * `npx fulmine profile` uses, so the failure says what to change.
156
+ * Throws unless every route named is answered by uWS itself. The message names each route that
157
+ * fell back and why, in the same words `npx fulmine profile` uses.
159
158
  *
160
- * @param {any} app
159
+ * @param {Application} app
161
160
  * @param {string|string[]} patterns paths as they were registered, "GET /path" to pin the method,
162
161
  * a trailing "*" for everything under a prefix
163
162
  */
@@ -174,12 +173,12 @@ function expectNative(app, patterns) {
174
173
  }
175
174
 
176
175
  /**
177
- * Why a route µWS already matches is still not compiled into a response.
176
+ * Why a route uWS already matches is still not compiled into a response.
178
177
  *
179
178
  * The handler is the last answer, not the first: three refusals come before it, and blaming the
180
- * handler for one of those sends the reader to rewrite something that was already simple enough.
179
+ * handler sends the reader to rewrite something that was already simple enough.
181
180
  *
182
- * @param {any} app
181
+ * @param {Application} app
183
182
  * @param {{path: string}} entry
184
183
  * @returns {string}
185
184
  */
@@ -200,10 +199,10 @@ function whyNotCompiled(app, entry) {
200
199
  }
201
200
 
202
201
  /**
203
- * Throws unless every route named is answered from a response written at startup, which is the
204
- * step past native: µWS answers it without entering javascript at all.
202
+ * Throws unless every route named is answered from a response written at startup. One step past
203
+ * native: uWS answers it without entering javascript.
205
204
  *
206
- * @param {any} app
205
+ * @param {Application} app
207
206
  * @param {string|string[]} patterns as in expectNative
208
207
  */
209
208
  function expectDeclarative(app, patterns) {
@@ -228,31 +227,29 @@ function expectDeclarative(app, patterns) {
228
227
  * What this one request made the framework do, asked from inside a handler or from a `finish`
229
228
  * listener. See src/work.js for what each field means and why asking is free.
230
229
  *
231
- * @param {any} req
232
- * @param {any} res
230
+ * @param {Request} req
231
+ * @param {Response} res
233
232
  * @returns {import("./work.js").Work}
234
233
  */
235
234
  function workReport(req, res) {
236
235
  return work(req, res);
237
236
  }
238
237
 
239
- // The work a fast request does none of, which is what expectLazy is about. The route verdict is
240
- // not in here: expectNative and expectDeclarative are what assert on that.
238
+ // The work a fast request does none of. The route verdict is not here, expectNative and
239
+ // expectDeclarative assert on that.
241
240
  const LAZY = ["headers", "query", "body", "requestStream", "responseStream", "socket"];
242
241
 
243
242
  /**
244
243
  * Throws if this request built anything it did not have to.
245
244
  *
246
- * The route verdict is a property of the application and holds for every request; this is the
247
- * other half, which holds for one. A route can stay native and still slow down request by request,
248
- * because a middleware read `req.headers.host` or piped instead of sending: the answer stays
249
- * correct, the route report stays green, and the throughput does not.
245
+ * The route verdict holds for every request, this holds for one. A native route still slows down
246
+ * request by request if a middleware reads `req.headers.host` or pipes instead of sending.
250
247
  *
251
- * `allow` names what is fine here, which is most of the point: a route that parses a body is
252
- * asserted as one that parses a body and nothing else.
248
+ * `allow` names what is fine here: a route that parses a body is asserted as one that parses a
249
+ * body and nothing else.
253
250
  *
254
- * @param {any} req
255
- * @param {any} res
251
+ * @param {Request} req
252
+ * @param {Response} res
256
253
  * @param {object} [options]
257
254
  * @param {string[]} [options.allow] fields of the report this route is expected to do anyway
258
255
  */