From c49217345a00abe5f12268e3dacd61073d47727f Mon Sep 17 00:00:00 2001 From: yehorbk Date: Tue, 3 Oct 2023 10:05:13 +0300 Subject: [PATCH] basic project structure added, routing functionality implemented --- bun.lockb | Bin 0 -> 1279 bytes lib/core/helpers.ts | 37 +++++++++++++++++++++++++++++++++++++ lib/core/http.ts | 10 ++++++++++ lib/router.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/types.ts | 6 ++++++ main.ts | 3 +++ package.json | 11 +++++++++++ tsconfig.json | 22 ++++++++++++++++++++++ 8 files changed, 131 insertions(+) create mode 100755 bun.lockb create mode 100644 lib/core/helpers.ts create mode 100644 lib/core/http.ts create mode 100644 lib/router.ts create mode 100644 lib/types.ts create mode 100644 main.ts create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..b9762ca36fb5ca93fac4a25c6b60dced701f27c6 GIT binary patch literal 1279 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_p*BPZlln zmv|#}QsDcSv;VmGl7mYA?BHtI!Vz;XE8XR9V?GN|5fFgXf&d4Eg3}F9eicjsn9s$) z&>#Y2aRO{U-fvhtO*!M&E39MyM_j^&be3xCEI@Ne8n)`elLG3TQse zEErAD0H6YpW04(<%Kyjzj}*g*u@I<+kpW^Zve7JWZe3p`%#m?vSyb&@&qRUeKFTt$ZpaB>3R-8gY*iN=CUa+O4iHFD=taQ$mlS2@rQ0bOBCNN7TQ303m+4R% z7$QKI8e(xMDDxNPmjcZ!sVqn>PAv(2It8CFxZ_-w#Rv0L;hTjQ{`u literal 0 HcmV?d00001 diff --git a/lib/core/helpers.ts b/lib/core/helpers.ts new file mode 100644 index 0000000..8452ed2 --- /dev/null +++ b/lib/core/helpers.ts @@ -0,0 +1,37 @@ +export function parseURI(uri: string): { + query: string; + filters: { [key: string]: string }; +} { + const { pathname: query, searchParams } = new URL(uri); + const filters = Object.fromEntries(searchParams); + return { query, filters }; +} + +export function match(query: string, template: string): boolean { + const queryEntries = query.split("/").filter(Boolean); + const templateEntries = template.split("/").filter(Boolean); + if (queryEntries.length !== templateEntries.length) return false; + const filteredQueryEntries = queryEntries.filter((entry) => + isNaN(Number(entry)) + ); + const filteredTemplateEntries = templateEntries.filter( + (entry) => !entry.startsWith(":") + ); + return filteredQueryEntries.join("/") === filteredTemplateEntries.join("/"); +} + +export function parseParams( + query: string, + template: string +): { [key: string]: string } { + const result: { [key: string]: string } = {}; + const queryEntries = query.split("/"); + const templateEntries = template.split("/"); + for (const [index, entry] of templateEntries.entries()) { + if (!entry.startsWith(":")) continue; + const key = entry.substring(1); + const value = queryEntries[index]; + result[key] = value; + } + return result; +} diff --git a/lib/core/http.ts b/lib/core/http.ts new file mode 100644 index 0000000..122f5b4 --- /dev/null +++ b/lib/core/http.ts @@ -0,0 +1,10 @@ +export type HTTPMethod = + | "GET" + | "POST" + | "PUT" + | "PATCH" + | "DELETE" + | "HEAD" + | "OPTIONS" + | "CONNECT" + | "TRACE"; diff --git a/lib/router.ts b/lib/router.ts new file mode 100644 index 0000000..bb7e1f4 --- /dev/null +++ b/lib/router.ts @@ -0,0 +1,42 @@ +import { match, parseParams, parseURI } from "./core/helpers"; +import { HTTPMethod } from "./core/http"; +import { Handler } from "./types"; + +interface Endpoint { + method: HTTPMethod; + template: string; + handler: Handler; +} + +export default class Router { + endpoints: Endpoint[] = []; + + register(method: HTTPMethod, template: string, handler: Handler) { + this.endpoints.push({ method, template, handler }); + } + + async handle(request: Request): Promise { + const { method, url } = request; + const { query, filters } = parseURI(url); + const endpoint = this.endpoints.find( + ({ method: endpointMethod, template }) => + endpointMethod === method && match(query, template) + ); + if (!endpoint) return new Response(null, { status: 404 }); + const body = await request.json(); + const { handler, template } = endpoint; + const params = parseParams(query, template); + let result; + try { + result = await handler({ body, filters, params, request }); + } catch (error: unknown) { + const message = (error as Error).message; + return new Response(message, { status: 500 }); + } + if (result instanceof Response) return result; + else if (typeof result === "string") return new Response(result); + else if (typeof result === "object") + return new Response(JSON.stringify(result)); + else return new Response(); + } +} diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..71a8a60 --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,6 @@ +export type Handler = (props: { + params: { [key: string]: string }; + filters: { [key: string]: string }; + body: object | undefined; + request: Request; +}) => Response | object | string | void; diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..33ed013 --- /dev/null +++ b/main.ts @@ -0,0 +1,3 @@ +import Router from "./lib/router"; + +export const router = new Router(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..ebe76f7 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "@genericst/bun-router", + "module": "main.ts", + "type": "module", + "devDependencies": { + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c937271 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "preserve", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" + ] + } +}