Porting to typescript of the famous MediatR for C#. It work out-of-the-box with an internal resolver, however it can be 'plugged in' with dependency injection frameworks like Inversify.
Below the requestHandler
pattern with internal resolver and with the inversify library
// request.ts -> Define the request
class Request implements IRequest<string> {
name: string;
}
// handlertest.ts -> Add the attribute to the request handler
@requestHandler(Request)
class HandlerTest implements IRequestHandler<Request, string> {
handle(value: Request): Promise<string> {
return Promise.resolve(`Value passed ${value.name}`);
}
}
// main.ts -> Instantiate the mediator
const mediator = new Mediator();
// Create the request
const r = new Request();
r.name = "Foo";
// Send the command
const result = await mediator.send<string>(r);
// result = "Value passed Foo"
import { Mediator } from "@mediatr-ts";
const result: string[] = [];
// The notification class
class Ping implements INotification {
constructor(public value?: string){}
}
// The notification handler
@notificationHandler(Ping)
class Pong1 implements INotificationHandler<Ping> {
async handle(notification: Ping): Promise<void> {
result.push(notification.value);
}
}
const mediator = new Mediator();
mediator.publish(new Ping(message));
// result: [ "Foo" ]
By default, the notification handlers will run in the order that they are loaded in. This might not be desirable, since it depends on the order of the imports. To change the order, you can set it explicitly.
import { mediatorSettings, Mediator } from "@mediatr-ts";
const result: string[] = [];
// The notification class
class Ping implements INotification {
constructor(public value?: string){}
}
// The notification handler
@notificationHandler(Ping)
class Pong2 implements INotificationHandler<Ping> {
async handle(notification: Ping): Promise<void> {
result.push(notification.value);
}
}
// The notification handler
@notificationHandler(Ping)
class Pong1 implements INotificationHandler<Ping> {
async handle(notification: Ping): Promise<void> {
result.push(notification.value);
}
}
const mediator = new Mediator();
mediator.publish(new Ping(message));
// result: [ "Foo" ]
// Set the order of the pipeline behaviors. PipelineBehaviorTest2 will be executed first, and then PipelineBehaviorTest1.
mediatorSettings.dispatcher.notifications.setOrder(Ping, [
Pong2,
Pong1
]);
const mediator = new Mediator();
//...
class Request {
name?: string;
}
@requestHandler(Request)
class HandlerTest implements IRequestHandler<Request, string> {
handle(value: Request): Promise<string> {
return Promise.resolve(`Value passed ${value.name}`);
}
}
@pipelineBehavior()
class PipelineBehaviorTest1 implements IPipelineBehavior {
async handle(request: IRequest<unknown>, next: () => unknown): Promise<unknown> {
if(request instanceof Request) {
request.name += " with stuff 1";
}
let result = await next();
if(typeof result === "string") {
result += " after 1";
}
return result;
}
}
@pipelineBehavior()
class PipelineBehaviorTest2 implements IPipelineBehavior {
async handle(request: IRequest<unknown>, next: () => unknown): Promise<unknown> {
if(request instanceof Request) {
request.name += " with stuff 2";
}
let result = await next();
if(typeof result === "string") {
result += " after 2";
}
return result;
}
}
const r = new Request();
r.name = "Foo";
const mediator = new Mediator();
const result = await mediator.send(r);
// result will be "Value passed Foo with stuff 2 with stuff 1 after 1 after 2"
By default, the pipeline behaviors will run in the order that they are loaded in. This might not be desirable, since it depends on the order of the imports. To change the order, you can set it explicitly.
import { mediatorSettings, Mediator } from "@mediatr-ts";
@pipelineBehavior()
class PipelineBehaviorTest1 implements IPipelineBehavior {
async handle(request: IRequest<unknown>, next: () => unknown): Promise<unknown> {
return result;
}
}
@pipelineBehavior()
class PipelineBehaviorTest2 implements IPipelineBehavior {
async handle(request: IRequest<unknown>, next: () => unknown): Promise<unknown> {
return result;
}
}
// Set the order of the pipeline behaviors. PipelineBehaviorTest2 will be executed first, and then PipelineBehaviorTest1.
mediatorSettings.dispatcher.behaviors.setOrder([
PipelineBehaviorTest2,
PipelineBehaviorTest1
]);
const mediator = new Mediator();
//...
At the very beginning of your app you MUST setup the resolver with Inversify, or at least BEFORE using the @requestHandler
attribute and/or the Mediator
class.
import { Container } from "inversify";
import { mediatorSettings, Mediator } from "@mediatr-ts";
const container = new Container();
// inversify.resolver.ts -> Implement the resolver
class InversifyResolver implements IResolver {
resolve<T>(name: string): <T> {
return container.get(name);
}
add(name: string, instance: Function): void {
container.bind(name).to(instance as any);
}
remove(name: string): void {
// not necessary- can be blank, never called by the lib, for debugging / testing only
container.unbind(name);
}
clear(): void {
// not necessary- can be blank, never called by the lib, for debugging / testing only
container.unbindAll();
}
}
// Set the resolver of MediatR-TS
mediatorSettings.resolver = new InversifyResolver();
// You can later configure the inversify container
interface IWarrior {
fight(): string;
}
const TYPES = {
IWarrior: Symbol.for("IWarrior"),
};
@injectable()
class Ninja implements IWarrior {
fight(): string {
return "ninja fight";
}
}
container.bind<IWarrior>(TYPES.IWarrior).to(Ninja);
// The request object
class Request implements IRequest<number> {
public thenumber: number;
constructor(thenumber: number) {
this.thenumber = thenumber;
}
}
// Decorate the handler request with Handler and injectable attribute, notice the warrior property
@requestHandler(Request)
@injectable()
class HandlerRequest implements IRequestHandler<Request, string> {
@inject(TYPES.IWarrior)
public warrior: IWarrior; // Instantiated by the inversify
public handle(value: Request): Promise<string> {
return Promise.resolve(`We has ${value.thenumber} ${this.warrior.fight()}`);
}
}
const mediator = new Mediator();
const result = await mediator.send<string>(new Request(99));
// result => "We has 99 ninja fight"