fulmine.js 5.19.3 → 5.19.5
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 +9 -4
- package/package.json +1 -1
- package/src/application.js +25 -36
- package/src/cli.js +22 -16
- package/src/cluster.js +2 -2
- package/src/compression.js +102 -61
- package/src/declarative.js +61 -32
- package/src/hot-settings.js +5 -5
- package/src/lazy-readable.js +8 -6
- package/src/lazy-writable.js +3 -3
- package/src/middlewares.js +168 -96
- package/src/nest.js +3 -2
- package/src/node-shim.js +10 -5
- package/src/optimizer.js +9 -7
- package/src/options.d.ts +9 -4
- package/src/request-utils.js +3 -2
- package/src/request.js +49 -38
- package/src/response-utils.js +4 -1
- package/src/response.js +244 -119
- package/src/route.js +3 -3
- package/src/router-utils.js +62 -14
- package/src/router.js +69 -35
- package/src/server-shape.js +14 -10
- package/src/server-timing.js +16 -4
- package/src/socket.js +3 -3
- package/src/testing.js +4 -3
- package/src/usage.js +10 -5
- package/src/utils.js +132 -23
- package/src/verify.js +4 -3
- package/src/view.js +1 -1
- package/src/walk.js +8 -7
- package/src/websocket.js +17 -8
- package/src/work.js +3 -3
package/src/declarative.js
CHANGED
|
@@ -18,13 +18,15 @@ limitations under the License.
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const acorn = require("acorn");
|
|
21
|
-
const { stringify,
|
|
21
|
+
const { stringify, contentTypeSet, withUtf8Charset, contentTypeFor } = 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
24
|
const uWS = require("uWebSockets.js");
|
|
25
25
|
const uWSAny = /** @type {any} */ (uWS);
|
|
26
26
|
const statuses = require("statuses");
|
|
27
27
|
|
|
28
|
+
/** @typedef {import("./application.js").Application} Application */
|
|
29
|
+
|
|
28
30
|
const parser = acorn.Parser;
|
|
29
31
|
|
|
30
32
|
const allowedResMethods = [
|
|
@@ -89,7 +91,8 @@ const understoodNodeTypes = new Set([
|
|
|
89
91
|
* Every node type in the tree. Walks all the keys instead of named edges, so the answer does not
|
|
90
92
|
* depend on the walk being complete.
|
|
91
93
|
*
|
|
92
|
-
* @param {any} node an acorn
|
|
94
|
+
* @param {any} node an acorn node, or an array or a scalar under one: walked by key, so no shape
|
|
95
|
+
* is assumed
|
|
93
96
|
* @param {Set<string>} types
|
|
94
97
|
*/
|
|
95
98
|
function collectNodeTypes(node, types) {
|
|
@@ -112,7 +115,7 @@ function collectNodeTypes(node, types) {
|
|
|
112
115
|
/**
|
|
113
116
|
* The key a property writes. Only a plain name or a literal, never computed, a getter or a spread.
|
|
114
117
|
*
|
|
115
|
-
* @param {
|
|
118
|
+
* @param {import("acorn").AnyNode} property an acorn node, a Property when it is one this reads
|
|
116
119
|
* @returns {string|null} null when the shape is not one of those
|
|
117
120
|
*/
|
|
118
121
|
function literalKeyOf(property) {
|
|
@@ -129,8 +132,8 @@ function literalKeyOf(property) {
|
|
|
129
132
|
* The value of a literal expression, for the shapes known at registration time. Anything else
|
|
130
133
|
* throws, and the catch around the compiler turns it into ordinary routing.
|
|
131
134
|
*
|
|
132
|
-
* @param {
|
|
133
|
-
* @returns {
|
|
135
|
+
* @param {import("acorn").AnyNode} node
|
|
136
|
+
* @returns {unknown} whatever the literal denotes
|
|
134
137
|
*/
|
|
135
138
|
function literalValue(node) {
|
|
136
139
|
switch (node.type) {
|
|
@@ -187,8 +190,9 @@ function literalValue(node) {
|
|
|
187
190
|
* The status and the headers the calls set, in the order they were first written. null when one
|
|
188
191
|
* of them is not a literal this can read.
|
|
189
192
|
*
|
|
190
|
-
* @param {any[]} callExprs the res calls, in run order
|
|
191
|
-
*
|
|
193
|
+
* @param {any[]} callExprs the res calls, in run order, each carrying what readResCalls read off
|
|
194
|
+
* its callee as `obj`; loose because the arguments are taken as whatever literal they hold
|
|
195
|
+
* @param {[string, string][]} headers written to, so the caller keeps the array the body reader also uses
|
|
192
196
|
* @returns {{statusCode: number, sendStatusUsed: boolean}|null}
|
|
193
197
|
*/
|
|
194
198
|
function readStatusAndHeaders(callExprs, headers) {
|
|
@@ -246,10 +250,16 @@ function readStatusAndHeaders(callExprs, headers) {
|
|
|
246
250
|
|
|
247
251
|
for (let [header, value] of pairs) {
|
|
248
252
|
const name = String(header).toLowerCase();
|
|
249
|
-
// res.set
|
|
250
|
-
// is node's and node does not know what a media type is
|
|
253
|
+
// res.set resolves a content-type through the mime database, res.setHeader does
|
|
254
|
+
// not: setHeader is node's and node does not know what a media type is
|
|
251
255
|
if (call.obj.propertyName !== "setHeader" && name === "content-type") {
|
|
252
|
-
|
|
256
|
+
const resolved = contentTypeSet(String(value));
|
|
257
|
+
if (resolved === false) {
|
|
258
|
+
// res.set stores false for it and the body method writes its own type
|
|
259
|
+
// instead; left to the ordinary path rather than worked out twice here
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
value = resolved;
|
|
253
263
|
}
|
|
254
264
|
const index = headers.findIndex((entry) => String(entry[0]).toLowerCase() === name);
|
|
255
265
|
if (index === -1) {
|
|
@@ -286,10 +296,10 @@ function readStatusAndHeaders(callExprs, headers) {
|
|
|
286
296
|
* The body parts the calls write, pushed into `body`, with the content-type decisions they imply
|
|
287
297
|
* pushed into `headers`. null when one of the calls writes something this cannot read.
|
|
288
298
|
*
|
|
289
|
-
* @param {any[]} callExprs the res calls, in run order
|
|
290
|
-
* @param {
|
|
291
|
-
* @param {any[]} body the body parts, written to
|
|
292
|
-
* @param {
|
|
299
|
+
* @param {any[]} callExprs the res calls, in run order, as readStatusAndHeaders takes them
|
|
300
|
+
* @param {[string, string][]} headers the headers read so far, written to
|
|
301
|
+
* @param {any[]} body the body parts, written to; loose because a literal's value is kept as it is
|
|
302
|
+
* @param {Application} app the application, for the json settings
|
|
293
303
|
* @param {string[]} queries names bound by a destructured req.query
|
|
294
304
|
* @param {string[]} params names bound by a destructured req.params
|
|
295
305
|
* @returns {{sendUsed: boolean, bodyFromSend: boolean}|null}
|
|
@@ -310,6 +320,11 @@ function readBody(callExprs, headers, body, app, queries, params) {
|
|
|
310
320
|
if (call.obj.propertyName !== "end") {
|
|
311
321
|
bodyFromSend = true;
|
|
312
322
|
}
|
|
323
|
+
// one argument at most: res.end(data, encoding) and res.end(data, cb) are shapes a
|
|
324
|
+
// compiled response cannot stand for, and stood for them as if the extra were not there
|
|
325
|
+
if (call.arguments.length > 1) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
313
328
|
const arg = call.arguments[0];
|
|
314
329
|
|
|
315
330
|
if (call.obj.propertyName === "json") {
|
|
@@ -420,7 +435,8 @@ function readBody(callExprs, headers, body, app, queries, params) {
|
|
|
420
435
|
* Reads a chain of string concatenations right to left. Each side must be a literal or a
|
|
421
436
|
* param or query value, anything else makes the whole handler fall back.
|
|
422
437
|
*
|
|
423
|
-
* @param {any} node a BinaryExpression
|
|
438
|
+
* @param {any} node a BinaryExpression, read loosely: the literal on either
|
|
439
|
+
* side is kept as it is
|
|
424
440
|
* @returns {boolean}
|
|
425
441
|
*/
|
|
426
442
|
function check(node) {
|
|
@@ -479,7 +495,7 @@ function readBody(callExprs, headers, body, app, queries, params) {
|
|
|
479
495
|
* through, too few parameters, or a return that is not the last statement.
|
|
480
496
|
*
|
|
481
497
|
* @param {Function} cb
|
|
482
|
-
* @returns {{fn:
|
|
498
|
+
* @returns {{fn: import("acorn").FunctionDeclaration|import("acorn").ArrowFunctionExpression, args: string[]}|null}
|
|
483
499
|
*/
|
|
484
500
|
function readHandler(cb) {
|
|
485
501
|
let code = cb.toString();
|
|
@@ -491,7 +507,7 @@ function readHandler(cb) {
|
|
|
491
507
|
// Anything not understood returns false and falls back to ordinary routing. Widening the
|
|
492
508
|
// list below is not worth it: over the 1113 handlers in tests, demo and benchmark, 42.6%
|
|
493
509
|
// call something that is not res, `const` would unlock 7 (0.6%), a conditional 0.1% more.
|
|
494
|
-
/** @type {any[]} */
|
|
510
|
+
/** @type {any[]} the tokens, loose because acorn's Token type leaves out value */
|
|
495
511
|
const tokens = [...acorn.tokenizer(code, { ecmaVersion: "latest" })];
|
|
496
512
|
|
|
497
513
|
if (
|
|
@@ -520,7 +536,7 @@ function readHandler(cb) {
|
|
|
520
536
|
return null;
|
|
521
537
|
}
|
|
522
538
|
|
|
523
|
-
/** @type {any[]} */
|
|
539
|
+
/** @type {any[]} the statements, read loosely: what a parameter may be is checked by hand in readParamNames */
|
|
524
540
|
const parsed = parser.parse(code, { ecmaVersion: "latest" }).body;
|
|
525
541
|
let fn = parsed[0];
|
|
526
542
|
|
|
@@ -563,14 +579,25 @@ function readHandler(cb) {
|
|
|
563
579
|
return { fn, args };
|
|
564
580
|
}
|
|
565
581
|
|
|
582
|
+
/**
|
|
583
|
+
* What readParamNames found: the two parameter names, and what a destructured req bound.
|
|
584
|
+
* @typedef {object} ParamNames
|
|
585
|
+
* @property {string} req
|
|
586
|
+
* @property {string} res
|
|
587
|
+
* @property {string|undefined} queryName
|
|
588
|
+
* @property {string|undefined} paramsName
|
|
589
|
+
* @property {string[]} queries
|
|
590
|
+
* @property {string[]} params
|
|
591
|
+
*/
|
|
592
|
+
|
|
566
593
|
/**
|
|
567
594
|
* The names a destructured `req` binds for query and params, so the body reader can tell one of
|
|
568
595
|
* them from an identifier it must refuse. null when the pattern is one this cannot read.
|
|
569
596
|
*
|
|
570
|
-
* @param {any} fn the handler's AST
|
|
597
|
+
* @param {any} fn the handler's AST, read loosely: a destructured parameter is checked shape by
|
|
598
|
+
* shape, and anything else throws into the fallback
|
|
571
599
|
* @param {string[]} args its parameter names
|
|
572
|
-
* @returns {
|
|
573
|
-
* queries: string[], params: string[]}|null}
|
|
600
|
+
* @returns {ParamNames|null}
|
|
574
601
|
*/
|
|
575
602
|
function readParamNames(fn, args) {
|
|
576
603
|
const [req, res] = args;
|
|
@@ -615,9 +642,10 @@ function readParamNames(fn, args) {
|
|
|
615
642
|
* Every call the handler makes, in the order they run, cut after the one that writes the body.
|
|
616
643
|
* null when it calls anything but `res`, or a method a compiled response cannot stand for.
|
|
617
644
|
*
|
|
618
|
-
* @param {
|
|
645
|
+
* @param {import("acorn").FunctionDeclaration|import("acorn").ArrowFunctionExpression} fn the handler's AST
|
|
619
646
|
* @param {string} res the name its second parameter was given
|
|
620
|
-
* @returns {any[]|null}
|
|
647
|
+
* @returns {any[]|null} the call nodes, each carrying what was read off its callee as `obj`; loose
|
|
648
|
+
* because the readers take their arguments as whatever literal they hold
|
|
621
649
|
*/
|
|
622
650
|
function readResCalls(fn, res) {
|
|
623
651
|
// check if it calls any other function other than the one in `res`
|
|
@@ -698,9 +726,9 @@ function readResCalls(fn, res) {
|
|
|
698
726
|
/**
|
|
699
727
|
* Whether every identifier in the handler is one a compiled response can stand for.
|
|
700
728
|
*
|
|
701
|
-
* @param {
|
|
729
|
+
* @param {import("acorn").FunctionDeclaration|import("acorn").ArrowFunctionExpression} fn the handler's AST
|
|
702
730
|
* @param {string[]} args its parameter names
|
|
703
|
-
* @param {
|
|
731
|
+
* @param {ParamNames} names what a destructured req bound, from readParamNames
|
|
704
732
|
* @returns {boolean}
|
|
705
733
|
*/
|
|
706
734
|
function identifiersAllowed(fn, args, names) {
|
|
@@ -801,10 +829,10 @@ module.exports = function compileDeclarative(cb, app) {
|
|
|
801
829
|
if (!connection && advertise) {
|
|
802
830
|
decRes = decRes.writeHeader("connection", "keep-alive");
|
|
803
831
|
}
|
|
804
|
-
// not when the
|
|
805
|
-
// the
|
|
806
|
-
|
|
807
|
-
if (advertise && !
|
|
832
|
+
// not when the route wrote its own Connection, whatever it says: node writes the two as a
|
|
833
|
+
// pair and writes neither once the response has set Connection, and the ordinary path
|
|
834
|
+
// leaves it out for the same reason
|
|
835
|
+
if (advertise && !connection && !headers.some((header) => header[0].toLowerCase() === "keep-alive")) {
|
|
808
836
|
decRes = decRes.writeHeader("keep-alive", "timeout=10");
|
|
809
837
|
}
|
|
810
838
|
|
|
@@ -869,9 +897,10 @@ module.exports = function compileDeclarative(cb, app) {
|
|
|
869
897
|
* Every node matching the predicate, in the order of the named edges below. The edges are written
|
|
870
898
|
* by hand, which is why compileDeclarative first refuses any node type not on the understood list.
|
|
871
899
|
*
|
|
872
|
-
* @param {any} node an acorn
|
|
873
|
-
*
|
|
874
|
-
* @
|
|
900
|
+
* @param {any} node an acorn node, walked along the named edges below, so nothing is assumed
|
|
901
|
+
* about its shape
|
|
902
|
+
* @param {(node: import("acorn").AnyNode) => boolean} fn
|
|
903
|
+
* @returns {any[]} the matching nodes, as loose as the input
|
|
875
904
|
*/
|
|
876
905
|
function filterNodes(node, fn) {
|
|
877
906
|
const filtered = [];
|
package/src/hot-settings.js
CHANGED
|
@@ -47,9 +47,9 @@ class HotSettings {
|
|
|
47
47
|
*/
|
|
48
48
|
const settingsWriteTraps = {
|
|
49
49
|
/**
|
|
50
|
-
* @param {
|
|
50
|
+
* @param {Record<string|symbol, unknown>} target the settings object itself, whose keys are the application's
|
|
51
51
|
* @param {string|symbol} key
|
|
52
|
-
* @param {
|
|
52
|
+
* @param {unknown} value whatever the application is setting
|
|
53
53
|
*/
|
|
54
54
|
set(target, key, value) {
|
|
55
55
|
target[key] = value;
|
|
@@ -57,7 +57,7 @@ const settingsWriteTraps = {
|
|
|
57
57
|
return true;
|
|
58
58
|
},
|
|
59
59
|
/**
|
|
60
|
-
* @param {
|
|
60
|
+
* @param {Record<string|symbol, unknown>} target the settings object itself
|
|
61
61
|
* @param {string|symbol} key
|
|
62
62
|
*/
|
|
63
63
|
deleteProperty(target, key) {
|
|
@@ -66,9 +66,9 @@ const settingsWriteTraps = {
|
|
|
66
66
|
return true;
|
|
67
67
|
},
|
|
68
68
|
/**
|
|
69
|
-
* @param {
|
|
69
|
+
* @param {Record<string|symbol, unknown>} target the settings object itself
|
|
70
70
|
* @param {string|symbol} key
|
|
71
|
-
* @param {
|
|
71
|
+
* @param {PropertyDescriptor} descriptor
|
|
72
72
|
*/
|
|
73
73
|
defineProperty(target, key, descriptor) {
|
|
74
74
|
Object.defineProperty(target, key, descriptor);
|
package/src/lazy-readable.js
CHANGED
|
@@ -74,7 +74,7 @@ for (const member of [
|
|
|
74
74
|
const inner = descriptor.value;
|
|
75
75
|
Object.defineProperty(LazyReadableBase.prototype, member, {
|
|
76
76
|
...descriptor,
|
|
77
|
-
/** @this {
|
|
77
|
+
/** @this {import("stream").Readable} @param {...unknown} args */
|
|
78
78
|
value: function (...args) {
|
|
79
79
|
materialise(this);
|
|
80
80
|
return inner.apply(this, args);
|
|
@@ -86,13 +86,13 @@ for (const member of [
|
|
|
86
86
|
Object.defineProperty(LazyReadableBase.prototype, member, {
|
|
87
87
|
...descriptor,
|
|
88
88
|
get: innerGet
|
|
89
|
-
? /** @this {
|
|
89
|
+
? /** @this {import("stream").Readable} */ function () {
|
|
90
90
|
materialise(this);
|
|
91
91
|
return innerGet.call(this);
|
|
92
92
|
}
|
|
93
93
|
: undefined,
|
|
94
94
|
set: innerSet
|
|
95
|
-
? /** @this {
|
|
95
|
+
? /** @this {import("stream").Readable} @param {unknown} value */ function (value) {
|
|
96
96
|
materialise(this);
|
|
97
97
|
innerSet.call(this, value);
|
|
98
98
|
}
|
|
@@ -110,19 +110,21 @@ const nodeReadable = /** @type {PropertyDescriptor} */ (
|
|
|
110
110
|
Object.defineProperty(LazyReadableBase.prototype, "readable", {
|
|
111
111
|
configurable: true,
|
|
112
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
|
|
113
115
|
/** @this {any} */
|
|
114
116
|
get: function () {
|
|
115
117
|
return this._readableState === undefined
|
|
116
118
|
? this._readableFlag === true
|
|
117
|
-
: /** @type {
|
|
119
|
+
: /** @type {() => boolean} */ (nodeReadable.get).call(this);
|
|
118
120
|
},
|
|
119
|
-
/** @this {any} @param {
|
|
121
|
+
/** @this {any} @param {unknown} value */
|
|
120
122
|
set: function (value) {
|
|
121
123
|
if (this._readableState === undefined) {
|
|
122
124
|
this._readableFlag = !!value;
|
|
123
125
|
return;
|
|
124
126
|
}
|
|
125
|
-
/** @type {
|
|
127
|
+
/** @type {(value: unknown) => void} */ (nodeReadable.set).call(this, value);
|
|
126
128
|
}
|
|
127
129
|
});
|
|
128
130
|
|
package/src/lazy-writable.js
CHANGED
|
@@ -67,7 +67,7 @@ for (const member of [
|
|
|
67
67
|
const inner = descriptor.value;
|
|
68
68
|
Object.defineProperty(LazyWritableBase.prototype, member, {
|
|
69
69
|
...descriptor,
|
|
70
|
-
/** @this {
|
|
70
|
+
/** @this {import("stream").Writable} @param {...unknown} args */
|
|
71
71
|
value: function (...args) {
|
|
72
72
|
materialiseWritable(this);
|
|
73
73
|
return inner.apply(this, args);
|
|
@@ -79,13 +79,13 @@ for (const member of [
|
|
|
79
79
|
Object.defineProperty(LazyWritableBase.prototype, member, {
|
|
80
80
|
...descriptor,
|
|
81
81
|
get: innerGet
|
|
82
|
-
? /** @this {
|
|
82
|
+
? /** @this {import("stream").Writable} */ function () {
|
|
83
83
|
materialiseWritable(this);
|
|
84
84
|
return innerGet.call(this);
|
|
85
85
|
}
|
|
86
86
|
: undefined,
|
|
87
87
|
set: innerSet
|
|
88
|
-
? /** @this {
|
|
88
|
+
? /** @this {import("stream").Writable} @param {unknown} value */ function (value) {
|
|
89
89
|
materialiseWritable(this);
|
|
90
90
|
innerSet.call(this, value);
|
|
91
91
|
}
|