fulmine.js 5.4.0 → 5.4.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/README.md CHANGED
@@ -96,7 +96,7 @@ to run it yourself.
96
96
  Numbers produced by a project about itself deserve suspicion, so Fulmine also stands in public arenas, run by their own rigs under their own rules:
97
97
 
98
98
  - **[HttpArena](https://www.http-arena.com/#sort=rps:-1&q=Js)** (the link lands filtered on the JavaScript entries): first among the JavaScript entries, across fifteen subscribed profiles. The saved runs measure 24.3 million WebSocket echoes per second pipelined and 3.77 million one-at-a-time (past Bun's own dedicated WebSocket entry), 7.3 million pipelined HTTP requests per second, 1.12 million on the json profile with 1.04 million of that surviving TLS, 457 thousand on compressed json, and 359 thousand on the Postgres CRUD profile, within ten percent of the leading Rust and C# entries there.
99
- - **[web-frameworks](https://github.com/the-benchmarker/web-frameworks)**: entry merged, numbers arrive with their next published round.
99
+ - **[web-frameworks](https://web-frameworks-benchmark.netlify.app/result?l=javascript)**: entry merged, numbers arrive with their next published round.
100
100
 
101
101
  More to come as their maintainers take the entries in.
102
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fulmine.js",
3
- "version": "5.4.0",
3
+ "version": "5.4.1",
4
4
  "description": "Drop-in Express 5 replacement on uWebSockets.js. Your existing middleware keeps working.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/request.js CHANGED
@@ -266,9 +266,6 @@ Object.defineProperty(LazyReadableBase.prototype, "readable", {
266
266
  });
267
267
 
268
268
  module.exports = class Request extends LazyReadable {
269
- /** @type {Record<string, any>|null} */
270
- #cachedQuery = null;
271
-
272
269
  /** @type {Record<string, any>|null} */
273
270
  #cachedHeaders = null;
274
271
 
@@ -892,7 +889,6 @@ module.exports = class Request extends LazyReadable {
892
889
  this._rawQuery = queryIndex === -1 ? "" : newUrl.slice(queryIndex + 1);
893
890
  // a rewrite to "/a?" keeps its "?", as one arriving that way does
894
891
  this.urlQuery = queryIndex === -1 ? "" : "?" + this._rawQuery;
895
- this.#cachedQuery = null;
896
892
  this._originalPath = prefix + newPath;
897
893
  this.path = newPath;
898
894
  this.endsWithSlash = newPath.charCodeAt(newPath.length - 1) === 0x2f;
@@ -913,27 +909,28 @@ module.exports = class Request extends LazyReadable {
913
909
  * the parse cached and handed out as itself, the sanitised value leaked into req.query here and
914
910
  * a handler written against express read a trimmed value where express gives it the raw one.
915
911
  *
916
- * The parse itself is still done once. What is copied per read is the shallow result, which is
917
- * cheaper than express's re-parse and answers the same for everything but a write to a nested
918
- * key, which only the extended parser can produce.
912
+ * And that is why there is no cache: the fresh object comes from parsing the raw string again,
913
+ * not from copying a kept parse. As first shipped this was parse-once-copy-per-read, and the
914
+ * copy was the expensive half: Object.assign between null-prototype objects, which live in
915
+ * V8's dictionary mode, measured 638ns for a two-parameter query where parsing the same string
916
+ * measures 119ns, and on a benchmark whose every request carries such a query it cost +1.5us
917
+ * of CPU per request, which a public arena saw as -8% on its query-carrying rows. A handler
918
+ * that reads req.query once per request now pays exactly what it paid when the parse was
919
+ * cached, one parse, and a handler that reads it N times pays N parses, which is express's
920
+ * own cost shape.
919
921
  *
920
922
  * @returns {Record<string, any>}
921
923
  */
922
924
  get query() {
923
- let parsed = this.#cachedQuery;
924
- if (parsed === null) {
925
- const qp = this.app.get("query parser fn");
926
- // the vendored default already answers on a bare null prototype, so it goes out as is;
927
- // any other parser is copied onto one, which is what kept fast-querystring's result from
928
- // inspecting as "Empty <[Object: null prototype] {}>" where Express shows the bare form
929
- parsed = qp
930
- ? qp === parseQuery
931
- ? parseQuery(this._rawQuery)
932
- : Object.assign(Object.create(null), qp(this._rawQuery))
933
- : Object.create(null);
934
- this.#cachedQuery = parsed;
935
- }
936
- return Object.assign(Object.create(null), parsed);
925
+ const qp = this.app.get("query parser fn");
926
+ // the vendored default already answers on a bare null prototype, so it goes out as is;
927
+ // any other parser is copied onto one, which is what kept fast-querystring's result from
928
+ // inspecting as "Empty <[Object: null prototype] {}>" where Express shows the bare form
929
+ return qp
930
+ ? qp === parseQuery
931
+ ? parseQuery(this._rawQuery)
932
+ : Object.assign(Object.create(null), qp(this._rawQuery))
933
+ : Object.create(null);
937
934
  }
938
935
 
939
936
  /**