peepal-router 0.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/README.md +209 -0
- package/dist/router.d.ts +26 -0
- package/dist/router.js +124 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+

|
|
2
|
+
# Peepar
|
|
3
|
+
|
|
4
|
+
A fast and minimal Trie based HTTP router.
|
|
5
|
+
|
|
6
|
+
Peepar name is inspired by the **Peepal (Sacred fig) tree**, known for its deep roots and branching structure. Just like the tree, Peepar organizes routes using a Trie data structure, enabling fast and predictable path matching with very low overhead.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
* Trie based routing for fast lookups
|
|
13
|
+
* Zero dependencies
|
|
14
|
+
* Very small footprint
|
|
15
|
+
* Middleware chaining support
|
|
16
|
+
* Dynamic route parameters
|
|
17
|
+
* Wildcard route matching
|
|
18
|
+
* Works in Node.js and Bun
|
|
19
|
+
* TypeScript types included
|
|
20
|
+
* Inbuilt params parser
|
|
21
|
+
* Inbuilt query string parser
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install peepar
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Basic Usage
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { TrieRouter } from "peepar";
|
|
37
|
+
|
|
38
|
+
const router = new TrieRouter();
|
|
39
|
+
|
|
40
|
+
// Global Middleware
|
|
41
|
+
router.pushMiddleware('/', function GlobalMiddleware1() => 'global middleware')
|
|
42
|
+
router.pushMiddleware('/', function Middleware2() => 'global middleware 2')
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
router.add("GET", "/", function handler () => "home");
|
|
46
|
+
|
|
47
|
+
const matchedhandler = router.search("GET", "/");
|
|
48
|
+
|
|
49
|
+
Output:
|
|
50
|
+
{
|
|
51
|
+
params: {},
|
|
52
|
+
handler: [GlobalMiddleware1,GlobalMiddleware2,handler]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Route specific middleware
|
|
56
|
+
router.pushMiddleware("/users", function userMiddleware(ctx) => {
|
|
57
|
+
console.log("/users middleware");
|
|
58
|
+
});
|
|
59
|
+
router.add("GET", "/users/:id", function userHandler() => "user profile");
|
|
60
|
+
|
|
61
|
+
const result = router.find("GET", "/users/42");
|
|
62
|
+
// /users/42 is incoming path from the server
|
|
63
|
+
const params = router.parseParams('/users/42',result.params);
|
|
64
|
+
// you will have to manually parse params until we support inbuilt params parse inside our router seach method.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Output:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
{
|
|
72
|
+
params: { id: 1 }, // id : 1 is'nt real params , it's just index of ":id" in path
|
|
73
|
+
handler: [GlobalMiddleware1,GlobalMiddleware2,userMiddleware,userHandler]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// params
|
|
77
|
+
{ id: 42 }
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Middleware
|
|
84
|
+
- peepar supports global and route specific middleware
|
|
85
|
+
|
|
86
|
+
Global middleware:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
router.pushMiddleware("/", (ctx) => {
|
|
90
|
+
console.log("global middleware");
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Route specific middleware:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
router.pushMiddleware("/users", (ctx) => {
|
|
98
|
+
console.log("/users middleware");
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Wildcard Routes
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
router.add("GET", "/static/*", () => "HTML page");
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Matches:
|
|
111
|
+
|
|
112
|
+
* /static/app.js
|
|
113
|
+
* /static/css/style.css
|
|
114
|
+
* /static/images/logo.png
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Performance
|
|
119
|
+
|
|
120
|
+
Peepar is designed for speed and low allocation during hot path. we try to minimise allocation and make our fast as much as possible.
|
|
121
|
+
|
|
122
|
+
Key design goals:
|
|
123
|
+
|
|
124
|
+
* Try to Avoid unnecessary allocations in hot path
|
|
125
|
+
* Minimal overhead during lookup
|
|
126
|
+
* Fast static and dynamic route matching
|
|
127
|
+
* Lightweight and cache friendly structure
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## API
|
|
132
|
+
|
|
133
|
+
### router.add(method, path, handler)
|
|
134
|
+
### router.insert(method, path, handler)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
Register a route.
|
|
138
|
+
|
|
139
|
+
### router.pushMiddleware(path, middleware)
|
|
140
|
+
|
|
141
|
+
Register middleware for a path or globally.
|
|
142
|
+
|
|
143
|
+
### router.search(method, path)
|
|
144
|
+
### router.find(method, path)
|
|
145
|
+
|
|
146
|
+
Find matching route and collect handlers.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
{
|
|
152
|
+
params: Record<string, number>;
|
|
153
|
+
handler: Function[];
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Example
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
import { TrieRouter } from "peepar";
|
|
163
|
+
|
|
164
|
+
const router = new TrieRouter();
|
|
165
|
+
|
|
166
|
+
router.pushMiddleware("/", () => console.log("global"));
|
|
167
|
+
router.pushMiddleware("/api/*", () => console.log("api middleware"));
|
|
168
|
+
|
|
169
|
+
router.add("GET", "/api/users/:id", () => console.log("user handler"));
|
|
170
|
+
|
|
171
|
+
const res = router.find("GET", "/api/users/100");
|
|
172
|
+
|
|
173
|
+
for (const fn of res.handler) {
|
|
174
|
+
fn();
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Roadmap
|
|
181
|
+
|
|
182
|
+
* Route priority improvements
|
|
183
|
+
* Param parsing inbuilt in search method
|
|
184
|
+
* Optional parameter support
|
|
185
|
+
* Regex based params
|
|
186
|
+
* Zero allocation path parser
|
|
187
|
+
* Extended benchmarking
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Contributing
|
|
192
|
+
|
|
193
|
+
Contributions, ideas, and performance improvements are welcome.
|
|
194
|
+
|
|
195
|
+
If you find a bug or want to improve performance, open an issue or submit a pull request.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Author
|
|
206
|
+
|
|
207
|
+
Pradeep Kumar
|
|
208
|
+
|
|
209
|
+
GitHub: [https://github.com/pradeepbgs/peepar-router](https://github.com/pradeepbgs/peepar-router)
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
declare class TrieNodes {
|
|
2
|
+
children: Record<string, TrieNodes>;
|
|
3
|
+
isEndOfWord: boolean;
|
|
4
|
+
handlers: Record<string, Function>;
|
|
5
|
+
middlewares: Function[];
|
|
6
|
+
params: Record<string, number>;
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export declare class TrieRouter {
|
|
10
|
+
root: TrieNodes;
|
|
11
|
+
globalMiddlewares: Function[];
|
|
12
|
+
constructor();
|
|
13
|
+
pushMiddleware(path: string, handlers: Function | Function[]): void;
|
|
14
|
+
insert(method: string, path: string, handler: Function): void;
|
|
15
|
+
add(method: string, path: string, handler: Function): void;
|
|
16
|
+
search(method: string, path: string): {
|
|
17
|
+
params: Record<string, number>;
|
|
18
|
+
handler: Function[];
|
|
19
|
+
};
|
|
20
|
+
find(method: string, path: string): {
|
|
21
|
+
params: Record<string, number>;
|
|
22
|
+
handler: Function[];
|
|
23
|
+
};
|
|
24
|
+
parseParams(inComingpath: string | null, param: Record<string, number> | null): Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
class TrieNodes {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.children = {};
|
|
4
|
+
this.handlers = {};
|
|
5
|
+
this.isEndOfWord = false;
|
|
6
|
+
this.middlewares = [];
|
|
7
|
+
this.params = {};
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//
|
|
11
|
+
export class TrieRouter {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.root = new TrieNodes();
|
|
14
|
+
this.globalMiddlewares = [];
|
|
15
|
+
}
|
|
16
|
+
pushMiddleware(path, handlers) {
|
|
17
|
+
if (!Array.isArray(handlers))
|
|
18
|
+
handlers = [handlers];
|
|
19
|
+
if (path === "/") {
|
|
20
|
+
this.globalMiddlewares.push(...handlers);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let node = this.root;
|
|
24
|
+
const pathSegments = path.split("/").filter(Boolean);
|
|
25
|
+
for (const element of pathSegments) {
|
|
26
|
+
let key = element;
|
|
27
|
+
if (element.startsWith(":")) {
|
|
28
|
+
key = ":";
|
|
29
|
+
}
|
|
30
|
+
else if (element.startsWith("*")) {
|
|
31
|
+
node.middlewares.push(...handlers);
|
|
32
|
+
}
|
|
33
|
+
if (!node.children[key])
|
|
34
|
+
node.children[key] = new TrieNodes();
|
|
35
|
+
node = node.children[key];
|
|
36
|
+
}
|
|
37
|
+
node.middlewares.push(...handlers);
|
|
38
|
+
// node.isEndOfWord = true
|
|
39
|
+
}
|
|
40
|
+
insert(method, path, handler) {
|
|
41
|
+
let node = this.root;
|
|
42
|
+
const pathSegments = path.split("/").filter(Boolean);
|
|
43
|
+
// handle if path is /
|
|
44
|
+
if (path === "/") {
|
|
45
|
+
node.isEndOfWord = true;
|
|
46
|
+
node.handlers[method] = handler;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let routeparams = {};
|
|
50
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
|
51
|
+
const element = pathSegments[i];
|
|
52
|
+
let key = element;
|
|
53
|
+
let cleanParam = '';
|
|
54
|
+
if (element.startsWith(":")) {
|
|
55
|
+
key = ":";
|
|
56
|
+
cleanParam = element.slice(1);
|
|
57
|
+
}
|
|
58
|
+
if (!node.children[key])
|
|
59
|
+
node.children[key] = new TrieNodes();
|
|
60
|
+
node = node.children[key];
|
|
61
|
+
if (cleanParam) {
|
|
62
|
+
routeparams[cleanParam] = i;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
node.params = routeparams;
|
|
66
|
+
node.handlers[method] = handler;
|
|
67
|
+
node.isEndOfWord = true;
|
|
68
|
+
}
|
|
69
|
+
add(method, path, handler) {
|
|
70
|
+
return this.insert(method, path, handler);
|
|
71
|
+
}
|
|
72
|
+
search(method, path) {
|
|
73
|
+
let node = this.root;
|
|
74
|
+
const pathSegments = path.split("/");
|
|
75
|
+
let collected = this.globalMiddlewares.slice();
|
|
76
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
|
77
|
+
const element = pathSegments[i];
|
|
78
|
+
if (element.length === 0) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (node.children[element]) {
|
|
82
|
+
node = node.children[element];
|
|
83
|
+
}
|
|
84
|
+
else if (node.children[":"]) {
|
|
85
|
+
node = node.children[":"];
|
|
86
|
+
}
|
|
87
|
+
else if (node.children["*"]) {
|
|
88
|
+
node = node.children["*"];
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return { params: node.params, handler: collected };
|
|
93
|
+
}
|
|
94
|
+
if (node.middlewares.length > 0) {
|
|
95
|
+
const mw = node.middlewares;
|
|
96
|
+
for (let j = 0; j < mw.length; j++) {
|
|
97
|
+
collected.push(mw[j]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const methodHandler = node.handlers[method];
|
|
102
|
+
if (methodHandler)
|
|
103
|
+
collected.push(methodHandler);
|
|
104
|
+
return {
|
|
105
|
+
params: node.params,
|
|
106
|
+
handler: collected,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
find(method, path) {
|
|
110
|
+
return this.search(method, path);
|
|
111
|
+
}
|
|
112
|
+
// param parser
|
|
113
|
+
parseParams(inComingpath, param) {
|
|
114
|
+
const paramObject = {};
|
|
115
|
+
const pathWithoutQuery = inComingpath?.split('?')[0];
|
|
116
|
+
// URL = /user/id/register?name=pradeep
|
|
117
|
+
// [ "/user/id/register", "name=pradeep" ]
|
|
118
|
+
const paths = pathWithoutQuery?.split('/').filter(Boolean);
|
|
119
|
+
for (const key in param) {
|
|
120
|
+
paramObject[key] = paths?.[param[key]];
|
|
121
|
+
}
|
|
122
|
+
return paramObject;
|
|
123
|
+
}
|
|
124
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "peepal-router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "a fast and minimal trie based http router",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"http-router",
|
|
7
|
+
"trie-router",
|
|
8
|
+
"hono",
|
|
9
|
+
"diesel",
|
|
10
|
+
"express-router",
|
|
11
|
+
"radix-tree",
|
|
12
|
+
"tree"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/pradeepbgs/peepar-router#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/pradeepbgs/peepar-router/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/pradeepbgs/peepar-router.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "pradeepkumar",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/router.js",
|
|
26
|
+
"types": "dist/router.d.ts",
|
|
27
|
+
"files": ["dist"],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"private": false,
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"typescript": "^5"
|
|
38
|
+
}
|
|
39
|
+
}
|