diff --git a/lib/instances/mongodb.d.ts b/lib/instances/mongodb.d.ts index db967b2..409fb98 100644 --- a/lib/instances/mongodb.d.ts +++ b/lib/instances/mongodb.d.ts @@ -1,5 +1,6 @@ declare class Mongo { private client; + private isConnected; connect(url: string): Promise; getCollection(db: string, col: string): Promise; getDatabase(db: string): Promise; diff --git a/lib/instances/postgres.d.ts b/lib/instances/postgres.d.ts index 48398d8..6ea3092 100644 --- a/lib/instances/postgres.d.ts +++ b/lib/instances/postgres.d.ts @@ -1,5 +1,6 @@ declare class Pg { private pool; + private isConnected; connect(config: object): Promise; endConnection(): Promise; query(text: string, params?: any[]): Promise; diff --git a/lib/main/index.js b/lib/main/index.js index c04253b..9952ccb 100644 --- a/lib/main/index.js +++ b/lib/main/index.js @@ -1,2 +1,2 @@ // @bun -var{Glob}=globalThis.Bun;import*as path from"path/posix";import chalk3 from"chalk";import*as fs from"fs";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)};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}}import{MongoClient} from"mongodb";import chalk from"chalk";class Mongo{client=null;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`))})}async getCollection(db,col){return this.client.db(db).collection(col)}async getDatabase(db){return this.client.db(db)}async update(db,col,query2,update){const collection=await this.getCollection(db,col);return collection.updateOne(query2,update)}async insert(db,col,data){const collection=await this.getCollection(db,col);return collection.insertOne(data)}async find(db,col,query2){const collection=await this.getCollection(db,col);return collection.find(query2).toArray()}async findOne(db,col,query2){const collection=await this.getCollection(db,col);return collection.findOne(query2)}async delete(db,col,query2){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;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`))}catch(error){console.log("Error while trying to establish postgres connection",error)}}async endConnection(){await this.pool?.end()}async query(text,params){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;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,"/");file=file.replace(/routes\//g,"");const splits=file.split("/");const filePath=path.join(process.cwd(),"routes",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("/")}get[`${file}`]=getModule}if(postModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}post[`${file}`]=postModule}if(putModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}put[`${file}`]=putModule}if(deleteModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}del[`${file}`]=deleteModule}if(patchModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}patch[`${file}`]=patchModule}}const folders=fs.readdirSync(folder);for(const subfolder of folders){if(subfolder.includes(".")){continue}await loadFolder(path.join(folder,subfolder))}}async function loadRoutes(){const start=Date.now();await loadFolder("routes");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(userMethod==="get"){if(isIndex){matchingRoute=get["index"]}else{matchingRoute=get[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=get[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=get[newPath+"/params"]}}}if(userMethod==="post"){if(isIndex){matchingRoute=post["index"]}else{matchingRoute=post[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=post[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=post[newPath+"/params"]}}}if(userMethod==="put"){if(isIndex){matchingRoute=put["index"]}else{matchingRoute=put[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=put[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=put[newPath+"/params"]}}}if(userMethod==="delete"){if(isIndex){matchingRoute=del["index"]}else{matchingRoute=del[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=del[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=del[newPath+"/params"]}}}if(userMethod==="patch"){if(isIndex){matchingRoute=patch["index"]}else{matchingRoute=patch[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=patch[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=patch[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();console.log(`Starting server on port ${port}...`);Bun.serve({port,fetch:handleRequest})}var log=false;var get={};var post={};var put={};var del={};var 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,Redirect,ProBun,postgres_default as PgService,mongodb_default as MongoService,Html,Failure}; +var{Glob}=globalThis.Bun;import*as path from"path/posix";import chalk3 from"chalk";import*as fs from"fs";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)};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}}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;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,"/");file=file.replace(/routes\//g,"");const splits=file.split("/");const filePath=path.join(process.cwd(),"routes",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("/")}get[`${file}`]=getModule}if(postModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}post[`${file}`]=postModule}if(putModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}put[`${file}`]=putModule}if(deleteModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}del[`${file}`]=deleteModule}if(patchModule){if(file.includes("[")&&file.includes("]")){const parts=file.split("/");parts[parts.length-1]="params";file=parts.join("/")}patch[`${file}`]=patchModule}}const folders=fs.readdirSync(folder);for(const subfolder of folders){if(subfolder.includes(".")){continue}await loadFolder(path.join(folder,subfolder))}}async function loadRoutes(){const start=Date.now();await loadFolder("routes");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(userMethod==="get"){if(isIndex){matchingRoute=get["index"]}else{matchingRoute=get[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=get[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=get[newPath+"/params"]}}}if(userMethod==="post"){if(isIndex){matchingRoute=post["index"]}else{matchingRoute=post[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=post[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=post[newPath+"/params"]}}}if(userMethod==="put"){if(isIndex){matchingRoute=put["index"]}else{matchingRoute=put[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=put[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=put[newPath+"/params"]}}}if(userMethod==="delete"){if(isIndex){matchingRoute=del["index"]}else{matchingRoute=del[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=del[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=del[newPath+"/params"]}}}if(userMethod==="patch"){if(isIndex){matchingRoute=patch["index"]}else{matchingRoute=patch[parsedUrl.pathname.substring(1)];if(!matchingRoute){matchingRoute=patch[parsedUrl.pathname.substring(1)+"/index"]}if(!matchingRoute){const parts=parsedUrl.pathname.split("/");parts.pop();let newPath=parts.join("/");newPath=newPath.substring(1);matchingRoute=patch[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();console.log(`Starting server on port ${port}...`);Bun.serve({port,fetch:handleRequest})}var log=false;var get={};var post={};var put={};var del={};var 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,Redirect,ProBun,postgres_default as PgService,mongodb_default as MongoService,Html,Failure}; diff --git a/package.json b/package.json index e123e3c..321f003 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.6", + "version": "0.1.7", "main": "./lib/main/index.js", "type": "module", "devDependencies": {