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 ADDED
@@ -0,0 +1,209 @@
1
+ ![Trie Structure](assets/peepar.jpeg)
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)
@@ -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
+ }