import { IEvent, IEventClass, IRequest, IRequestClass } from "@uxland/harmonix"; import { Mediator, mediatorSettings, notificationHandler, requestHandler } from "mediatr-ts"; import { BROKER_EVENTS } from "./broker-events"; import { type PrimariaBroker, type BrokerDisposableHandler } from "./primaria-broker"; type messageHandler = (payload: unknown) => unknown | Promise; const disposableFactory = (handler: any) => { return { dispose: () => { mediatorSettings.resolver.remove(handler.name); const mappingIndex = mediatorSettings.dispatcher.notifications._mappings.findIndex( (mapping) => mapping.handler.name === handler.name, ); mappingIndex > -1 && mediatorSettings.dispatcher.notifications._mappings.splice(mappingIndex, 1); }, }; }; class Broker implements PrimariaBroker { public readonly events = BROKER_EVENTS; private mediator: Mediator; constructor() { this.mediator = new Mediator(); mediatorSettings.resolver.clear(); } // biome-ignore lint/complexity/noBannedTypes: private eventConstructorMap: Map = new Map(); // biome-ignore lint/complexity/noBannedTypes: private requestConstructorMap: Map = new Map(); send, TResponse>(message: TRequest): Promise; send(requestName: string, payload: TPayload): Promise; send(request: IRequest | string, payload?: unknown): Promise { const eventPayload = typeof request === "string" ? this.getRequest(request as string, payload) : request; return this.mediator.send(eventPayload); } publish(event: TEvent): Promise; publish(eventName: string, payload: TPayload): Promise; publish(event: string | IEvent, payload?: unknown): Promise { const eventPayload = typeof event === "string" ? this.getEvent(event as string, payload) : event; const classConstructor = eventPayload.constructor || Object.getPrototypeOf(eventPayload).constructor; const handlerSubscriptions = mediatorSettings.dispatcher.notifications._mappings.filter( (m) => m.notification === classConstructor, ); if (!handlerSubscriptions.length) return Promise.resolve(); return this.mediator.publish(eventPayload); } subscribe( event: TEvent, handler: (message: TEvent) => void, ): BrokerDisposableHandler; subscribe( eventName: string, handler: (message: TPayload) => void, ): BrokerDisposableHandler; subscribe(event: string | IEventClass, handler: messageHandler): BrokerDisposableHandler { const eventType = typeof event === "function" ? event : this.getEventType(event as string); const wrappedHandler = createDynamicEventHandler(handler); notificationHandler(eventType)(wrappedHandler); return disposableFactory(wrappedHandler); } registerRequest, TResponse>( request: TRequest, handler: (message: TRequest) => TResponse, ): BrokerDisposableHandler; registerRequest( requestName: string, handler: (message: TPayload) => TResponse, ): BrokerDisposableHandler; registerRequest( request: string | IRequestClass, handler: messageHandler, ): BrokerDisposableHandler { const requestType = typeof request === "function" ? request : this.getRequestType(request as string); const wrappedHandler = createDynamicRequestHandler(handler); requestHandler(requestType)(wrappedHandler); return disposableFactory(wrappedHandler); } private getEvent(eventName: string, payload: unknown): IEvent { const eventConstructor = this.getEventType(eventName); return new eventConstructor(payload) as IEvent; } private getRequest(requestName: string, payload: unknown): IRequest { const requestConstructor = this.getRequestType(requestName); return new requestConstructor(payload) as IRequest; } private getEventType(eventName: string): IEventClass { if (!this.eventConstructorMap.has(eventName)) { const clazz = createDynamicEventClass(eventName); this.eventConstructorMap.set(eventName, clazz); } return this.eventConstructorMap.get(eventName) as IEventClass; } private getRequestType(requestName: string): IRequestClass { if (!this.requestConstructorMap.has(requestName)) { const clazz = createDynamicRequestClass(requestName); this.requestConstructorMap.set(requestName, clazz); } return this.requestConstructorMap.get(requestName) as IRequestClass; } } const createDynamicEventClass = (eventName: string) => { return new Function(`return class Event_${eventName} { constructor(payload) { this.payload = payload; } }`)(); }; const createDynamicRequestClass = (requestName: string) => { return new Function(`return class Request_${requestName} { constructor(payload) { Object.assign(this, payload); } }`)(); }; const usedSuffixes = new Set(); function generateUniqueRandomSuffix() { let suffix; do { suffix = Math.floor(Math.random() * 10000); } while (usedSuffixes.has(suffix)); usedSuffixes.add(suffix); return suffix; } const createDynamicRequestHandler = (handler: messageHandler) => { const className = `RequestHandler_${generateUniqueRandomSuffix()}`; return new Function( "handler", `return class ${className}{ handle(notification){ const handlerResult = handler({...notification}); return handlerResult instanceof Promise ? handlerResult : Promise.resolve(handlerResult); } }`, )(handler); }; const createDynamicEventHandler = (handler: messageHandler) => { const className = `EventHandler_${generateUniqueRandomSuffix()}`; return new Function( "handler", `return class ${className}{ handle(notification){ const payload = notification.payload !== undefined ? notification.payload : {...notification}; const handlerResult = handler(payload); return handlerResult instanceof Promise ? handlerResult : Promise.resolve(handlerResult); } }`, )(handler); }; export const createBroker = (): PrimariaBroker => new Broker();