import { injectAll, injectable } from "@codemation/core"; import type { Command } from "../../application/bus/Command"; import type { CommandBus } from "../../application/bus/CommandBus"; import type { CommandHandler } from "../../application/bus/CommandHandler"; import { ApplicationTokens } from "../../applicationTokens"; import { commandHandlerMetadataKey } from "./HandlesCommandRegistry"; type CommandType = abstract new (...args: any[]) => Command; @injectable() export class InMemoryCommandBus implements CommandBus { private readonly handlersByCommandType: ReadonlyMap, unknown>>; constructor( @injectAll(ApplicationTokens.CommandHandler) handlers: ReadonlyArray, unknown>>, ) { this.handlersByCommandType = this.createHandlersByCommandType(handlers); } async execute(command: Command): Promise { const handler = this.handlersByCommandType.get(command.constructor as CommandType); if (!handler) { throw new Error(`No command handler registered for ${command.constructor.name}`); } return (await handler.execute(command as Command)) as TResult; } private createHandlersByCommandType( handlers: ReadonlyArray, unknown>>, ): ReadonlyMap, unknown>> { const handlersByCommandType = new Map, unknown>>(); for (const handler of handlers) { const commandType = Reflect.getMetadata(commandHandlerMetadataKey, handler.constructor) as | CommandType | undefined; if (!commandType) { throw new Error(`Command handler ${handler.constructor.name} is missing @HandlesCommand metadata.`); } if (handlersByCommandType.has(commandType)) { throw new Error(`Duplicate command handler registered for ${commandType.name}.`); } handlersByCommandType.set(commandType, handler); } return handlersByCommandType; } }