fulmine.js 5.19.3 → 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 +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 +47 -24
- 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 +94 -55
- 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.js +145 -109
- package/src/route.js +3 -3
- package/src/router-utils.js +62 -14
- package/src/router.js +46 -28
- 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 +120 -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
|
@@ -25,6 +25,8 @@ 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) {
|
|
@@ -286,10 +290,10 @@ function readStatusAndHeaders(callExprs, headers) {
|
|
|
286
290
|
* The body parts the calls write, pushed into `body`, with the content-type decisions they imply
|
|
287
291
|
* pushed into `headers`. null when one of the calls writes something this cannot read.
|
|
288
292
|
*
|
|
289
|
-
* @param {any[]} callExprs the res calls, in run order
|
|
290
|
-
* @param {
|
|
291
|
-
* @param {any[]} body the body parts, written to
|
|
292
|
-
* @param {
|
|
293
|
+
* @param {any[]} callExprs the res calls, in run order, as readStatusAndHeaders takes them
|
|
294
|
+
* @param {[string, string][]} headers the headers read so far, written to
|
|
295
|
+
* @param {any[]} body the body parts, written to; loose because a literal's value is kept as it is
|
|
296
|
+
* @param {Application} app the application, for the json settings
|
|
293
297
|
* @param {string[]} queries names bound by a destructured req.query
|
|
294
298
|
* @param {string[]} params names bound by a destructured req.params
|
|
295
299
|
* @returns {{sendUsed: boolean, bodyFromSend: boolean}|null}
|
|
@@ -310,6 +314,11 @@ function readBody(callExprs, headers, body, app, queries, params) {
|
|
|
310
314
|
if (call.obj.propertyName !== "end") {
|
|
311
315
|
bodyFromSend = true;
|
|
312
316
|
}
|
|
317
|
+
// one argument at most: res.end(data, encoding) and res.end(data, cb) are shapes a
|
|
318
|
+
// compiled response cannot stand for, and stood for them as if the extra were not there
|
|
319
|
+
if (call.arguments.length > 1) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
313
322
|
const arg = call.arguments[0];
|
|
314
323
|
|
|
315
324
|
if (call.obj.propertyName === "json") {
|
|
@@ -420,7 +429,8 @@ function readBody(callExprs, headers, body, app, queries, params) {
|
|
|
420
429
|
* Reads a chain of string concatenations right to left. Each side must be a literal or a
|
|
421
430
|
* param or query value, anything else makes the whole handler fall back.
|
|
422
431
|
*
|
|
423
|
-
* @param {any} node a BinaryExpression
|
|
432
|
+
* @param {any} node a BinaryExpression, read loosely: the literal on either
|
|
433
|
+
* side is kept as it is
|
|
424
434
|
* @returns {boolean}
|
|
425
435
|
*/
|
|
426
436
|
function check(node) {
|
|
@@ -479,7 +489,7 @@ function readBody(callExprs, headers, body, app, queries, params) {
|
|
|
479
489
|
* through, too few parameters, or a return that is not the last statement.
|
|
480
490
|
*
|
|
481
491
|
* @param {Function} cb
|
|
482
|
-
* @returns {{fn:
|
|
492
|
+
* @returns {{fn: import("acorn").FunctionDeclaration|import("acorn").ArrowFunctionExpression, args: string[]}|null}
|
|
483
493
|
*/
|
|
484
494
|
function readHandler(cb) {
|
|
485
495
|
let code = cb.toString();
|
|
@@ -491,7 +501,7 @@ function readHandler(cb) {
|
|
|
491
501
|
// Anything not understood returns false and falls back to ordinary routing. Widening the
|
|
492
502
|
// list below is not worth it: over the 1113 handlers in tests, demo and benchmark, 42.6%
|
|
493
503
|
// call something that is not res, `const` would unlock 7 (0.6%), a conditional 0.1% more.
|
|
494
|
-
/** @type {any[]} */
|
|
504
|
+
/** @type {any[]} the tokens, loose because acorn's Token type leaves out value */
|
|
495
505
|
const tokens = [...acorn.tokenizer(code, { ecmaVersion: "latest" })];
|
|
496
506
|
|
|
497
507
|
if (
|
|
@@ -520,7 +530,7 @@ function readHandler(cb) {
|
|
|
520
530
|
return null;
|
|
521
531
|
}
|
|
522
532
|
|
|
523
|
-
/** @type {any[]} */
|
|
533
|
+
/** @type {any[]} the statements, read loosely: what a parameter may be is checked by hand in readParamNames */
|
|
524
534
|
const parsed = parser.parse(code, { ecmaVersion: "latest" }).body;
|
|
525
535
|
let fn = parsed[0];
|
|
526
536
|
|
|
@@ -563,14 +573,25 @@ function readHandler(cb) {
|
|
|
563
573
|
return { fn, args };
|
|
564
574
|
}
|
|
565
575
|
|
|
576
|
+
/**
|
|
577
|
+
* What readParamNames found: the two parameter names, and what a destructured req bound.
|
|
578
|
+
* @typedef {object} ParamNames
|
|
579
|
+
* @property {string} req
|
|
580
|
+
* @property {string} res
|
|
581
|
+
* @property {string|undefined} queryName
|
|
582
|
+
* @property {string|undefined} paramsName
|
|
583
|
+
* @property {string[]} queries
|
|
584
|
+
* @property {string[]} params
|
|
585
|
+
*/
|
|
586
|
+
|
|
566
587
|
/**
|
|
567
588
|
* The names a destructured `req` binds for query and params, so the body reader can tell one of
|
|
568
589
|
* them from an identifier it must refuse. null when the pattern is one this cannot read.
|
|
569
590
|
*
|
|
570
|
-
* @param {any} fn the handler's AST
|
|
591
|
+
* @param {any} fn the handler's AST, read loosely: a destructured parameter is checked shape by
|
|
592
|
+
* shape, and anything else throws into the fallback
|
|
571
593
|
* @param {string[]} args its parameter names
|
|
572
|
-
* @returns {
|
|
573
|
-
* queries: string[], params: string[]}|null}
|
|
594
|
+
* @returns {ParamNames|null}
|
|
574
595
|
*/
|
|
575
596
|
function readParamNames(fn, args) {
|
|
576
597
|
const [req, res] = args;
|
|
@@ -615,9 +636,10 @@ function readParamNames(fn, args) {
|
|
|
615
636
|
* Every call the handler makes, in the order they run, cut after the one that writes the body.
|
|
616
637
|
* null when it calls anything but `res`, or a method a compiled response cannot stand for.
|
|
617
638
|
*
|
|
618
|
-
* @param {
|
|
639
|
+
* @param {import("acorn").FunctionDeclaration|import("acorn").ArrowFunctionExpression} fn the handler's AST
|
|
619
640
|
* @param {string} res the name its second parameter was given
|
|
620
|
-
* @returns {any[]|null}
|
|
641
|
+
* @returns {any[]|null} the call nodes, each carrying what was read off its callee as `obj`; loose
|
|
642
|
+
* because the readers take their arguments as whatever literal they hold
|
|
621
643
|
*/
|
|
622
644
|
function readResCalls(fn, res) {
|
|
623
645
|
// check if it calls any other function other than the one in `res`
|
|
@@ -698,9 +720,9 @@ function readResCalls(fn, res) {
|
|
|
698
720
|
/**
|
|
699
721
|
* Whether every identifier in the handler is one a compiled response can stand for.
|
|
700
722
|
*
|
|
701
|
-
* @param {
|
|
723
|
+
* @param {import("acorn").FunctionDeclaration|import("acorn").ArrowFunctionExpression} fn the handler's AST
|
|
702
724
|
* @param {string[]} args its parameter names
|
|
703
|
-
* @param {
|
|
725
|
+
* @param {ParamNames} names what a destructured req bound, from readParamNames
|
|
704
726
|
* @returns {boolean}
|
|
705
727
|
*/
|
|
706
728
|
function identifiersAllowed(fn, args, names) {
|
|
@@ -869,9 +891,10 @@ module.exports = function compileDeclarative(cb, app) {
|
|
|
869
891
|
* Every node matching the predicate, in the order of the named edges below. The edges are written
|
|
870
892
|
* by hand, which is why compileDeclarative first refuses any node type not on the understood list.
|
|
871
893
|
*
|
|
872
|
-
* @param {any} node an acorn
|
|
873
|
-
*
|
|
874
|
-
* @
|
|
894
|
+
* @param {any} node an acorn node, walked along the named edges below, so nothing is assumed
|
|
895
|
+
* about its shape
|
|
896
|
+
* @param {(node: import("acorn").AnyNode) => boolean} fn
|
|
897
|
+
* @returns {any[]} the matching nodes, as loose as the input
|
|
875
898
|
*/
|
|
876
899
|
function filterNodes(node, fn) {
|
|
877
900
|
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
|
}
|