diesel-core 0.0.12 → 0.0.14
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/CONTRIBUTING.md +1 -1
- package/README.md +78 -0
- package/build.js +10 -8
- package/dist/handleRequest.js +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.js +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.js +1 -1
- package/dist/types.d.ts +1 -1
- package/example/bun.lockb +0 -0
- package/example/main.ts +51 -12
- package/example/package-lock.json +177 -0
- package/example/package.json +4 -1
- package/example/route.js +2 -2
- package/example/tester.js +3 -1
- package/package.json +1 -1
- package/src/handleRequest.ts +54 -58
- package/src/main.ts +22 -23
- package/src/route.ts +56 -0
- package/src/trie.ts +5 -3
- package/src/types.ts +3 -2
- package/tsconfig.json +3 -3
- package/src/router.ts +0 -55
package/CONTRIBUTING.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if you changes code OR fixing bugs so after doing make sure to run bun build.js
|
|
1
|
+
if you changes code OR fixing bugs so after doing make sure to run tsc and bun build.js
|
package/README.md
CHANGED
|
@@ -38,11 +38,89 @@ app.listen(port, () => {
|
|
|
38
38
|
})
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
+
## Filter and Route Security
|
|
42
|
+
**Diesel** provides a simple way to manage public and protected routes by using a filter() method. You can define specific routes to be publicly accessible, while others will require authentication or custom middleware functions.
|
|
43
|
+
|
|
44
|
+
### How to Use the Filter
|
|
45
|
+
The **filter()** method allows you to secure certain endpoints while keeping others public. You can specify routes that should be publicly accessible using permitAll(), and apply authentication or other middleware to the remaining routes with require().
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Example Usage
|
|
49
|
+
```javascript
|
|
50
|
+
import Diesel from "diesel-core";
|
|
51
|
+
import jwt from 'jsonwebtoken';
|
|
52
|
+
|
|
53
|
+
const app = new Diesel();
|
|
54
|
+
|
|
55
|
+
async function authJwt (ctx:ContextType, server?:Server): Promise<void | Response> {
|
|
56
|
+
const token = await ctx.getCookie("accessToken"); // Retrieve the JWT token from cookies
|
|
57
|
+
if (!token) {
|
|
58
|
+
return ctx.status(401).json({ message: "Authentication token missing" });
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
// Verify the JWT token using a secret key
|
|
62
|
+
const user = jwt.verify(token, secret); // Replace with your JWT secret
|
|
63
|
+
// Set the user data in context
|
|
64
|
+
ctx.set("user", user);
|
|
65
|
+
|
|
66
|
+
// Proceed to the next middleware/route handler
|
|
67
|
+
return ctx.next();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return ctx.status(403).json({ message: "Invalid token" });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Define routes and apply filter
|
|
74
|
+
app
|
|
75
|
+
.filter()
|
|
76
|
+
.routeMatcher('/api/user/register', '/api/user/login', '/test/:id', '/cookie') // Define public routes
|
|
77
|
+
.permitAll() // Mark these routes as public (no auth required)
|
|
78
|
+
.require(authJwt); // Apply the authJwt middleware to all other routes
|
|
79
|
+
|
|
80
|
+
// Example public route (no auth required)
|
|
81
|
+
app.get("/api/user/register", async (xl) => {
|
|
82
|
+
return xl.json({ msg: "This is a public route. No authentication needed." });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Example protected route (requires auth)
|
|
86
|
+
app.get("/api/user/profile", async (xl) => {
|
|
87
|
+
// This route is protected, so the auth middleware will run before this handler
|
|
88
|
+
return xl.json({ msg: "You are authenticated!" });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Start the server
|
|
92
|
+
const port = 3000;
|
|
93
|
+
app.listen(port, () => {
|
|
94
|
+
console.log(`Diesel is running on port ${port}`);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
## Filter Methods
|
|
98
|
+
1. **routeMatcher(...routes: string[])** : Passed endpoints in this routeMatcher will be ***Public*** means they don't need authentication, including those with dynamic parameters (e.g., /test/:id).
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
.routeMatcher('/api/user/register', '/api/user/login', '/test/:id')
|
|
102
|
+
```
|
|
103
|
+
1. **permitAll()** : Marks the routes specified in routeMatcher() as publicly accessible, meaning no middleware (like authentication) will be required for these routes.
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
.permitAll()
|
|
107
|
+
```
|
|
108
|
+
1. **require(fnc?: middlewareFunc)** :Means that defined routes in ***routeMatcher*** is public & All endpoints needs authentication.
|
|
109
|
+
|
|
110
|
+
*Note* : If you don't pass a middleware function to require(), DieselJS will throw an "Unauthorized" error by default. Ensure that you implement and pass a valid authentication function
|
|
111
|
+
```javascript
|
|
112
|
+
.require(authJwt)
|
|
113
|
+
```
|
|
114
|
+
## Use Case
|
|
115
|
+
* **Public Routes** : Some routes ***(like /api/user/register or /api/user/login)*** are often open to all users without authentication. These routes can be specified with permitAll().
|
|
116
|
+
|
|
117
|
+
* **Protected Routes** : For other routes ***(like /api/user/profile)***, you'll want to require authentication or custom middleware. Use require(authJwt) to ensure that the user is authenticated before accessing these routes.
|
|
41
118
|
|
|
42
119
|
## Using Hooks in DieselJS
|
|
43
120
|
|
|
44
121
|
DieselJS allows you to enhance your request handling by utilizing hooks at various stages of the request lifecycle. This gives you the flexibility to execute custom logic for logging, authentication, data manipulation, and more.
|
|
45
122
|
|
|
123
|
+
|
|
46
124
|
### Available Hooks
|
|
47
125
|
|
|
48
126
|
1. **onRequest**: Triggered when a request is received.
|
package/build.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// tsc will create main folder
|
|
2
|
+
Bun.build({
|
|
3
3
|
entrypoints: [
|
|
4
|
-
'./
|
|
5
|
-
'./
|
|
6
|
-
'./
|
|
7
|
-
'./
|
|
8
|
-
'./
|
|
4
|
+
'./main/main.ts',
|
|
5
|
+
'./main/ctx.ts',
|
|
6
|
+
'./main/handleRequest.ts',
|
|
7
|
+
'./main/trie.ts',
|
|
8
|
+
'./main/router.ts'
|
|
9
9
|
],
|
|
10
10
|
outdir: './dist',
|
|
11
11
|
minify: true, // Enable minification
|
|
12
|
-
})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
await Bun.spawn(['rm', '-rf', './main']);
|
package/dist/handleRequest.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function W(G,E,I){let z=new Headers,J={},
|
|
1
|
+
function W(G,E,I){let z=new Headers,J={},U=!1,Z=null,L=null,Y=null,$,M=200;return{req:G,server:E,url:I,next:()=>{},status(F){return M=F,this},getIP(){return this.server.requestIP(this.req)},async body(){if(!$)$=await j(G);if($.error)return new Response(JSON.stringify({error:$.error}),{status:400});return $},setHeader(F,X){return z.set(F,X),this},set(F,X){return J[F]=X,this},get(F){return J[F]||null},setAuth(F){return U=F,this},getAuth(){return U},text(F,X){return new Response(F,{status:X??M,headers:z})},json(F,X){return new Response(JSON.stringify(F),{status:X??M,headers:z})},html(F,X){return new Response(Bun.file(F),{status:X??M,headers:z})},file(F,X){return new Response(Bun.file(F),{status:X??M,headers:z})},redirect(F,X){return z.set("Location",F),new Response(null,{status:X??302,headers:z})},getParams(F){if(!Y)Y=V(G?.routePattern,I?.pathname);return F?Y[F]||null:Y},getQuery(F){if(!Z)Z=Object.fromEntries(I.searchParams);return F?Z[F]||null:Z},async cookie(F,X,_={}){let K=`${encodeURIComponent(F)}=${encodeURIComponent(X)}`;if(_.maxAge)K+=`; Max-Age=${_.maxAge}`;if(_.expires)K+=`; Expires=${_.expires.toUTCString()}`;if(_.path)K+=`; Path=${_.path}`;if(_.domain)K+=`; Domain=${_.domain}`;if(_.secure)K+="; Secure";if(_.httpOnly)K+="; HttpOnly";if(_.sameSite)K+=`; SameSite=${_.sameSite}`;return z?.append("Set-Cookie",K),this},async getCookie(F){if(!L){let X=G.headers.get("cookie");if(X)L=await A(X)}return F?L[F]||null:L}}}async function A(G){let E={};if(!G)return E;return G.split(";").forEach((z)=>{let[J,U]=z?.trim()?.split("=");if(J&&U)E[J.trim()]=U.split(" ")[0].trim()}),E}function V(G,E){let I={},z=G.split("/"),[J]=E.split("?"),U=J.split("/");if(z.length!==U.length)return null;return z.forEach((Z,L)=>{if(Z.startsWith(":")){let Y=Z.slice(1);I[Y]=U[L]}}),I}async function j(G){let E=G.headers.get("Content-Type")||"";if(!E)return{};try{if(E.startsWith("application/json"))return await G.json();if(E.startsWith("application/x-www-form-urlencoded")){let I=await G.text();return Object.fromEntries(new URLSearchParams(I))}if(E.startsWith("multipart/form-data")){let I=await G.formData();return D(I)}return{error:"Unknown request body type"}}catch(I){return{error:"Invalid request body format"}}}function D(G){let E={};for(let[I,z]of G.entries())E[I]=z;return E}async function Q(G,E,I,z){let J=W(G,E,I),U=z.trie.search(I.pathname,G.method);if(!U||U.method!==G.method)return new Response(U?"Method not allowed":`Route not found for ${I.pathname}`,{status:U?405:404});if(U.isDynamic)G.routePattern=U.path;if(z.corsConfig){let L=await T(G,J,z.corsConfig);if(L)return L}if(z.hasOnReqHook&&z.hooks.onRequest)z.hooks.onRequest(J,E);if(z.filters.length>0){let L=G.routePattern??I.pathname;if(z.filters.includes(L)===!1)if(z.filterFunction){let $=await z?.filterFunction(J,E);if($)return $}else return new Response(JSON.stringify({message:"Authentication required"}),{status:400})}if(z.hasMiddleware){let L=[...z.globalMiddlewares,...z.middlewares.get(I.pathname)||[]];for(let Y of L){let $=await Y(J,E);if($)return $}}if(z.hasPreHandlerHook&&z.hooks.preHandler){let L=await z.hooks.preHandler(J);if(L)return L}let Z=z.hasPreHandlerHook?await z.hooks.preHandler?.(J):null;if(Z)return Z;try{let L=await U.handler(J);if(z.hasPostHandlerHook&&z.hooks.postHandler)await z.hooks.postHandler(J);if(z.hasOnSendHook&&z.hooks.onSend){let Y=await z.hooks.onSend(J,L);if(Y)return Y}return L??new Response("No response from handler",{status:204})}catch(L){return new Response("Internal Server Error",{status:500})}}async function T(G,E,I={}){let z=G.headers.get("origin")??"*",J=I?.origin,U=I?.allowedHeaders??["Content-Type","Authorization"],Z=I?.methods??["GET","POST","PUT","DELETE","OPTIONS"],L=I?.credentials??!1,Y=I?.exposedHeaders??[];if(E.setHeader("Access-Control-Allow-Methods",Z),E.setHeader("Access-Control-Allow-Headers",U),E.setHeader("Access-Control-Allow-Credentials",L),Y.length)E.setHeader("Access-Control-Expose-Headers",Y);if(J==="*")E.setHeader("Access-Control-Allow-Origin","*");else if(Array.isArray(J))if(z&&J.includes(z))E.setHeader("Access-Control-Allow-Origin",z);else if(J.includes("*"))E.setHeader("Access-Control-Allow-Origin","*");else return E.status(403).json({message:"CORS not allowed"});else if(typeof J==="string")if(z===J)E.setHeader("Access-Control-Allow-Origin",z);else return E.status(403).json({message:"CORS not allowed"});else return E.status(403).json({message:"CORS not allowed"});if(E.setHeader("Access-Control-Allow-Origin",z),G.method==="OPTIONS")return E.setHeader("Access-Control-Max-Age","86400"),E.status(204).text("");return null}export{Q as default};
|
package/dist/main.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import Trie from "./trie.js";
|
|
|
2
2
|
import rateLimit from "./utils.js";
|
|
3
3
|
import { corsT, FilterMethods, HookFunction, HookType, middlewareFunc, type handlerFunction, type Hooks, type listenCalllBackType } from "./types.js";
|
|
4
4
|
import { Server } from "bun";
|
|
5
|
-
|
|
5
|
+
export default class Diesel {
|
|
6
6
|
#private;
|
|
7
7
|
routes: string[] | undefined;
|
|
8
8
|
globalMiddlewares: middlewareFunc[];
|
|
@@ -31,4 +31,4 @@ declare class Diesel {
|
|
|
31
31
|
patch(path: string, ...handlers: handlerFunction[]): this;
|
|
32
32
|
delete(path: any, ...handlers: handlerFunction[]): this;
|
|
33
33
|
}
|
|
34
|
-
export {
|
|
34
|
+
export { rateLimit, };
|
package/dist/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class
|
|
1
|
+
class D{children;isEndOfWord;handler;isDynamic;pattern;path;method;subMiddlewares;constructor(){this.children={},this.isEndOfWord=!1,this.handler=[],this.isDynamic=!1,this.pattern="",this.path="",this.method=[],this.subMiddlewares=new Map}}class Q{root;constructor(){this.root=new D}insert(z,F){let G=this.root,J=z.split("/").filter(Boolean);if(z==="/"){G.isEndOfWord=!0,G.handler.push(F.handler),G.path=z,G.method.push(F.method);return}for(let U of J){let X=!1,_=U;if(U.startsWith(":"))X=!0,_=":";if(!G.children[_])G.children[_]=new D;G=G.children[_],G.isDynamic=X,G.pattern=U}G.isEndOfWord=!0,G.method.push(F.method),G.handler.push(F.handler),G.path=z}search(z,F){let G=this.root,J=z.split("/").filter(Boolean);for(let X of J){let _=X;if(!G.children[_])if(G.children[":"])G=G.children[":"];else return null;else G=G.children[_]}let U=G.method.indexOf(F);if(U!==-1)return{path:G.path,handler:G.handler[U],isDynamic:G.isDynamic,pattern:G.pattern,method:G.method[U]};return{path:G.path,handler:G.handler,isDynamic:G.isDynamic,pattern:G.pattern,method:G.method[U]}}}function K(z,F,G){let J=new Headers,U={},X=!1,_=null,Y=null,$=null,L,V=200;return{req:z,server:F,url:G,next:()=>{},status(Z){return V=Z,this},getIP(){return this.server.requestIP(this.req)},async body(){if(!L)L=await b(z);if(L.error)return new Response(JSON.stringify({error:L.error}),{status:400});return L},setHeader(Z,E){return J.set(Z,E),this},set(Z,E){return U[Z]=E,this},get(Z){return U[Z]||null},setAuth(Z){return X=Z,this},getAuth(){return X},text(Z,E){return new Response(Z,{status:E??V,headers:J})},json(Z,E){return new Response(JSON.stringify(Z),{status:E??V,headers:J})},html(Z,E){return new Response(Bun.file(Z),{status:E??V,headers:J})},file(Z,E){return new Response(Bun.file(Z),{status:E??V,headers:J})},redirect(Z,E){return J.set("Location",Z),new Response(null,{status:E??302,headers:J})},getParams(Z){if(!$)$=T(z?.routePattern,G?.pathname);return Z?$[Z]||null:$},getQuery(Z){if(!_)_=Object.fromEntries(G.searchParams);return Z?_[Z]||null:_},async cookie(Z,E,W={}){let A=`${encodeURIComponent(Z)}=${encodeURIComponent(E)}`;if(W.maxAge)A+=`; Max-Age=${W.maxAge}`;if(W.expires)A+=`; Expires=${W.expires.toUTCString()}`;if(W.path)A+=`; Path=${W.path}`;if(W.domain)A+=`; Domain=${W.domain}`;if(W.secure)A+="; Secure";if(W.httpOnly)A+="; HttpOnly";if(W.sameSite)A+=`; SameSite=${W.sameSite}`;return J?.append("Set-Cookie",A),this},async getCookie(Z){if(!Y){let E=z.headers.get("cookie");if(E)Y=await N(E)}return Z?Y[Z]||null:Y}}}async function N(z){let F={};if(!z)return F;return z.split(";").forEach((J)=>{let[U,X]=J?.trim()?.split("=");if(U&&X)F[U.trim()]=X.split(" ")[0].trim()}),F}function T(z,F){let G={},J=z.split("/"),[U]=F.split("?"),X=U.split("/");if(J.length!==X.length)return null;return J.forEach((_,Y)=>{if(_.startsWith(":")){let $=_.slice(1);G[$]=X[Y]}}),G}async function b(z){let F=z.headers.get("Content-Type")||"";if(!F)return{};try{if(F.startsWith("application/json"))return await z.json();if(F.startsWith("application/x-www-form-urlencoded")){let G=await z.text();return Object.fromEntries(new URLSearchParams(G))}if(F.startsWith("multipart/form-data")){let G=await z.formData();return C(G)}return{error:"Unknown request body type"}}catch(G){return{error:"Invalid request body format"}}}function C(z){let F={};for(let[G,J]of z.entries())F[G]=J;return F}async function M(z,F,G,J){let U=K(z,F,G),X=J.trie.search(G.pathname,z.method);if(!X||X.method!==z.method)return new Response(X?"Method not allowed":`Route not found for ${G.pathname}`,{status:X?405:404});if(X.isDynamic)z.routePattern=X.path;if(J.corsConfig){let Y=await v(z,U,J.corsConfig);if(Y)return Y}if(J.hasOnReqHook&&J.hooks.onRequest)J.hooks.onRequest(U,F);if(J.filters.length>0){let Y=z.routePattern??G.pathname;if(J.filters.includes(Y)===!1)if(J.filterFunction){let L=await J?.filterFunction(U,F);if(L)return L}else return new Response(JSON.stringify({message:"Authentication required"}),{status:400})}if(J.hasMiddleware){let Y=[...J.globalMiddlewares,...J.middlewares.get(G.pathname)||[]];for(let $ of Y){let L=await $(U,F);if(L)return L}}if(J.hasPreHandlerHook&&J.hooks.preHandler){let Y=await J.hooks.preHandler(U);if(Y)return Y}let _=J.hasPreHandlerHook?await J.hooks.preHandler?.(U):null;if(_)return _;try{let Y=await X.handler(U);if(J.hasPostHandlerHook&&J.hooks.postHandler)await J.hooks.postHandler(U);if(J.hasOnSendHook&&J.hooks.onSend){let $=await J.hooks.onSend(U,Y);if($)return $}return Y??new Response("No response from handler",{status:204})}catch(Y){return new Response("Internal Server Error",{status:500})}}async function v(z,F,G={}){let J=z.headers.get("origin")??"*",U=G?.origin,X=G?.allowedHeaders??["Content-Type","Authorization"],_=G?.methods??["GET","POST","PUT","DELETE","OPTIONS"],Y=G?.credentials??!1,$=G?.exposedHeaders??[];if(F.setHeader("Access-Control-Allow-Methods",_),F.setHeader("Access-Control-Allow-Headers",X),F.setHeader("Access-Control-Allow-Credentials",Y),$.length)F.setHeader("Access-Control-Expose-Headers",$);if(U==="*")F.setHeader("Access-Control-Allow-Origin","*");else if(Array.isArray(U))if(J&&U.includes(J))F.setHeader("Access-Control-Allow-Origin",J);else if(U.includes("*"))F.setHeader("Access-Control-Allow-Origin","*");else return F.status(403).json({message:"CORS not allowed"});else if(typeof U==="string")if(J===U)F.setHeader("Access-Control-Allow-Origin",J);else return F.status(403).json({message:"CORS not allowed"});else return F.status(403).json({message:"CORS not allowed"});if(F.setHeader("Access-Control-Allow-Origin",J),z.method==="OPTIONS")return F.setHeader("Access-Control-Max-Age","86400"),F.status(204).text("");return null}function B(z){let{time:F=60000,max:G=100,message:J="Rate limit exceeded. Please try again later."}=z,U=new Map;return(X)=>{let _=new Date,Y=X.getIP().address;if(!U.has(Y))U.set(Y,{count:0,startTime:_});let $=U.get(Y);if($)if(_-$.startTime>F)$.count=1,$.startTime=_;else $.count++;if($&&$.count>G)return X.status(429).json({error:J});X.next()}}class j{routes;globalMiddlewares;middlewares;trie;hasOnReqHook;hasMiddleware;hasPreHandlerHook;hasPostHandlerHook;hasOnSendHook;hooks;corsConfig;filters;filterFunction;constructor(){this.routes=[],this.filters=[],this.filterFunction=null,this.globalMiddlewares=[],this.middlewares=new Map,this.trie=new Q,this.corsConfig=null,this.hasMiddleware=!1,this.hasOnReqHook=!1,this.hasPreHandlerHook=!1,this.hasPostHandlerHook=!1,this.hasOnSendHook=!1,this.hooks={onRequest:null,preHandler:null,postHandler:null,onSend:null,onError:null,onClose:null}}filter(){return{routeMatcher:(...z)=>{return this.routes=z.sort(),this.filter()},permitAll:()=>{for(let z of this?.routes)this.filters.push(z);return this.filter()},require:(z)=>{if(!z||typeof z!=="function")return new Response(JSON.stringify({message:"Authentication required"}),{status:400});this.filterFunction=z}}}cors(z){this.corsConfig=z}addHooks(z,F){if(typeof z!=="string")throw new Error("hookName must be a string");if(typeof F!=="function")throw new Error("callback must be a instance of function");if(this.hooks.hasOwnProperty(z))this.hooks[z]=F;else throw new Error(`Unknown hook type: ${z}`)}compile(){if(this.globalMiddlewares.length>0)this.hasMiddleware=!0;for(let[z,F]of this.middlewares.entries())if(F.length>0){this.hasMiddleware=!0;break}if(this.hooks.onRequest)this.hasOnReqHook=!0;if(this.hooks.preHandler)this.hasPreHandlerHook=!0;if(this.hooks.postHandler)this.hasPostHandlerHook=!0;if(this.hooks.onSend)this.hasOnSendHook=!0}listen(z,F,{sslCert:G=null,sslKey:J=null}={}){if(typeof Bun==="undefined")throw new Error(".listen() is designed to run on Bun only...");if(typeof z!=="number")throw new Error("Port must be a numeric value");this.compile();let U=this,X={port:z,fetch:async(Y,$)=>{let L=new URL(Y.url);try{return await M(Y,$,L,this)}catch(V){return new Response("Internal Server Error",{status:500})}},onClose(){console.log("Server is shutting down...")}};if(G&&J)X.certFile=G,X.keyFile=J;let _=Bun.serve(X);if(typeof F==="function")return F();if(G&&J)console.log(`HTTPS server is running on https://localhost:${z}`);else console.log(`HTTP server is running on http://localhost:${z}`);return _}register(z,F){if(typeof z!=="string")throw new Error("path must be a string");if(typeof F!=="object")throw new Error("handler parameter should be a instance of router object",F);let G=Object.entries(F.trie.root.children);F.trie.root.subMiddlewares.forEach((J,U)=>{if(!this.middlewares.has(z+U))this.middlewares.set(z+U,[]);J?.forEach((X)=>{if(!this.middlewares.get(z+U)?.includes(X))this.middlewares.get(z+U)?.push(X)})});for(let[J,U]of G){let X=z+U?.path,_=U.handler[0],Y=U.method[0];this.trie.insert(X,{handler:_,method:Y})}F.trie=new Q}#z(z,F,G){if(typeof F!=="string")throw new Error("Path must be a string type");if(typeof z!=="string")throw new Error("method must be a string type");let J=G.slice(0,-1),U=G[G.length-1];if(!this.middlewares.has(F))this.middlewares.set(F,[]);J.forEach((X)=>{if(F==="/"){if(!this.globalMiddlewares.includes(X))this.globalMiddlewares.push(X)}else if(!this.middlewares.get(F)?.includes(X))this.middlewares.get(F)?.push(X)}),this.trie.insert(F,{handler:U,method:z})}use(z,F){if(typeof z==="function"){if(!this.globalMiddlewares.includes(z))this.globalMiddlewares.push(z);return}let G=z;if(!this.middlewares.has(G))this.middlewares.set(G,[]);if(F){if(!this.middlewares.get(G)?.includes(F))this.middlewares.get(G)?.push(F)}}get(z,...F){return this.#z("GET",z,F),this}post(z,...F){return this.#z("POST",z,F),this}put(z,...F){return this.#z("PUT",z,F),this}patch(z,...F){return this.#z("PATCH",z,F),this}delete(z,...F){return this.#z("DELETE",z,F),this}}export{B as rateLimit,j as default};
|
package/dist/router.d.ts
CHANGED
package/dist/router.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class K{children;isEndOfWord;handler;isDynamic;pattern;path;method;subMiddlewares;constructor(){this.children={},this.isEndOfWord=!1,this.handler=[],this.isDynamic=!1,this.pattern="",this.path="",this.method=[],this.subMiddlewares=new Map}}class Q{root;constructor(){this.root=new K}insert(F,z){let G=this.root,J=F.split("/").filter(Boolean);if(F==="/"){G.isEndOfWord=!0,G.handler.push(z.handler),G.path=F,G.method.push(z.method);return}for(let U of J){let X=!1,Y=U;if(U.startsWith(":"))X=!0,Y=":";if(!G.children[Y])G.children[Y]=new K;G=G.children[Y],G.isDynamic=X,G.pattern=U}G.isEndOfWord=!0,G.method.push(z.method),G.handler.push(z.handler),G.path=F}search(F,z){let G=this.root,J=F.split("/").filter(Boolean);for(let X of J){let Y=X;if(!G.children[Y])if(G.children[":"])G=G.children[":"];else return null;else G=G.children[Y]}let U=G.method.indexOf(z);if(U!==-1)return{path:G.path,handler:G.handler[U],isDynamic:G.isDynamic,pattern:G.pattern,method:G.method[U]};return{path:G.path,handler:G.handler,isDynamic:G.isDynamic,pattern:G.pattern,method:G.method[U]}}}function B(F,z,G){let J=new Headers,U={},X=!1,Y=null,$=null,_=null,A,V=200;return{req:F,server:z,url:G,next:()=>{},status(Z){return V=Z,this},getIP(){return this.server.requestIP(this.req)},async body(){if(!A)A=await b(F);if(A.error)return new Response(JSON.stringify({error:A.error}),{status:400});return A},setHeader(Z,E){return J.set(Z,E),this},set(Z,E){return U[Z]=E,this},get(Z){return U[Z]||null},setAuth(Z){return X=Z,this},getAuth(){return X},text(Z,E){return new Response(Z,{status:E??V,headers:J})},json(Z,E){return new Response(JSON.stringify(Z),{status:E??V,headers:J})},html(Z,E){return new Response(Bun.file(Z),{status:E??V,headers:J})},file(Z,E){return new Response(Bun.file(Z),{status:E??V,headers:J})},redirect(Z,E){return J.set("Location",Z),new Response(null,{status:E??302,headers:J})},getParams(Z){if(!_)_=C(F?.routePattern,G?.pathname);return Z?_[Z]||null:_},getQuery(Z){if(!Y)Y=Object.fromEntries(G.searchParams);return Z?Y[Z]||null:Y},async cookie(Z,E,L={}){let W=`${encodeURIComponent(Z)}=${encodeURIComponent(E)}`;if(L.maxAge)W+=`; Max-Age=${L.maxAge}`;if(L.expires)W+=`; Expires=${L.expires.toUTCString()}`;if(L.path)W+=`; Path=${L.path}`;if(L.domain)W+=`; Domain=${L.domain}`;if(L.secure)W+="; Secure";if(L.httpOnly)W+="; HttpOnly";if(L.sameSite)W+=`; SameSite=${L.sameSite}`;return J?.append("Set-Cookie",W),this},async getCookie(Z){if(!$){let E=F.headers.get("cookie");if(E)$=await T(E)}return Z?$[Z]||null:$}}}async function T(F){let z={};if(!F)return z;return F.split(";").forEach((J)=>{let[U,X]=J?.trim()?.split("=");if(U&&X)z[U.trim()]=X.split(" ")[0].trim()}),z}function C(F,z){let G={},J=F.split("/"),[U]=z.split("?"),X=U.split("/");if(J.length!==X.length)return null;return J.forEach((Y,$)=>{if(Y.startsWith(":")){let _=Y.slice(1);G[_]=X[$]}}),G}async function b(F){let z=F.headers.get("Content-Type")||"";if(!z)return{};try{if(z.startsWith("application/json"))return await F.json();if(z.startsWith("application/x-www-form-urlencoded")){let G=await F.text();return Object.fromEntries(new URLSearchParams(G))}if(z.startsWith("multipart/form-data")){let G=await F.formData();return v(G)}return{error:"Unknown request body type"}}catch(G){return{error:"Invalid request body format"}}}function v(F){let z={};for(let[G,J]of F.entries())z[G]=J;return z}async function j(F,z,G,J){let U=B(F,z,G),X=J.trie.search(G.pathname,F.method);if(!X||!X.handler)return new Response(`Route not found for ${G.pathname}`,{status:404});if(X.method!==F.method)return new Response("Method not allowed",{status:405});if(X.isDynamic)F.routePattern=X.path;if(J.corsConfig){let Y=await I(F,U,J.corsConfig);if(Y)return Y}if(J.hasOnReqHook&&J.hooks.onRequest)J.hooks.onRequest(U,z);if(J.filters.length>0){let Y=F.routePattern??G.pathname;if(J.filters.includes(Y)===!1)if(J.filterFunction){let _=await J?.filterFunction(U);if(_)return _}else return new Response(JSON.stringify({message:"Authentication required"}),{status:400})}if(J.hasMiddleware){let Y=[...J.globalMiddlewares,...J.middlewares.get(G.pathname)||[]];for(let $ of Y){let _=await $(U,z);if(_)return _}}if(J.hasPreHandlerHook&&J.hooks.preHandler){let Y=await J.hooks.preHandler(U);if(Y)return Y}try{let Y=await X.handler(U);if(J.hasPostHandlerHook&&J.hooks.postHandler)await J.hooks.postHandler(U);if(J.hasOnSendHook&&J.hooks.onSend){let $=await J.hooks.onSend(U,Y);if($)return $}return Y??new Response("No response from handler",{status:204})}catch(Y){return new Response("Internal Server Error",{status:500})}}async function I(F,z,G={}){let J=F.headers.get("origin")??"*",U=G?.origin,X=G?.allowedHeaders??["Content-Type","Authorization"],Y=G?.methods??["GET","POST","PUT","DELETE","OPTIONS"],$=G?.credentials??!1,_=G?.exposedHeaders??[];if(z.setHeader("Access-Control-Allow-Methods",Y),z.setHeader("Access-Control-Allow-Headers",X),z.setHeader("Access-Control-Allow-Credentials",$),_.length)z.setHeader("Access-Control-Expose-Headers",_);if(U==="*")z.setHeader("Access-Control-Allow-Origin","*");else if(Array.isArray(U))if(J&&U.includes(J))z.setHeader("Access-Control-Allow-Origin",J);else if(U.includes("*"))z.setHeader("Access-Control-Allow-Origin","*");else return z.status(403).json({message:"CORS not allowed"});else if(typeof U==="string")if(J===U)z.setHeader("Access-Control-Allow-Origin",J);else return z.status(403).json({message:"CORS not allowed"});else return z.status(403).json({message:"CORS not allowed"});if(z.setHeader("Access-Control-Allow-Origin",J),F.method==="OPTIONS")return z.setHeader("Access-Control-Max-Age","86400"),z.status(204).text("");return null}function M(F){let{time:z=60000,max:G=100,message:J="Rate limit exceeded. Please try again later."}=F,U=new Map;return(X)=>{let Y=new Date,$=X.getIP().address;if(!U.has($))U.set($,{count:0,startTime:Y});let _=U.get($);if(_)if(Y-_.startTime>z)_.count=1,_.startTime=Y;else _.count++;if(_&&_.count>G)return X.status(429).json({error:J});X.next()}}class D{routes;globalMiddlewares;middlewares;trie;hasOnReqHook;hasMiddleware;hasPreHandlerHook;hasPostHandlerHook;hasOnSendHook;hooks;corsConfig;filters;filterFunction;constructor(){this.routes=[],this.filters=[],this.filterFunction=null,this.globalMiddlewares=[],this.middlewares=new Map,this.trie=new Q,this.corsConfig=null,this.hasMiddleware=!1,this.hasOnReqHook=!1,this.hasPreHandlerHook=!1,this.hasPostHandlerHook=!1,this.hasOnSendHook=!1,this.hooks={onRequest:null,preHandler:null,postHandler:null,onSend:null,onError:null,onClose:null}}filter(){return{routeMatcher:(...F)=>{return this.routes=F.sort(),this.filter()},permitAll:()=>{for(let F of this?.routes)this.filters.push(F);return this.filter()},require:(F)=>{if(!F||typeof F!=="function")return new Response(JSON.stringify({message:"Authentication required"}),{status:400});this.filterFunction=F}}}cors(F){this.corsConfig=F}addHooks(F,z){if(typeof F!=="string")throw new Error("hookName must be a string");if(typeof z!=="function")throw new Error("callback must be a instance of function");if(this.hooks.hasOwnProperty(F))this.hooks[F]=z;else throw new Error(`Unknown hook type: ${F}`)}compile(){if(this.globalMiddlewares.length>0)this.hasMiddleware=!0;for(let[F,z]of this.middlewares.entries())if(z.length>0){this.hasMiddleware=!0;break}if(this.hooks.onRequest)this.hasOnReqHook=!0;if(this.hooks.preHandler)this.hasPreHandlerHook=!0;if(this.hooks.postHandler)this.hasPostHandlerHook=!0;if(this.hooks.onSend)this.hasOnSendHook=!0}listen(F,z,{sslCert:G=null,sslKey:J=null}={}){if(typeof Bun==="undefined")throw new Error(".listen() is designed to run on Bun only...");if(typeof F!=="number")throw new Error("Port must be a numeric value");this.compile();let U=this,X={port:F,fetch:async($,_)=>{let A=new URL($.url);try{return await j($,_,A,this)}catch(V){return new Response("Internal Server Error",{status:500})}},onClose(){console.log("Server is shutting down...")}};if(G&&J)X.certFile=G,X.keyFile=J;let Y=Bun.serve(X);if(typeof z==="function")return z();if(G&&J)console.log(`HTTPS server is running on https://localhost:${F}`);else console.log(`HTTP server is running on http://localhost:${F}`);return Y}register(F,z){if(typeof F!=="string")throw new Error("path must be a string");if(typeof z!=="object")throw new Error("handler parameter should be a instance of router object",z);let G=Object.entries(z.trie.root.children);z.trie.root.subMiddlewares.forEach((J,U)=>{if(!this.middlewares.has(F+U))this.middlewares.set(F+U,[]);J?.forEach((X)=>{if(!this.middlewares.get(F+U)?.includes(X))this.middlewares.get(F+U)?.push(X)})});for(let[J,U]of G){let X=F+U?.path,Y=U.handler[0],$=U.method[0];this.trie.insert(X,{handler:Y,method:$})}z.trie=new Q}#z(F,z,G){if(typeof z!=="string")throw new Error("Path must be a string type");if(typeof F!=="string")throw new Error("method must be a string type");let J=G.slice(0,-1),U=G[G.length-1];if(!this.middlewares.has(z))this.middlewares.set(z,[]);J.forEach((X)=>{if(z==="/"){if(!this.globalMiddlewares.includes(X))this.globalMiddlewares.push(X)}else if(!this.middlewares.get(z)?.includes(X))this.middlewares.get(z)?.push(X)}),this.trie.insert(z,{handler:U,method:F})}use(F,z){if(typeof F==="function"){if(!this.globalMiddlewares.includes(F))this.globalMiddlewares.push(F);return}let G=F;if(!this.middlewares.has(G))this.middlewares.set(G,[]);if(z){if(!this.middlewares.get(G)?.includes(z))this.middlewares.get(G)?.push(z)}}get(F,...z){return this.#z("GET",F,z),this}post(F,...z){return this.#z("POST",F,z),this}put(F,...z){return this.#z("PUT",F,z),this}patch(F,...z){return this.#z("PATCH",F,z),this}delete(F,...z){return this.#z("DELETE",F,z),this}}class N extends D{constructor(){super()}#z(F,z,G){if(!this.trie.root.subMiddlewares.has(z))this.trie.root.subMiddlewares.set(z,[]);let J=G.slice(0,-1),U=this.trie.root.subMiddlewares.get(z);J.forEach((Y)=>{if(!U?.includes(Y))U?.push(Y)});let X=G[G.length-1];this.trie.insert(z,{handler:X,method:F})}get(F,...z){return this.#z("GET",F,z),this}post(F,...z){return this.#z("POST",F,z),this}put(F,...z){return this.#z("PUT",F,z),this}patch(F,...z){return this.#z("PATCH",F,z),this}delete(F,...z){return this.#z("DELETE",F,z),this}}var x=N;export{x as default};
|
|
1
|
+
class B{children;isEndOfWord;handler;isDynamic;pattern;path;method;subMiddlewares;constructor(){this.children={},this.isEndOfWord=!1,this.handler=[],this.isDynamic=!1,this.pattern="",this.path="",this.method=[],this.subMiddlewares=new Map}}class Q{root;constructor(){this.root=new B}insert(F,z){let G=this.root,J=F.split("/").filter(Boolean);if(F==="/"){G.isEndOfWord=!0,G.handler.push(z.handler),G.path=F,G.method.push(z.method);return}for(let U of J){let X=!1,Y=U;if(U.startsWith(":"))X=!0,Y=":";if(!G.children[Y])G.children[Y]=new B;G=G.children[Y],G.isDynamic=X,G.pattern=U}G.isEndOfWord=!0,G.method.push(z.method),G.handler.push(z.handler),G.path=F}search(F,z){let G=this.root,J=F.split("/").filter(Boolean);for(let X of J){let Y=X;if(!G.children[Y])if(G.children[":"])G=G.children[":"];else return null;else G=G.children[Y]}let U=G.method.indexOf(z);if(U!==-1)return{path:G.path,handler:G.handler[U],isDynamic:G.isDynamic,pattern:G.pattern,method:G.method[U]};return{path:G.path,handler:G.handler,isDynamic:G.isDynamic,pattern:G.pattern,method:G.method[U]}}}function D(F,z,G){let J=new Headers,U={},X=!1,Y=null,Z=null,$=null,L,V=200;return{req:F,server:z,url:G,next:()=>{},status(_){return V=_,this},getIP(){return this.server.requestIP(this.req)},async body(){if(!L)L=await b(F);if(L.error)return new Response(JSON.stringify({error:L.error}),{status:400});return L},setHeader(_,E){return J.set(_,E),this},set(_,E){return U[_]=E,this},get(_){return U[_]||null},setAuth(_){return X=_,this},getAuth(){return X},text(_,E){return new Response(_,{status:E??V,headers:J})},json(_,E){return new Response(JSON.stringify(_),{status:E??V,headers:J})},html(_,E){return new Response(Bun.file(_),{status:E??V,headers:J})},file(_,E){return new Response(Bun.file(_),{status:E??V,headers:J})},redirect(_,E){return J.set("Location",_),new Response(null,{status:E??302,headers:J})},getParams(_){if(!$)$=C(F?.routePattern,G?.pathname);return _?$[_]||null:$},getQuery(_){if(!Y)Y=Object.fromEntries(G.searchParams);return _?Y[_]||null:Y},async cookie(_,E,W={}){let A=`${encodeURIComponent(_)}=${encodeURIComponent(E)}`;if(W.maxAge)A+=`; Max-Age=${W.maxAge}`;if(W.expires)A+=`; Expires=${W.expires.toUTCString()}`;if(W.path)A+=`; Path=${W.path}`;if(W.domain)A+=`; Domain=${W.domain}`;if(W.secure)A+="; Secure";if(W.httpOnly)A+="; HttpOnly";if(W.sameSite)A+=`; SameSite=${W.sameSite}`;return J?.append("Set-Cookie",A),this},async getCookie(_){if(!Z){let E=F.headers.get("cookie");if(E)Z=await T(E)}return _?Z[_]||null:Z}}}async function T(F){let z={};if(!F)return z;return F.split(";").forEach((J)=>{let[U,X]=J?.trim()?.split("=");if(U&&X)z[U.trim()]=X.split(" ")[0].trim()}),z}function C(F,z){let G={},J=F.split("/"),[U]=z.split("?"),X=U.split("/");if(J.length!==X.length)return null;return J.forEach((Y,Z)=>{if(Y.startsWith(":")){let $=Y.slice(1);G[$]=X[Z]}}),G}async function b(F){let z=F.headers.get("Content-Type")||"";if(!z)return{};try{if(z.startsWith("application/json"))return await F.json();if(z.startsWith("application/x-www-form-urlencoded")){let G=await F.text();return Object.fromEntries(new URLSearchParams(G))}if(z.startsWith("multipart/form-data")){let G=await F.formData();return v(G)}return{error:"Unknown request body type"}}catch(G){return{error:"Invalid request body format"}}}function v(F){let z={};for(let[G,J]of F.entries())z[G]=J;return z}async function j(F,z,G,J){let U=D(F,z,G),X=J.trie.search(G.pathname,F.method);if(!X||X.method!==F.method)return new Response(X?"Method not allowed":`Route not found for ${G.pathname}`,{status:X?405:404});if(X.isDynamic)F.routePattern=X.path;if(J.corsConfig){let Z=await I(F,U,J.corsConfig);if(Z)return Z}if(J.hasOnReqHook&&J.hooks.onRequest)J.hooks.onRequest(U,z);if(J.filters.length>0){let Z=F.routePattern??G.pathname;if(J.filters.includes(Z)===!1)if(J.filterFunction){let L=await J?.filterFunction(U,z);if(L)return L}else return new Response(JSON.stringify({message:"Authentication required"}),{status:400})}if(J.hasMiddleware){let Z=[...J.globalMiddlewares,...J.middlewares.get(G.pathname)||[]];for(let $ of Z){let L=await $(U,z);if(L)return L}}if(J.hasPreHandlerHook&&J.hooks.preHandler){let Z=await J.hooks.preHandler(U);if(Z)return Z}let Y=J.hasPreHandlerHook?await J.hooks.preHandler?.(U):null;if(Y)return Y;try{let Z=await X.handler(U);if(J.hasPostHandlerHook&&J.hooks.postHandler)await J.hooks.postHandler(U);if(J.hasOnSendHook&&J.hooks.onSend){let $=await J.hooks.onSend(U,Z);if($)return $}return Z??new Response("No response from handler",{status:204})}catch(Z){return new Response("Internal Server Error",{status:500})}}async function I(F,z,G={}){let J=F.headers.get("origin")??"*",U=G?.origin,X=G?.allowedHeaders??["Content-Type","Authorization"],Y=G?.methods??["GET","POST","PUT","DELETE","OPTIONS"],Z=G?.credentials??!1,$=G?.exposedHeaders??[];if(z.setHeader("Access-Control-Allow-Methods",Y),z.setHeader("Access-Control-Allow-Headers",X),z.setHeader("Access-Control-Allow-Credentials",Z),$.length)z.setHeader("Access-Control-Expose-Headers",$);if(U==="*")z.setHeader("Access-Control-Allow-Origin","*");else if(Array.isArray(U))if(J&&U.includes(J))z.setHeader("Access-Control-Allow-Origin",J);else if(U.includes("*"))z.setHeader("Access-Control-Allow-Origin","*");else return z.status(403).json({message:"CORS not allowed"});else if(typeof U==="string")if(J===U)z.setHeader("Access-Control-Allow-Origin",J);else return z.status(403).json({message:"CORS not allowed"});else return z.status(403).json({message:"CORS not allowed"});if(z.setHeader("Access-Control-Allow-Origin",J),F.method==="OPTIONS")return z.setHeader("Access-Control-Max-Age","86400"),z.status(204).text("");return null}function M(F){let{time:z=60000,max:G=100,message:J="Rate limit exceeded. Please try again later."}=F,U=new Map;return(X)=>{let Y=new Date,Z=X.getIP().address;if(!U.has(Z))U.set(Z,{count:0,startTime:Y});let $=U.get(Z);if($)if(Y-$.startTime>z)$.count=1,$.startTime=Y;else $.count++;if($&&$.count>G)return X.status(429).json({error:J});X.next()}}class K{routes;globalMiddlewares;middlewares;trie;hasOnReqHook;hasMiddleware;hasPreHandlerHook;hasPostHandlerHook;hasOnSendHook;hooks;corsConfig;filters;filterFunction;constructor(){this.routes=[],this.filters=[],this.filterFunction=null,this.globalMiddlewares=[],this.middlewares=new Map,this.trie=new Q,this.corsConfig=null,this.hasMiddleware=!1,this.hasOnReqHook=!1,this.hasPreHandlerHook=!1,this.hasPostHandlerHook=!1,this.hasOnSendHook=!1,this.hooks={onRequest:null,preHandler:null,postHandler:null,onSend:null,onError:null,onClose:null}}filter(){return{routeMatcher:(...F)=>{return this.routes=F.sort(),this.filter()},permitAll:()=>{for(let F of this?.routes)this.filters.push(F);return this.filter()},require:(F)=>{if(!F||typeof F!=="function")return new Response(JSON.stringify({message:"Authentication required"}),{status:400});this.filterFunction=F}}}cors(F){this.corsConfig=F}addHooks(F,z){if(typeof F!=="string")throw new Error("hookName must be a string");if(typeof z!=="function")throw new Error("callback must be a instance of function");if(this.hooks.hasOwnProperty(F))this.hooks[F]=z;else throw new Error(`Unknown hook type: ${F}`)}compile(){if(this.globalMiddlewares.length>0)this.hasMiddleware=!0;for(let[F,z]of this.middlewares.entries())if(z.length>0){this.hasMiddleware=!0;break}if(this.hooks.onRequest)this.hasOnReqHook=!0;if(this.hooks.preHandler)this.hasPreHandlerHook=!0;if(this.hooks.postHandler)this.hasPostHandlerHook=!0;if(this.hooks.onSend)this.hasOnSendHook=!0}listen(F,z,{sslCert:G=null,sslKey:J=null}={}){if(typeof Bun==="undefined")throw new Error(".listen() is designed to run on Bun only...");if(typeof F!=="number")throw new Error("Port must be a numeric value");this.compile();let U=this,X={port:F,fetch:async(Z,$)=>{let L=new URL(Z.url);try{return await j(Z,$,L,this)}catch(V){return new Response("Internal Server Error",{status:500})}},onClose(){console.log("Server is shutting down...")}};if(G&&J)X.certFile=G,X.keyFile=J;let Y=Bun.serve(X);if(typeof z==="function")return z();if(G&&J)console.log(`HTTPS server is running on https://localhost:${F}`);else console.log(`HTTP server is running on http://localhost:${F}`);return Y}register(F,z){if(typeof F!=="string")throw new Error("path must be a string");if(typeof z!=="object")throw new Error("handler parameter should be a instance of router object",z);let G=Object.entries(z.trie.root.children);z.trie.root.subMiddlewares.forEach((J,U)=>{if(!this.middlewares.has(F+U))this.middlewares.set(F+U,[]);J?.forEach((X)=>{if(!this.middlewares.get(F+U)?.includes(X))this.middlewares.get(F+U)?.push(X)})});for(let[J,U]of G){let X=F+U?.path,Y=U.handler[0],Z=U.method[0];this.trie.insert(X,{handler:Y,method:Z})}z.trie=new Q}#z(F,z,G){if(typeof z!=="string")throw new Error("Path must be a string type");if(typeof F!=="string")throw new Error("method must be a string type");let J=G.slice(0,-1),U=G[G.length-1];if(!this.middlewares.has(z))this.middlewares.set(z,[]);J.forEach((X)=>{if(z==="/"){if(!this.globalMiddlewares.includes(X))this.globalMiddlewares.push(X)}else if(!this.middlewares.get(z)?.includes(X))this.middlewares.get(z)?.push(X)}),this.trie.insert(z,{handler:U,method:F})}use(F,z){if(typeof F==="function"){if(!this.globalMiddlewares.includes(F))this.globalMiddlewares.push(F);return}let G=F;if(!this.middlewares.has(G))this.middlewares.set(G,[]);if(z){if(!this.middlewares.get(G)?.includes(z))this.middlewares.get(G)?.push(z)}}get(F,...z){return this.#z("GET",F,z),this}post(F,...z){return this.#z("POST",F,z),this}put(F,...z){return this.#z("PUT",F,z),this}patch(F,...z){return this.#z("PATCH",F,z),this}delete(F,...z){return this.#z("DELETE",F,z),this}}class N extends K{constructor(){super()}#z(F,z,G){if(!this.trie.root.subMiddlewares.has(z))this.trie.root.subMiddlewares.set(z,[]);let J=G.slice(0,-1),U=this.trie.root.subMiddlewares.get(z);J.forEach((Y)=>{if(!U?.includes(Y))U?.push(Y)});let X=G[G.length-1];this.trie.insert(z,{handler:X,method:F})}get(F,...z){return this.#z("GET",F,z),this}post(F,...z){return this.#z("POST",F,z),this}put(F,...z){return this.#z("PUT",F,z),this}patch(F,...z){return this.#z("PATCH",F,z),this}delete(F,...z){return this.#z("DELETE",F,z),this}}var x=N;export{x as default};
|
package/dist/types.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export interface DieselT {
|
|
|
76
76
|
onSend: ((ctx?: ContextType, result?: Response | null | void, serer?: Server) => Promise<Response | void | null>) | null;
|
|
77
77
|
};
|
|
78
78
|
filters: string[];
|
|
79
|
-
filterFunction: (ctx: ContextType) => void | Response | Promise<Response | void>;
|
|
79
|
+
filterFunction: (ctx: ContextType, serer?: Server) => void | Response | Promise<Response | void>;
|
|
80
80
|
corsConfig: corsT | null;
|
|
81
81
|
globalMiddlewares: Array<(ctx: ContextType, serer?: Server) => void | Promise<Response | null | void>>;
|
|
82
82
|
middlewares: Map<string, Array<(ctx: ContextType, serer?: Server) => void | Promise<Response | null | void>>>;
|
package/example/bun.lockb
CHANGED
|
Binary file
|
package/example/main.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Server } from "bun";
|
|
2
|
+
import Diesel ,{rateLimit} from "../src/main";
|
|
3
|
+
import { ContextType, CookieOptions, middlewareFunc } from "../dist/types";
|
|
4
|
+
import jwt from 'jsonwebtoken'
|
|
2
5
|
|
|
3
6
|
const app = new Diesel()
|
|
4
|
-
|
|
7
|
+
const secret = 'pradeep'
|
|
5
8
|
// app.cors({
|
|
6
9
|
// origin: ['http://localhost:5173','*'],
|
|
7
10
|
// methods: 'GET,POST,PUT,DELETE',
|
|
8
11
|
// allowedHeaders: 'Content-Type,Authorization'
|
|
9
12
|
// })
|
|
10
13
|
|
|
11
|
-
function
|
|
12
|
-
|
|
14
|
+
async function authJwt (ctx:ContextType, server?:Server): Promise<void | Response> {
|
|
15
|
+
const token = await ctx.getCookie("accessToken"); // Retrieve the JWT token from cookies
|
|
16
|
+
if (!token) {
|
|
17
|
+
return ctx.status(401).json({ message: "Authentication token missing" });
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
// Verify the JWT token using a secret key
|
|
21
|
+
const user = jwt.verify(token, secret); // Replace with your JWT secret
|
|
22
|
+
// Set the user data in context
|
|
23
|
+
ctx.set("user", user);
|
|
24
|
+
|
|
25
|
+
// Proceed to the next middleware/route handler
|
|
26
|
+
return ctx.next();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return ctx.status(403).json({ message: "Invalid token" });
|
|
29
|
+
}
|
|
13
30
|
}
|
|
14
31
|
|
|
15
32
|
const limiter = rateLimit({
|
|
@@ -22,21 +39,19 @@ const limiter = rateLimit({
|
|
|
22
39
|
|
|
23
40
|
app
|
|
24
41
|
.filter()
|
|
25
|
-
.routeMatcher('/api/user/register','/api/user/login','/test/:id','/')
|
|
42
|
+
.routeMatcher('/api/user/register','/api/user/login','/test/:id','/cookie')
|
|
26
43
|
.permitAll()
|
|
27
|
-
.require()
|
|
44
|
+
.require(authJwt)
|
|
45
|
+
|
|
46
|
+
// app.use(authJwt)
|
|
28
47
|
|
|
29
48
|
// .require(you can pass jwt auth parser)
|
|
30
49
|
|
|
31
50
|
app.get("/", async(xl) => {
|
|
32
51
|
// const ip = xl.req
|
|
33
52
|
// console.log(ip)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
author: 'Pradeep',
|
|
37
|
-
app: 'Express App',
|
|
38
|
-
features: ['Fast', 'Flexible', 'Lightweight']
|
|
39
|
-
});
|
|
53
|
+
const user = xl.get('user')
|
|
54
|
+
return xl.json({user:user});
|
|
40
55
|
});
|
|
41
56
|
|
|
42
57
|
app.get("/test/:id", async (xl) => {
|
|
@@ -49,4 +64,28 @@ app.get("/test/:id", async (xl) => {
|
|
|
49
64
|
return xl.status(200).text("kaise ho??")
|
|
50
65
|
})
|
|
51
66
|
|
|
67
|
+
app.get("/cookie", async(xl) => {
|
|
68
|
+
const user = {
|
|
69
|
+
name: "pk",
|
|
70
|
+
age: 22,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const accessToken = jwt.sign(user, secret, { expiresIn: "1d" });
|
|
74
|
+
const refreshToken = jwt.sign(user, secret, { expiresIn: "10d" });
|
|
75
|
+
const options : CookieOptions= {
|
|
76
|
+
httpOnly: true, // Makes cookie accessible only by the web server (not JS)
|
|
77
|
+
secure: true, // Ensures the cookie is sent over HTTPS
|
|
78
|
+
maxAge: 24 * 60 * 60 * 1000, // 1 day in milliseconds
|
|
79
|
+
sameSite: "Strict", // Prevents CSRF (strict origin policy)
|
|
80
|
+
path: "/", // Cookie available for all routes
|
|
81
|
+
};
|
|
82
|
+
await xl.cookie("accessToken", accessToken, options)
|
|
83
|
+
await xl.cookie("refreshToken", refreshToken, options)
|
|
84
|
+
// xl.cookie("refreshToken", refreshToken, options);
|
|
85
|
+
await xl.getCookie()
|
|
86
|
+
return xl.json({msg:"setting cookies"})
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
52
91
|
app.listen(3000)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "test",
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
13
|
+
"diesel-core": "^0.0.12",
|
|
14
|
+
"jsonwebtoken": "^9.0.2",
|
|
15
|
+
"jwt": "^0.2.0"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"node_modules/@types/jsonwebtoken": {
|
|
19
|
+
"version": "9.0.7",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@types/node": "*"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"node_modules/@types/node": {
|
|
26
|
+
"version": "22.7.7",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"undici-types": "~6.19.2"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"node_modules/buffer-equal-constant-time": {
|
|
33
|
+
"version": "1.0.1",
|
|
34
|
+
"license": "BSD-3-Clause"
|
|
35
|
+
},
|
|
36
|
+
"node_modules/diesel-core": {
|
|
37
|
+
"version": "0.0.12",
|
|
38
|
+
"license": "ISC",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"node_modules/ecdsa-sig-formatter": {
|
|
44
|
+
"version": "1.0.11",
|
|
45
|
+
"license": "Apache-2.0",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"safe-buffer": "^5.0.1"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"node_modules/jsonwebtoken": {
|
|
51
|
+
"version": "9.0.2",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"jws": "^3.2.2",
|
|
55
|
+
"lodash.includes": "^4.3.0",
|
|
56
|
+
"lodash.isboolean": "^3.0.3",
|
|
57
|
+
"lodash.isinteger": "^4.0.4",
|
|
58
|
+
"lodash.isnumber": "^3.0.3",
|
|
59
|
+
"lodash.isplainobject": "^4.0.6",
|
|
60
|
+
"lodash.isstring": "^4.0.1",
|
|
61
|
+
"lodash.once": "^4.0.0",
|
|
62
|
+
"ms": "^2.1.1",
|
|
63
|
+
"semver": "^7.5.4"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=12",
|
|
67
|
+
"npm": ">=6"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"node_modules/jwa": {
|
|
71
|
+
"version": "1.4.1",
|
|
72
|
+
"license": "MIT",
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"buffer-equal-constant-time": "1.0.1",
|
|
75
|
+
"ecdsa-sig-formatter": "1.0.11",
|
|
76
|
+
"safe-buffer": "^5.0.1"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"node_modules/jws": {
|
|
80
|
+
"version": "3.2.2",
|
|
81
|
+
"license": "MIT",
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"jwa": "^1.4.1",
|
|
84
|
+
"safe-buffer": "^5.0.1"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"node_modules/jwt": {
|
|
88
|
+
"version": "0.2.0",
|
|
89
|
+
"dependencies": {
|
|
90
|
+
"node-base64": "0.0.1"
|
|
91
|
+
},
|
|
92
|
+
"engines": {
|
|
93
|
+
"node": "*"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"node_modules/lodash.includes": {
|
|
97
|
+
"version": "4.3.0",
|
|
98
|
+
"license": "MIT"
|
|
99
|
+
},
|
|
100
|
+
"node_modules/lodash.isboolean": {
|
|
101
|
+
"version": "3.0.3",
|
|
102
|
+
"license": "MIT"
|
|
103
|
+
},
|
|
104
|
+
"node_modules/lodash.isinteger": {
|
|
105
|
+
"version": "4.0.4",
|
|
106
|
+
"license": "MIT"
|
|
107
|
+
},
|
|
108
|
+
"node_modules/lodash.isnumber": {
|
|
109
|
+
"version": "3.0.3",
|
|
110
|
+
"license": "MIT"
|
|
111
|
+
},
|
|
112
|
+
"node_modules/lodash.isplainobject": {
|
|
113
|
+
"version": "4.0.6",
|
|
114
|
+
"license": "MIT"
|
|
115
|
+
},
|
|
116
|
+
"node_modules/lodash.isstring": {
|
|
117
|
+
"version": "4.0.1",
|
|
118
|
+
"license": "MIT"
|
|
119
|
+
},
|
|
120
|
+
"node_modules/lodash.once": {
|
|
121
|
+
"version": "4.1.1",
|
|
122
|
+
"license": "MIT"
|
|
123
|
+
},
|
|
124
|
+
"node_modules/ms": {
|
|
125
|
+
"version": "2.1.3",
|
|
126
|
+
"license": "MIT"
|
|
127
|
+
},
|
|
128
|
+
"node_modules/node-base64": {
|
|
129
|
+
"version": "0.0.1",
|
|
130
|
+
"hasInstallScript": true
|
|
131
|
+
},
|
|
132
|
+
"node_modules/safe-buffer": {
|
|
133
|
+
"version": "5.2.1",
|
|
134
|
+
"funding": [
|
|
135
|
+
{
|
|
136
|
+
"type": "github",
|
|
137
|
+
"url": "https://github.com/sponsors/feross"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"type": "patreon",
|
|
141
|
+
"url": "https://www.patreon.com/feross"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"type": "consulting",
|
|
145
|
+
"url": "https://feross.org/support"
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"license": "MIT"
|
|
149
|
+
},
|
|
150
|
+
"node_modules/semver": {
|
|
151
|
+
"version": "7.6.3",
|
|
152
|
+
"license": "ISC",
|
|
153
|
+
"bin": {
|
|
154
|
+
"semver": "bin/semver.js"
|
|
155
|
+
},
|
|
156
|
+
"engines": {
|
|
157
|
+
"node": ">=10"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"node_modules/typescript": {
|
|
161
|
+
"version": "5.6.3",
|
|
162
|
+
"license": "Apache-2.0",
|
|
163
|
+
"peer": true,
|
|
164
|
+
"bin": {
|
|
165
|
+
"tsc": "bin/tsc",
|
|
166
|
+
"tsserver": "bin/tsserver"
|
|
167
|
+
},
|
|
168
|
+
"engines": {
|
|
169
|
+
"node": ">=14.17"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"node_modules/undici-types": {
|
|
173
|
+
"version": "6.19.8",
|
|
174
|
+
"license": "MIT"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
package/example/package.json
CHANGED
package/example/route.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Router from "../src/
|
|
1
|
+
import Router from "../src/route";
|
|
2
2
|
|
|
3
3
|
const route = new Router();
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ const s = () =>{
|
|
|
10
10
|
console.log('s')
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
route.get("/register", h,(xl) => {
|
|
13
|
+
route.get("/register/:id", h,(xl) => {
|
|
14
14
|
return xl.text("from register user");
|
|
15
15
|
})
|
|
16
16
|
|
package/example/tester.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Diesel from "../dist/main";
|
|
2
2
|
import jwt from "jsonwebtoken";
|
|
3
3
|
|
|
4
4
|
const app = new Diesel();
|
|
5
5
|
const secret = "secret";
|
|
6
6
|
|
|
7
|
+
// app.filter().permitAll().require()
|
|
8
|
+
|
|
7
9
|
app.get("/r", (xl) => {
|
|
8
10
|
return xl.html(`${import.meta.dir}/index.html`);
|
|
9
11
|
});
|
package/package.json
CHANGED
package/src/handleRequest.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { Server } from "bun";
|
|
2
2
|
import createCtx from "./ctx";
|
|
3
3
|
import type { ContextType, corsT, DieselT, handlerFunction, middlewareFunc, RouteCache, RouteHandlerT } from "./types";
|
|
4
|
-
import { binaryS } from "./utils";
|
|
5
4
|
|
|
6
|
-
// const routeCache:RouteCache = {}
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
const routeCache:RouteCache = {}
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
export default async function handleRequest(req: Request, server: Server, url: URL, diesel: DieselT): Promise<Response> {
|
|
9
|
+
|
|
10
|
+
const ctx: ContextType = createCtx(req, server, url);
|
|
11
11
|
|
|
12
12
|
// Try to find the route handler in the trie
|
|
13
|
-
let routeHandler
|
|
13
|
+
let routeHandler: RouteHandlerT | undefined = diesel.trie.search(url.pathname, req.method);
|
|
14
|
+
|
|
14
15
|
// if(routeCache[url.pathname+req.method]) {
|
|
15
16
|
// routeHandler = routeCache[url.pathname+req.method]
|
|
16
17
|
// } else {
|
|
@@ -19,99 +20,94 @@ export default async function handleRequest(req:Request, server:Server ,url:URL,
|
|
|
19
20
|
// }
|
|
20
21
|
|
|
21
22
|
// Early return if route or method is not found
|
|
22
|
-
if (!routeHandler ||
|
|
23
|
-
return new Response(`Route not found for ${url.pathname}`, {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (routeHandler.method !== req.method) {
|
|
27
|
-
return new Response("Method not allowed", { status: 405 })
|
|
23
|
+
if (!routeHandler || routeHandler.method !== req.method) {
|
|
24
|
+
return new Response(routeHandler ? "Method not allowed" : `Route not found for ${url.pathname}`, {
|
|
25
|
+
status: routeHandler ? 405 : 404,
|
|
26
|
+
});
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
// If the route is dynamic, we only set routePattern if necessary
|
|
31
30
|
if (routeHandler.isDynamic) req.routePattern = routeHandler.path;
|
|
32
31
|
|
|
32
|
+
// cors execution
|
|
33
33
|
if (diesel.corsConfig) {
|
|
34
34
|
const corsResult = await applyCors(req, ctx, diesel.corsConfig)
|
|
35
|
-
if(corsResult) return corsResult;
|
|
35
|
+
if (corsResult) return corsResult;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// OnReq hook 1
|
|
39
|
-
if (diesel.hasOnReqHook && diesel.hooks.onRequest)
|
|
40
|
-
diesel.hooks.onRequest(ctx,server)
|
|
41
|
-
}
|
|
39
|
+
if (diesel.hasOnReqHook && diesel.hooks.onRequest) diesel.hooks.onRequest(ctx, server)
|
|
42
40
|
|
|
41
|
+
// filter applying
|
|
42
|
+
if (diesel.hasFilterEnabled) {
|
|
43
43
|
|
|
44
|
-
if (diesel.filters.length > 0) {
|
|
45
44
|
const path = req.routePattern ?? url.pathname
|
|
46
45
|
const hasRoute = diesel.filters.includes(path)
|
|
47
|
-
|
|
48
46
|
if (hasRoute === false) {
|
|
49
47
|
if (diesel.filterFunction) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
try {
|
|
49
|
+
const filterResult = await diesel.filterFunction(ctx, server)
|
|
50
|
+
if (filterResult) return filterResult
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("Error in filterFunction:", error);
|
|
53
|
+
return new Response(JSON.stringify({
|
|
54
|
+
message: "Internal Server Error"
|
|
55
|
+
}), { status: 500 });
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
54
58
|
return new Response(JSON.stringify({
|
|
55
|
-
message:"Authentication required"
|
|
56
|
-
}),{status:400})
|
|
59
|
+
message: "Authentication required"
|
|
60
|
+
}), { status: 400 })
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
// middleware execution
|
|
62
66
|
if (diesel.hasMiddleware) {
|
|
63
|
-
const middlewares
|
|
67
|
+
const middlewares = [
|
|
64
68
|
...diesel.globalMiddlewares,
|
|
65
69
|
...diesel.middlewares.get(url.pathname) || []
|
|
66
|
-
]
|
|
70
|
+
] as middlewareFunc[]
|
|
67
71
|
|
|
68
72
|
for (const middleware of middlewares) {
|
|
69
|
-
const result = await middleware(ctx,server);
|
|
70
|
-
if (result) return result;
|
|
73
|
+
const result = await middleware(ctx, server);
|
|
74
|
+
if (result) return result;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
79
|
// Run preHandler hooks 2
|
|
78
80
|
if (diesel.hasPreHandlerHook && diesel.hooks.preHandler) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const Hookresult = await diesel.hooks.preHandler(ctx);
|
|
82
|
+
if (Hookresult) return Hookresult;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const preHandlerResult = diesel.hasPreHandlerHook ? await diesel.hooks.preHandler?.(ctx) : null;
|
|
86
|
+
if (preHandlerResult) return preHandlerResult;
|
|
83
87
|
|
|
84
88
|
// Finally, execute the route handler and return its result
|
|
85
89
|
try {
|
|
86
|
-
const result = await routeHandler.handler(ctx) as Response | null | void
|
|
90
|
+
const result = await routeHandler.handler(ctx) as Response | null | void;
|
|
87
91
|
|
|
88
92
|
// 3. run the postHandler hooks
|
|
89
93
|
if (diesel.hasPostHandlerHook && diesel.hooks.postHandler) {
|
|
90
|
-
await diesel.hooks.postHandler(ctx)
|
|
94
|
+
await diesel.hooks.postHandler(ctx)
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
// 4. Run onSend hooks before sending the response
|
|
94
98
|
if (diesel.hasOnSendHook && diesel.hooks.onSend) {
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
const hookResponse = await diesel.hooks.onSend(ctx, result);
|
|
100
|
+
if (hookResponse) return hookResponse
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
return result ??
|
|
103
|
+
return result ?? new Response("No response from handler", { status: 204 })
|
|
100
104
|
} catch (error) {
|
|
101
105
|
return new Response("Internal Server Error", { status: 500 });
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
// middleware execution
|
|
106
|
-
async function executeMiddleware(middlewares: middlewareFunc[], ctx: ContextType, server: Server): Promise<Response | null | void> {
|
|
107
|
-
for (const middleware of middlewares) {
|
|
108
|
-
const result = await middleware(ctx,server);
|
|
109
|
-
if (result) return result;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
109
|
|
|
114
|
-
async function applyCors(req:Request,ctx:ContextType,config:corsT={})
|
|
110
|
+
async function applyCors(req: Request, ctx: ContextType, config: corsT = {}): Promise<Response | null> {
|
|
115
111
|
const origin = req.headers.get('origin') ?? '*'
|
|
116
112
|
const allowedOrigins = config?.origin
|
|
117
113
|
const allowedHeaders = config?.allowedHeaders ?? ["Content-Type", "Authorization"]
|
|
@@ -119,27 +115,27 @@ async function applyCors(req:Request,ctx:ContextType,config:corsT={}) :Promise<R
|
|
|
119
115
|
const allowedCredentials = config?.credentials ?? false
|
|
120
116
|
const exposedHeaders = config?.exposedHeaders ?? []
|
|
121
117
|
|
|
122
|
-
ctx.setHeader('Access-Control-Allow-Methods',allowedMethods)
|
|
118
|
+
ctx.setHeader('Access-Control-Allow-Methods', allowedMethods)
|
|
123
119
|
ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders);
|
|
124
120
|
ctx.setHeader("Access-Control-Allow-Credentials", allowedCredentials);
|
|
125
121
|
if (exposedHeaders.length) {
|
|
126
122
|
ctx.setHeader("Access-Control-Expose-Headers", exposedHeaders);
|
|
127
123
|
}
|
|
128
124
|
|
|
129
|
-
if (allowedOrigins === '*'){
|
|
125
|
+
if (allowedOrigins === '*') {
|
|
130
126
|
ctx.setHeader("Access-Control-Allow-Origin", "*")
|
|
131
|
-
} else if (Array.isArray(allowedOrigins)){
|
|
127
|
+
} else if (Array.isArray(allowedOrigins)) {
|
|
132
128
|
if (origin && allowedOrigins.includes(origin)) {
|
|
133
|
-
ctx.setHeader("Access-Control-Allow-Origin",origin)
|
|
134
|
-
} else if(allowedOrigins.includes('*')){
|
|
135
|
-
ctx.setHeader("Access-Control-Allow-Origin",'*')
|
|
129
|
+
ctx.setHeader("Access-Control-Allow-Origin", origin)
|
|
130
|
+
} else if (allowedOrigins.includes('*')) {
|
|
131
|
+
ctx.setHeader("Access-Control-Allow-Origin", '*')
|
|
136
132
|
}
|
|
137
133
|
else {
|
|
138
134
|
return ctx.status(403).json({ message: "CORS not allowed" })
|
|
139
135
|
}
|
|
140
|
-
} else if (typeof allowedOrigins === 'string'){
|
|
141
|
-
if(origin === allowedOrigins){
|
|
142
|
-
ctx.setHeader("Access-Control-Allow-Origin",origin)
|
|
136
|
+
} else if (typeof allowedOrigins === 'string') {
|
|
137
|
+
if (origin === allowedOrigins) {
|
|
138
|
+
ctx.setHeader("Access-Control-Allow-Origin", origin)
|
|
143
139
|
}
|
|
144
140
|
else {
|
|
145
141
|
return ctx.status(403).json({ message: "CORS not allowed" });
|
|
@@ -148,10 +144,10 @@ async function applyCors(req:Request,ctx:ContextType,config:corsT={}) :Promise<R
|
|
|
148
144
|
return ctx.status(403).json({ message: "CORS not allowed" })
|
|
149
145
|
}
|
|
150
146
|
|
|
151
|
-
ctx.setHeader("Access-Control-Allow-Origin",origin)
|
|
147
|
+
ctx.setHeader("Access-Control-Allow-Origin", origin)
|
|
152
148
|
|
|
153
149
|
if (req.method === 'OPTIONS') {
|
|
154
|
-
ctx.setHeader('Access-Control-Max-Age','86400')
|
|
150
|
+
ctx.setHeader('Access-Control-Max-Age', '86400')
|
|
155
151
|
return ctx.status(204).text('')
|
|
156
152
|
}
|
|
157
153
|
|
package/src/main.ts
CHANGED
|
@@ -3,6 +3,7 @@ import handleRequest from "./handleRequest.js";
|
|
|
3
3
|
import rateLimit from "./utils.js";
|
|
4
4
|
import {
|
|
5
5
|
corsT,
|
|
6
|
+
DieselT,
|
|
6
7
|
FilterMethods,
|
|
7
8
|
HookFunction,
|
|
8
9
|
HookType,
|
|
@@ -16,9 +17,8 @@ import {
|
|
|
16
17
|
import { Server } from "bun";
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
export default class Diesel {
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
class Diesel {
|
|
22
22
|
routes : string[] | undefined
|
|
23
23
|
globalMiddlewares: middlewareFunc[]
|
|
24
24
|
middlewares: Map<string, middlewareFunc[]>;
|
|
@@ -32,11 +32,11 @@ class Diesel {
|
|
|
32
32
|
corsConfig: corsT
|
|
33
33
|
filters: string[]
|
|
34
34
|
filterFunction : middlewareFunc | null
|
|
35
|
+
hasFilterEnabled: boolean
|
|
36
|
+
wss : WebSocket | null | undefined
|
|
35
37
|
|
|
36
38
|
constructor() {
|
|
37
39
|
this.routes = []
|
|
38
|
-
this.filters = []
|
|
39
|
-
this.filterFunction = null
|
|
40
40
|
this.globalMiddlewares = [];
|
|
41
41
|
this.middlewares = new Map();
|
|
42
42
|
this.trie = new Trie();
|
|
@@ -54,10 +54,15 @@ class Diesel {
|
|
|
54
54
|
onError: null,
|
|
55
55
|
onClose: null
|
|
56
56
|
}
|
|
57
|
+
this.filters = []
|
|
58
|
+
this.filterFunction = null
|
|
59
|
+
this.hasFilterEnabled = false
|
|
60
|
+
this.wss = null
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
|
|
60
64
|
filter() :FilterMethods {
|
|
65
|
+
this.hasFilterEnabled = true
|
|
61
66
|
|
|
62
67
|
return {
|
|
63
68
|
routeMatcher: (...routes:string[]) => {
|
|
@@ -69,17 +74,13 @@ class Diesel {
|
|
|
69
74
|
for(const route of this?.routes!){
|
|
70
75
|
this.filters.push(route)
|
|
71
76
|
}
|
|
72
|
-
// this.routes = []
|
|
73
77
|
return this.filter()
|
|
74
78
|
},
|
|
75
79
|
|
|
76
80
|
require: (fnc?:middlewareFunc) => {
|
|
77
|
-
if(
|
|
78
|
-
|
|
79
|
-
message:"Authentication required"
|
|
80
|
-
}),{status:400})
|
|
81
|
+
if (fnc) {
|
|
82
|
+
this.filterFunction = fnc
|
|
81
83
|
}
|
|
82
|
-
this.filterFunction = fnc
|
|
83
84
|
}
|
|
84
85
|
};
|
|
85
86
|
}
|
|
@@ -140,13 +141,13 @@ class Diesel {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
this.compile();
|
|
143
|
-
|
|
144
|
+
|
|
144
145
|
const options: any = {
|
|
145
146
|
port,
|
|
146
147
|
fetch: async (req: Request, server: Server) => {
|
|
147
148
|
const url = new URL(req.url);
|
|
148
149
|
try {
|
|
149
|
-
return await handleRequest(req, server, url, this);
|
|
150
|
+
return await handleRequest(req, server, url, this as DieselT);
|
|
150
151
|
} catch (error) {
|
|
151
152
|
return new Response("Internal Server Error", { status: 500 });
|
|
152
153
|
}
|
|
@@ -162,7 +163,7 @@ class Diesel {
|
|
|
162
163
|
}
|
|
163
164
|
const server = Bun.serve(options);
|
|
164
165
|
|
|
165
|
-
|
|
166
|
+
Bun?.gc(false)
|
|
166
167
|
|
|
167
168
|
if (typeof callback === "function") {
|
|
168
169
|
return callback();
|
|
@@ -188,7 +189,7 @@ class Diesel {
|
|
|
188
189
|
throw new Error("handler parameter should be a instance of router object", handlerInstance)
|
|
189
190
|
}
|
|
190
191
|
const routeEntries: [string, RouteNodeType][] = Object.entries(handlerInstance.trie.root.children) as [string, RouteNodeType][];
|
|
191
|
-
|
|
192
|
+
|
|
192
193
|
handlerInstance.trie.root.subMiddlewares.forEach((middleware: middlewareFunc[], path: string) => {
|
|
193
194
|
if (!this.middlewares.has(pathPrefix + path)) {
|
|
194
195
|
this.middlewares.set(pathPrefix + path, []);
|
|
@@ -210,7 +211,7 @@ class Diesel {
|
|
|
210
211
|
handlerInstance.trie = new Trie();
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
|
|
214
|
+
addRoute(
|
|
214
215
|
method: HttpMethod,
|
|
215
216
|
path: string,
|
|
216
217
|
handlers: handlerFunction[]
|
|
@@ -254,7 +255,7 @@ class Diesel {
|
|
|
254
255
|
}
|
|
255
256
|
return
|
|
256
257
|
}
|
|
257
|
-
|
|
258
|
+
|
|
258
259
|
const path: string = pathORHandler as string;
|
|
259
260
|
|
|
260
261
|
if (!this.middlewares.has(path)) {
|
|
@@ -272,7 +273,7 @@ class Diesel {
|
|
|
272
273
|
path: string,
|
|
273
274
|
...handlers: handlerFunction[]
|
|
274
275
|
) : this {
|
|
275
|
-
this
|
|
276
|
+
this.addRoute("GET", path, handlers);
|
|
276
277
|
return this
|
|
277
278
|
}
|
|
278
279
|
|
|
@@ -280,12 +281,12 @@ class Diesel {
|
|
|
280
281
|
path: string,
|
|
281
282
|
...handlers: handlerFunction[]
|
|
282
283
|
): this {
|
|
283
|
-
this
|
|
284
|
+
this.addRoute("POST", path, handlers);
|
|
284
285
|
return this
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
put(path: string, ...handlers: handlerFunction[]) : this{
|
|
288
|
-
this
|
|
289
|
+
this.addRoute("PUT", path, handlers);
|
|
289
290
|
return this
|
|
290
291
|
}
|
|
291
292
|
|
|
@@ -293,7 +294,7 @@ class Diesel {
|
|
|
293
294
|
path: string,
|
|
294
295
|
...handlers: handlerFunction[]
|
|
295
296
|
) : this {
|
|
296
|
-
this
|
|
297
|
+
this.addRoute("PATCH", path, handlers);
|
|
297
298
|
return this
|
|
298
299
|
}
|
|
299
300
|
|
|
@@ -301,14 +302,12 @@ class Diesel {
|
|
|
301
302
|
path: any,
|
|
302
303
|
...handlers: handlerFunction[]
|
|
303
304
|
) : this {
|
|
304
|
-
this
|
|
305
|
+
this.addRoute("DELETE", path, handlers);
|
|
305
306
|
return this;
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
309
|
|
|
309
310
|
|
|
310
311
|
export {
|
|
311
|
-
Diesel,
|
|
312
312
|
rateLimit,
|
|
313
|
-
// filter
|
|
314
313
|
}
|
package/src/route.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Diesel from "./main";
|
|
2
|
+
import type { handlerFunction, HttpMethod } from "./types";
|
|
3
|
+
|
|
4
|
+
class Router extends Diesel {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// #addRoute(method:HttpMethod, path:string, handlers:handlerFunction[]) {
|
|
10
|
+
// if (!this.trie.root.subMiddlewares.has(path)) {
|
|
11
|
+
// this.trie.root.subMiddlewares.set(path,[])
|
|
12
|
+
// }
|
|
13
|
+
// const middlewareHandlers : handlerFunction[]= handlers.slice(0, -1);
|
|
14
|
+
|
|
15
|
+
// const currentMiddlewares = this.trie.root.subMiddlewares.get(path)
|
|
16
|
+
|
|
17
|
+
// middlewareHandlers.forEach((midl:handlerFunction) => {
|
|
18
|
+
// if (!currentMiddlewares?.includes(midl)) {
|
|
19
|
+
// currentMiddlewares?.push(midl)
|
|
20
|
+
// }
|
|
21
|
+
// })
|
|
22
|
+
|
|
23
|
+
// // if (!this.trie.root.subMiddlewares.get(path).includes(...middlewareHandlers)) {
|
|
24
|
+
// // this.trie.root.subMiddlewares.get(path).push(...middlewareHandlers)
|
|
25
|
+
// // }
|
|
26
|
+
|
|
27
|
+
// const handler : handlerFunction = handlers[handlers.length - 1];
|
|
28
|
+
// this.trie.insert(path, { handler, method });
|
|
29
|
+
// }
|
|
30
|
+
get(path:string, ...handlers:handlerFunction[]) {
|
|
31
|
+
this.addRoute("GET", path, handlers);
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
post(path:string, ...handlers:handlerFunction[]) {
|
|
36
|
+
this.addRoute("POST", path, handlers);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
put(path:string, ...handlers:handlerFunction[]) {
|
|
41
|
+
this.addRoute("PUT", path, handlers);
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
patch(path:string, ...handlers:handlerFunction[]) {
|
|
46
|
+
this.addRoute("PATCH", path, handlers);
|
|
47
|
+
return this
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
delete(path:string, ...handlers:handlerFunction[]) {
|
|
51
|
+
this.addRoute("DELETE", path, handlers);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default Router;
|
package/src/trie.ts
CHANGED
|
@@ -59,9 +59,11 @@ class TrieNode {
|
|
|
59
59
|
node = node.children[key];
|
|
60
60
|
// Set dynamic route information if applicable
|
|
61
61
|
node.isDynamic = isDynamic;
|
|
62
|
-
node.pattern = segment;
|
|
62
|
+
node.pattern = segment;
|
|
63
|
+
node.method.push(route.method)
|
|
64
|
+
node.handler.push(route.handler)
|
|
65
|
+
node.path = path; // Store the actual pattern like ':id'
|
|
63
66
|
}
|
|
64
|
-
|
|
65
67
|
// After looping through the entire path, assign route details
|
|
66
68
|
node.isEndOfWord = true;
|
|
67
69
|
node.method.push(route.method);
|
|
@@ -95,7 +97,7 @@ class TrieNode {
|
|
|
95
97
|
node = node.children[key];
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
|
-
|
|
100
|
+
|
|
99
101
|
// Method matching
|
|
100
102
|
let routeMethodIndex = node.method.indexOf(method); // More efficient method match
|
|
101
103
|
if (routeMethodIndex !== -1) {
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Server } from "bun";
|
|
|
2
2
|
|
|
3
3
|
export type listenCalllBackType = () => void;
|
|
4
4
|
export type handlerFunction = (ctx: ContextType, server?: Server) => Response | Promise<Response | null | void>;
|
|
5
|
-
export type middlewareFunc = (ctx:ContextType,server?:Server) => void | Response | Promise<Response>
|
|
5
|
+
export type middlewareFunc = (ctx:ContextType,server?:Server | undefined) => void | Response | Promise<Response>
|
|
6
6
|
export type HookFunction = (ctx: ContextType, result?: Response | null | void, server?: Server) => Response | Promise<Response | null | void>
|
|
7
7
|
// export type onSendHookFunc = (result?: Response | null | void, ctx?:ContextType) => Response | Promise<Response | null | void>
|
|
8
8
|
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD";
|
|
@@ -86,7 +86,8 @@ export interface DieselT {
|
|
|
86
86
|
onSend: ((ctx?: ContextType, result?: Response | null | void, serer?: Server) => Promise<Response | void | null>) | null;
|
|
87
87
|
};
|
|
88
88
|
filters: string[]
|
|
89
|
-
|
|
89
|
+
hasFilterEnabled:boolean
|
|
90
|
+
filterFunction: (ctx:ContextType,serer?:Server) => void | Response | Promise<Response|void>
|
|
90
91
|
corsConfig: corsT | null
|
|
91
92
|
globalMiddlewares: Array<(ctx: ContextType, serer?: Server) => void | Promise<Response | null | void>>
|
|
92
93
|
middlewares: Map<string, Array<(ctx: ContextType, serer?: Server) => void | Promise<Response | null | void>>>
|
package/tsconfig.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES6", // Specify ECMAScript target version
|
|
4
|
-
"module": "
|
|
5
|
-
"outDir": "./
|
|
4
|
+
"module": "ESNext", // Specify module code generation
|
|
5
|
+
"outDir": "./main", // Redirect output structure to the 'dist' directory
|
|
6
6
|
"rootDir": "./src",
|
|
7
7
|
"declaration": true,
|
|
8
|
-
"emitDeclarationOnly": true, // Specify the root directory of input files
|
|
8
|
+
// "emitDeclarationOnly": true, // Specify the root directory of input files
|
|
9
9
|
"strict": true, // Enable all strict type-checking options
|
|
10
10
|
"esModuleInterop": true, // Enables emit interoperability between CommonJS and ES Modules
|
|
11
11
|
"skipLibCheck": true, // Skip type checking of declaration files
|
package/src/router.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import {Diesel} from "./main";
|
|
2
|
-
import type { handlerFunction, HttpMethod } from "./types";
|
|
3
|
-
|
|
4
|
-
class Router extends Diesel {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
}
|
|
8
|
-
#addRoute(method:HttpMethod, path:string, handlers:handlerFunction[]) {
|
|
9
|
-
if (!this.trie.root.subMiddlewares.has(path)) {
|
|
10
|
-
this.trie.root.subMiddlewares.set(path,[])
|
|
11
|
-
}
|
|
12
|
-
const middlewareHandlers : handlerFunction[]= handlers.slice(0, -1);
|
|
13
|
-
|
|
14
|
-
const currentMiddlewares = this.trie.root.subMiddlewares.get(path)
|
|
15
|
-
|
|
16
|
-
middlewareHandlers.forEach((midl:handlerFunction) => {
|
|
17
|
-
if (!currentMiddlewares?.includes(midl)) {
|
|
18
|
-
currentMiddlewares?.push(midl)
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
// if (!this.trie.root.subMiddlewares.get(path).includes(...middlewareHandlers)) {
|
|
23
|
-
// this.trie.root.subMiddlewares.get(path).push(...middlewareHandlers)
|
|
24
|
-
// }
|
|
25
|
-
|
|
26
|
-
const handler : handlerFunction = handlers[handlers.length - 1];
|
|
27
|
-
this.trie.insert(path, { handler, method });
|
|
28
|
-
}
|
|
29
|
-
get(path:string, ...handlers:handlerFunction[]) {
|
|
30
|
-
this.#addRoute("GET", path, handlers);
|
|
31
|
-
return this
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
post(path:string, ...handlers:handlerFunction[]) {
|
|
35
|
-
this.#addRoute("POST", path, handlers);
|
|
36
|
-
return this;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
put(path:string, ...handlers:handlerFunction[]) {
|
|
40
|
-
this.#addRoute("PUT", path, handlers);
|
|
41
|
-
return this
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
patch(path:string, ...handlers:handlerFunction[]) {
|
|
45
|
-
this.#addRoute("PATCH", path, handlers);
|
|
46
|
-
return this
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
delete(path : string, ...handlers:handlerFunction[]) {
|
|
50
|
-
this.#addRoute("DELETE", path, handlers);
|
|
51
|
-
return this;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export default Router;
|