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.
@@ -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 AST node. acorn ships no useful node types, and every shape here is checked by hand
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 {any} property an acorn Property node
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 {any} node an acorn AST node. acorn ships no useful node types, and every shape here is checked by hand
133
- * @returns {any} whatever the literal denotes
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
- * @param {any[]} headers written to, so the caller keeps the array the body reader also uses
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 {any[]} headers the headers read so far, written to
291
- * @param {any[]} body the body parts, written to
292
- * @param {any} app the application, for the json settings
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: any, args: string[]}|null}
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 {{req: string, res: string, queryName: string|undefined, paramsName: string|undefined,
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 {any} fn the handler's AST
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 {any} fn the handler's AST
723
+ * @param {import("acorn").FunctionDeclaration|import("acorn").ArrowFunctionExpression} fn the handler's AST
702
724
  * @param {string[]} args its parameter names
703
- * @param {any} names what a destructured req bound, from readParamNames
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 AST node. acorn ships no useful node types, and every shape here is checked by hand
873
- * @param {(node: any) => boolean} fn
874
- * @returns {any[]}
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 = [];
@@ -47,9 +47,9 @@ class HotSettings {
47
47
  */
48
48
  const settingsWriteTraps = {
49
49
  /**
50
- * @param {any} target the settings object itself, whose keys are the application's
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 {any} value whatever the application is setting
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 {any} target the settings object itself
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 {any} target the settings object itself
69
+ * @param {Record<string|symbol, unknown>} target the settings object itself
70
70
  * @param {string|symbol} key
71
- * @param {any} descriptor
71
+ * @param {PropertyDescriptor} descriptor
72
72
  */
73
73
  defineProperty(target, key, descriptor) {
74
74
  Object.defineProperty(target, key, descriptor);
@@ -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 {any} @param {...any} args */
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 {any} */ function () {
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 {any} @param {any} value */ function (value) {
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 {any} */ (nodeReadable.get).call(this);
119
+ : /** @type {() => boolean} */ (nodeReadable.get).call(this);
118
120
  },
119
- /** @this {any} @param {any} value */
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 {any} */ (nodeReadable.set).call(this, value);
127
+ /** @type {(value: unknown) => void} */ (nodeReadable.set).call(this, value);
126
128
  }
127
129
  });
128
130
 
@@ -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 {any} @param {...any} args */
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 {any} */ function () {
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 {any} @param {any} value */ function (value) {
88
+ ? /** @this {import("stream").Writable} @param {unknown} value */ function (value) {
89
89
  materialiseWritable(this);
90
90
  innerSet.call(this, value);
91
91
  }