1.0.5-alpha.6 • Published 2 months ago

express.mediator v1.0.5-alpha.6

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

An lightweight and easy implementation of an mediator pattern with express.js.

const app = new ExpressMediator();
app.get("/", IndexRequest);
@body
export class IndexRequest extends IRequest {
    name!;
}

@requestHandler(IndexRequest)
export class IndexRequestHandler implements IRequestHandler<IndexRequest, string> {
    async handle(value: IndexRequest): Promise<string> {
        return `Hello ${value.name}!`
    }
}

See usage to learn all the different types of implementation.


Getting Started

Installation

npm i express.mediator

or if you want to access early builds

npm i express.mediator@X.X.X-alpha.X

Usage

The most magic of the mediator happens in the background. You basically just have to place some decorators onto your existing requests and replace the express app with the new ExpressMediator. In the following captions are all possible types of implementations and examples. Just pick what fits best for your purpose.

Request (required)

A request defines the whole processing for the resource. If you request data is located specifically use an request type to tell the parse pipeline where exactly to expect the data.

!IMPORTANT If no request type is placed onto the given class the default type body will be used to parse the data.

@body
class ExampleRequest {
	user!: string;
	password!: string;
}

@body
class ExampleRequest {
	user = "";
	password = "";
}

Request Property Problematic

To ensure that all required properties are provided by the request you have to initialize them

class ExampleRequest {
	exampleProperty = "";
}

or mark them as defined

class ExampleRequest {
	exampleProperty!: string;
}

This is required due to the fact that typescript won't know the exact type of the provided class when not initialized in the runtime of the application. That will cause many problems for example if the requests does not have required properties because those couldn't be catched by the parse pipeline.

Request Type

All usable http request types are accessable via an specific typescript decorator. There are two different (class and property) decorator types with two (non nullable and nullable) features available. These decorator are needed for the parse pipeline to determine where the data is in the request.

The class decorator function require a functional constructor to access the name of the provided class.

{{HTTPTYPE}}<T extends { new (): {} }>(constructor: T);

An property decorator function require the property and a specific name. !!! NOT IMPLEMENTED YET !!!

{{HTTPTYPE}}Property(target: any, propertyName: string);

Therefor both decorators must be placed on the correct type otherwise an error will be thrown.

!NOTE Both decorators add an default auth entry into the IRequestAuthResolver without any role configuration and disabled anonymous request mode. This is required to ensure that every available handler request is covered by the auth component and a missed configuration will be highlighted correctly.

Request Type Body

Reads the data from the body of express request.

@body
@bodyNullable
Request Type Params

Reads the data from the params of express request.

@params
@paramsNullable
Request Type Query

Reads the data from the query of express request.

@query
@queryNullable
Request Type Empty

Ignores the parsing and won't proceed any value of the request.

@empty

Request Handler (required)

Every request has logic which is located in an handler.

The mediator assigns the handler to the request with a specific decorator.

@body
class ExampleRequest { ... }

@requestHandler(ExampleRequest)
class ExampleHandler implements IRequestHandler<ExampleRequest, string> { ... }

Request Handler in another file

If the handler is not in the same file like the request you have to put the following decorator onto the request. This step is required due to the fact that the handler won't be called by the application setup because the mediator only knowns the request.

Request File
@params
@handler(TestRequestHandler)
class TestRequest_Param {
	data!: string;
}
Handler File
class TestRequestHandler implements IRequestHandler<TestRequest_Param, string> {
	async handle(value: TestRequest_Param): Promise<string> {
		return await Promise.resolve(value.data);
	}
}

Access Root Express Application

To access the root express application just call the app singleton on the ExpressMediator instance.

const app = new ExpressMediator().app;

The singleton ensures that all the provided middlewares are registered correctly inside the express app.

Tests

The tests are located inside the folder ./tests/ and are constructed with jest respectively ts-jest. To configure jest use the config file jest.config.js.

npm run test

Before uploading the files to npm run the following script.

npm run preupload

This script runs the jest test file jest ./tests/default.test.ts which tests the basic functionalities of the build files in ./lib.

All test files with the prefix ! will be ignored by jest. If you want to change this behavior remove this line inside the jest configuration.

Component

Component Infrastructure

interface IExpressMediatorComponent {
    key: string;
    pipelines: IExpressMediatorPipelines;
	allowCustom: boolean;
	I__defaults: { new (): IExpressMediatorComponentDefaults };
	I__instance: { new (): any };
	dependencies?: { new (): IExpressMediatorComponent }[];
	isActive?: boolean;
	isSkeleton?: boolean;
	defaults?: IExpressMediatorComponentDefaults;
	instance?: any;
}
PropertyPurpose
KeyIdentifier of the component. Must be unique
PipelinesSee pipelines
AllowCustomIf disabled the component won't allow any custom instances or defaults
I__DefaultsSee defaults
I__InstanceSee instance
DependenciesArray of dependend components. The setup will ensure that these components are available
IsActiveIf disabled the component won't add any functionalities to the application
IsSkeletonIf enabled the component was created by an decorator and will be created setup again with the latest definitions
DefaultsSee defaults
InstanceSee instance

Component Pipelines

interface IExpressMediatorPipelines {
	pre: IExpressMediatorPipeline<IPreHandlerPipeline>[];
	post: IExpressMediatorPipeline<IPostHandlerPipeline>[];
}

Component Pre Pipelines

interface IPreHandlerPipeline extends IPipeline {
	handle<T extends IRequest>(
		context: MediatorContext,
		type: { new (): T },
		value: T | undefined
	): Promise<PrePipelineResponse<T>>;
}

Component Post Pipelines

interface IPostHandlerPipeline extends IPipeline {
	handle<T extends IRequest>(context: MediatorContext, type: { new (): T }, value: T): Promise<void>;
}

Component Pipelines Dependency Injection

All previous pipeline types (pre and post) extend the base interface IPipeline which again extend the interface IDependencyInjection.

interface IPipeline extends IDependencyInjection {}

interface IDependencyInjection {
	dependencyInjection(resolver: IDependencyChainResolver): Promise<void>;
}

Component Defaults

The defaults of the components contain informations which can be overridden or extended over the lifetime of the app only. A constructor is required and the class must have an implementation of IExpressMediatorComponentDefaults.

Component Defaults Interface

interface IExpressMediatorComponentDefaults {
	[key: string | symbol | number]: AllowedDefaultTypes | Map<string, any> | AllowedDefaultTypes[];
}
Component Defaults Interface Example
interface IMediatorDefaults extends IExpressMediatorComponentDefaults {
	useAbsolutePath: boolean;
	locations: Map<string, Function>;
	middlewares: Map<string, RequestHandler<any, any, any, any, Record<string, any>>>;
}

Component Defaults Abstract Class

abstract class ExpressMediatorComponentDefaults implements IExpressMediatorComponentDefaults {
	[key: number | string | symbol]: AllowedDefaultTypes | AllowedDefaultTypes[] | Map<string, any>;
}
Component Defaults Abstract Class Example
class MediatorDefaults extends ExpressMediatorComponentDefaults implements IMediatorDefaults {
	useAbsolutePath = false;
	locations = new Map();

	middlewares = new Map([
		["CORS", cors()],
		["JSON format", express.json()],
		["OK response", Ok],
		["BadRequest response", BadRequest],
		["Pipeline error response", PipelineError],
	]);
}

Component Defaults Allowed Types

type AllowedDefaultTypes = string | boolean | number;

Component Instance

A instance of an component must contain a constructor to invoke. All static informations of the component should be saved into the defaults because those will be invoked only once.

1.0.5-alpha.6

2 months ago

1.0.5-alpha.5

3 months ago

1.0.5-alpha.4

4 months ago

1.0.5-alpha.2

4 months ago

1.0.5-alpha.3

4 months ago

1.0.5-alpha.0

5 months ago

1.0.5-alpha.1

5 months ago

1.0.4

5 months ago

1.0.3

5 months ago

1.0.2

9 months ago

1.0.1

9 months ago

1.0.0

9 months ago