@rhythmjs/router 0.0.4 → 0.0.6
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/dist/radix-tree.d.ts +1 -0
- package/dist/radix-tree.js +64 -30
- package/dist/rhythm-router.d.ts +11 -0
- package/dist/rhythm-router.js +3 -0
- package/package.json +2 -2
package/dist/radix-tree.d.ts
CHANGED
|
@@ -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>;
|
package/dist/radix-tree.js
CHANGED
|
@@ -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
|
|
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,
|
|
102
|
+
for (const variant of expandOptionalParams(path)) insertAt(root, variant, method, payload);
|
|
71
103
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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/dist/rhythm-router.d.ts
CHANGED
|
@@ -4,13 +4,24 @@ import { DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
|
|
|
4
4
|
export interface RhythmRouterContext {
|
|
5
5
|
readonly params: Readonly<Record<string, string>>;
|
|
6
6
|
}
|
|
7
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
7
8
|
export interface RhythmRouterOptions {
|
|
8
9
|
prefix?: string;
|
|
9
10
|
}
|
|
11
|
+
export type RouterEntry = {
|
|
12
|
+
readonly kind: "middleware";
|
|
13
|
+
readonly fn: Middleware<any>;
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: "route";
|
|
16
|
+
readonly method: HttpMethod;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly handlers: readonly Middleware<any>[];
|
|
19
|
+
};
|
|
10
20
|
type RouteHandler<TContext> = Middleware<TContext & RhythmRouterContext>;
|
|
11
21
|
export declare class RhythmRouter<TContext extends RhythmHttpContext = RhythmHttpContext> {
|
|
12
22
|
#private;
|
|
13
23
|
constructor(options?: RhythmRouterOptions);
|
|
24
|
+
get entries(): readonly RouterEntry[];
|
|
14
25
|
use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): RhythmRouter<TContext & TExtra>;
|
|
15
26
|
use(fn: Middleware<TContext>): this;
|
|
16
27
|
get<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmRouterContext, TExtra>, ...handlers: RouteHandler<TContext & TExtra>[]): this;
|
package/dist/rhythm-router.js
CHANGED
|
@@ -10,6 +10,9 @@ var RhythmRouter = class {
|
|
|
10
10
|
get #prefix() {
|
|
11
11
|
return this.#options.prefix ?? "";
|
|
12
12
|
}
|
|
13
|
+
get entries() {
|
|
14
|
+
return [...this.#entries];
|
|
15
|
+
}
|
|
13
16
|
use(fn) {
|
|
14
17
|
if (typeof fn !== "function") throw new TypeError("middleware must be a function!");
|
|
15
18
|
this.#entries.push({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhythmjs/router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
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.
|
|
52
|
+
"@rhythmjs/rhythm": "0.0.6"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^26.6.3",
|