From 25172ab7ddf157ea8ad443eab740770dabdf0222 Mon Sep 17 00:00:00 2001 From: Maciej Sikorski Date: Wed, 1 Nov 2023 12:01:55 +0100 Subject: [PATCH] feat: typed results Add Query and Command classes. Thenks to that if they have a generic type we can assign it to one of its props and infer the return type of the handler using this property. --- src/command-bus.ts | 3 +++ .../commands/command-bus.interface.ts | 2 ++ .../commands/command-handler.interface.ts | 17 +++++++++++++---- src/interfaces/commands/command-result.type.ts | 4 ++++ src/interfaces/commands/command.ts | 5 +++++ src/interfaces/index.ts | 4 ++++ .../queries/query-handler.interface.ts | 14 +++++++++++++- src/interfaces/queries/query-result.type.ts | 7 +++++++ src/interfaces/queries/query.ts | 5 +++++ src/query-bus.ts | 3 +++ 10 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 src/interfaces/commands/command-result.type.ts create mode 100644 src/interfaces/commands/command.ts create mode 100644 src/interfaces/queries/query-result.type.ts create mode 100644 src/interfaces/queries/query.ts diff --git a/src/command-bus.ts b/src/command-bus.ts index f14c4329..ee3e5124 100644 --- a/src/command-bus.ts +++ b/src/command-bus.ts @@ -8,6 +8,7 @@ import { import { CommandHandlerNotFoundException } from './exceptions/command-not-found.exception'; import { DefaultCommandPubSub } from './helpers/default-command-pubsub'; import { InvalidCommandHandlerException } from './index'; +import { Command } from './interfaces/commands/command'; import { CommandMetadata } from './interfaces/commands/command-metadata.interface'; import { ICommand, @@ -54,6 +55,8 @@ export class CommandBus * @param command The command to execute. * @returns A promise that, when resolved, will contain the result returned by the command's handler. */ + execute(query: Command): Promise; + execute(command: T): Promise; execute(command: T): Promise { const commandId = this.getCommandId(command); const handler = this.handlers.get(commandId); diff --git a/src/interfaces/commands/command-bus.interface.ts b/src/interfaces/commands/command-bus.interface.ts index a58677f6..9da3ac48 100644 --- a/src/interfaces/commands/command-bus.interface.ts +++ b/src/interfaces/commands/command-bus.interface.ts @@ -1,3 +1,4 @@ +import { Command } from './command'; import { ICommand } from './command.interface'; /** @@ -8,5 +9,6 @@ export interface ICommandBus { * Executes a command. * @param command The command to execute. */ + execute(query: Command): Promise; execute(command: T): Promise; } diff --git a/src/interfaces/commands/command-handler.interface.ts b/src/interfaces/commands/command-handler.interface.ts index 343f28c3..7d8713e3 100644 --- a/src/interfaces/commands/command-handler.interface.ts +++ b/src/interfaces/commands/command-handler.interface.ts @@ -1,13 +1,22 @@ +import { Command } from './command'; import { ICommand } from './command.interface'; /** * Represents a command handler. * Command handlers are used to execute commands. */ -export interface ICommandHandler< - TCommand extends ICommand = any, - TResult = any, -> { +export type ICommandHandler< + CommandType extends ICommand = any, + TRes = any, +> = CommandType extends Command + ? BaseICommandHandler + : BaseICommandHandler; + +/** + * Basic interface for CommandHandlers + * Can be used for both: inferred and declared return types + */ +interface BaseICommandHandler { /** * Executes a command. * @param command The command to execute. diff --git a/src/interfaces/commands/command-result.type.ts b/src/interfaces/commands/command-result.type.ts new file mode 100644 index 00000000..d878c452 --- /dev/null +++ b/src/interfaces/commands/command-result.type.ts @@ -0,0 +1,4 @@ +import { Command } from './command'; + +export type CommandResult> = + CommandT extends Command ? ResultT : never; diff --git a/src/interfaces/commands/command.ts b/src/interfaces/commands/command.ts new file mode 100644 index 00000000..ab5c7e28 --- /dev/null +++ b/src/interfaces/commands/command.ts @@ -0,0 +1,5 @@ +import { ICommand } from './command.interface'; + +export class Command implements ICommand { + resultType$e1ca39fa!: T; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index a9df2105..36717b0e 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,6 +1,8 @@ +export * from './commands/command'; export * from './commands/command-bus.interface'; export * from './commands/command-handler.interface'; export * from './commands/command-publisher.interface'; +export * from './commands/command-result.type'; export * from './commands/command.interface'; export * from './events/event-bus.interface'; export * from './events/event-handler.interface'; @@ -9,9 +11,11 @@ export * from './events/event.interface'; export * from './events/message-source.interface'; export * from './exceptions/unhandled-exception-info.interface'; export * from './exceptions/unhandled-exception-publisher.interface'; +export * from './queries/query'; export * from './queries/query-bus.interface'; export * from './queries/query-handler.interface'; export * from './queries/query-publisher.interface'; export * from './queries/query-result.interface'; +export * from './queries/query-result.type'; export * from './queries/query.interface'; export * from './saga.type'; diff --git a/src/interfaces/queries/query-handler.interface.ts b/src/interfaces/queries/query-handler.interface.ts index 612be8de..87b6c3dd 100644 --- a/src/interfaces/queries/query-handler.interface.ts +++ b/src/interfaces/queries/query-handler.interface.ts @@ -1,9 +1,21 @@ +import { Query } from './query'; import { IQuery } from './query.interface'; /** * Represents a query handler. */ -export interface IQueryHandler { +export type IQueryHandler< + QueryType extends IQuery = any, + TRes = any, +> = QueryType extends Query + ? BaseIQueryHandler + : BaseIQueryHandler; + +/** + * Basic interface for QueryHandlers + * Can be used for both: inferred and declared return types + */ +interface BaseIQueryHandler { /** * Executes a query. * @param query The query to execute. diff --git a/src/interfaces/queries/query-result.type.ts b/src/interfaces/queries/query-result.type.ts new file mode 100644 index 00000000..797ee040 --- /dev/null +++ b/src/interfaces/queries/query-result.type.ts @@ -0,0 +1,7 @@ +import { Query } from './query'; + +export type QueryResult> = QueryT extends Query< + infer ResultT +> + ? ResultT + : never; diff --git a/src/interfaces/queries/query.ts b/src/interfaces/queries/query.ts new file mode 100644 index 00000000..d0515968 --- /dev/null +++ b/src/interfaces/queries/query.ts @@ -0,0 +1,5 @@ +import { IQuery } from './query.interface'; + +export class Query implements IQuery { + resultType$f9fbca36!: T; +} diff --git a/src/query-bus.ts b/src/query-bus.ts index 39666bb3..f745dafa 100644 --- a/src/query-bus.ts +++ b/src/query-bus.ts @@ -12,6 +12,7 @@ import { IQueryPublisher, IQueryResult, } from './interfaces'; +import { Query } from './interfaces/queries/query'; import { QueryMetadata } from './interfaces/queries/query-metadata.interface'; import { ObservableBus } from './utils/observable-bus'; @@ -53,6 +54,8 @@ export class QueryBus * Executes a query. * @param query The query to execute. */ + execute(query: Query): Promise; + execute(query: T): Promise; async execute( query: T, ): Promise {