Skip to content

Commit

Permalink
Create third-party.mediatr
Browse files Browse the repository at this point in the history
  • Loading branch information
ycanardeau committed Jul 2, 2024
1 parent 261f636 commit 72216eb
Show file tree
Hide file tree
Showing 26 changed files with 568 additions and 1 deletion.
11 changes: 11 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ module.exports = {
type: '@yohira/third-party.inversify',
pattern: 'packages/third-party.inversify/*',
},
{
type: '@yohira/third-party.mediatr',
pattern: 'packages/third-party.mediatr/*',
},
{
type: '@yohira/third-party.ts-results',
pattern: 'packages/third-party.ts-results/*',
Expand Down Expand Up @@ -831,6 +835,13 @@ module.exports = {
from: '@yohira/third-party.inversify',
allow: [],
},
{
from: '@yohira/third-party.mediatr',
allow: [
'@yohira/base',
'@yohira/extensions.dependency-injection.abstractions',
],
},
{
from: '@yohira/third-party.ts-results',
allow: [],
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export * from '@yohira/server.node';
export * from '@yohira/server.node.core';
export * from '@yohira/static-files';
export * from '@yohira/third-party.inversify';
export * from '@yohira/third-party.mediatr';
export * from '@yohira/third-party.ts-results';
5 changes: 4 additions & 1 deletion packages/base/src/StringWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { TextWriter } from './TextWriter';

// https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/IO/StringWriter.cs,fd76db5d443fe076,references
export class StringWriter extends TextWriter {
private readonly sb = new StringBuilder();
private isOpen = true;

constructor(private readonly sb: StringBuilder = new StringBuilder()) {
super();
}

[Symbol.dispose](): void {
this.isOpen = false;
super[Symbol.dispose]();
Expand Down
14 changes: 14 additions & 0 deletions packages/base/src/TextWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,18 @@ export abstract class TextWriter implements Disposable {
this.writeChar(buffer[index + i]);
}
}

writeString(value: string | undefined): void {
if (value !== undefined) {
const chars = value.split('').map((char) => char.charCodeAt(0));
this.writeChars(chars, 0, chars.length);
}
}

writeLine(value: string | undefined): void {
if (value !== undefined) {
this.writeString(value);
}
this.writeString('\n');
}
}
11 changes: 11 additions & 0 deletions packages/third-party.mediatr/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export * from './mediatr/extensions-di/ServiceCollectionExtensions';
export * from './mediatr/notification-publishers/ForOfAwaitPublisher';
export * from './mediatr/notification-publishers/PromiseAllPublisher';
export * from './mediatr/registrar/ServiceRegistrar';
export * from './mediatr/IMediator';
export * from './mediatr/INotificationHandler';
export * from './mediatr/IPublisher';
export * from './mediatr/IRequestHandler';
export * from './mediatr/ISender';
export * from './mediatr.contracts/INotification';
export * from './mediatr.contracts/IRequest';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// https://github.com/jbogard/MediatR/blob/a533cf288a50201aba3087ee7b521ec37f4337fd/src/MediatR.Contracts/INotification.cs#L6
export interface INotification {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// https://github.com/jbogard/MediatR/blob/761fb0b1b420f5a8c2cb4a751617dce7ab9c3fe3/src/MediatR.Contracts/IRequest.cs#L17
export interface IBaseRequest {}

// https://github.com/jbogard/MediatR/blob/761fb0b1b420f5a8c2cb4a751617dce7ab9c3fe3/src/MediatR.Contracts/IRequest.cs#L12
export interface IRequest<TResponse> extends IBaseRequest {}
6 changes: 6 additions & 0 deletions packages/third-party.mediatr/src/mediatr/IMediator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IPublisher } from './IPublisher';
import { ISender } from './ISender';

export const IMediator = Symbol.for('IMediator');
// https://github.com/jbogard/MediatR/blob/c4f1a918b4cb90030f2df0878f5930b9ed7baf16/src/MediatR/IMediator.cs#L6
export interface IMediator extends ISender, IPublisher {}
19 changes: 19 additions & 0 deletions packages/third-party.mediatr/src/mediatr/INotificationHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { INotification } from '../mediatr.contracts/INotification';

// https://github.com/jbogard/MediatR/blob/c4f1a918b4cb90030f2df0878f5930b9ed7baf16/src/MediatR/INotificationHandler.cs#L10
export interface INotificationHandler<TNotification extends INotification> {
handle(notification: TNotification): Promise<void>;
}

// https://github.com/jbogard/MediatR/blob/c4f1a918b4cb90030f2df0878f5930b9ed7baf16/src/MediatR/INotificationHandler.cs#L25
export abstract class NotificationHandler<TNotification extends INotification>
implements INotificationHandler<TNotification>
{
protected abstract handleCore(notification: TNotification): void;

handle(notification: TNotification): Promise<void> {
this.handleCore(notification);

return Promise.resolve();
}
}
11 changes: 11 additions & 0 deletions packages/third-party.mediatr/src/mediatr/INotificationPublisher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { INotification } from '../mediatr.contracts/INotification';
import { NotificationHandlerExecutor } from './NotificationHandlerExecutor';

export const INotificationPublisher = Symbol.for('INotificationPublisher');
// https://github.com/jbogard/MediatR/blob/838a8e12b62ee95f2f1caa503d282a8d9bce6047/src/MediatR/INotificationPublisher.cs#L7
export interface INotificationPublisher {
publish(
handlerExecutors: NotificationHandlerExecutor[],
notification: INotification,
): Promise<void>;
}
8 changes: 8 additions & 0 deletions packages/third-party.mediatr/src/mediatr/IPublisher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { INotification } from '../mediatr.contracts/INotification';

// https://github.com/jbogard/MediatR/blob/c4f1a918b4cb90030f2df0878f5930b9ed7baf16/src/MediatR/IPublisher.cs#L9
export interface IPublisher {
publish<TNotification extends INotification>(
notification: TNotification,
): Promise<void>;
}
9 changes: 9 additions & 0 deletions packages/third-party.mediatr/src/mediatr/IRequestHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IRequest } from '../mediatr.contracts/IRequest';

// https://github.com/jbogard/MediatR/blob/761fb0b1b420f5a8c2cb4a751617dce7ab9c3fe3/src/MediatR/IRequestHandler.cs#L11
export interface IRequestHandler<
TRequest extends IRequest<TResponse>,
TResponse,
> {
handle(request: TRequest): Promise<TResponse>;
}
6 changes: 6 additions & 0 deletions packages/third-party.mediatr/src/mediatr/ISender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IRequest } from '../mediatr.contracts/IRequest';

// https://github.com/jbogard/MediatR/blob/761fb0b1b420f5a8c2cb4a751617dce7ab9c3fe3/src/MediatR/ISender.cs#L10
export interface ISender {
send<TResponse>(request: IRequest<TResponse>): Promise<TResponse>;
}
70 changes: 70 additions & 0 deletions packages/third-party.mediatr/src/mediatr/Mediatr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Ctor, IServiceProvider, getOrAdd } from '@yohira/base';
import { inject } from '@yohira/extensions.dependency-injection.abstractions';

import { INotification } from '../mediatr.contracts/INotification';
import { IRequest } from '../mediatr.contracts/IRequest';
import { IMediator } from './IMediator';
import { INotificationPublisher } from './INotificationPublisher';
import { NotificationHandlerExecutor } from './NotificationHandlerExecutor';
import {
NotificationHandlerWrapper,
NotificationHandlerWrapperImpl,
} from './wrappers/NotificationHandlerWrapper';

// https://github.com/jbogard/MediatR/blob/f4de8196adafd37faff274ce819ada93a3d7531b/src/MediatR/Mediator.cs#L16
export class Mediator implements IMediator {
private readonly notificationHandlers = new Map<
Ctor<INotification>,
NotificationHandlerWrapper
>();

constructor(
@inject(IServiceProvider)
private readonly serviceProvider: IServiceProvider,
@inject(INotificationPublisher)
private readonly publisher: INotificationPublisher,
) {}

send<TResponse>(request: IRequest<TResponse>): Promise<TResponse> {
throw new Error('Method not implemented.');
}

/**
* Override in a derived class to control how the tasks are awaited. By default the implementation calls the {@link INotificationPublisher}.
* @param handlerExecutors Enumerable of tasks representing invoking each notification handler
* @param notification The notification being published
* @returns A task representing invoking all handlers
*/
protected publishCore(
handlerExecutors: NotificationHandlerExecutor[],
notification: INotification,
): Promise<void> {
return this.publisher.publish(handlerExecutors, notification);
}

private publishNotification(notification: INotification): Promise<void> {
const handler = getOrAdd(
this.notificationHandlers,
notification.constructor as Ctor<INotification>,
(notificationCtor) => {
const wrapper = new NotificationHandlerWrapperImpl(
notificationCtor,
);
return wrapper;
},
);

return handler.handle(
notification,
this.serviceProvider,
(executors, notification) =>
this.publishCore(executors, notification),
);
}

publish<TNotification extends INotification>(
notification: TNotification,
): Promise<void> {
return this.publishNotification(notification);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { INotification } from '../mediatr.contracts/INotification';

// https://github.com/jbogard/MediatR/blob/838a8e12b62ee95f2f1caa503d282a8d9bce6047/src/MediatR/NotificationHandlerExecutor.cs#L7
export class NotificationHandlerExecutor {
constructor(
readonly handlerInstance: object,
readonly handlerCallback: (
notification: INotification,
) => Promise<void>,
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Ctor } from '@yohira/base';
import { ServiceLifetime } from '@yohira/extensions.dependency-injection.abstractions';

import { INotificationPublisher } from '../INotificationPublisher';
import { Mediator } from '../Mediatr';
import { ForOfAwaitPublisher } from '../notification-publishers/ForOfAwaitPublisher';

// https://github.com/jbogard/MediatR/blob/f4de8196adafd37faff274ce819ada93a3d7531b/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs#L9
export class MediatRServiceConfig {
/**
* Mediator implementation type to register. Default is {@link Mediator}
*/
mediatorImplCtor: Ctor<object> = Mediator;
/**
* Strategy for publishing notifications. Defaults to {@link ForeachAwaitPublisher}
*/
notificationPublisher: INotificationPublisher = new ForOfAwaitPublisher();
/**
* Type of notification publisher strategy to register. If set, overrides {@link NotificationPublisher}
*/
notificationPublisherCtor?: Ctor<object>;
/**
* Service lifetime to register services under. Default value is {@link ServiceLifetime.Transient}
*/
lifetime: ServiceLifetime = ServiceLifetime.Transient;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { IServiceCollection } from '@yohira/extensions.dependency-injection.abstractions';

import { addRequiredServices } from '../registrar/ServiceRegistrar';
import { MediatRServiceConfig } from './MediatRServiceConfig';

function addMediatRCore(
services: IServiceCollection,
config: MediatRServiceConfig,
): IServiceCollection {
// TODO: addMediatRClasses(services, config);

addRequiredServices(services, config);

return services;
}

// https://github.com/jbogard/MediatR/blob/f28cdc331faea401479d8e765b6f4dd536b2b085/src/MediatR/MicrosoftExtensionsDI/ServiceCollectionExtensions.cs#L26
export function addMediatR(
services: IServiceCollection,
config: (serviceConfig: MediatRServiceConfig) => void,
): IServiceCollection {
const serviceConfig = new MediatRServiceConfig();

config(serviceConfig);

return addMediatRCore(services, serviceConfig);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { INotification } from '../../mediatr.contracts/INotification';
import { INotificationPublisher } from '../INotificationPublisher';
import { NotificationHandlerExecutor } from '../NotificationHandlerExecutor';

// https://github.com/jbogard/MediatR/blob/838a8e12b62ee95f2f1caa503d282a8d9bce6047/src/MediatR/NotificationPublishers/ForeachAwaitPublisher.cs#L17
export class ForOfAwaitPublisher implements INotificationPublisher {
async publish(
handlerExecutors: NotificationHandlerExecutor[],
notification: INotification,
): Promise<void> {
for (const handler of handlerExecutors) {
await handler.handlerCallback(notification);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { INotification } from '../../mediatr.contracts/INotification';
import { INotificationPublisher } from '../INotificationPublisher';
import { NotificationHandlerExecutor } from '../NotificationHandlerExecutor';

// https://github.com/jbogard/MediatR/blob/40afa9fc6ec7ddcfc8fac2584861916fb571f817/src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs#L20
export class PromiseAllPublisher implements INotificationPublisher {
async publish(
handlerExecutors: NotificationHandlerExecutor[],
notification: INotification,
): Promise<void> {
const tasks = handlerExecutors.map((handler) =>
handler.handlerCallback(notification),
);

await Promise.all(tasks);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
IServiceCollection,
ServiceDescriptor,
ServiceLifetime,
tryAddServiceDescriptor,
} from '@yohira/extensions.dependency-injection.abstractions';

import { IMediator } from '../IMediator';
import { INotificationPublisher } from '../INotificationPublisher';
import { MediatRServiceConfig } from '../extensions-di/MediatRServiceConfig';

// https://github.com/jbogard/MediatR/blob/43fb46f39020ab4880fefe75fa2315351f347742/src/MediatR/Registration/ServiceRegistrar.cs#L300
export function addRequiredServices(
services: IServiceCollection,
serviceConfig: MediatRServiceConfig,
): void {
tryAddServiceDescriptor(
services,
ServiceDescriptor.fromCtor(
serviceConfig.lifetime,
IMediator,
serviceConfig.mediatorImplCtor,
),
);
// TODO

const notificationPublisherServiceDescriptor =
serviceConfig.notificationPublisherCtor !== undefined
? ServiceDescriptor.fromCtor(
serviceConfig.lifetime,
INotificationPublisher,
serviceConfig.notificationPublisherCtor,
)
: ServiceDescriptor.fromInstance(
ServiceLifetime.Singleton,
INotificationPublisher,
serviceConfig.notificationPublisher,
);

tryAddServiceDescriptor(services, notificationPublisherServiceDescriptor);
}
Loading

0 comments on commit 72216eb

Please sign in to comment.