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.
@@ -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 all exist at runtime but are missing from the
21
- // declaration file the package ships, so the module is read through a loose alias
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, in node since 22.8: the next boot of the same code skips compiling it.
40
- // Asked for here because in practice the framework is the entry point of the application
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
- // Written as `module.exports.name = ...` and never through an alias: cjs-module-lexer reads this
51
- // file as text to decide which named exports an ESM importer gets, and it cannot see through one.
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, for the same reason an app is: it has to be callable to be usable as
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
- // the third of the trio: adding a method here adds it to every app, the same as express.application
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 rather than something
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
- // not one of express's, since express has none: the compression module is what everyone installs
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, which no other framework can report because no
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 };