diesel-core 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ /.vscode
2
+ /node_modules
3
+ ./dist
4
+
5
+ *.env
6
+ .env
7
+ .env.*
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "singleQuote": false,
3
+ "bracketSpacing": true,
4
+ "tabWidth": 2,
5
+ "semi": true,
6
+ "trailingComma": "es5"
7
+ }
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "diesel-core",
3
+ "version": "0.0.1",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": [],
9
+ "author": "",
10
+ "license": "ISC",
11
+ "description": ""
12
+ }
package/src/ctx.js ADDED
@@ -0,0 +1,192 @@
1
+ export default function createCtx(req, url) {
2
+ let headers = {};
3
+ let settedValue = {};
4
+ let isAuthenticated = false;
5
+ let parsedQuery = null;
6
+ let parsedCookie = null;
7
+ let parsedParams = null;
8
+ let parsedBody= null
9
+
10
+ return {
11
+ req,
12
+ url,
13
+ next: () => {},
14
+
15
+ async body() {
16
+ if (!parsedBody) {
17
+ parsedBody = await parseBody(req)
18
+ return parsedBody;
19
+ }
20
+ return parsedBody;
21
+ },
22
+ setHeader(key, value) {
23
+ headers[key] = value;
24
+ },
25
+
26
+ set(key, value) {
27
+ settedValue[key] = value;
28
+ },
29
+
30
+ get(key) {
31
+ return settedValue[key];
32
+ },
33
+
34
+ setAuth(authStatus) {
35
+ isAuthenticated = authStatus;
36
+ },
37
+
38
+ getAuth() {
39
+ return isAuthenticated;
40
+ },
41
+
42
+ text(data, status = 200) {
43
+ return new Response(data, {
44
+ status,
45
+ headers: headers,
46
+ });
47
+ },
48
+
49
+ json(data, status = 200) {
50
+ return new Response(JSON.stringify(data), {
51
+ status,
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ ...headers,
55
+ },
56
+ });
57
+ },
58
+
59
+ html(filepath) {
60
+ return new Response(Bun.file(filepath), {
61
+ status: 200,
62
+ headers: {
63
+ ...headers,
64
+ },
65
+ });
66
+ },
67
+ file(filePath) {
68
+ return new Response(Bun.file(filePath), {
69
+ status: 200,
70
+ headers: {
71
+ ...headers,
72
+ },
73
+ });
74
+ },
75
+
76
+ redirect(path, status = 302) {
77
+ return new Response(null, {
78
+ status,
79
+ headers: {
80
+ Location: path,
81
+ ...headers,
82
+ },
83
+ });
84
+ },
85
+
86
+ getParams(props) {
87
+ if (!parsedParams) {
88
+ parsedParams = extractDynamicParams(req.routePattern, url.pathname);
89
+ }
90
+ return props ? parsedParams[props] : parsedParams;
91
+ },
92
+
93
+ getQuery(props) {
94
+ if (!parsedQuery) {
95
+ parsedQuery = Object.fromEntries(url.searchParams.entries());
96
+ }
97
+ return props ? parsedQuery[props] : parsedQuery;
98
+ },
99
+
100
+ cookie(name, value, options = {}) {
101
+ let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
102
+
103
+ // Add options to cookie string (e.g., expiration, path, HttpOnly, etc.)
104
+ if (options.maxAge) cookieString += `; Max-Age=${options.maxAge}`;
105
+ if (options.expires) cookieString += `; Expires=${options.expires.toUTCString()}`;
106
+ if (options.path) cookieString += `; Path=${options.path}`;
107
+ if (options.domain) cookieString += `; Domain=${options.domain}`;
108
+ if (options.secure) cookieString += `; Secure`;
109
+ if (options.httpOnly) cookieString += `; HttpOnly`;
110
+ if (options.sameSite) cookieString += `; SameSite=${options.sameSite}`;
111
+
112
+ if (headers["Set-Cookie"]) {
113
+ // If it's already an array, push the new cookie, otherwise convert to array
114
+ const existingCookies = Array.isArray(headers["Set-Cookie"]) ? headers["Set-Cookie"] : [headers["Set-Cookie"]];
115
+
116
+ // Add the new cookie string to the array
117
+ existingCookies.push(cookieString);
118
+
119
+ // Update Set-Cookie header
120
+ headers["Set-Cookie"] = existingCookies;
121
+ } else {
122
+ // If no cookies exist, initialize the header
123
+ headers["Set-Cookie"] = cookieString;
124
+ }
125
+ },
126
+ getCookie(cookieName) {
127
+ if (!parsedCookie) {
128
+ parsedCookie = parseCookie(req.headers.get("cookie"));
129
+ }
130
+ return cookieName ? parsedCookie[cookieName] : parsedCookie;
131
+ },
132
+ };
133
+ }
134
+
135
+ function parseCookie(header) {
136
+ const cookies = {};
137
+ if (!header) return cookies;
138
+
139
+ const cookieArray = header.split(";");
140
+ cookieArray.forEach((cookie) => {
141
+ const [cookieName, cookievalue] = cookie.trim().split("=");
142
+ cookies[cookieName] = cookievalue.split(" ")[0];
143
+ });
144
+ return cookies;
145
+ }
146
+
147
+ const extractDynamicParams = (routePattern, path) => {
148
+ const object = {};
149
+ const routeSegments = routePattern.split("/");
150
+ const [pathWithoutQuery] = path.split("?"); // Ignore the query string in the path
151
+ const pathSegments = pathWithoutQuery.split("/"); // Re-split after removing query
152
+
153
+ if (routeSegments.length !== pathSegments.length) {
154
+ return null; // Path doesn't match the pattern
155
+ }
156
+
157
+ routeSegments.forEach((segment, index) => {
158
+ if (segment.startsWith(":")) {
159
+ const dynamicKey = segment.slice(1); // Remove ':' to get the key name
160
+ object[dynamicKey] = pathSegments[index]; // Map the path segment to the key
161
+ }
162
+ });
163
+
164
+ return object;
165
+ };
166
+
167
+ async function parseBody(req) {
168
+ const contentType = req.headers.get("Content-Type");
169
+ if (contentType.includes("application/json")) {
170
+ try {
171
+ return await req.json();
172
+ } catch (error) {
173
+ return new Response({ error: "Invalid JSON format" });
174
+ }
175
+ } else if (contentType.startsWith("application/x-www-form-urlencoded")) {
176
+ const body = await req.text();
177
+ return Object.fromEntries(new URLSearchParams(body));
178
+ } else if (contentType.startsWith("multipart/form-data")) {
179
+ const formData = await req.formData();
180
+ return formDataToObject(formData);
181
+ } else {
182
+ return new Response({ error: "unknown request body" });
183
+ }
184
+ }
185
+
186
+ function formDataToObject(formData) {
187
+ const obj = {};
188
+ for (const [key, value] of formData.entries()) {
189
+ obj[key] = value;
190
+ }
191
+ return obj;
192
+ }
@@ -0,0 +1,63 @@
1
+ import createCtx from "./ctx";
2
+
3
+ export default async function handleRequest(req, url, diesel) {
4
+ const { pathname } = url;
5
+ const { method } = req;
6
+
7
+ // Try to find the route handler in the trie
8
+ const routeHandler = diesel.trie.search(pathname, method);
9
+
10
+ // Early return if route or method is not found
11
+ if (!routeHandler || !routeHandler.handler) return responseNotFound(pathname);
12
+ if (routeHandler.method !== method) return responseMethodNotAllowed();
13
+
14
+ // If the route is dynamic, we only set routePattern if necessary
15
+ if (routeHandler.isDynamic) req.routePattern = routeHandler.path;
16
+
17
+ const ctx = createCtx(req, url);
18
+
19
+ if (diesel.hasMiddleware) {
20
+ const middlewares = [
21
+ ...diesel.globalMiddlewares,
22
+ ...(diesel.middlewares.get(pathname) || [])
23
+ ];
24
+
25
+ const middlewareResult = await executeMiddleware(middlewares, ctx);
26
+ if (middlewareResult) return middlewareResult;
27
+
28
+ }
29
+
30
+
31
+ // Finally, execute the route handler and return its result
32
+ try {
33
+ const result = await routeHandler.handler(ctx);
34
+ return result ?? responseNoHandler();
35
+ } catch (error) {
36
+ return responseServerError();
37
+ }
38
+ }
39
+
40
+ // Optimized middleware execution: stops as soon as a middleware returns a response
41
+ async function executeMiddleware(middlewares, ctx) {
42
+ for (const middleware of middlewares) {
43
+ const result = await middleware(ctx);
44
+ if (result) return result; // Early exit if middleware returns a result
45
+ }
46
+ }
47
+
48
+ // Reused response functions for better organization and clarity
49
+ function responseNotFound(path) {
50
+ return new Response(`Route not found for ${path}`, { status: 404 });
51
+ }
52
+
53
+ function responseMethodNotAllowed() {
54
+ return new Response("Method not allowed", { status: 405 });
55
+ }
56
+
57
+ function responseNoHandler() {
58
+ return new Response("No response from handler", { status: 204 });
59
+ }
60
+
61
+ function responseServerError() {
62
+ return new Response("Internal Server Error", { status: 500 });
63
+ }
package/src/router.js ADDED
@@ -0,0 +1,53 @@
1
+ import diesel from "./server";
2
+
3
+ class Router extends diesel{
4
+ constructor(){
5
+ super()
6
+ }
7
+ #addRoute(method,path,handlers){
8
+
9
+ const middlewareHandlers = handlers.slice(0, -1);
10
+
11
+ if (!this.middlewares.has(path)) {
12
+ this.middlewares.set(path,[])
13
+ }
14
+ if (path === '/') {
15
+ middlewareHandlers.forEach(midlleware => {
16
+ if(!this.globalMiddlewares.includes(midlleware)){
17
+ this.globalMiddlewares.push(midlleware)
18
+ }
19
+ })
20
+ } else {
21
+ if (!this.middlewares.get(path).includes(...middlewareHandlers)) {
22
+ this.middlewares.get(path).push(...middlewareHandlers);
23
+ }
24
+ }
25
+
26
+ const handler = handlers[handlers.length-1]
27
+ this.trie.insert(path,{handler,method})
28
+
29
+ }
30
+ get(path,...handlers){
31
+ return this.#addRoute("GET",path,handlers)
32
+ }
33
+
34
+ post(path,...handlers){
35
+ return this.#addRoute("POST",path,handlers)
36
+ }
37
+
38
+ put(path,...handlers){
39
+ return this.#addRoute("PUT",path,handlers)
40
+ }
41
+
42
+ patch(path,...handlers){
43
+ if (handlers.length>0) {
44
+ return this.#addRoute("PATCH",path,handlers)
45
+ }
46
+ }
47
+
48
+ delete(path,...handlers){
49
+ return this.#addRoute("DELETE",path,handlers)
50
+ }
51
+ }
52
+
53
+ export default Router
package/src/server.js ADDED
@@ -0,0 +1,131 @@
1
+ import {serve} from 'bun'
2
+ import Trie from './trie.js';
3
+ import handleRequest from './handleRequest.js'
4
+
5
+ class diesel {
6
+ constructor(){
7
+ this.routes = new Map()
8
+ this.globalMiddlewares = [];
9
+ this.middlewares = new Map()
10
+ this.trie = new Trie()
11
+ this.hasMiddleware = false;
12
+ }
13
+
14
+ compile (){
15
+ if (this.globalMiddlewares.length > 0) {
16
+ this.hasMiddleware = true;
17
+ }
18
+ for (const [path, middlewares] of this.middlewares.entries()) {
19
+ if (middlewares.length > 0) {
20
+ this.hasMiddleware = true;
21
+ break;
22
+ }
23
+ }
24
+ }
25
+
26
+ listen(port,callback){
27
+ this.compile()
28
+ const server = serve({
29
+ port,
30
+ fetch: (req) => {
31
+ const url = new URL(req.url)
32
+ return handleRequest(req,url,this)
33
+ },
34
+ onClose() {
35
+ console.log("Server is shutting down...");
36
+ }
37
+ });
38
+ if (typeof callback === 'function') {
39
+ return callback();
40
+ }
41
+ console.log(`Server is running on http://localhost:${port}`);
42
+ return server;
43
+ }
44
+
45
+ register(pathPrefix,handlerInstance){
46
+ const routeEntries = Object.entries(handlerInstance.trie.root.children);
47
+ handlerInstance.trie.root.subMiddlewares.forEach((middleware,path)=>{
48
+ if (!this.middlewares.has(pathPrefix+path)) {
49
+ this.middlewares.set(pathPrefix+path, []);
50
+ }
51
+ if (!this.middlewares.get(pathPrefix+path).includes(...middleware)) {
52
+ this.middlewares.get(pathPrefix+path).push(...middleware);
53
+ }
54
+ })
55
+ for (const [routeKey, routeNode] of routeEntries) {
56
+ const fullpath = pathPrefix + routeNode?.path;
57
+ const routeHandler = routeNode.handler[0];
58
+ const httpMethod = routeNode.method[0];
59
+ this.trie.insert(fullpath, { handler: routeHandler, method: httpMethod });
60
+ }
61
+ handlerInstance.trie = new Trie();
62
+ }
63
+
64
+ #addRoute(method,path,handlers){
65
+
66
+ const middlewareHandlers = handlers.slice(0, -1);
67
+
68
+ if (!this.middlewares.has(path)) {
69
+ this.middlewares.set(path,[])
70
+ }
71
+ if (path === '/') {
72
+ middlewareHandlers.forEach(midlleware => {
73
+ if(!this.globalMiddlewares.includes(midlleware)){
74
+ this.globalMiddlewares.push(midlleware)
75
+ }
76
+ })
77
+ } else {
78
+ if (!this.middlewares.get(path).includes(...middlewareHandlers)) {
79
+ this.middlewares.get(path).push(...middlewareHandlers);
80
+ }
81
+ }
82
+
83
+ const handler = handlers[handlers.length-1]
84
+ this.trie.insert(path,{handler,method})
85
+
86
+ }
87
+
88
+
89
+ use(pathORHandler,handler){
90
+ if (typeof pathORHandler === 'function') {
91
+ if (!this.globalMiddlewares.includes(pathORHandler)) {
92
+ return this.globalMiddlewares.push(pathORHandler)
93
+ }
94
+ }
95
+ // now it means it is path midl
96
+ const path = pathORHandler
97
+ if (!this.middlewares.has(path)) {
98
+ this.middlewares.set(path,[])
99
+ }
100
+
101
+ if(!this.middlewares.get(path).includes(handler)){
102
+ this.middlewares.get(path).push(handler)
103
+ }
104
+ }
105
+
106
+ get(path,...handlers){
107
+ return this.#addRoute("GET",path,handlers)
108
+ }
109
+
110
+ post(path,...handlers){
111
+ return this.#addRoute("POST",path,handlers)
112
+ }
113
+
114
+ put(path,...handlers){
115
+ return this.#addRoute("PUT",path,handlers)
116
+ }
117
+
118
+ patch(path,...handlers){
119
+ if (handlers.length>0) {
120
+ return this.#addRoute("PATCH",path,handlers)
121
+ }
122
+ }
123
+
124
+ delete(path,...handlers){
125
+ return this.#addRoute("DELETE",path,handlers)
126
+ }
127
+
128
+ }
129
+
130
+
131
+ export default diesel;
package/src/trie.js ADDED
@@ -0,0 +1,132 @@
1
+ class TrieNode {
2
+ constructor() {
3
+ this.children = {};
4
+ this.isEndOfWord = false;
5
+ this.handler = [];
6
+ this.isImportant = false;
7
+ this.isDynamic = false;
8
+ this.pattern = '';
9
+ this.path = "";
10
+ this.method = []
11
+ this.subMiddlewares= new Map()
12
+ }
13
+ }
14
+
15
+ export default class Trie {
16
+ constructor() {
17
+ this.root = new TrieNode();
18
+ }
19
+
20
+ insert(path, route) {
21
+ let node = this.root;
22
+ const pathSegments = path.split('/').filter(Boolean); // Split path by segments
23
+
24
+ // If it's the root path '/', treat it separately
25
+ if (path === '/') {
26
+ node.isEndOfWord = true;
27
+ node.handler.push(route.handler)
28
+ node.isImportant = route.isImportant;
29
+ node.path = path;
30
+ node.method.push(route.method)
31
+ return;
32
+ }
33
+
34
+ for (const segment of pathSegments) {
35
+ let isDynamic = false;
36
+ let key = segment;
37
+
38
+ if (segment.startsWith(':')) {
39
+ isDynamic = true;
40
+ key = ':'; // Store dynamic routes under the key ':'
41
+ }
42
+
43
+ if (!node.children[key]) {
44
+ node.children[key] = new TrieNode();
45
+ }
46
+
47
+ node = node.children[key];
48
+ // Set dynamic route information if applicable
49
+ node.isDynamic = isDynamic;
50
+ node.pattern = segment; // Store the actual pattern like ':id'
51
+ }
52
+
53
+ // After looping through the entire path, assign route details
54
+ node.isEndOfWord = true;
55
+ node.method.push(route.method);
56
+ node.handler.push(route.handler)
57
+ node.isImportant = route.isImportant;
58
+ node.path = path; // Store the original path
59
+ }
60
+
61
+ insertMidl(midl){
62
+ if (!this.root.subMiddlewares.has(midl)) {
63
+ this.root.subMiddlewares.set(midl)
64
+ }
65
+ }
66
+
67
+
68
+ search(path, method) {
69
+ let node = this.root;
70
+ const pathSegments = path.split('/').filter(Boolean); // Split path into segments
71
+
72
+ for (const segment of pathSegments) {
73
+ let key = segment;
74
+
75
+ // Check for exact match first (static)
76
+ if (!node.children[key]) {
77
+ // Try dynamic match (e.g., ':id')
78
+ if (node.children[':']) {
79
+ node = node.children[':'];
80
+ } else {
81
+ return null; // No match
82
+ }
83
+ } else {
84
+ node = node.children[key];
85
+ }
86
+ }
87
+
88
+ // Method matching
89
+ let routeMethodIndex = node.method.indexOf(method); // More efficient method match
90
+ if (routeMethodIndex !== -1) {
91
+ return {
92
+ path: node.path,
93
+ handler: node.handler[routeMethodIndex],
94
+ isDynamic: node.isDynamic,
95
+ pattern: node.pattern,
96
+ method: node.method[routeMethodIndex]
97
+ };
98
+ }
99
+
100
+ // Fallback if method is not found
101
+ return null;
102
+ }
103
+
104
+
105
+
106
+ // New getAllRoutes method
107
+
108
+ getAllRoutes() {
109
+ const routes = [];
110
+ // Helper function to recursively collect all routes
111
+ const traverse = (node, currentPath) => {
112
+ if (node.isEndOfWord) {
113
+ routes.push({
114
+ path: currentPath,
115
+ handler: node.handler,
116
+ isImportant: node.isImportant,
117
+ isDynamic: node.isDynamic,
118
+ pattern: node.pattern,
119
+ });
120
+ }
121
+ // Recursively traverse all children
122
+ for (const key in node.children) {
123
+ const child = node.children[key];
124
+ const newPath = currentPath + (key === ':' ? '/:' + child.pattern : '/' + key); // Reconstruct the full path
125
+ traverse(child, newPath);
126
+ }
127
+ };
128
+ // Start traversal from the root
129
+ traverse(this.root, "");
130
+ return routes;
131
+ }
132
+ }