From 61b799c97745125c626ecf70877138d77280c24a Mon Sep 17 00:00:00 2001 From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:03:07 +0200 Subject: [PATCH] feat(wobe): add custom context type (#28) --- packages/wobe/src/Context.ts | 6 ++-- packages/wobe/src/Wobe.test.ts | 22 ++++++++++++- packages/wobe/src/Wobe.ts | 39 +++++++++++------------- packages/wobe/src/hooks/bearerAuth.ts | 2 +- packages/wobe/src/hooks/bodyLimit.ts | 2 +- packages/wobe/src/hooks/cors.ts | 2 +- packages/wobe/src/hooks/csrf.ts | 2 +- packages/wobe/src/hooks/logger.ts | 2 +- packages/wobe/src/hooks/rateLimit.ts | 2 +- packages/wobe/src/hooks/secureHeaders.ts | 2 +- packages/wobe/src/router/RadixTree.ts | 12 ++++---- 11 files changed, 55 insertions(+), 38 deletions(-) diff --git a/packages/wobe/src/Context.ts b/packages/wobe/src/Context.ts index 040e5a2..3266611 100644 --- a/packages/wobe/src/Context.ts +++ b/packages/wobe/src/Context.ts @@ -14,9 +14,9 @@ export class Context { public requestStartTimeInMs: number | undefined = undefined public getIpAdress: () => string = () => '' - public handler: WobeHandler | undefined = undefined - public beforeHandlerHook: Array = [] - public afterHandlerHook: Array = [] + public handler: WobeHandler | undefined = undefined + public beforeHandlerHook: Array> = [] + public afterHandlerHook: Array> = [] constructor(request: Request, router?: RadixTree) { this.request = request diff --git a/packages/wobe/src/Wobe.test.ts b/packages/wobe/src/Wobe.test.ts index 46246e5..962f56f 100644 --- a/packages/wobe/src/Wobe.test.ts +++ b/packages/wobe/src/Wobe.test.ts @@ -18,7 +18,7 @@ import { bearerAuth, csrf, logger } from './hooks' import { WobeStore } from './tools' describe('Wobe', () => { - let wobe: Wobe + let wobe: Wobe const mockHookOnSpecificRoute = mock(() => {}) const mockHook = mock(() => {}) const mockSecondHook = mock(() => {}) @@ -252,6 +252,26 @@ describe('Wobe', () => { }, ) + it('should create wobe app with custom context', async () => { + const wobeWithContext = new Wobe<{ + customType: string + }>().get('/test', (ctx) => { + ctx.customType = 'test' + + return ctx.res.send(ctx.customType) + }) + + const port = await getPort() + + wobeWithContext.listen(port) + + const res = await fetch(`http://127.0.0.1:${port}/test`) + + expect(await res.text()).toEqual('test') + + wobeWithContext.stop() + }) + it('should call callback on listen', async () => { const localPort = await getPort() diff --git a/packages/wobe/src/Wobe.ts b/packages/wobe/src/Wobe.ts index f6c81e2..308ccdd 100644 --- a/packages/wobe/src/Wobe.ts +++ b/packages/wobe/src/Wobe.ts @@ -5,12 +5,6 @@ import type { Context } from './Context' export type MaybePromise = T | Promise -export type Routes = Array<{ - path: string - handler: WobeHandler - method: HttpMethod -}> - export interface WobeOptions { hostname?: string onError?: (error: Error) => void @@ -31,9 +25,9 @@ export type WobeHandlerOutput = | Response | Promise -export type WobeHandler = (ctx: Context) => WobeHandlerOutput +export type WobeHandler = (ctx: Context & T) => WobeHandlerOutput -export type WobePlugin = (wobe: Wobe) => void +export type WobePlugin = (wobe: Wobe) => void /** * Hook is the state of the request, it can be before the handler, after the handler or both @@ -61,7 +55,7 @@ export interface WobeWebSocket { idleTimeout?: number backpressureLimit?: number closeOnBackpressureLimit?: boolean - beforeWebSocketUpgrade?: Array + beforeWebSocketUpgrade?: Array> onOpen?(ws: ServerWebSocket): void onMessage?(ws: ServerWebSocket, message: string | Buffer): void onClose?( @@ -82,12 +76,12 @@ const factoryOfRuntime = (): RuntimeAdapter => { /** * Wobe is the main class of the framework */ -export class Wobe { +export class Wobe { private options?: WobeOptions private server: Server | null private hooks: Array<{ pathname: string - handler: WobeHandler + handler: WobeHandler hook: Hook method: HttpMethod }> @@ -119,7 +113,7 @@ export class Wobe { * @param handler The handler of the request * @param hook The hook of the request (optional) */ - get(path: string, handler: WobeHandler, hook?: WobeHandler) { + get(path: string, handler: WobeHandler, hook?: WobeHandler) { if (hook) this._addHook('beforeHandler', 'GET')(path, hook) this.router.addRoute('GET', path, handler) @@ -133,7 +127,7 @@ export class Wobe { * @param handler The handler of the request * @param hook The hook of the request (optional) */ - post(path: string, handler: WobeHandler, hook?: WobeHandler) { + post(path: string, handler: WobeHandler, hook?: WobeHandler) { if (hook) this._addHook('beforeHandler', 'POST')(path, hook) this.router.addRoute('POST', path, handler) @@ -147,7 +141,7 @@ export class Wobe { * @param handler The handler of the request * @param hook The hook of the request (optional) */ - put(path: string, handler: WobeHandler, hook?: WobeHandler) { + put(path: string, handler: WobeHandler, hook?: WobeHandler) { if (hook) this._addHook('beforeHandler', 'PUT')(path, hook) this.router.addRoute('PUT', path, handler) @@ -161,7 +155,7 @@ export class Wobe { * @param handler The handler of the request * @param hook The hook of the request (optional) */ - delete(path: string, handler: WobeHandler, hook?: WobeHandler) { + delete(path: string, handler: WobeHandler, hook?: WobeHandler) { if (hook) this._addHook('beforeHandler', 'DELETE')(path, hook) this.router.addRoute('DELETE', path, handler) @@ -175,7 +169,7 @@ export class Wobe { * @param handler The handler of the request * @param hook The hook of the request (optional) */ - all(path: string, handler: WobeHandler, hook?: WobeHandler) { + all(path: string, handler: WobeHandler, hook?: WobeHandler) { if (hook) { this.httpMethods.map((method) => this._addHook('beforeHandler', method)(path, hook), @@ -189,7 +183,7 @@ export class Wobe { private _addHook = (hook: Hook, method: HttpMethod) => - (arg1: string | WobeHandler, ...handlers: WobeHandler[]) => { + (arg1: string | WobeHandler, ...handlers: WobeHandler[]) => { let path = arg1 if (typeof arg1 !== 'string') { @@ -216,8 +210,8 @@ export class Wobe { * @param handlers The handlers of the request */ beforeAndAfterHandler( - arg1: string | WobeHandler, - ...handlers: WobeHandler[] + arg1: string | WobeHandler, + ...handlers: WobeHandler[] ) { this.httpMethods.map((method) => this._addHook('beforeAndAfterHandler', method)(arg1, ...handlers), @@ -231,7 +225,10 @@ export class Wobe { * @param arg1 The path of the request or the handler * @param handlers The handlers of the request */ - beforeHandler(arg1: string | WobeHandler, ...handlers: WobeHandler[]) { + beforeHandler( + arg1: string | WobeHandler, + ...handlers: WobeHandler[] + ) { this.httpMethods.map((method) => this._addHook('beforeHandler', method)(arg1, ...handlers), ) @@ -244,7 +241,7 @@ export class Wobe { * @param arg1 The path of the request or the handler * @param handlers The handlers of the request */ - afterHandler(arg1: string | WobeHandler, ...handlers: WobeHandler[]) { + afterHandler(arg1: string | WobeHandler, ...handlers: WobeHandler[]) { this.httpMethods.map((method) => this._addHook('afterHandler', method)(arg1, ...handlers), ) diff --git a/packages/wobe/src/hooks/bearerAuth.ts b/packages/wobe/src/hooks/bearerAuth.ts index 3283c14..c413fa3 100644 --- a/packages/wobe/src/hooks/bearerAuth.ts +++ b/packages/wobe/src/hooks/bearerAuth.ts @@ -20,7 +20,7 @@ export const bearerAuth = ({ token, hashFunction = defaultHash, realm = '', -}: BearerAuthOptions): WobeHandler => { +}: BearerAuthOptions): WobeHandler => { return (ctx) => { const requestAuthorization = ctx.request.headers.get('Authorization') diff --git a/packages/wobe/src/hooks/bodyLimit.ts b/packages/wobe/src/hooks/bodyLimit.ts index 14660db..aacbff2 100644 --- a/packages/wobe/src/hooks/bodyLimit.ts +++ b/packages/wobe/src/hooks/bodyLimit.ts @@ -8,7 +8,7 @@ interface BodyLimitOptions { /** * bodyLimit is a hook that checks if the request body is too large */ -export const bodyLimit = (options: BodyLimitOptions): WobeHandler => { +export const bodyLimit = (options: BodyLimitOptions): WobeHandler => { return (ctx) => { // The content-length header is not always present if (ctx.request.headers.get('Content-Length')) { diff --git a/packages/wobe/src/hooks/cors.ts b/packages/wobe/src/hooks/cors.ts index 9199ae9..9be15e5 100644 --- a/packages/wobe/src/hooks/cors.ts +++ b/packages/wobe/src/hooks/cors.ts @@ -17,7 +17,7 @@ export interface CorsOptions { /** * cors is a hook that adds the necessary headers to enable CORS */ -export const cors = (options?: CorsOptions): WobeHandler => { +export const cors = (options?: CorsOptions): WobeHandler => { const defaults: CorsOptions = { origin: '*', allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], diff --git a/packages/wobe/src/hooks/csrf.ts b/packages/wobe/src/hooks/csrf.ts index 197bb3a..d54b46a 100644 --- a/packages/wobe/src/hooks/csrf.ts +++ b/packages/wobe/src/hooks/csrf.ts @@ -22,7 +22,7 @@ const isSameOrigin = (optsOrigin: Origin, requestOrigin: string) => { /** * csrf is a hook that checks if the request has a valid CSRF token */ -export const csrf = (options: CsrfOptions): WobeHandler => { +export const csrf = (options: CsrfOptions): WobeHandler => { return (ctx) => { const requestOrigin = ctx.request.headers.get('origin') || '' diff --git a/packages/wobe/src/hooks/logger.ts b/packages/wobe/src/hooks/logger.ts index 1078259..6c7eb21 100644 --- a/packages/wobe/src/hooks/logger.ts +++ b/packages/wobe/src/hooks/logger.ts @@ -37,7 +37,7 @@ export const logger = ( { loggerFunction }: LoggerOptions = { loggerFunction: defaultLoggerFunction, }, -): WobeHandler => { +): WobeHandler => { return (ctx) => { const { state, request } = ctx diff --git a/packages/wobe/src/hooks/rateLimit.ts b/packages/wobe/src/hooks/rateLimit.ts index 68202fa..f279e4f 100644 --- a/packages/wobe/src/hooks/rateLimit.ts +++ b/packages/wobe/src/hooks/rateLimit.ts @@ -13,7 +13,7 @@ export interface RateLimitOptions { export const rateLimit = ({ interval, numberOfRequests, -}: RateLimitOptions): WobeHandler => { +}: RateLimitOptions): WobeHandler => { const store = new WobeStore({ interval, }) diff --git a/packages/wobe/src/hooks/secureHeaders.ts b/packages/wobe/src/hooks/secureHeaders.ts index 2792cd9..0e0b994 100644 --- a/packages/wobe/src/hooks/secureHeaders.ts +++ b/packages/wobe/src/hooks/secureHeaders.ts @@ -48,7 +48,7 @@ export const secureHeaders = ({ strictTransportSecurity = ['max-age=31536000; includeSubDomains'], xContentTypeOptions = 'nosniff', xDownloadOptions = 'noopen', -}: SecureHeadersOptions): WobeHandler => { +}: SecureHeadersOptions): WobeHandler => { return (ctx) => { if (contentSecurityPolicy) { const formatContentSecurityPolicy = Object.entries( diff --git a/packages/wobe/src/router/RadixTree.ts b/packages/wobe/src/router/RadixTree.ts index 9754d61..ff80feb 100644 --- a/packages/wobe/src/router/RadixTree.ts +++ b/packages/wobe/src/router/RadixTree.ts @@ -3,9 +3,9 @@ import type { Hook, HttpMethod, WobeHandler } from '../Wobe' export interface Node { name: string children: Array - handler?: WobeHandler - beforeHandlerHook?: Array - afterHandlerHook?: Array + handler?: WobeHandler + beforeHandlerHook?: Array> + afterHandlerHook?: Array> method?: HttpMethod isParameterNode?: boolean isWildcardNode?: boolean @@ -16,7 +16,7 @@ export class RadixTree { public root: Node = { name: '/', children: [] } private isOptimized = false - addRoute(method: HttpMethod, path: string, handler: WobeHandler) { + addRoute(method: HttpMethod, path: string, handler: WobeHandler) { const pathParts = path.split('/').filter(Boolean) let currentNode = this.root @@ -57,7 +57,7 @@ export class RadixTree { currentNode.method = method } - _addHookToNode(node: Node, hook: Hook, handler: WobeHandler) { + _addHookToNode(node: Node, hook: Hook, handler: WobeHandler) { switch (hook) { case 'beforeHandler': { if (!node.beforeHandlerHook) node.beforeHandlerHook = [] @@ -89,7 +89,7 @@ export class RadixTree { addHook( hook: Hook, path: string, - handler: WobeHandler, + handler: WobeHandler, method: HttpMethod, node?: Node, ) {