fulmine.js 5.0.0 → 5.1.0

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/src/utils.js CHANGED
@@ -102,8 +102,10 @@ function getPatternMeta(pattern) {
102
102
  * throw: a route that quietly stops matching is worse than one that fails at startup. The names it
103
103
  * captures go in a WeakMap beside the regex, see PatternMeta.
104
104
  */
105
- function patternToRegex(pattern, isPrefix = false) {
105
+ function patternToRegex(pattern, isPrefix = false, caseSensitive = true) {
106
106
  if (pattern instanceof RegExp) {
107
+ // the application's own RegExp matches as written, whatever the routing setting says,
108
+ // which is what express does with one
107
109
  return pattern;
108
110
  }
109
111
  if (isPrefix && pattern === "") {
@@ -247,7 +249,9 @@ function patternToRegex(pattern, isPrefix = false) {
247
249
  i++;
248
250
  }
249
251
 
250
- const regex = /** @type {PathRegExp} */ (new RegExp(`^${regexPattern}${isPrefix ? "(?=$|/)" : "$"}`));
252
+ const regex = /** @type {PathRegExp} */ (
253
+ new RegExp(`^${regexPattern}${isPrefix ? "(?=$|/)" : "$"}`, caseSensitive ? "" : "i")
254
+ );
251
255
  // read back out of the finished pattern, so the list cannot disagree with the regex. Asking for
252
256
  // each name in turn beats walking match.groups with for-in: 176ns against 349
253
257
  const paramNames = [...regexPattern.matchAll(NAMED_GROUP)].map((m) => m[1]);
@@ -364,6 +368,37 @@ function pathsCanOverlap(a, b, aIsPrefix = false) {
364
368
  return true;
365
369
  }
366
370
 
371
+ // a capture group opening: "(" not followed by "?", or "(?<name>". The same expression express
372
+ // scans a user-supplied RegExp with, so the keys come out in the same order
373
+ const MATCHING_GROUP_REGEXP = /\((?:\?<(.*?)>)?(?!\?)/g;
374
+
375
+ /** @type {WeakMap<RegExp, (string|number)[]>} */
376
+ const groupKeysCache = new WeakMap();
377
+
378
+ /**
379
+ * What each capture group of a user-supplied RegExp is called in req.params: its own name when it
380
+ * has one, otherwise its position among the unnamed groups, counting from zero. Worked out once
381
+ * per pattern, since the source never changes.
382
+ *
383
+ * @param {RegExp} pattern
384
+ * @returns {(string|number)[]} one entry per capture group, in source order
385
+ */
386
+ function regexpGroupKeys(pattern) {
387
+ let keys = groupKeysCache.get(pattern);
388
+ if (keys) {
389
+ return keys;
390
+ }
391
+ keys = [];
392
+ let unnamed = 0;
393
+ let match;
394
+ MATCHING_GROUP_REGEXP.lastIndex = 0;
395
+ while ((match = MATCHING_GROUP_REGEXP.exec(pattern.source)) !== null) {
396
+ keys.push(match[1] || unnamed++);
397
+ }
398
+ groupKeysCache.set(pattern, keys);
399
+ return keys;
400
+ }
401
+
367
402
  /**
368
403
  * Whether µWS would answer with `earlier` every path that both paths match, which is what makes its
369
404
  * order agree with express's registration order.
@@ -538,7 +573,9 @@ const defaultSettings = {
538
573
  // asking which framework is running, and every hardening guide says to remove it. Set it back
539
574
  // to true if something depends on it.
540
575
  "x-powered-by": false,
541
- "case sensitive routing": true,
576
+ // "case sensitive routing" is deliberately absent: unset means insensitive, as in Express 5.
577
+ // The native µWS router matches bytes, so the compiler in _compileOptimizedRoutes only hands
578
+ // it routes whose earlier siblings it can prove agree under either case rule.
542
579
  "declarative responses": true
543
580
  };
544
581
 
@@ -1032,6 +1069,7 @@ module.exports = {
1032
1069
  canBeOptimizedWithParams,
1033
1070
  pathsCanOverlap,
1034
1071
  uwsPrefersEarlier,
1072
+ regexpGroupKeys,
1035
1073
  escapeHtml,
1036
1074
  withDefaultCharset,
1037
1075
  withUtf8Charset,