feat: express decorators for rest apis and websocket (#6818)
* feat: express decorators for rest apis and websocket * fix: added package dependency * fix: refactor decorators
This commit is contained in:
parent
ae6e5a48fa
commit
993713925a
14 changed files with 690 additions and 7 deletions
61
packages/decorators/src/controller.ts
Normal file
61
packages/decorators/src/controller.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { RequestHandler, Router } from "express";
|
||||
import "reflect-metadata";
|
||||
|
||||
type HttpMethod =
|
||||
| "get"
|
||||
| "post"
|
||||
| "put"
|
||||
| "delete"
|
||||
| "patch"
|
||||
| "options"
|
||||
| "head"
|
||||
| "ws";
|
||||
|
||||
interface ControllerInstance {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ControllerConstructor {
|
||||
new (...args: any[]): ControllerInstance;
|
||||
prototype: ControllerInstance;
|
||||
}
|
||||
|
||||
export function registerControllers(
|
||||
router: Router,
|
||||
Controller: ControllerConstructor,
|
||||
): void {
|
||||
const instance = new Controller();
|
||||
const baseRoute = Reflect.getMetadata("baseRoute", Controller) as string;
|
||||
|
||||
Object.getOwnPropertyNames(Controller.prototype).forEach((methodName) => {
|
||||
if (methodName === "constructor") return; // Skip the constructor
|
||||
|
||||
const method = Reflect.getMetadata(
|
||||
"method",
|
||||
instance,
|
||||
methodName,
|
||||
) as HttpMethod;
|
||||
const route = Reflect.getMetadata("route", instance, methodName) as string;
|
||||
const middlewares =
|
||||
(Reflect.getMetadata(
|
||||
"middlewares",
|
||||
instance,
|
||||
methodName,
|
||||
) as RequestHandler[]) || [];
|
||||
|
||||
if (method && route) {
|
||||
const handler = instance[methodName] as unknown;
|
||||
|
||||
if (typeof handler === "function") {
|
||||
if (method !== "ws") {
|
||||
(
|
||||
router[method] as (
|
||||
path: string,
|
||||
...handlers: RequestHandler[]
|
||||
) => void
|
||||
)(`${baseRoute}${route}`, ...middlewares, handler.bind(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
15
packages/decorators/src/index.ts
Normal file
15
packages/decorators/src/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Export individual decorators
|
||||
export { Controller, Middleware } from "./rest";
|
||||
export { Get, Post, Put, Patch, Delete } from "./rest";
|
||||
export { WebSocket } from "./websocket";
|
||||
export { registerControllers } from "./controller";
|
||||
export { registerWebSocketControllers } from "./websocket-controller";
|
||||
|
||||
// Also provide namespaced exports for better organization
|
||||
import * as RestDecorators from "./rest";
|
||||
import * as WebSocketDecorators from "./websocket";
|
||||
|
||||
// Named namespace exports
|
||||
export const Rest = RestDecorators;
|
||||
export const WebSocketNS = WebSocketDecorators;
|
||||
|
||||
61
packages/decorators/src/rest.ts
Normal file
61
packages/decorators/src/rest.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import "reflect-metadata";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
// Define valid HTTP methods
|
||||
type RestMethod = "get" | "post" | "put" | "patch" | "delete";
|
||||
|
||||
/**
|
||||
* Controller decorator
|
||||
* @param baseRoute
|
||||
* @returns
|
||||
*/
|
||||
export function Controller(baseRoute: string = ""): ClassDecorator {
|
||||
return function (target: Function) {
|
||||
Reflect.defineMetadata("baseRoute", baseRoute, target);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to create HTTP method decorators
|
||||
* @param method HTTP method to handle
|
||||
* @returns Method decorator
|
||||
*/
|
||||
function createHttpMethodDecorator(
|
||||
method: RestMethod
|
||||
): (route: string) => MethodDecorator {
|
||||
return function (route: string): MethodDecorator {
|
||||
return function (
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
Reflect.defineMetadata("method", method, target, propertyKey);
|
||||
Reflect.defineMetadata("route", route, target, propertyKey);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Export HTTP method decorators using the factory
|
||||
export const Get = createHttpMethodDecorator("get");
|
||||
export const Post = createHttpMethodDecorator("post");
|
||||
export const Put = createHttpMethodDecorator("put");
|
||||
export const Patch = createHttpMethodDecorator("patch");
|
||||
export const Delete = createHttpMethodDecorator("delete");
|
||||
|
||||
/**
|
||||
* Middleware decorator
|
||||
* @param middleware
|
||||
* @returns
|
||||
*/
|
||||
export function Middleware(middleware: RequestHandler): MethodDecorator {
|
||||
return function (
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
const middlewares =
|
||||
Reflect.getMetadata("middlewares", target, propertyKey) || [];
|
||||
middlewares.push(middleware);
|
||||
Reflect.defineMetadata("middlewares", middlewares, target, propertyKey);
|
||||
};
|
||||
}
|
||||
85
packages/decorators/src/websocket-controller.ts
Normal file
85
packages/decorators/src/websocket-controller.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { Router, Request } from "express";
|
||||
import type { WebSocket } from "ws";
|
||||
import "reflect-metadata";
|
||||
|
||||
interface ControllerInstance {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ControllerConstructor {
|
||||
new (...args: any[]): ControllerInstance;
|
||||
prototype: ControllerInstance;
|
||||
}
|
||||
|
||||
export function registerWebSocketControllers(
|
||||
router: Router,
|
||||
Controller: ControllerConstructor,
|
||||
existingInstance?: ControllerInstance,
|
||||
): void {
|
||||
const instance = existingInstance || new Controller();
|
||||
const baseRoute = Reflect.getMetadata("baseRoute", Controller) as string;
|
||||
|
||||
Object.getOwnPropertyNames(Controller.prototype).forEach((methodName) => {
|
||||
if (methodName === "constructor") return; // Skip the constructor
|
||||
|
||||
const method = Reflect.getMetadata(
|
||||
"method",
|
||||
instance,
|
||||
methodName,
|
||||
) as string;
|
||||
const route = Reflect.getMetadata("route", instance, methodName) as string;
|
||||
|
||||
if (method === "ws" && route) {
|
||||
const handler = instance[methodName] as unknown;
|
||||
|
||||
if (
|
||||
typeof handler === "function" &&
|
||||
typeof (router as any).ws === "function"
|
||||
) {
|
||||
(router as any).ws(
|
||||
`${baseRoute}${route}`,
|
||||
(ws: WebSocket, req: Request) => {
|
||||
try {
|
||||
handler.call(instance, ws, req);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`WebSocket error in ${Controller.name}.${methodName}`,
|
||||
error,
|
||||
);
|
||||
ws.close(
|
||||
1011,
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Internal server error",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Base controller class for WebSocket endpoints
|
||||
*/
|
||||
export abstract class BaseWebSocketController {
|
||||
protected router: Router;
|
||||
|
||||
constructor() {
|
||||
this.router = Router();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base route for this controller
|
||||
*/
|
||||
protected getBaseRoute(): string {
|
||||
return Reflect.getMetadata("baseRoute", this.constructor) || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method to handle WebSocket connections
|
||||
* Implement this in your derived class
|
||||
*/
|
||||
abstract handleConnection(ws: WebSocket, req: Request): void;
|
||||
}
|
||||
17
packages/decorators/src/websocket.ts
Normal file
17
packages/decorators/src/websocket.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import "reflect-metadata";
|
||||
|
||||
/**
|
||||
* WebSocket method decorator
|
||||
* @param route
|
||||
* @returns
|
||||
*/
|
||||
export function WebSocket(route: string): MethodDecorator {
|
||||
return function (
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
Reflect.defineMetadata("method", "ws", target, propertyKey);
|
||||
Reflect.defineMetadata("route", route, target, propertyKey);
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue