Bringing local and remote repository in line with one another and adding Github Actions to publish package

This commit is contained in:
Alan Bridgeman 2025-04-29 12:06:39 -05:00
parent 28db152b87
commit 74997cb676
8 changed files with 479 additions and 0 deletions

21
src/decorators/DELETE.ts Normal file
View file

@ -0,0 +1,21 @@
import { NextFunction } from 'express';
import { NextHandleFunction } from 'connect';
export const DELETE_METADATA_KEY = 'Delete';
/**
* Method decorator intended to be used on methods of a class decorated with the `@Controller` decorator.
*
* This in conjunction with the `@Controller` decorator will automatically setup a DELETE route that executes the decorated method.
* The specific path for the route is defined by the path parameter.
* Controller authors can also specify middleware to be used with the DELETE route (because a middleware is commonly required to parse the body of a DELETE request).
*
* @param path The path for the DELETE route.
* @param middleware The middleware to use with the DELETE route.
*/
export function DELETE(path: string, ...middleware: (NextHandleFunction | NextFunction)[]) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// Define a metadata key with the path and middleware as the value on the target's propertyKey
Reflect.defineMetadata(DELETE_METADATA_KEY, { path, middleware }, target, propertyKey);
};
}

View file

@ -0,0 +1,62 @@
import { Request, Response, NextFunction } from 'express';
import { ErrorController } from '../controllers/ErrorController';
/**
* Class decorator to create custom error handling for the app.
*
* That is, so that custom error pages can easily be created and used in the app.
*
* @example
* The following example show how to use the Controller decorator to setup a path `/path` with a GET, POST, PUT and DELETE method.
* ```ts
* import { Request, Response, NextFunction } from 'express';
*
* import { ErrorController, ErrorHandler } from '@BridgemanAccessible/ba-web-framework';
*
* @ErrorHandler(404)
* export class Custom404PageController extends ErrorController {
* handle(error: unknown, req: Request, res: Response, next: NextFunction) {
* // ...
* }
* }
* ```
*
* @param errorCode The error code to handle. This is the status code that will be returned by the server.
* @param description A human readable string to describe the error this controller handles. This is used for debugging purposes only and is not used in the app itself.
*/
export function ErrorHandler<T extends { new(...args: any): ErrorController }>(errorCode: number, description?: string) {
// Technically the function we return here with the target parameter is the actual decorator itself but we wrap it in case we ever want to add parameters to the decorator
return function(target: T){
Reflect.defineMetadata('errorHandler', target, target);
if(typeof target.prototype.handlesError === 'undefined') {
// If the handlesError property is not set, we set it to the description passed to the decorator
target.prototype.handlesError = description || `${errorCode}`;
}
// We extend the class that is decorated and override the setup method to automatically setup the routes
return class extends target {
async handle(error: unknown, req: Request, res: Response, next: NextFunction) {
// Because the headers have already been sent, we cannot send a response again.
// So we check if the headers have been sent and if so, we call the next middleware (default) in the error chain.
if(res.headersSent) {
return next(error);
}
// Create an instance of the decorated (original) class
const controller: ErrorController = new (Reflect.getMetadata('errorHandler', target))();
// We only want to call the handle method if the error code matches the one denoted.
if(res.statusCode === errorCode) {
// Call the handle method of the controller and return the result
return await controller.handle.bind(controller)(error, req, res, next);
}
else {
// If the error code does not match, we call the next middleware in the error chain
return next(error);
}
}
}
}
}

21
src/decorators/PUT.ts Normal file
View file

@ -0,0 +1,21 @@
import { NextFunction } from 'express';
import { NextHandleFunction } from 'connect';
export const PUT_METADATA_KEY = 'Put';
/**
* Method decorator intended to be used on methods of a class decorated with the `@Controller` decorator.
*
* This in conjunction with the `@Controller` decorator will automatically setup a PUT route that executes the decorated method.
* The specific path for the route is defined by the path parameter.
* Controller authors can also specify middleware to be used with the PUT route (because a middleware is commonly required to parse the body of a PUT request).
*
* @param path The path for the PUT route.
* @param middleware The middleware to use with the PUT route.
*/
export function PUT(path: string, ...middleware: (NextHandleFunction | NextFunction)[]) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// Define a metadata key with the path and middleware as the value on the target's propertyKey
Reflect.defineMetadata(PUT_METADATA_KEY, { path, middleware }, target, propertyKey);
};
}