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
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
const { settingsEpoch } = require("./utils.js");
|
|
21
|
+
|
|
22
|
+
// The settings the hot paths read, resolved to plain fields: each read was a variadic get() whose
|
|
23
|
+
// rest array escapes into createRoute, plus a dictionary miss per mount level for the json keys.
|
|
24
|
+
// One shape for every router, stale when the epoch moves.
|
|
25
|
+
class HotSettings {
|
|
26
|
+
/** Every field declared up front, one hidden class for every router's copy. */
|
|
27
|
+
constructor() {
|
|
28
|
+
this.epoch = 0;
|
|
29
|
+
this.xPoweredBy = false;
|
|
30
|
+
this.etagFn = undefined;
|
|
31
|
+
// null means every method, which is express's behaviour and the default
|
|
32
|
+
this.etagMethods = null;
|
|
33
|
+
this.queryParserFn = undefined;
|
|
34
|
+
this.trustProxyFn = undefined;
|
|
35
|
+
this.trustProxyProtocol = false;
|
|
36
|
+
this.jsonEscape = undefined;
|
|
37
|
+
this.jsonReplacer = undefined;
|
|
38
|
+
this.jsonSpaces = undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* What app.settings is wrapped in, so a write that never went through set() still bumps the epoch.
|
|
44
|
+
* Only writes are trapped, a read is the plain operation on the object.
|
|
45
|
+
*
|
|
46
|
+
* defineProperty is here for the trust proxy default marker, which set() writes that way.
|
|
47
|
+
*/
|
|
48
|
+
const settingsWriteTraps = {
|
|
49
|
+
/**
|
|
50
|
+
* @param {Record<string|symbol, unknown>} target the settings object itself, whose keys are the application's
|
|
51
|
+
* @param {string|symbol} key
|
|
52
|
+
* @param {unknown} value whatever the application is setting
|
|
53
|
+
*/
|
|
54
|
+
set(target, key, value) {
|
|
55
|
+
target[key] = value;
|
|
56
|
+
settingsEpoch.n++;
|
|
57
|
+
return true;
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* @param {Record<string|symbol, unknown>} target the settings object itself
|
|
61
|
+
* @param {string|symbol} key
|
|
62
|
+
*/
|
|
63
|
+
deleteProperty(target, key) {
|
|
64
|
+
delete target[key];
|
|
65
|
+
settingsEpoch.n++;
|
|
66
|
+
return true;
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* @param {Record<string|symbol, unknown>} target the settings object itself
|
|
70
|
+
* @param {string|symbol} key
|
|
71
|
+
* @param {PropertyDescriptor} descriptor
|
|
72
|
+
*/
|
|
73
|
+
defineProperty(target, key, descriptor) {
|
|
74
|
+
Object.defineProperty(target, key, descriptor);
|
|
75
|
+
settingsEpoch.n++;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
module.exports = { HotSettings, settingsWriteTraps };
|
package/src/index.js
CHANGED
|
@@ -17,8 +17,8 @@ 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
|
|
21
|
-
//
|
|
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
22
|
const uWS = require("uWebSockets.js");
|
|
23
23
|
const uWSAny = /** @type {any} */ (uWS);
|
|
24
24
|
const Application = require("./application.js");
|
|
@@ -36,9 +36,8 @@ try {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
|
-
// the compile cache,
|
|
40
|
-
//
|
|
41
|
-
// using it. Respects NODE_DISABLE_COMPILE_CACHE, and booting without a cache is not an error
|
|
39
|
+
// the compile cache, node 22.8 and up: the next boot skips compiling the same code. Respects
|
|
40
|
+
// NODE_DISABLE_COMPILE_CACHE, and booting without a cache is not an error
|
|
42
41
|
require("node:module").enableCompileCache?.();
|
|
43
42
|
} catch (error) {
|
|
44
43
|
// node below 22.8, or a disk the cache cannot be written to
|
|
@@ -47,9 +46,8 @@ try {
|
|
|
47
46
|
// The factory doubles as a namespace, as in Express: Router, static and the body parsers hang off
|
|
48
47
|
// the function that creates an app.
|
|
49
48
|
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
// Nothing here is executed to find that out.
|
|
49
|
+
// Always `module.exports.name = ...`, never through an alias: cjs-module-lexer reads this file as
|
|
50
|
+
// text to decide the named exports an ESM importer gets, and it cannot see through an alias.
|
|
53
51
|
/**
|
|
54
52
|
* @type {typeof Application & {
|
|
55
53
|
* Router: Function,
|
|
@@ -69,8 +67,7 @@ try {
|
|
|
69
67
|
*/
|
|
70
68
|
module.exports = /** @type {any} */ (Application);
|
|
71
69
|
|
|
72
|
-
// a router is a function too
|
|
73
|
-
// middleware
|
|
70
|
+
// a router is a function too: it has to be callable to be used as middleware
|
|
74
71
|
module.exports.Router = function (options) {
|
|
75
72
|
return new Router(options)._asCallable();
|
|
76
73
|
};
|
|
@@ -80,18 +77,15 @@ module.exports.Route = Route;
|
|
|
80
77
|
|
|
81
78
|
module.exports.request = Request.prototype;
|
|
82
79
|
module.exports.response = Response.prototype;
|
|
83
|
-
//
|
|
80
|
+
// adding a method here adds it to every app, the same as express.application
|
|
84
81
|
module.exports.application = Application.Application.prototype;
|
|
85
82
|
|
|
86
83
|
module.exports.static = middlewares.static;
|
|
87
|
-
// what listen() decided about each route, as something a test can assert on
|
|
88
|
-
// to read in a terminal. See src/testing.js
|
|
84
|
+
// what listen() decided about each route, as something a test can assert on. See src/testing.js
|
|
89
85
|
module.exports.testing = require("./testing.js");
|
|
90
|
-
//
|
|
91
|
-
// instead, and this is that middleware's options and behaviour without the install
|
|
86
|
+
// express has none: this is the compression module's options and behaviour, without the install
|
|
92
87
|
module.exports.compression = require("./compression.js");
|
|
93
|
-
// Server-Timing with the routing verdict in it
|
|
94
|
-
// other framework has two routes to tell apart. See src/server-timing.js
|
|
88
|
+
// Server-Timing with the routing verdict in it. See src/server-timing.js
|
|
95
89
|
module.exports.serverTiming = require("./server-timing.js");
|
|
96
90
|
module.exports.json = middlewares.json;
|
|
97
91
|
module.exports.urlencoded = middlewares.urlencoded;
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
const { Readable } = require("stream");
|
|
21
|
+
|
|
22
|
+
// 128 KB of body buffered before uWS is asked to pause
|
|
23
|
+
const READABLE_OPTIONS = { highWaterMark: 128 * 1024 };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A Readable that has not been built yet.
|
|
27
|
+
*
|
|
28
|
+
* Every request pays for the stream and almost none use it: a GET carries no body, and the bodies
|
|
29
|
+
* that arrive are collected by uWS and handed to the parsers without touching the stream. Measured
|
|
30
|
+
* on this machine, Readable's constructor is about 90ns of the 900ns a hello-world request costs.
|
|
31
|
+
*
|
|
32
|
+
* So the chain says Readable and the constructor does not run. `Request extends LazyReadable`, and
|
|
33
|
+
* LazyReadable's prototype is Readable's, so `req instanceof Readable` stays true and every
|
|
34
|
+
* Readable method is reachable. What is missing is `_readableState`, built on the first touch.
|
|
35
|
+
*
|
|
36
|
+
* The wrapping below is generated, not written out: every own member of Readable's prototype gets a
|
|
37
|
+
* version that materialises first, so there is no list to keep in step. Missing one would be a
|
|
38
|
+
* TypeError on `undefined._readableState`, not a slow path.
|
|
39
|
+
*/
|
|
40
|
+
class LazyReadableBase {}
|
|
41
|
+
Object.setPrototypeOf(LazyReadableBase.prototype, Readable.prototype);
|
|
42
|
+
Object.setPrototypeOf(LazyReadableBase, Readable);
|
|
43
|
+
|
|
44
|
+
// what the chain says at runtime, said again for the type checker, which cannot see a prototype
|
|
45
|
+
// being reassigned: everything a Readable offers is reachable from a Request, and is a Readable's
|
|
46
|
+
const LazyReadable = /** @type {typeof Readable} */ (/** @type {unknown} */ (LazyReadableBase));
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Builds the stream this object has been pretending to be. Idempotent: everything that can be
|
|
50
|
+
* reached from outside goes through it, so it is called far more often than it does anything.
|
|
51
|
+
*
|
|
52
|
+
* EventEmitter's init keeps an _events that is already there, so listeners added before this
|
|
53
|
+
* survive it.
|
|
54
|
+
*
|
|
55
|
+
* @param {any} stream the Request pretending to be one, before its state exists
|
|
56
|
+
*/
|
|
57
|
+
function materialise(stream) {
|
|
58
|
+
if (stream._readableState === undefined) {
|
|
59
|
+
Readable.call(stream, READABLE_OPTIONS);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const member of [
|
|
64
|
+
...Object.getOwnPropertyNames(Readable.prototype),
|
|
65
|
+
...Object.getOwnPropertySymbols(Readable.prototype)
|
|
66
|
+
]) {
|
|
67
|
+
// the constructor is not a door, and `readable` is handled below because a request writes it
|
|
68
|
+
// and writing it must not build the very thing this is avoiding
|
|
69
|
+
if (member === "constructor" || member === "readable") {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(Readable.prototype, member));
|
|
73
|
+
if (typeof descriptor.value === "function") {
|
|
74
|
+
const inner = descriptor.value;
|
|
75
|
+
Object.defineProperty(LazyReadableBase.prototype, member, {
|
|
76
|
+
...descriptor,
|
|
77
|
+
/** @this {import("stream").Readable} @param {...unknown} args */
|
|
78
|
+
value: function (...args) {
|
|
79
|
+
materialise(this);
|
|
80
|
+
return inner.apply(this, args);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} else if (descriptor.get || descriptor.set) {
|
|
84
|
+
const innerGet = descriptor.get;
|
|
85
|
+
const innerSet = descriptor.set;
|
|
86
|
+
Object.defineProperty(LazyReadableBase.prototype, member, {
|
|
87
|
+
...descriptor,
|
|
88
|
+
get: innerGet
|
|
89
|
+
? /** @this {import("stream").Readable} */ function () {
|
|
90
|
+
materialise(this);
|
|
91
|
+
return innerGet.call(this);
|
|
92
|
+
}
|
|
93
|
+
: undefined,
|
|
94
|
+
set: innerSet
|
|
95
|
+
? /** @this {import("stream").Readable} @param {unknown} value */ function (value) {
|
|
96
|
+
materialise(this);
|
|
97
|
+
innerSet.call(this, value);
|
|
98
|
+
}
|
|
99
|
+
: undefined
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const nodeReadable = /** @type {PropertyDescriptor} */ (
|
|
105
|
+
Object.getOwnPropertyDescriptor(Readable.prototype, "readable")
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// `readable` on its own: a request sets it while it is being built, and node's setter is a no-op
|
|
109
|
+
// without the state anyway, so the flag is kept as a plain field until there is a stream to ask
|
|
110
|
+
Object.defineProperty(LazyReadableBase.prototype, "readable", {
|
|
111
|
+
configurable: true,
|
|
112
|
+
enumerable: false,
|
|
113
|
+
// `this` is loose in both: node's state field, which its typings do not declare, and this
|
|
114
|
+
// project's own flag
|
|
115
|
+
/** @this {any} */
|
|
116
|
+
get: function () {
|
|
117
|
+
return this._readableState === undefined
|
|
118
|
+
? this._readableFlag === true
|
|
119
|
+
: /** @type {() => boolean} */ (nodeReadable.get).call(this);
|
|
120
|
+
},
|
|
121
|
+
/** @this {any} @param {unknown} value */
|
|
122
|
+
set: function (value) {
|
|
123
|
+
if (this._readableState === undefined) {
|
|
124
|
+
this._readableFlag = !!value;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
/** @type {(value: unknown) => void} */ (nodeReadable.set).call(this, value);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
module.exports = { LazyReadable, READABLE_OPTIONS };
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
const { Writable } = require("stream");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A Writable that has not built its state yet, the mirror of LazyReadable and for the same reason:
|
|
24
|
+
* a response is a Writable because middleware expects one, and the ordinary one never uses it.
|
|
25
|
+
* `send()` reaches `end()`, which is overridden here and goes straight to _finish, so the
|
|
26
|
+
* WritableState was allocated for every response and read by nobody. Only res.write(), a pipe,
|
|
27
|
+
* cork and the writableX getters need it.
|
|
28
|
+
*
|
|
29
|
+
* `Response extends LazyWritable`, whose prototype is Writable's, so `res instanceof Writable`
|
|
30
|
+
* stays true. `_writableState` is built on the first touch. Measured at 45ns a response.
|
|
31
|
+
*
|
|
32
|
+
* As on the Readable side the wrapping is generated: every own member of Writable's prototype gets
|
|
33
|
+
* a version that materialises first, so there is no list to keep in step.
|
|
34
|
+
*/
|
|
35
|
+
class LazyWritableBase {}
|
|
36
|
+
Object.setPrototypeOf(LazyWritableBase.prototype, Writable.prototype);
|
|
37
|
+
Object.setPrototypeOf(LazyWritableBase, Writable);
|
|
38
|
+
|
|
39
|
+
// what the chain says at runtime, said again for the type checker, which cannot see a prototype
|
|
40
|
+
// being reassigned
|
|
41
|
+
const LazyWritable = /** @type {typeof Writable} */ (/** @type {unknown} */ (LazyWritableBase));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Builds the stream this object has been pretending to be. Idempotent: everything reachable from
|
|
45
|
+
* outside goes through it, so it is called far more often than it does anything.
|
|
46
|
+
*
|
|
47
|
+
* EventEmitter's init keeps an _events that is already there, so both the shape the constructor
|
|
48
|
+
* wrote and any listener added before this survive it.
|
|
49
|
+
*
|
|
50
|
+
* @param {any} stream the Response pretending to be one, before its state exists
|
|
51
|
+
*/
|
|
52
|
+
function materialiseWritable(stream) {
|
|
53
|
+
if (stream._writableState === undefined) {
|
|
54
|
+
Writable.call(stream);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const member of [
|
|
59
|
+
...Object.getOwnPropertyNames(Writable.prototype),
|
|
60
|
+
...Object.getOwnPropertySymbols(Writable.prototype)
|
|
61
|
+
]) {
|
|
62
|
+
if (member === "constructor") {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(Writable.prototype, member));
|
|
66
|
+
if (typeof descriptor.value === "function") {
|
|
67
|
+
const inner = descriptor.value;
|
|
68
|
+
Object.defineProperty(LazyWritableBase.prototype, member, {
|
|
69
|
+
...descriptor,
|
|
70
|
+
/** @this {import("stream").Writable} @param {...unknown} args */
|
|
71
|
+
value: function (...args) {
|
|
72
|
+
materialiseWritable(this);
|
|
73
|
+
return inner.apply(this, args);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
} else if (descriptor.get || descriptor.set) {
|
|
77
|
+
const innerGet = descriptor.get;
|
|
78
|
+
const innerSet = descriptor.set;
|
|
79
|
+
Object.defineProperty(LazyWritableBase.prototype, member, {
|
|
80
|
+
...descriptor,
|
|
81
|
+
get: innerGet
|
|
82
|
+
? /** @this {import("stream").Writable} */ function () {
|
|
83
|
+
materialiseWritable(this);
|
|
84
|
+
return innerGet.call(this);
|
|
85
|
+
}
|
|
86
|
+
: undefined,
|
|
87
|
+
set: innerSet
|
|
88
|
+
? /** @this {import("stream").Writable} @param {unknown} value */ function (value) {
|
|
89
|
+
materialiseWritable(this);
|
|
90
|
+
innerSet.call(this, value);
|
|
91
|
+
}
|
|
92
|
+
: undefined
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { LazyWritable };
|