diff --git a/lib/helpers/helper.d.ts b/lib/helpers/helper.d.ts index 2d0bf49..34464fb 100644 --- a/lib/helpers/helper.d.ts +++ b/lib/helpers/helper.d.ts @@ -1,6 +1,6 @@ declare function SendJSON(json: any, status?: number): Promise; declare function Success(message: string, status?: number): Promise; -declare function SendFile(filePath: string, status?: number): Promise; +declare function SendFile(filePath: any, status?: number): Promise; declare function Failure(message: string, status?: number): Promise; declare function ServerFailure(message: string, status?: number): Promise; declare function Redirect(destination: string, status?: number): Response; diff --git a/lib/main/index.js b/lib/main/index.js index f046424..ede89b5 100644 --- a/lib/main/index.js +++ b/lib/main/index.js @@ -1,2 +1,2 @@ // @bun -var{Glob}=globalThis.Bun;import*as path2 from"path/posix";import chalk3 from"chalk";import*as fs from"fs";import*as path from"path/posix";async function SendJSON(json,status=200){return new Response(JSON.stringify(json),{headers:{"Content-Type":"application/json"},status})}var Success=function(message,status=200){return SendJSON({message},status)};async function SendFile(filePath,status=200){const file=await Bun.file(path.join(__dirname,filePath));return new Response(file,{headers:{"Content-Type":"application/octet-stream","Content-Disposition":`attachment; filename="${filePath.split("/").pop()}"`},status})}var Failure=function(message,status=400){return SendJSON({message},status)};var ServerFailure=function(message,status=500){return SendJSON({message},status)};var Redirect=function(destination,status=302){return new Response(null,{status,headers:{Location:destination}})};var Html=function(html,status=200){return new Response(html,{headers:{"Content-Type":"text/html"},status})};var __dirname="/home/ben/probun/src/helpers";async function query(query2,req){const url=new URL(req.url);const params=url.searchParams;const value=params.get(query2);if(!value){return}else{return value}}async function param(req){const url=new URL(req.url);let splitUrl=req.url.split("/");let id=splitUrl[splitUrl.length-1];if(id===""){id=splitUrl[splitUrl.length-2]}if(!id){return null}else{return id.replace(/\?.*/,"")}}import{MongoClient} from"mongodb";import chalk from"chalk";class Mongo{client=null;isConnected=false;async connect(url){this.client=new MongoClient(url);const start=Date.now();await this.client.connect().then(()=>{console.log(chalk.bold.whiteBright(`MongoDB connected in `)+chalk.bold.greenBright(`${Date.now()-start}ms`));this.isConnected=true})}async getCollection(db,col){if(!this.isConnected){throw new Error("Not connected to MongoDB")}return this.client.db(db).collection(col)}async getDatabase(db){if(!this.isConnected){throw new Error("Not connected to MongoDB")}return this.client.db(db)}async update(db,col,query2,update){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.updateOne(query2,update)}async insert(db,col,data){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.insertOne(data)}async find(db,col,query2){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.find(query2).toArray()}async findOne(db,col,query2){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.findOne(query2)}async delete(db,col,query2){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.deleteOne(query2)}async close(){await this.client.close()}}class MongoService{static instance=null;constructor(){throw new Error("Use MongoService.getInstance()")}static getInstance(){if(!MongoService.instance){MongoService.instance=new Mongo}return MongoService.instance}}var mongodb_default=MongoService;import{Pool} from"pg";import chalk2 from"chalk";class Pg{pool=null;isConnected=false;async connect(config){this.pool=new Pool({ssl:{rejectUnauthorized:false},...config});const start=Date.now();try{await this.pool.connect();console.log(chalk2.bold.whiteBright(`PostgreSQL connected in `)+chalk2.bold.greenBright(`${Date.now()-start}ms`));this.isConnected=true}catch(error){console.log("Error while trying to establish postgres connection",error)}}async endConnection(){await this.pool?.end()}async query(text,params){if(!this.isConnected){throw new Error("Not connected to PostgreSQL")}return this.pool?.query(text,params)}}class PgService{static instance=null;constructor(){throw new Error("Use PgService.getInstance()")}static getInstance(){if(!PgService.instance){PgService.instance=new Pg}return PgService.instance}}var postgres_default=PgService;var version="0.1.16";async function loadFolder(folder){if(log){console.log(`${chalk3.bold.white(`Loading`)} ${chalk3.bold.green(`${folder}`)}...`)}const allRoutes=new Glob(`${folder}/*.ts`);for await(let file of allRoutes.scan(".")){file=file.replace(/\\/g,"/");const realfile=file.split("/").slice(1).join("/").replace(/.ts/g,"");file=file.split("/")[file.split("/").length-1];const splits=file.split("/");const filePath=path2.join(process.cwd(),folder,file);const routeModule=await import(filePath).then((m)=>m.default||m);const getModule=typeof routeModule==="object"?routeModule?.GET:routeModule;const postModule=typeof routeModule==="object"?routeModule?.POST:routeModule;const putModule=typeof routeModule==="object"?routeModule?.PUT:routeModule;const deleteModule=typeof routeModule==="object"?routeModule?.DELETE:routeModule;const patchModule=typeof routeModule==="object"?routeModule?.PATCH:routeModule;file=file.replace(/.ts/g,"");if(getModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.get[`${realfile}`]=getModule}if(postModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.post[`${realfile}`]=postModule}if(putModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.put[`${realfile}`]=putModule}if(deleteModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.delete[`${realfile}`]=deleteModule}if(patchModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.patch[`${realfile}`]=patchModule}}const folders=fs.readdirSync(folder);for(const subfolder of folders){if(subfolder.includes(".")){continue}await loadFolder(path2.join(folder,subfolder))}}async function loadRoutes(folder){const start=Date.now();await loadFolder(folder);console.log(methods);if(log){console.log(`${chalk3.bold.white(`Loaded all routes in`)} ${chalk3.bold.green(`${Date.now()-start}ms`)}`)}}async function handleRequest(req){const start=Date.now();let customHeaders=new Headers;for(const middleware of premiddlewares){try{await middleware(req,{headers:customHeaders})}catch(error){console.error(`${chalk3.bold.red(`Error while processing middleware ${middleware.name}:`)} ${error}`);return ServerFailure("Internal Server Error")}}const userMethod=req.method.toLowerCase();const url=req.url;let isIndex=false;let parsedUrl=new URL(url??"","http://localhost");let reqMessage=`${chalk3.bold.white(userMethod.toUpperCase())} ${parsedUrl.pathname}`;if(parsedUrl.pathname==="/favicon.ico"){return new Response("",{status:204})}if(parsedUrl.pathname==="/"){isIndex=true}if(parsedUrl.pathname.endsWith("/")){parsedUrl.pathname=parsedUrl.pathname.substring(0,parsedUrl.pathname.length-1)}let matchingRoute;if(isIndex){matchingRoute=methods[userMethod]["index"]}else{matchingRoute=methods[userMethod][parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=methods[userMethod][parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=methods[userMethod][newPath+"/params"]}}if(!matchingRoute){if(log){reqMessage+=` ${chalk3.bold.red("404")} ${chalk3.bold.gray(`${Date.now()-start}ms`)}`;console.log(reqMessage)}return new Response("Not found.",{status:404})}try{const response=await matchingRoute(req);for(const middleware of postmiddlewares){try{await middleware(req,{headers:customHeaders})}catch(error){console.error(`${chalk3.bold.red(`Error while processing middleware ${middleware.name}:`)} ${error}`);return ServerFailure("Internal Server Error")}}const end=Date.now();response.headers.set("x-response-time",`${end-start}ms`);for(const[key,value]of customHeaders){response.headers.set(key,value)}if(log){let color="green";if(response.status>=100&&response.status<200){color="blue"}else if(response.status>=200&&response.status<300){color="green"}else if(response.status>=300&&response.status<400){color="yellow"}else if(response.status>=400&&response.status<500){color="magenta"}else if(response.status>=500){color="red"}reqMessage+=` ${chalk3.bold[color](response.status)}`;reqMessage+=` ${chalk3.bold.gray(`${end-start}ms`)}`;console.log(reqMessage)}return response}catch(error){console.error("Error while processing the requested route: ",error);if(log){reqMessage+=` ${chalk3.bold.red("500")} ${chalk3.bold.gray(`${Date.now()-start}ms`)}`;console.log(reqMessage)}return ServerFailure("Internal Server Error")}}async function startServer(port=3000,routes="routes",logger=true){await loadRoutes(routes);console.log(chalk3.bold.white(`Using ProBun ${chalk3.bold.green(`v${version}`)}`));console.log(chalk3.bold.white(`Starting server on port ${chalk3.bold.cyan(`${port}...`)}`));Bun.serve({port,fetch:handleRequest})}var log=false;var methods={get:{},post:{},put:{},delete:{},patch:{}};var premiddlewares=[];var postmiddlewares=[];class ProBun{port;routes;logger;mongoUri;pgConfig;constructor(props){const{port,routes,logger,mongoUri,pgConfig}=props;this.port=port;this.routes=routes;this.logger=logger;this.mongoUri=mongoUri;this.pgConfig=pgConfig}async start(){log=this.logger;if(this.mongoUri){await mongodb_default.getInstance().connect(this.mongoUri)}if(this.pgConfig){await postgres_default.getInstance().connect(this.pgConfig)}startServer(this.port,this.routes,this.logger)}definePreMiddleware(middleware){premiddlewares.push(middleware);if(this.logger){console.log(`Added pre-middleware: ${chalk3.bold.green(middleware.name)}`)}}definePostMiddleware(middleware){postmiddlewares.push(middleware);if(this.logger){console.log(`Added post-middleware: ${chalk3.bold.green(middleware.name)}`)}}}export{query,param,Success,ServerFailure,SendJSON,SendFile,Redirect,ProBun,postgres_default as PgService,mongodb_default as MongoService,Html,Failure}; +var{Glob}=globalThis.Bun;import*as path2 from"path/posix";import chalk3 from"chalk";import*as fs from"fs";import*as path from"path/posix";async function SendJSON(json,status=200){return new Response(JSON.stringify(json),{headers:{"Content-Type":"application/json"},status})}var Success=function(message,status=200){return SendJSON({message},status)};async function SendFile(filePath,status=200){const file=Bun.file(filePath);let rawFileName=path.basename(filePath);rawFileName=rawFileName.replace(/ /g,"_");rawFileName=rawFileName.replace(/\\/g,"_");rawFileName=rawFileName.split("_")[rawFileName.split("_").length-1];return new Response(file,{headers:{"Content-Type":"application/octet-stream","Content-Disposition":`attachment; filename="${rawFileName}"`},status})}var Failure=function(message,status=400){return SendJSON({message},status)};var ServerFailure=function(message,status=500){return SendJSON({message},status)};var Redirect=function(destination,status=302){return new Response(null,{status,headers:{Location:destination}})};var Html=function(html,status=200){return new Response(html,{headers:{"Content-Type":"text/html"},status})};async function query(query2,req){const url=new URL(req.url);const params=url.searchParams;const value=params.get(query2);if(!value){return}else{return value}}async function param(req){const url=new URL(req.url);let splitUrl=req.url.split("/");let id=splitUrl[splitUrl.length-1];if(id===""){id=splitUrl[splitUrl.length-2]}if(!id){return null}else{return id.replace(/\?.*/,"")}}import{MongoClient} from"mongodb";import chalk from"chalk";class Mongo{client=null;isConnected=false;async connect(url){this.client=new MongoClient(url);const start=Date.now();await this.client.connect().then(()=>{console.log(chalk.bold.whiteBright(`MongoDB connected in `)+chalk.bold.greenBright(`${Date.now()-start}ms`));this.isConnected=true})}async getCollection(db,col){if(!this.isConnected){throw new Error("Not connected to MongoDB")}return this.client.db(db).collection(col)}async getDatabase(db){if(!this.isConnected){throw new Error("Not connected to MongoDB")}return this.client.db(db)}async update(db,col,query2,update){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.updateOne(query2,update)}async insert(db,col,data){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.insertOne(data)}async find(db,col,query2){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.find(query2).toArray()}async findOne(db,col,query2){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.findOne(query2)}async delete(db,col,query2){if(!this.isConnected){throw new Error("Not connected to MongoDB")}const collection=await this.getCollection(db,col);return collection.deleteOne(query2)}async close(){await this.client.close()}}class MongoService{static instance=null;constructor(){throw new Error("Use MongoService.getInstance()")}static getInstance(){if(!MongoService.instance){MongoService.instance=new Mongo}return MongoService.instance}}var mongodb_default=MongoService;import{Pool} from"pg";import chalk2 from"chalk";class Pg{pool=null;isConnected=false;async connect(config){this.pool=new Pool({ssl:{rejectUnauthorized:false},...config});const start=Date.now();try{await this.pool.connect();console.log(chalk2.bold.whiteBright(`PostgreSQL connected in `)+chalk2.bold.greenBright(`${Date.now()-start}ms`));this.isConnected=true}catch(error){console.log("Error while trying to establish postgres connection",error)}}async endConnection(){await this.pool?.end()}async query(text,params){if(!this.isConnected){throw new Error("Not connected to PostgreSQL")}return this.pool?.query(text,params)}}class PgService{static instance=null;constructor(){throw new Error("Use PgService.getInstance()")}static getInstance(){if(!PgService.instance){PgService.instance=new Pg}return PgService.instance}}var postgres_default=PgService;var version="0.1.17";async function loadFolder(folder){if(log){console.log(`${chalk3.bold.white(`Loading`)} ${chalk3.bold.green(`${folder}`)}...`)}const allRoutes=new Glob(`${folder}/*.ts`);for await(let file of allRoutes.scan(".")){file=file.replace(/\\/g,"/");const realfile=file.split("/").slice(1).join("/").replace(/.ts/g,"");file=file.split("/")[file.split("/").length-1];const splits=file.split("/");const filePath=path2.join(process.cwd(),folder,file);const routeModule=await import(filePath).then((m)=>m.default||m);const getModule=typeof routeModule==="object"?routeModule?.GET:routeModule;const postModule=typeof routeModule==="object"?routeModule?.POST:routeModule;const putModule=typeof routeModule==="object"?routeModule?.PUT:routeModule;const deleteModule=typeof routeModule==="object"?routeModule?.DELETE:routeModule;const patchModule=typeof routeModule==="object"?routeModule?.PATCH:routeModule;file=file.replace(/.ts/g,"");if(getModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.get[`${realfile}`]=getModule}if(postModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.post[`${realfile}`]=postModule}if(putModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.put[`${realfile}`]=putModule}if(deleteModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.delete[`${realfile}`]=deleteModule}if(patchModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}methods.patch[`${realfile}`]=patchModule}}const folders=fs.readdirSync(folder);for(const subfolder of folders){if(subfolder.includes(".")){continue}await loadFolder(path2.join(folder,subfolder))}}async function loadRoutes(folder){const start=Date.now();await loadFolder(folder);if(log){console.log(`${chalk3.bold.white(`Loaded all routes in`)} ${chalk3.bold.green(`${Date.now()-start}ms`)}`)}}async function handleRequest(req){const start=Date.now();let customHeaders=new Headers;for(const middleware of premiddlewares){try{await middleware(req,{headers:customHeaders})}catch(error){console.error(`${chalk3.bold.red(`Error while processing middleware ${middleware.name}:`)} ${error}`);return ServerFailure("Internal Server Error")}}const userMethod=req.method.toLowerCase();const url=req.url;let isIndex=false;let parsedUrl=new URL(url??"","http://localhost");let reqMessage=`${chalk3.bold.white(userMethod.toUpperCase())} ${parsedUrl.pathname}`;if(parsedUrl.pathname==="/favicon.ico"){return new Response("",{status:204})}if(parsedUrl.pathname==="/"){isIndex=true}if(parsedUrl.pathname.endsWith("/")){parsedUrl.pathname=parsedUrl.pathname.substring(0,parsedUrl.pathname.length-1)}let matchingRoute;if(isIndex){matchingRoute=methods[userMethod]["index"]}else{matchingRoute=methods[userMethod][parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=methods[userMethod][parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=methods[userMethod][newPath+"/params"]}}if(!matchingRoute){if(log){reqMessage+=` ${chalk3.bold.red("404")} ${chalk3.bold.gray(`${Date.now()-start}ms`)}`;console.log(reqMessage)}return new Response("Not found.",{status:404})}try{const response=await matchingRoute(req);for(const middleware of postmiddlewares){try{await middleware(req,{headers:customHeaders})}catch(error){console.error(`${chalk3.bold.red(`Error while processing middleware ${middleware.name}:`)} ${error}`);return ServerFailure("Internal Server Error")}}const end=Date.now();response.headers.set("x-response-time",`${end-start}ms`);for(const[key,value]of customHeaders){response.headers.set(key,value)}if(log){let color="green";if(response.status>=100&&response.status<200){color="blue"}else if(response.status>=200&&response.status<300){color="green"}else if(response.status>=300&&response.status<400){color="yellow"}else if(response.status>=400&&response.status<500){color="magenta"}else if(response.status>=500){color="red"}reqMessage+=` ${chalk3.bold[color](response.status)}`;reqMessage+=` ${chalk3.bold.gray(`${end-start}ms`)}`;console.log(reqMessage)}return response}catch(error){console.error("Error while processing the requested route: ",error);if(log){reqMessage+=` ${chalk3.bold.red("500")} ${chalk3.bold.gray(`${Date.now()-start}ms`)}`;console.log(reqMessage)}return ServerFailure("Internal Server Error")}}async function startServer(port=3000,routes="routes",logger=true){await loadRoutes(routes);console.log(chalk3.bold.white(`Using ProBun ${chalk3.bold.green(`v${version}`)}`));console.log(chalk3.bold.white(`Starting server on port ${chalk3.bold.cyan(`${port}...`)}`));Bun.serve({port,fetch:handleRequest})}var log=false;var methods={get:{},post:{},put:{},delete:{},patch:{}};var premiddlewares=[];var postmiddlewares=[];class ProBun{port;routes;logger;mongoUri;pgConfig;constructor(props){const{port,routes,logger,mongoUri,pgConfig}=props;this.port=port;this.routes=routes;this.logger=logger;this.mongoUri=mongoUri;this.pgConfig=pgConfig}async start(){log=this.logger;if(this.mongoUri){await mongodb_default.getInstance().connect(this.mongoUri)}if(this.pgConfig){await postgres_default.getInstance().connect(this.pgConfig)}startServer(this.port,this.routes,this.logger)}definePreMiddleware(middleware){premiddlewares.push(middleware);if(this.logger){console.log(`Added pre-middleware: ${chalk3.bold.green(middleware.name)}`)}}definePostMiddleware(middleware){postmiddlewares.push(middleware);if(this.logger){console.log(`Added post-middleware: ${chalk3.bold.green(middleware.name)}`)}}}export{query,param,Success,ServerFailure,SendJSON,SendFile,Redirect,ProBun,postgres_default as PgService,mongodb_default as MongoService,Html,Failure}; diff --git a/package.json b/package.json index cc00f9e..cc5e3b8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "probun", "description": "Powerful file-based routing for Bun servers", "module": "src/main/index.ts", - "version": "0.1.16", + "version": "0.1.17", "main": "./lib/main/index.js", "type": "module", "homepage": "https://probun.dev",