fulmine.js 5.20.0 → 5.21.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fulmine.js",
3
- "version": "5.20.0",
3
+ "version": "5.21.1",
4
4
  "description": "Drop-in Express 5 replacement on uWebSockets.js, up to 20x faster. Same API, your middleware and framework keep working.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -116,7 +116,7 @@
116
116
  "statuses": "^2.0.2",
117
117
  "tseep": "^1.3.1",
118
118
  "type-is": "^2.1.0",
119
- "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.69.0",
119
+ "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.71.0",
120
120
  "vary": "^1.1.2"
121
121
  },
122
122
  "peerDependencies": {
@@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
17
17
  limitations under the License.
18
18
  */
19
19
 
20
- const uWS = require("uWebSockets.js");
20
+ const { loadUWS } = require("./uws.js");
21
21
  const Router = require("./router.js");
22
22
  const {
23
23
  removeDuplicateSlashes,
@@ -59,7 +59,10 @@ class FSWorker {
59
59
  */
60
60
  constructor() {
61
61
  this.busy = false;
62
- this.worker = new Worker(path.join(__dirname, "worker.js"));
62
+ // its own execArgv, not the parent thread's: a worker inherits them, and a --require or
63
+ // --import written for the parent (Angular's route extraction registers a loader that reads
64
+ // workerData) throws inside a thread that only reads files
65
+ this.worker = new Worker(path.join(__dirname, "worker.js"), { execArgv: [] });
63
66
 
64
67
  this.worker.on("message", (message) => {
65
68
  // node speaks on this channel too: under --watch a worker reports the files it loaded
@@ -113,6 +116,15 @@ class Application extends Router {
113
116
  */
114
117
  _testingCompiled;
115
118
 
119
+ /**
120
+ * The uWS app once made, or the one settings.uwsApp handed in. See the uwsApp getter.
121
+ * @type {any}
122
+ */
123
+ _uwsApp;
124
+
125
+ /** What uWS.App or uWS.SSLApp is given, kept until the app is made. */
126
+ _uwsOptions;
127
+
116
128
  /**
117
129
  * @param {object} [settings] the options express() takes. uwsOptions goes to uWS and decides
118
130
  * between an HTTP, an HTTPS and an HTTP/3 server; threads sizes the file-reading pool, and 0
@@ -135,9 +147,7 @@ class Application extends Router {
135
147
  if (this._clusterWorkers > 0 && cluster.isPrimary) {
136
148
  becomeSupervisor();
137
149
  }
138
- if (settings.uwsApp) {
139
- this.uwsApp = /** @type {import("uWebSockets.js").TemplatedApp} */ (settings.uwsApp);
140
- } else if (settings.http3) {
150
+ if (settings.http3) {
141
151
  // uWS.H3App exists in the pinned build but its QUIC stack does not: the constructor
142
152
  // segfaults on Linux and hangs forever on Windows before serving a single request,
143
153
  // verified 2026-08-05 with uWS alone. A clear throw beats a native crash; this
@@ -146,12 +156,12 @@ class Application extends Router {
146
156
  "http3 is not usable with the pinned uWebSockets.js build: its H3App crashes " +
147
157
  "during construction. Track uNetworking/uWebSockets.js for working QUIC support."
148
158
  );
149
- } else if (settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name) {
150
- this.uwsApp = uWS.SSLApp(settings.uwsOptions);
151
- } else {
152
- this.uwsApp = uWS.App(settings.uwsOptions);
153
159
  }
154
160
  this.ssl = settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name;
161
+ // the uWS app is made on first use, see the uwsApp getter: what listen(), ws() and the
162
+ // optimizer need, and what an app served through node's http never asks for
163
+ this._uwsApp = settings.uwsApp;
164
+ this._uwsOptions = settings.uwsOptions;
155
165
  this.cache = new NullObject();
156
166
  this.engines = { __proto__: null };
157
167
  // a null prototype, as express gives app.locals, so a local named like an Object method
@@ -509,6 +519,22 @@ class Application extends Router {
509
519
  return request;
510
520
  }
511
521
 
522
+ /**
523
+ * The µWS app underneath, for anything µWS offers that this does not: socket.io attaches to
524
+ * it. Made the first time it is asked for, so an application that only ever answers through
525
+ * node's http, a test through supertest or Angular's build extracting routes in a worker
526
+ * thread, never loads the binary. See src/uws.js for why that matters.
527
+ *
528
+ * @returns {any}
529
+ */
530
+ get uwsApp() {
531
+ if (this._uwsApp === undefined) {
532
+ const uWS = loadUWS();
533
+ this._uwsApp = this.ssl ? uWS.SSLApp(this._uwsOptions) : uWS.App(this._uwsOptions);
534
+ }
535
+ return this._uwsApp;
536
+ }
537
+
512
538
  /**
513
539
  * Registers the catch-all uWS handler, which is what serves every request that no optimized
514
540
  * route took natively. It walks this app's own chain and, when nothing in it answered, decides
@@ -617,7 +643,7 @@ class Application extends Router {
617
643
  // listen() returns. The callback is not: running it here would run it before listen()
618
644
  // had returned, and `const server = app.listen(p, () => server.address())` - the form
619
645
  // the Express docs use - would die on the temporal dead zone.
620
- this.port = uWS.us_socket_local_port(socket);
646
+ this.port = loadUWS().us_socket_local_port(socket);
621
647
  this.listening = true;
622
648
  this._listenHost = host;
623
649
  // kept so close() can stop accepting without dropping what is in flight
@@ -888,7 +914,7 @@ class Application extends Router {
888
914
  return this;
889
915
  }
890
916
  if (this._listenSocket) {
891
- uWS.us_listen_socket_close(this._listenSocket);
917
+ loadUWS().us_listen_socket_close(this._listenSocket);
892
918
  this._listenSocket = undefined;
893
919
  }
894
920
  this._draining = true;
package/src/cli.js CHANGED
@@ -107,10 +107,11 @@ const DIFFERENCES = [
107
107
  [
108
108
  "a compiled route keeps its connection header",
109
109
  "A handler simple enough to be read at registration time is answered natively, and a client\n" +
110
- "that sent Connection: close is still told keep-alive, though the socket does close. A body\n" +
111
- "with a piece of the query in it is framed chunked, since its length is not known until the\n" +
112
- "request arrives. A response that would carry a validator is never compiled, so conditional\n" +
113
- 'requests behave as on Express. app.set("declarative responses", false) turns it off.'
110
+ "that sent Connection: close is still told keep-alive, though the socket does close. A\n" +
111
+ "response that would carry a validator is never compiled, so conditional requests behave as\n" +
112
+ 'on Express. app.set("declarative responses", false) turns it off. A body with a piece of\n' +
113
+ 'the query or a route parameter in it is compiled only under app.set("declarative request\n' +
114
+ 'values", true), which takes the value as uWS reads it: undecoded, the first one, or none.'
114
115
  ],
115
116
  [
116
117
  "headers are capped at 4096 bytes by default",
@@ -21,8 +21,7 @@ const acorn = require("acorn");
21
21
  const { stringify, contentTypeSet, withUtf8Charset, contentTypeFor, headerIsWritable } = require("./utils.js");
22
22
  // H3App, DeclarativeResponse and _cfg exist at runtime but are missing from the .d.ts the
23
23
  // package ships, so the module is read through a loose alias
24
- const uWS = require("uWebSockets.js");
25
- const uWSAny = /** @type {any} */ (uWS);
24
+ const { loadUWS } = require("./uws.js");
26
25
  const statuses = require("statuses");
27
26
 
28
27
  /** @typedef {import("./application.js").Application} Application */
@@ -818,6 +817,12 @@ module.exports = function compileDeclarative(cb, app) {
818
817
  }
819
818
  const { sendUsed, bodyFromSend } = read;
820
819
 
820
+ // a part copied out of the request is written by uWS with its own reading of it, so the
821
+ // route is compiled only where the application asked for that
822
+ if (!app.get("declarative request values") && body.some((part) => part.type !== "text")) {
823
+ return false;
824
+ }
825
+
821
826
  // a handler that never sends is not a response: Express leaves the request waiting, so this
822
827
  // has to fall back instead of answering a bare 200
823
828
  if (!sendUsed && !sendStatusUsed) {
@@ -830,7 +835,7 @@ module.exports = function compileDeclarative(cb, app) {
830
835
  return false;
831
836
  }
832
837
 
833
- let decRes = new uWSAny.DeclarativeResponse();
838
+ let decRes = new (loadUWS().DeclarativeResponse)();
834
839
 
835
840
  if (statusCode !== 200) {
836
841
  const statusMessage = statuses.message[statusCode] ?? "unknown";
package/src/index.js CHANGED
@@ -17,11 +17,6 @@ See the License for the specific language governing permissions and
17
17
  limitations under the License.
18
18
  */
19
19
 
20
- // H3App, DeclarativeResponse and _cfg exist at runtime but are missing from the .d.ts the
21
- // package ships, so the module is read through a loose alias
22
- const uWS = require("uWebSockets.js");
23
- const uWSAny = /** @type {any} */ (uWS);
24
-
25
20
  // A project on pnpm owns the uWebSockets.js dependency itself, see `npx fulmine.js pnpm`, so the
26
21
  // one installed can drift from the one this package pins and was tested against. Said once, at
27
22
  // require time, where it reaches every deployment rather than only the ones that run verify.
@@ -50,13 +45,6 @@ const middlewares = require("./middlewares.js");
50
45
  const Request = require("./request.js");
51
46
  const Response = require("./response.js");
52
47
 
53
- try {
54
- // disable Uwebsockets header
55
- uWSAny._cfg("999999990007");
56
- } catch (error) {
57
- // older uWS builds do not expose _cfg; there is nothing to fall back to
58
- }
59
-
60
48
  try {
61
49
  // the compile cache, node 22.8 and up: the next boot skips compiling the same code. Respects
62
50
  // NODE_DISABLE_COMPILE_CACHE, and booting without a cache is not an error
package/src/optimizer.js CHANGED
@@ -558,8 +558,9 @@ function registerUwsRoute(router, route, optimizedPath) {
558
558
  route.paramCallbacks.size === 0 && // a param callback has to run, and this answers without running anything
559
559
  // a captured value is decoded when the route runs, and one that cannot be decoded is a
560
560
  // 400 in express and on the ordinary path here. Nothing runs to raise it on a
561
- // declarative response, so GET /a-b%5Ec@d%e came back 200 from app.get("/:p12")
562
- route.optimizedParams === undefined &&
561
+ // declarative response, so GET /a-b%5Ec@d%e came back 200 from app.get("/:p12").
562
+ // "declarative request values" is the application accepting that
563
+ (route.optimizedParams === undefined || router.get("declarative request values")) &&
563
564
  // a declarative response is answered by µWS itself, so no javascript runs and the case
564
565
  // guard could not: a route that needs one has to stay an ordinary handler
565
566
  caseGuards === null &&
package/src/router.js CHANGED
@@ -100,9 +100,11 @@ module.exports = class Router extends EventEmitter {
100
100
  * it have already established that, so the honest `TemplatedApp|undefined` would only add
101
101
  * casts where the guard already is.
102
102
  *
103
- * @type {any}
103
+ * @returns {any}
104
104
  */
105
- uwsApp;
105
+ get uwsApp() {
106
+ return undefined;
107
+ }
106
108
 
107
109
  /**
108
110
  * Whether an unset routing flag reads on through the mount parent. Only an application does,
package/src/testing.js CHANGED
@@ -187,8 +187,11 @@ function whyNotCompiled(app, entry) {
187
187
  if (!app.get("declarative responses")) {
188
188
  return "answered by µWS, but declarative responses are turned off";
189
189
  }
190
- if (entry.path.includes(":")) {
191
- return "answered by µWS, but the route captures, and nothing runs to decode the value";
190
+ if (entry.path.includes(":") && !app.get("declarative request values")) {
191
+ return (
192
+ "answered by µWS, but the route captures, and nothing runs to decode the value: " +
193
+ 'app.set("declarative request values", true) is what puts a route here'
194
+ );
192
195
  }
193
196
  if (app.get("etag")) {
194
197
  return (
package/src/types.d.ts CHANGED
@@ -18,6 +18,7 @@ declare module "fulmine.js" {
18
18
  import e from "express";
19
19
  import uWS from "uWebSockets.js";
20
20
  import { ZlibOptions, BrotliOptions } from "zlib";
21
+ import serveStatic = require("serve-static");
21
22
 
22
23
  type Settings = {
23
24
  uwsOptions?: uWS.AppOptions;
@@ -36,7 +37,16 @@ declare module "fulmine.js" {
36
37
  export import request = e.request;
37
38
  export import response = e.response;
38
39
 
39
- export import static = e.static;
40
+ /** serve-static's options plus preCompressed, which is this project's. */
41
+ interface StaticOptions extends serveStatic.ServeStaticOptions<e.Response> {
42
+ /**
43
+ * Serve `file.br` or `file.gz` in place of `file` when one is on disk and the client
44
+ * takes it. Off by default. Which twins a path has is remembered for a second;
45
+ * `{ cache: false }` asks the disk every time, a duration sets how long.
46
+ */
47
+ preCompressed?: boolean | { cache?: number | string | false };
48
+ }
49
+ function static(root: string, options?: StaticOptions): e.RequestHandler;
40
50
  // export import query = e.query;
41
51
 
42
52
  // express has no compression middleware, so there is nothing to re-export: these are the
package/src/utils.js CHANGED
@@ -1028,6 +1028,11 @@ const defaultSettings = {
1028
1028
  // The native µWS router matches bytes, so the compiler in _compileOptimizedRoutes only hands
1029
1029
  // it routes whose earlier siblings it can prove agree under either case rule.
1030
1030
  "declarative responses": true,
1031
+ // off: a compiled body copies nothing out of the request. On, res.send(req.query.q) and
1032
+ // res.send(req.params.id) compile too, written by uWS as it reads them and not as Express
1033
+ // does: a query key repeated or missing gives the first value or nothing, a route parameter
1034
+ // goes out undecoded, and one that Express refuses with a 400 is answered
1035
+ "declarative request values": false,
1031
1036
  // on. Off hands every request to the ordinary chain instead of letting uWS match what it can,
1032
1037
  // which is slower and answers the same. Not a tuning knob: it exists so one application can be
1033
1038
  // served both ways and the answers compared, see `npm run fuzz -- --self`. A compiled response
package/src/uws.js ADDED
@@ -0,0 +1,45 @@
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
+
17
+ // µWebSockets.js, loaded the first time something needs it rather than when this package is
18
+ // required. An application that is built and never listens never loads the binary. Angular's
19
+ // build imports server.ts in a worker thread to extract the routes and serves it through node's
20
+ // http, and on Windows the binary crashes the process when a thread that loaded it exits
21
+ // (uNetworking/uWebSockets.js#668), so the build only works if nothing in that thread loads it.
22
+
23
+ /** @type {any} */
24
+ let uWS;
25
+
26
+ /**
27
+ * The µWS module. H3App, DeclarativeResponse and _cfg exist at runtime but are missing from the
28
+ * .d.ts the package ships, so it is handed back loosely typed.
29
+ *
30
+ * @returns {any}
31
+ */
32
+ function loadUWS() {
33
+ if (uWS === undefined) {
34
+ uWS = /** @type {any} */ (require("uWebSockets.js"));
35
+ try {
36
+ // disable Uwebsockets header
37
+ uWS._cfg("999999990007");
38
+ } catch (error) {
39
+ // older uWS builds do not expose _cfg; there is nothing to fall back to
40
+ }
41
+ }
42
+ return uWS;
43
+ }
44
+
45
+ module.exports = { loadUWS };