@jsenv/server 16.0.3 → 16.0.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.
@@ -1718,7 +1718,7 @@ const fromNodeRequest = (
1718
1718
  `formData() called on a request with content-type: "${contentType}". multipart/form-data was expected.`,
1719
1719
  );
1720
1720
  }
1721
- const { formidable } = await import("./js/formidable_index.js");
1721
+ const { formidable } = await import("./formidable_index/formidable_index.js");
1722
1722
  const form = formidable({});
1723
1723
  nodeRequest.resume(); // was paused in line #53
1724
1724
  const [fields, files] = await form.parse(nodeRequest);
@@ -3600,6 +3600,13 @@ const applyPattern = ({ url, pattern }) => {
3600
3600
  };
3601
3601
  };
3602
3602
 
3603
+ /**
3604
+ * Core pattern matching function that processes patterns against strings
3605
+ * @param {string} pattern - The pattern with special syntax (*,**,/) to match against
3606
+ * @param {string} string - The string to test against the pattern
3607
+ * @returns {Object} Result containing match status and capture groups
3608
+ * @private
3609
+ */
3603
3610
  const applyMatching = (pattern, string) => {
3604
3611
  const groups = [];
3605
3612
  let patternIndex = 0;
@@ -3624,22 +3631,6 @@ const applyMatching = (pattern, string) => {
3624
3631
  return consumeString(remainingString.length);
3625
3632
  };
3626
3633
 
3627
- let matched;
3628
- const iterate = () => {
3629
- const patternIndexBefore = patternIndex;
3630
- const indexBefore = index;
3631
- matched = matchOne();
3632
- if (matched === undefined) {
3633
- consumePattern(1);
3634
- consumeString(1);
3635
- iterate();
3636
- return;
3637
- }
3638
- if (matched === false && restoreIndexes) {
3639
- patternIndex = patternIndexBefore;
3640
- index = indexBefore;
3641
- }
3642
- };
3643
3634
  const matchOne = () => {
3644
3635
  // pattern consumed
3645
3636
  if (remainingPattern === "") {
@@ -3649,12 +3640,11 @@ const applyMatching = (pattern, string) => {
3649
3640
  if (remainingString[0] === "?") {
3650
3641
  // match search params
3651
3642
  consumeRemainingString();
3652
-
3653
3643
  return true;
3654
3644
  }
3655
- // if remainingString
3656
3645
  return false; // fails because string longer than expect
3657
3646
  }
3647
+
3658
3648
  // -- from this point pattern is not consumed --
3659
3649
  // string consumed, pattern not consumed
3660
3650
  if (remainingString === "") {
@@ -3668,6 +3658,7 @@ const applyMatching = (pattern, string) => {
3668
3658
  }
3669
3659
  return false; // fail because string shorter than expect
3670
3660
  }
3661
+
3671
3662
  // -- from this point pattern and string are not consumed --
3672
3663
  // fast path trailing slash
3673
3664
  if (remainingPattern === "/") {
@@ -3679,25 +3670,30 @@ const applyMatching = (pattern, string) => {
3679
3670
  }
3680
3671
  return false;
3681
3672
  }
3673
+
3682
3674
  // fast path trailing '**'
3683
3675
  if (remainingPattern === "**") {
3684
3676
  consumePattern(2);
3685
3677
  consumeRemainingString();
3686
3678
  return true;
3687
3679
  }
3680
+
3688
3681
  if (remainingPattern.slice(0, 4) === "/**/") {
3689
3682
  consumePattern(3); // consumes "/**/"
3690
- const skipResult = skipUntilMatch({
3683
+ const skipResult = skipUntilMatchIterative({
3691
3684
  pattern: remainingPattern,
3692
3685
  string: remainingString,
3693
3686
  canSkipSlash: true,
3694
3687
  });
3695
- groups.push(...skipResult.groups);
3688
+ for (let i = 0; i < skipResult.groups.length; i++) {
3689
+ groups.push(skipResult.groups[i]);
3690
+ }
3696
3691
  consumePattern(skipResult.patternIndex);
3697
3692
  consumeRemainingString();
3698
3693
  restoreIndexes = false;
3699
3694
  return skipResult.matched;
3700
3695
  }
3696
+
3701
3697
  // pattern leading **
3702
3698
  if (remainingPattern.slice(0, 2) === "**") {
3703
3699
  consumePattern(2); // consumes "**"
@@ -3707,7 +3703,7 @@ const applyMatching = (pattern, string) => {
3707
3703
  // when remainingPattern was preceeded by "**/"
3708
3704
  // and remainingString have no "/"
3709
3705
  // then skip is not allowed, a regular match will be performed
3710
- if (!remainingString.includes("/")) {
3706
+ if (remainingString.includes("/")) {
3711
3707
  skipAllowed = false;
3712
3708
  }
3713
3709
  }
@@ -3717,18 +3713,21 @@ const applyMatching = (pattern, string) => {
3717
3713
  return true;
3718
3714
  }
3719
3715
  if (skipAllowed) {
3720
- const skipResult = skipUntilMatch({
3716
+ const skipResult = skipUntilMatchIterative({
3721
3717
  pattern: remainingPattern,
3722
3718
  string: remainingString,
3723
3719
  canSkipSlash: true,
3724
3720
  });
3725
- groups.push(...skipResult.groups);
3721
+ for (let i = 0; i < skipResult.groups.length; i++) {
3722
+ groups.push(skipResult.groups[i]);
3723
+ }
3726
3724
  consumePattern(skipResult.patternIndex);
3727
3725
  consumeRemainingString();
3728
3726
  restoreIndexes = false;
3729
3727
  return skipResult.matched;
3730
3728
  }
3731
3729
  }
3730
+
3732
3731
  if (remainingPattern[0] === "*") {
3733
3732
  consumePattern(1); // consumes "*"
3734
3733
  if (remainingPattern === "") {
@@ -3748,23 +3747,53 @@ const applyMatching = (pattern, string) => {
3748
3747
  patternIndex = patternIndex - 1;
3749
3748
  return false;
3750
3749
  }
3751
- const skipResult = skipUntilMatch({
3750
+ const skipResult = skipUntilMatchIterative({
3752
3751
  pattern: remainingPattern,
3753
3752
  string: remainingString,
3754
3753
  canSkipSlash: false,
3755
3754
  });
3756
- groups.push(skipResult.group, ...skipResult.groups);
3755
+ groups.push(skipResult.group);
3756
+ for (let i = 0; i < skipResult.groups.length; i++) {
3757
+ groups.push(skipResult.groups[i]);
3758
+ }
3757
3759
  consumePattern(skipResult.patternIndex);
3758
3760
  consumeString(skipResult.index);
3759
3761
  restoreIndexes = false;
3760
3762
  return skipResult.matched;
3761
3763
  }
3764
+
3762
3765
  if (remainingPattern[0] !== remainingString[0]) {
3763
3766
  return false;
3764
3767
  }
3768
+
3765
3769
  return undefined;
3766
3770
  };
3767
- iterate();
3771
+
3772
+ // Replace recursive iterate() with iterative approach
3773
+ let matched;
3774
+ let continueIteration = true;
3775
+
3776
+ while (continueIteration) {
3777
+ const patternIndexBefore = patternIndex;
3778
+ const indexBefore = index;
3779
+
3780
+ matched = matchOne();
3781
+
3782
+ if (matched === undefined) {
3783
+ consumePattern(1);
3784
+ consumeString(1);
3785
+ // Continue the loop instead of recursion
3786
+ continue;
3787
+ }
3788
+
3789
+ if (matched === false && restoreIndexes) {
3790
+ patternIndex = patternIndexBefore;
3791
+ index = indexBefore;
3792
+ }
3793
+
3794
+ // End the loop
3795
+ continueIteration = false;
3796
+ }
3768
3797
 
3769
3798
  return {
3770
3799
  matched,
@@ -3774,7 +3803,15 @@ const applyMatching = (pattern, string) => {
3774
3803
  };
3775
3804
  };
3776
3805
 
3777
- const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
3806
+ /**
3807
+ * Iterative version of skipUntilMatch that avoids recursion
3808
+ * @param {Object} params
3809
+ * @param {string} params.pattern - The pattern to match
3810
+ * @param {string} params.string - The string to test against
3811
+ * @param {boolean} params.canSkipSlash - Whether slash characters can be skipped
3812
+ * @returns {Object} Result of the matching attempt
3813
+ */
3814
+ const skipUntilMatchIterative = ({ pattern, string, canSkipSlash }) => {
3778
3815
  let index = 0;
3779
3816
  let remainingString = string;
3780
3817
  let longestAttemptRange = null;
@@ -3792,8 +3829,10 @@ const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
3792
3829
  };
3793
3830
  };
3794
3831
 
3795
- const tryToMatch = () => {
3832
+ // Loop until a match is found or all attempts fail
3833
+ while (true) {
3796
3834
  const matchAttempt = applyMatching(pattern, remainingString);
3835
+
3797
3836
  if (matchAttempt.matched) {
3798
3837
  return {
3799
3838
  matched: true,
@@ -3808,6 +3847,7 @@ const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
3808
3847
  },
3809
3848
  };
3810
3849
  }
3850
+
3811
3851
  const attemptIndex = matchAttempt.index;
3812
3852
  const attemptRange = {
3813
3853
  patternIndex: matchAttempt.patternIndex,
@@ -3815,19 +3855,23 @@ const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
3815
3855
  length: attemptIndex,
3816
3856
  groups: matchAttempt.groups,
3817
3857
  };
3858
+
3818
3859
  if (
3819
3860
  !longestAttemptRange ||
3820
3861
  longestAttemptRange.length < attemptRange.length
3821
3862
  ) {
3822
3863
  longestAttemptRange = attemptRange;
3823
3864
  }
3865
+
3824
3866
  if (isLastAttempt) {
3825
3867
  return failure();
3826
3868
  }
3869
+
3827
3870
  const nextIndex = attemptIndex + 1;
3828
3871
  if (nextIndex >= remainingString.length) {
3829
3872
  return failure();
3830
3873
  }
3874
+
3831
3875
  if (remainingString[0] === "/") {
3832
3876
  if (!canSkipSlash) {
3833
3877
  return failure();
@@ -3837,12 +3881,11 @@ const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
3837
3881
  isLastAttempt = true;
3838
3882
  }
3839
3883
  }
3884
+
3840
3885
  // search against the next unattempted string
3841
3886
  index += nextIndex;
3842
3887
  remainingString = remainingString.slice(nextIndex);
3843
- return tryToMatch();
3844
- };
3845
- return tryToMatch();
3888
+ }
3846
3889
  };
3847
3890
 
3848
3891
  const applyPatternMatching = ({ url, pattern }) => {
@@ -3852,9 +3895,19 @@ const applyPatternMatching = ({ url, pattern }) => {
3852
3895
  return applyPattern({ url, pattern });
3853
3896
  };
3854
3897
 
3855
- const resolveAssociations = (associations, baseUrl) => {
3856
- if (baseUrl && typeof baseUrl.href === "string") baseUrl = baseUrl.href;
3857
- assertUrlLike(baseUrl, "baseUrl");
3898
+ const resolveAssociations = (associations, resolver) => {
3899
+ let resolve = () => {};
3900
+ if (typeof resolver === "function") {
3901
+ resolve = resolver;
3902
+ } else if (typeof resolver === "string") {
3903
+ const baseUrl = resolver;
3904
+ assertUrlLike(baseUrl, "baseUrl");
3905
+ resolve = (pattern) => new URL(pattern, baseUrl).href;
3906
+ } else if (resolver && typeof resolver.href === "string") {
3907
+ const baseUrl = resolver.href;
3908
+ assertUrlLike(baseUrl, "baseUrl");
3909
+ resolve = (pattern) => new URL(pattern, baseUrl).href;
3910
+ }
3858
3911
 
3859
3912
  const associationsResolved = {};
3860
3913
  for (const key of Object.keys(associations)) {
@@ -3865,12 +3918,11 @@ const resolveAssociations = (associations, baseUrl) => {
3865
3918
  const valueAssociated = value[pattern];
3866
3919
  let patternResolved;
3867
3920
  try {
3868
- patternResolved = String(new URL(pattern, baseUrl));
3921
+ patternResolved = resolve(pattern);
3869
3922
  } catch {
3870
3923
  // it's not really an url, no need to perform url resolution nor encoding
3871
3924
  patternResolved = pattern;
3872
3925
  }
3873
-
3874
3926
  valueMapResolved[patternResolved] = valueAssociated;
3875
3927
  }
3876
3928
  associationsResolved[key] = valueMapResolved;
@@ -3993,34 +4045,29 @@ const urlChildMayMatch = ({ url, associations, predicate }) => {
3993
4045
  };
3994
4046
 
3995
4047
  const applyAliases = ({ url, aliases }) => {
3996
- let aliasFullMatchResult;
3997
- const aliasMatchingKey = Object.keys(aliases).find((key) => {
3998
- const aliasMatchResult = applyPatternMatching({
4048
+ for (const key of Object.keys(aliases)) {
4049
+ const matchResult = applyPatternMatching({
3999
4050
  pattern: key,
4000
4051
  url,
4001
4052
  });
4002
- if (aliasMatchResult.matched) {
4003
- aliasFullMatchResult = aliasMatchResult;
4004
- return true;
4053
+ if (!matchResult.matched) {
4054
+ continue;
4005
4055
  }
4006
- return false;
4007
- });
4008
- if (!aliasMatchingKey) {
4009
- return url;
4010
- }
4011
- const { matchGroups } = aliasFullMatchResult;
4012
- const alias = aliases[aliasMatchingKey];
4013
- const parts = alias.split("*");
4014
- let newUrl = "";
4015
- let index = 0;
4016
- for (const part of parts) {
4017
- newUrl += `${part}`;
4018
- if (index < parts.length - 1) {
4019
- newUrl += matchGroups[index];
4056
+ const { matchGroups } = matchResult;
4057
+ const alias = aliases[key];
4058
+ const parts = alias.split("*");
4059
+ let newUrl = "";
4060
+ let index = 0;
4061
+ for (const part of parts) {
4062
+ newUrl += `${part}`;
4063
+ if (index < parts.length - 1) {
4064
+ newUrl += matchGroups[index];
4065
+ }
4066
+ index++;
4020
4067
  }
4021
- index++;
4068
+ return newUrl;
4022
4069
  }
4023
- return newUrl;
4070
+ return url;
4024
4071
  };
4025
4072
 
4026
4073
  const matches = (url, patterns) => {
@@ -6446,7 +6493,7 @@ const replacePlaceholdersInHtml = (html, replacers) => {
6446
6493
  });
6447
6494
  };
6448
6495
 
6449
- const clientErrorHtmlTemplateFileUrl = import.meta.resolve("./html/4xx.html");
6496
+ const clientErrorHtmlTemplateFileUrl = import.meta.resolve("./client/4xx/4xx.html");
6450
6497
 
6451
6498
  const jsenvServiceDefaultBody4xx5xx = () => {
6452
6499
  return {
@@ -6525,7 +6572,7 @@ const generateBadStatusResponse = (
6525
6572
  );
6526
6573
  };
6527
6574
 
6528
- const jsenvServerRootDirectoryUrl = import.meta.resolve("../");
6575
+ const jsenvServerRootDirectoryUrl = import.meta.resolve("./");
6529
6576
 
6530
6577
  const jsenvServiceInternalClientFiles = () => {
6531
6578
  return {
@@ -6593,7 +6640,7 @@ const jsenvServiceOpenFile = () => {
6593
6640
  };
6594
6641
 
6595
6642
  const routeInspectorHtmlFileUrl = import.meta.resolve(
6596
- "./html/route_inspector.html",
6643
+ "./client/route_inspector/route_inspector.html",
6597
6644
  );
6598
6645
 
6599
6646
  const jsenvServiceRouteInspector = () => {
@@ -7584,7 +7631,7 @@ const startServer = async ({
7584
7631
  const webSocketSet = new Set();
7585
7632
  let upgradeRequestToWebSocket;
7586
7633
  const loadUpgradeRequestToWebSocket = async () => {
7587
- const { WebSocketServer } = await import("./js/ws.js");
7634
+ const { WebSocketServer } = await import("./ws/ws.js");
7588
7635
  let webSocketServer = new WebSocketServer({ noServer: true });
7589
7636
  stopCallbackSet.add(() => {
7590
7637
  webSocketServer.close();
@@ -7769,7 +7816,7 @@ const PROCESS_TEARDOWN_EVENTS_MAP = {
7769
7816
  exit: STOP_REASON_PROCESS_EXIT,
7770
7817
  };
7771
7818
 
7772
- const internalErrorHtmlFileUrl = import.meta.resolve("./html/500.html");
7819
+ const internalErrorHtmlFileUrl = import.meta.resolve("./client/500/500.html");
7773
7820
 
7774
7821
  const jsenvServiceErrorHandler = ({ sendErrorDetails = false } = {}) => {
7775
7822
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "16.0.3",
3
+ "version": "16.0.5",
4
4
  "description": "A modern Node.js HTTP server with declarative routing, content negotiation, and WebSocket support",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -42,7 +42,7 @@
42
42
  "launch-editor": "2.10.0",
43
43
  "@jsenv/abort": "4.3.1",
44
44
  "@jsenv/humanize": "1.3.1",
45
- "@jsenv/url-meta": "8.5.4",
45
+ "@jsenv/url-meta": "8.5.6",
46
46
  "@jsenv/utils": "2.2.1",
47
47
  "ws": "8.18.1",
48
48
  "formidable": "3.5.2"
File without changes
File without changes
File without changes