@rhythmjs/router 0.0.4 → 0.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.
@@ -5,6 +5,7 @@ export declare class TreeNode<TMethod extends string = string, TPayload = unknow
5
5
  children: TreeNode<TMethod, TPayload>[];
6
6
  paramChild: TreeNode<TMethod, TPayload> | null;
7
7
  paramName: string;
8
+ wildcardChild: TreeNode<TMethod, TPayload> | null;
8
9
  methods: Map<TMethod, TPayload> | null;
9
10
  }
10
11
  export declare function createNode<TMethod extends string = string, TPayload = unknown>(): TreeNode<TMethod, TPayload>;
@@ -5,6 +5,7 @@ var TreeNode = class {
5
5
  children = [];
6
6
  paramChild = null;
7
7
  paramName = "";
8
+ wildcardChild = null;
8
9
  methods = null;
9
10
  };
10
11
  function createNode() {
@@ -23,12 +24,14 @@ function splitChild(node, at) {
23
24
  tail.children = node.children;
24
25
  tail.paramChild = node.paramChild;
25
26
  tail.paramName = node.paramName;
27
+ tail.wildcardChild = node.wildcardChild;
26
28
  tail.methods = node.methods;
27
29
  node.path = node.path.slice(0, at);
28
30
  node.children = [tail];
29
31
  node.indices = tail.path.charAt(0);
30
32
  node.paramChild = null;
31
33
  node.paramName = "";
34
+ node.wildcardChild = null;
32
35
  node.methods = null;
33
36
  }
34
37
  function insertAt(node, path, method, payload) {
@@ -48,8 +51,19 @@ function insertAt(node, path, method, payload) {
48
51
  insertAt(node.paramChild, rest, method, payload);
49
52
  return;
50
53
  }
54
+ if (path.charCodeAt(0) === 42) {
55
+ if (path.length !== 1) throw new Error("wildcard \"*\" is only allowed at the end of a path");
56
+ if (!node.wildcardChild) node.wildcardChild = new TreeNode();
57
+ if (!node.wildcardChild.methods) node.wildcardChild.methods = /* @__PURE__ */ new Map();
58
+ node.wildcardChild.methods.set(method, payload);
59
+ return;
60
+ }
51
61
  const colonIndex = path.indexOf(":");
52
- const staticPart = colonIndex === -1 ? path : path.slice(0, colonIndex);
62
+ const starIndex = path.indexOf("*");
63
+ let staticEnd = path.length;
64
+ if (colonIndex !== -1) staticEnd = colonIndex;
65
+ if (starIndex !== -1 && starIndex < staticEnd) staticEnd = starIndex;
66
+ const staticPart = path.slice(0, staticEnd);
53
67
  const firstChar = path.charAt(0);
54
68
  for (let i = 0; i < node.children.length; i++) {
55
69
  if (node.indices.charAt(i) !== firstChar) continue;
@@ -66,47 +80,67 @@ function insertAt(node, path, method, payload) {
66
80
  node.indices += firstChar;
67
81
  insertAt(child, path.slice(staticPart.length), method, payload);
68
82
  }
83
+ function expandOptionalParams(path) {
84
+ if (!path.includes("?")) return [path];
85
+ const segments = path.split("/");
86
+ let firstOptional = -1;
87
+ for (let i = 0; i < segments.length; i++) {
88
+ const segment = segments[i];
89
+ const isOptional = segment.length > 2 && segment.charCodeAt(0) === 58 && segment.endsWith("?");
90
+ if (!isOptional && segment.includes("?")) throw new Error(`"?" is only allowed to mark an optional param like ":name?" (in "${path}")`);
91
+ if (firstOptional !== -1 && !isOptional) throw new Error(`only optional params may follow an optional param (in "${path}")`);
92
+ if (isOptional && firstOptional === -1) firstOptional = i;
93
+ }
94
+ const variants = [];
95
+ for (let end = firstOptional; end <= segments.length; end++) {
96
+ const parts = segments.slice(0, end).map((segment) => segment.endsWith("?") ? segment.slice(0, -1) : segment);
97
+ variants.push(parts.join("/") || "/");
98
+ }
99
+ return variants;
100
+ }
69
101
  function insertRoute(root, method, path, payload) {
70
- insertAt(root, path, method, payload);
102
+ for (const variant of expandOptionalParams(path)) insertAt(root, variant, method, payload);
71
103
  }
72
- function lookupRoute(root, method, pathname) {
73
- const params = {};
74
- let node = root;
75
- let path = pathname;
76
- while (path.length > 0) {
104
+ function search(node, method, path) {
105
+ if (path.length === 0) {
106
+ const payload = node.methods?.get(method);
107
+ if (payload !== void 0) return {
108
+ payload,
109
+ params: {}
110
+ };
111
+ } else {
77
112
  const firstChar = path.charAt(0);
78
- let matched = null;
79
113
  for (let i = 0; i < node.children.length; i++) {
80
114
  if (node.indices.charAt(i) !== firstChar) continue;
81
115
  const child = node.children[i];
82
- if (path.startsWith(child.path)) {
83
- matched = child;
84
- break;
85
- }
86
- }
87
- if (matched) {
88
- path = path.slice(matched.path.length);
89
- node = matched;
90
- continue;
116
+ if (!path.startsWith(child.path)) continue;
117
+ const result = search(child, method, path.slice(child.path.length));
118
+ if (result) return result;
91
119
  }
92
120
  if (node.paramChild) {
93
121
  const slashIndex = path.indexOf("/");
94
122
  const value = slashIndex === -1 ? path : path.slice(0, slashIndex);
95
- if (value.length === 0) return null;
96
- params[node.paramChild.paramName] = decodeURIComponent(value);
97
- path = slashIndex === -1 ? "" : path.slice(slashIndex);
98
- node = node.paramChild;
99
- continue;
123
+ if (value.length > 0) {
124
+ const rest = slashIndex === -1 ? "" : path.slice(slashIndex);
125
+ const result = search(node.paramChild, method, rest);
126
+ if (result) {
127
+ result.params[node.paramChild.paramName] = decodeURIComponent(value);
128
+ return result;
129
+ }
130
+ }
100
131
  }
101
- return null;
102
132
  }
103
- if (!node.methods) return null;
104
- const payload = node.methods.get(method);
105
- if (payload === void 0) return null;
106
- return {
107
- payload,
108
- params
109
- };
133
+ if (node.wildcardChild) {
134
+ const payload = node.wildcardChild.methods?.get(method);
135
+ if (payload !== void 0) return {
136
+ payload,
137
+ params: { "*": decodeURIComponent(path) }
138
+ };
139
+ }
140
+ return null;
141
+ }
142
+ function lookupRoute(root, method, pathname) {
143
+ return search(root, method, pathname);
110
144
  }
111
145
  function joinPath(prefix, path) {
112
146
  if (!prefix) return path;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhythmjs/router",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Radix-tree HTTP router for the Rhythm middleware kernel, with Bun, Node, and Deno adapters.",
5
5
  "homepage": "https://rhythm.js.org/router",
6
6
  "license": "ISC",
@@ -49,7 +49,7 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@rhythmjs/rhythm": "0.0.4"
52
+ "@rhythmjs/rhythm": "0.0.5"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^26.6.3",