1- import Jrror , { JoorError } from '@/core/error' ;
2- import { validateHandler , validateRoute } from '@/core/router/validation' ;
3- import logger from '@/helpers/joorLogger' ;
4- import { ROUTE_HANDLER , ROUTES , ROUTE_METHOD , ROUTE_PATH } from '@/types/route' ;
5- /**
6- * Class representing a Router.
7- *
8- * @example
9- * const router = new Router();
10- * router.get('/', async (req) => {
11- * const response = new JoorResponse();
12- * response.setHeaders({ 'Content-Type': 'application/json' });
13- * response.setBody({ message: 'Hello World' });
14- * return response;
15- * });
16- *
17- * @example
18- * const router = new Router();
19- * router.post('/submit', async (req) => {
20- * const data = req.body;
21- * // Process the data
22- * const response = new JoorResponse();
23- * response.setHeaders({ 'Content-Type': 'application/json' });
24- * response.setBody({ status: 'success', data });
25- * return response;
26- * });
27- *
28- * @example
29- * const router = new Router();
30- * router.put('/update/:id', async (req) => {
31- * const { id } = req.params;
32- * const data = req.body;
33- * // Update the resource with the given id
34- * const response = new JoorResponse();
35- * response.setHeaders({ 'Content-Type': 'application/json' });
36- * response.setBody({ status: 'updated', id, data });
37- * return response;
38- * });
39- *
40- * @example
41- * const router = new Router();
42- * router.delete('/delete/:id', async (req) => {
43- * const { id } = req.params;
44- * // Delete the resource with the given id
45- * const response = new JoorResponse();
46- * response.setHeaders({ 'Content-Type': 'application/json' });
47- * response.setBody({ status: 'deleted', id });
48- * return response;
49- * });
50- *
51- * @rules
52- * - Routes must be unique and not conflict with existing routes.
53- * - Dynamic routes (e.g., `/update/:id`) should not conflict with other dynamic routes in the same parent. Foe example, `/update/:id` and `/update/:name` are not allowed.
54- * - Only one root level route (`/`) is allowed. Additional root level routes will be ignored.
55- * - Handlers or middlewares must be functions.
56- * - Routes must start with `/`.
57- * - Routes cannot be empty.
58- * - Routes can have multiple middlewares.
59- * - Route handler or middleware can be synchronous or asynchronous.
60- * - Route handler or middleware should return a `JoorResponse` object or `undefined`.
61- * - If handler or middleware returns `undefined`, the request will be passed to the next handler or middleware, otherwise it will be sent as a response.
62- */
63- class Router {
64- /**
65- * Static property to store routes.
66- */
67- static routes : ROUTES = {
68- '/' : { } ,
69- } as ROUTES ;
1+ import { ROUTE_METHOD , ROUTE_PATH , ROUTES , ROUTE_HANDLER } from "@/types/route" ;
2+ import { validateHandler , validateRoute } from "@/core/router/validation" ;
3+ import { handleError , jssert } from "@/core/error" ;
4+ import logger from "@/helpers/joorLogger" ;
705
71- /**
72- * Registers a GET route with the specified handlers.
73- *
74- * @param route - The route path.
75- * @param handlers - The route handlers.
76- */
77- public get ( route : ROUTE_PATH , ...handlers : ROUTE_HANDLER [ ] ) {
78- this . addRoute ( 'GET' , route , handlers ) ;
79- }
6+ class Router {
7+ // Static property to store routes.
8+ static routes : ROUTES = {
9+ "/" : { } ,
10+ } as ROUTES ;
8011
81- /**
82- * Registers a POST route with the specified handlers.
83- *
84- * @param route - The route path.
85- * @param handlers - The route handlers.
86- */
87- public post ( route : ROUTE_PATH , ...handlers : ROUTE_HANDLER [ ] ) {
88- this . addRoute ( 'POST' , route , handlers ) ;
89- }
12+ /**
13+ * Registers a GET route with the specified handlers.
14+ *
15+ * @param route - The route path.
16+ * @param handlers - The route handlers.
17+ */
18+ public get ( route : string , ...handlers : ROUTE_HANDLER [ ] ) {
19+ this . addRoute ( "GET" , route , handlers ) ;
20+ }
9021
91- /**
92- * Registers a PUT route with the specified handlers.
93- *
94- * @param route - The route path.
95- * @param handlers - The route handlers.
96- */
97- public put ( route : ROUTE_PATH , ...handlers : ROUTE_HANDLER [ ] ) {
98- this . addRoute ( 'PUT' , route , handlers ) ;
99- }
22+ /**
23+ * Registers a POST route with the specified handlers.
24+ *
25+ * @param route - The route path.
26+ * @param handlers - The route handlers.
27+ */
28+ public post ( route : string , ...handlers : ROUTE_HANDLER [ ] ) {
29+ this . addRoute ( "POST" , route , handlers ) ;
30+ }
31+ /**
32+ * Registers a PUT route with the specified handlers.
33+ *
34+ * @param route - The route path.
35+ * @param handlers - The route handlers.
36+ */
37+ public put ( route : string , ...handlers : ROUTE_HANDLER [ ] ) {
38+ this . addRoute ( "PUT" , route , handlers ) ;
39+ }
40+ /**
41+ * Registers a PATCH route with the specified handlers.
42+ *
43+ * @param route - The route path.
44+ * @param handlers - The route handlers.
45+ */
46+ public patch ( route : string , ...handlers : ROUTE_HANDLER [ ] ) {
47+ this . addRoute ( "PATCH" , route , handlers ) ;
48+ }
49+ /**
50+ * Registers a DELETE route with the specified handlers.
51+ *
52+ * @param route - The route path.
53+ * @param handlers - The route handlers.
54+ */
55+ public delete ( route : string , ...handlers : ROUTE_HANDLER [ ] ) {
56+ this . addRoute ( "DELETE" , route , handlers ) ;
57+ }
10058
101- /**
102- * Registers a PATCH route with the specified handlers.
103- *
104- * @param route - The route path.
105- * @param handlers - The route handlers.
106- */
107- public patch ( route : ROUTE_PATH , ...handlers : ROUTE_HANDLER [ ] ) {
108- this . addRoute ( 'PATCH' , route , handlers ) ;
109- }
59+ /**
60+ * Adds a route to the router.
61+ *
62+ * @private
63+ * @param httpMethod - The HTTP method.
64+ * @param route - The route path.
65+ * @param handlers - The route handlers.
66+ * @throws {Jrror } If there is a route conflict or duplicate.
67+ */
68+ private addRoute (
69+ httpMethod : ROUTE_METHOD ,
70+ route : ROUTE_PATH ,
71+ handlers : ROUTE_HANDLER [ ]
72+ ) {
73+ try {
74+ validateRoute ( route ) ;
75+ handlers . forEach ( validateHandler ) ;
76+ if ( ! Object . keys ( Router . routes ) . includes ( '/' ) ) {
77+ Router . routes [ '/' ] = { } ;
78+ }
11079
111- /**
112- * Registers a DELETE route with the specified handlers.
113- *
114- * @param route - The route path.
115- * @param handlers - The route handlers.
116- */
117- public delete ( route : ROUTE_PATH , ...handlers : ROUTE_HANDLER [ ] ) {
118- this . addRoute ( 'DELETE' , route , handlers ) ;
119- }
120- /**
121- * Adds a route to the router.
122- *
123- * @private
124- * @param httpMethod - The HTTP method.
125- * @param route - The route path.
126- * @param handlers - The route handlers.
127- * @throws {Jrror } If there is a route conflict or duplicate.
128- */
129- private addRoute (
130- httpMethod : ROUTE_METHOD ,
131- route : ROUTE_PATH ,
132- handlers : ROUTE_HANDLER [ ]
133- ) {
134- try {
135- validateRoute ( route ) ;
136- handlers . forEach ( validateHandler ) ;
137- if ( ! Object . keys ( Router . routes ) . includes ( '/' ) ) {
138- Router . routes [ '/' ] = { } ;
139- }
80+ if ( Object . keys ( Router . routes ) . length > 1 ) {
81+ Router . routes = {
82+ '/' : Router . routes [ '/' ] ,
83+ } ;
84+ logger . warn (
85+ 'Multiple root level routes detected. Only the first root level route will be considered. Rest will be ignored.'
86+ ) ;
87+ }
14088
141- if ( Object . keys ( Router . routes ) . length > 1 ) {
142- Router . routes = {
143- '/' : Router . routes [ '/' ] ,
144- } ;
145- logger . warn (
146- 'Multiple root level routes detected. Only the first root level route will be considered. Rest will be ignored.'
147- ) ;
148- }
89+ const routeParts = route . split ( '/' ) . filter ( ( part ) => part !== '' ) ;
14990
150- const routeParts = route . split ( '/' ) . filter ( ( part ) => part !== '' ) ;
91+ if ( routeParts . length === 0 ) {
92+ Router . routes [ '/' ] = {
93+ ...Router . routes [ '/' ] ,
94+ localMiddlewares : Router . routes [ '/' ] . localMiddlewares ?? [ ] ,
95+ globalMiddlewares : Router . routes [ '/' ] . globalMiddlewares ?? [ ] ,
96+ [ httpMethod ] : {
97+ handlers,
98+ } ,
99+ } ;
100+ return ;
101+ }
151102
152- if ( routeParts . length === 0 ) {
153- Router . routes [ '/' ] = {
154- ...Router . routes [ '/' ] ,
155- localMiddlewares : Router . routes [ '/' ] . localMiddlewares ?? [ ] ,
156- globalMiddlewares : Router . routes [ '/' ] . globalMiddlewares ?? [ ] ,
157- [ httpMethod ] : {
158- handlers,
159- } ,
160- } ;
161- return ;
162- }
103+ let currentNode = Router . routes [ '/' ] ;
163104
164- let currentNode = Router . routes [ '/' ] ;
105+ for ( const routePart of routeParts ) {
106+ // Remove query params and hash from route
107+ const [ node ] = routePart . split ( '#' ) [ 0 ] . split ( '?' ) ;
108+ // check if current node has children
109+ currentNode . children = currentNode . children ?? { } ;
110+ // check if current node is dynamic
111+ if ( node . startsWith ( ':' ) ) {
112+ // check if current parent node has other dynamic routes
113+ const keys = Object . keys ( currentNode . children ) . filter (
114+ ( key ) => key . startsWith ( ':' ) && key !== node
115+ ) ;
116+ // check if current node has other static routes
117+ jssert (
118+ keys . length === 0 ,
119+ `Route conflict: ${ route } conflicts with existing route ${ keys [ 0 ] } . You cannot have multiple dynamic routes in same parent` ,
120+ '/route' ,
121+ 'error'
122+ )
123+ }
124+ // check if current node has the same route, if no create a new node with middlwares
125+ currentNode . children [ node ] = currentNode . children [ node ] ?? {
126+ // these middlwares will be used by all the children of this node
127+ globalMiddlewares : currentNode . globalMiddlewares ?? [ ] ,
128+ localMiddlewares : currentNode . localMiddlewares ?? [ ] ,
129+ } ;
130+ currentNode = currentNode . children [ node ] ;
131+ }
165132
166- for ( const routePart of routeParts ) {
167- // Remove query params and hash from route
168- const [ node ] = routePart . split ( '#' ) [ 0 ] . split ( '?' ) ;
169- // check if current node has children
170- currentNode . children = currentNode . children ?? { } ;
171- // check if current node is dynamic
172- if ( node . startsWith ( ':' ) ) {
173- // check if current parent node has other dynamic routes
174- const keys = Object . keys ( currentNode . children ) . filter (
175- ( key ) => key . startsWith ( ':' ) && key !== node
176- ) ;
133+ // if same route with same method is already registered, show warning
134+ jssert (
135+ ! currentNode [ httpMethod ] ,
136+ `Route conflict: ${ route } with ${ httpMethod } method has already been registered. Trying to register the same route will override the previous one, and there might be unintended behaviors` ,
137+ '/route' ,
138+ 'warn'
139+ )
177140
178- if ( keys . length !== 0 ) {
179- throw new Jrror ( {
180- code : 'route-conflict' ,
181- message : `Route conflict: ${ route } conflicts with existing route ${ keys [ 0 ] } . You cannot have multiple dynamic routes in same parent` ,
182- type : 'error' ,
183- docsPath : '/routing' ,
184- } ) ;
185- }
141+ // after all above checks, register the route
142+ currentNode [ httpMethod ] = {
143+ handlers,
144+ } ;
145+ } catch ( error : unknown ) {
146+ handleError ( error ) ;
186147 }
187- // check if current node has the same route, if no create a new node with middlwares
188- currentNode . children [ node ] = currentNode . children [ node ] ?? {
189- // these middlwares will be used by all the children of this node
190- globalMiddlewares : currentNode . globalMiddlewares ?? [ ] ,
191- localMiddlewares : currentNode . localMiddlewares ?? [ ] ,
192- } ;
193- currentNode = currentNode . children [ node ] ;
194- }
195-
196- // if same route with same method is already registered, show warning
197- if ( currentNode [ httpMethod ] ) {
198- throw new Jrror ( {
199- code : 'route-duplicate' ,
200- message : `Route conflict: ${ route } with ${ httpMethod } method has already been registered. Trying to register the same route will override the previous one, and there might be unintended behaviors` ,
201- type : 'warn' ,
202- docsPath : '/routing' ,
203- } ) ;
204- }
205- // after all above checks, register the route
206- currentNode [ httpMethod ] = {
207- handlers,
208- } ;
209- } catch ( error : unknown ) {
210- if ( error instanceof Jrror || error instanceof JoorError ) {
211- error . handle ( ) ;
212- } else {
213- logger . error ( 'Router Error: ' , error ) ;
214- }
215148 }
216- }
217149}
218150
219- export default Router ;
151+ export default Router ;
0 commit comments