@hg-ts/cqrs 0.5.16 → 0.5.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/abstracts/base.command-handler.ts +6 -0
- package/src/abstracts/base.command.ts +5 -0
- package/src/abstracts/base.executor.test-suite.ts +63 -0
- package/src/abstracts/base.executor.ts +46 -0
- package/src/abstracts/base.query-handler.ts +6 -0
- package/src/abstracts/base.query.ts +5 -0
- package/src/abstracts/index.ts +7 -0
- package/src/cqrs.module.ts +130 -0
- package/src/decorators/command-handler.decorator.ts +14 -0
- package/src/decorators/index.ts +4 -0
- package/src/decorators/query-handler.decorator.ts +14 -0
- package/src/exceptions/handler-already-added.exception.ts +8 -0
- package/src/exceptions/handler-not-found.exception.ts +8 -0
- package/src/exceptions/index.ts +2 -0
- package/src/executors/command.executor.ts +18 -0
- package/src/executors/index.ts +2 -0
- package/src/executors/query.executor.ts +18 -0
- package/src/index.ts +10 -0
- package/src/tests/command-executor.test.ts +31 -0
- package/src/tests/commands/index.ts +2 -0
- package/src/tests/commands/test.command.ts +11 -0
- package/src/tests/commands/unknown.command.ts +11 -0
- package/src/tests/cqrs-test.module.ts +7 -0
- package/src/tests/cqrs.module.test.ts +76 -0
- package/src/tests/queries/index.ts +2 -0
- package/src/tests/queries/test.query.ts +11 -0
- package/src/tests/queries/unknown.query.ts +11 -0
- package/src/tests/query-executor.test.ts +30 -0
- package/src/tests/test-command.handler.ts +17 -0
- package/src/tests/test-query.handler.ts +18 -0
- package/src/types.ts +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hg-ts/cqrs",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.18",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"test:dev": "vitest watch"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@hg-ts-config/typescript": "0.5.
|
|
22
|
-
"@hg-ts/exception": "0.5.
|
|
23
|
-
"@hg-ts/linter": "0.5.
|
|
24
|
-
"@hg-ts/tests": "0.5.
|
|
25
|
-
"@hg-ts/types": "0.5.
|
|
21
|
+
"@hg-ts-config/typescript": "0.5.18",
|
|
22
|
+
"@hg-ts/exception": "0.5.18",
|
|
23
|
+
"@hg-ts/linter": "0.5.18",
|
|
24
|
+
"@hg-ts/tests": "0.5.18",
|
|
25
|
+
"@hg-ts/types": "0.5.18",
|
|
26
26
|
"@nestjs/common": "11.1.0",
|
|
27
27
|
"@nestjs/core": "11.1.0",
|
|
28
28
|
"@nestjs/testing": "11.1.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"vitest": "4.0.14"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@hg-ts/exception": "0.5.
|
|
41
|
+
"@hg-ts/exception": "0.5.18",
|
|
42
42
|
"@nestjs/common": "*",
|
|
43
43
|
"@nestjs/core": "*",
|
|
44
44
|
"reflect-metadata": "*",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { UnknownException } from '@hg-ts/exception';
|
|
2
|
+
import {
|
|
3
|
+
expect,
|
|
4
|
+
ExpectException,
|
|
5
|
+
Suite,
|
|
6
|
+
Test,
|
|
7
|
+
} from '@hg-ts/tests';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
HandlerAlreadyAddedException,
|
|
11
|
+
HandlerNotFoundException,
|
|
12
|
+
} from '../exceptions/index.js';
|
|
13
|
+
import {
|
|
14
|
+
BaseCommandOrQuery,
|
|
15
|
+
CommonHandler,
|
|
16
|
+
} from '../types.js';
|
|
17
|
+
import { BaseExecutor } from './base.executor.js';
|
|
18
|
+
|
|
19
|
+
export const INPUT_FOR_EXCEPTION = 'Some special string that will throw an exception';
|
|
20
|
+
|
|
21
|
+
export abstract class BaseExecutorTestSuite<CommandOrQuery extends BaseCommandOrQuery> extends Suite {
|
|
22
|
+
private executor: BaseExecutor<CommandOrQuery>;
|
|
23
|
+
|
|
24
|
+
@Test()
|
|
25
|
+
public async successExecute(): Promise<void> {
|
|
26
|
+
const expectedResult = String(Math.random());
|
|
27
|
+
|
|
28
|
+
this.executor['addHandler'](this.getCommandOrQueryCtor(), this.getHandler());
|
|
29
|
+
|
|
30
|
+
const result = await this.executor.execute(this.getCommandOrQuery(expectedResult));
|
|
31
|
+
|
|
32
|
+
expect(result).toBe(expectedResult);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@ExpectException(HandlerAlreadyAddedException)
|
|
36
|
+
@Test()
|
|
37
|
+
public async multipleHandlers(): Promise<void> {
|
|
38
|
+
this.executor['addHandler'](this.getCommandOrQueryCtor(), this.getHandler());
|
|
39
|
+
this.executor['addHandler'](this.getCommandOrQueryCtor(), this.getHandler());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@ExpectException(HandlerNotFoundException)
|
|
43
|
+
@Test()
|
|
44
|
+
public async handlerNotFound(): Promise<void> {
|
|
45
|
+
await this.executor.execute(this.getCommandOrQuery(''));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@ExpectException(UnknownException)
|
|
49
|
+
@Test()
|
|
50
|
+
public async successPassException(): Promise<void> {
|
|
51
|
+
this.executor['addHandler'](this.getCommandOrQueryCtor(), this.getHandler());
|
|
52
|
+
await this.executor.execute(this.getCommandOrQuery(INPUT_FOR_EXCEPTION));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public override async beforeEach(): Promise<void> {
|
|
56
|
+
this.executor = this.getExecutor();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected abstract getExecutor(): BaseExecutor<CommandOrQuery>;
|
|
60
|
+
protected abstract getHandler(): CommonHandler<CommandOrQuery>;
|
|
61
|
+
protected abstract getCommandOrQueryCtor(): Class<CommandOrQuery, [string]>;
|
|
62
|
+
protected abstract getCommandOrQuery(input: string): CommandOrQuery;
|
|
63
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {
|
|
3
|
+
HandlerAlreadyAddedException,
|
|
4
|
+
HandlerNotFoundException,
|
|
5
|
+
} from '../exceptions/index.js';
|
|
6
|
+
import {
|
|
7
|
+
CommonHandler,
|
|
8
|
+
CommonResult,
|
|
9
|
+
} from '../types.js';
|
|
10
|
+
|
|
11
|
+
import { BaseCommand } from './base.command.js';
|
|
12
|
+
import { BaseQuery } from './base.query.js';
|
|
13
|
+
|
|
14
|
+
export abstract class BaseExecutor<
|
|
15
|
+
BaseCommandOrQuery extends BaseQuery<any> | BaseCommand<any>
|
|
16
|
+
> {
|
|
17
|
+
protected readonly handlers = new Map<Class<BaseCommandOrQuery>, CommonHandler<BaseCommandOrQuery>>();
|
|
18
|
+
|
|
19
|
+
public async execute<CommandOrQuery extends BaseCommandOrQuery>(
|
|
20
|
+
commandOrQuery: CommandOrQuery,
|
|
21
|
+
): Promise<CommonResult<CommandOrQuery>> {
|
|
22
|
+
return this.executeHandler(commandOrQuery);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected addHandler<CommandOrQuery extends BaseCommandOrQuery>(
|
|
26
|
+
command: Class<CommandOrQuery, any[]>,
|
|
27
|
+
handler: CommonHandler<BaseCommandOrQuery>,
|
|
28
|
+
): void {
|
|
29
|
+
const existHandler = this.handlers.get(command);
|
|
30
|
+
|
|
31
|
+
assert.ok(!existHandler, new HandlerAlreadyAddedException(command));
|
|
32
|
+
|
|
33
|
+
this.handlers.set(command, handler);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected assertHandler(
|
|
37
|
+
handler: Nullable<CommonHandler<BaseCommandOrQuery>>,
|
|
38
|
+
commandOrQuery: Class<BaseCommandOrQuery, any[]>,
|
|
39
|
+
): asserts handler {
|
|
40
|
+
assert.ok(handler, new HandlerNotFoundException(commandOrQuery));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected abstract executeHandler<CommandOrQuery extends BaseCommandOrQuery>(
|
|
44
|
+
commandOrQuery: CommandOrQuery,
|
|
45
|
+
): Promise<CommonResult<CommandOrQuery>>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './base.command.js';
|
|
2
|
+
export * from './base.query.js';
|
|
3
|
+
export * from './base.executor.js';
|
|
4
|
+
export * from './base.query-handler.js';
|
|
5
|
+
export * from './base.command-handler.js';
|
|
6
|
+
export * from './base.executor.js';
|
|
7
|
+
export * from './base.executor.test-suite.js';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { WillNeverHappenedException } from '@hg-ts/exception';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Global,
|
|
5
|
+
Inject,
|
|
6
|
+
Module,
|
|
7
|
+
OnModuleInit,
|
|
8
|
+
} from '@nestjs/common';
|
|
9
|
+
import {
|
|
10
|
+
DiscoveryModule,
|
|
11
|
+
DiscoveryService,
|
|
12
|
+
} from '@nestjs/core';
|
|
13
|
+
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import {
|
|
16
|
+
BaseCommand,
|
|
17
|
+
BaseCommandHandler,
|
|
18
|
+
BaseQuery,
|
|
19
|
+
BaseQueryHandler,
|
|
20
|
+
} from './abstracts/index.js';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
COMMAND_HANDLER_METADATA,
|
|
24
|
+
QUERY_HANDLER_METADATA,
|
|
25
|
+
} from './decorators/index.js';
|
|
26
|
+
import {
|
|
27
|
+
CommandExecutor,
|
|
28
|
+
QueryExecutor,
|
|
29
|
+
} from './executors/index.js';
|
|
30
|
+
|
|
31
|
+
@Global()
|
|
32
|
+
@Module({
|
|
33
|
+
imports: [DiscoveryModule],
|
|
34
|
+
providers: [
|
|
35
|
+
CommandExecutor,
|
|
36
|
+
QueryExecutor,
|
|
37
|
+
],
|
|
38
|
+
exports: [
|
|
39
|
+
CommandExecutor,
|
|
40
|
+
QueryExecutor,
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
export class CqrsModule implements OnModuleInit {
|
|
44
|
+
@Inject()
|
|
45
|
+
private readonly discoveryService: DiscoveryService;
|
|
46
|
+
|
|
47
|
+
@Inject()
|
|
48
|
+
private readonly commandExecutor: CommandExecutor;
|
|
49
|
+
|
|
50
|
+
@Inject()
|
|
51
|
+
private readonly queryExecutor: QueryExecutor;
|
|
52
|
+
|
|
53
|
+
public async onModuleInit(): Promise<void> {
|
|
54
|
+
const providers = this.discoveryService.getProviders()
|
|
55
|
+
.filter(wrapper => wrapper.instance)
|
|
56
|
+
.map(wrapper => wrapper.instance);
|
|
57
|
+
|
|
58
|
+
providers.forEach(provider => {
|
|
59
|
+
const handlerCtor = Object.getPrototypeOf(provider)!.constructor;
|
|
60
|
+
|
|
61
|
+
const isCommandHandler = this.isCommandHandler(provider);
|
|
62
|
+
const isQueryHandler = this.isQueryHandler(provider);
|
|
63
|
+
|
|
64
|
+
const commandMetadata = this.getCommandMetadata(handlerCtor);
|
|
65
|
+
const queryMetadata = this.getQueryMetadata(handlerCtor);
|
|
66
|
+
|
|
67
|
+
if (isCommandHandler && commandMetadata) {
|
|
68
|
+
this.commandExecutor['addHandler'](commandMetadata, provider);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* v8 ignore next 3 */
|
|
73
|
+
if (isCommandHandler) {
|
|
74
|
+
throw new WillNeverHappenedException('Command handler instance provided without CommandHandler decorator');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* v8 ignore next 3 */
|
|
78
|
+
if (commandMetadata) {
|
|
79
|
+
throw new WillNeverHappenedException('CommandHandler decorate class that does not extended BaseCommandHandler');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isQueryHandler && queryMetadata) {
|
|
83
|
+
this.queryExecutor['addHandler'](queryMetadata, provider);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* v8 ignore next 3 */
|
|
88
|
+
if (isQueryHandler) {
|
|
89
|
+
throw new WillNeverHappenedException('Query handler instance provided without QueryHandler decorator');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* v8 ignore next 3 */
|
|
93
|
+
if (queryMetadata) {
|
|
94
|
+
throw new WillNeverHappenedException('QueryHandler decorate class that does not extended BaseQueryHandler');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private getCommandMetadata(handlerCtor: Class<any, any[]>): Nullable<Class<BaseCommand, any[]>> {
|
|
100
|
+
const metadata = Reflect.getMetadata(COMMAND_HANDLER_METADATA, handlerCtor);
|
|
101
|
+
|
|
102
|
+
if (!metadata) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
assert.ok(metadata.prototype instanceof BaseCommand);
|
|
107
|
+
|
|
108
|
+
return metadata;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getQueryMetadata(handlerCtor: Class<any, any[]>): Nullable<Class<BaseQuery<any>, any[]>> {
|
|
112
|
+
const metadata = Reflect.getMetadata(QUERY_HANDLER_METADATA, handlerCtor);
|
|
113
|
+
|
|
114
|
+
if (!metadata) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
assert.ok(metadata.prototype instanceof BaseQuery);
|
|
119
|
+
|
|
120
|
+
return metadata;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private isCommandHandler(provider: any): provider is BaseCommandHandler<BaseCommand<any>> {
|
|
124
|
+
return provider instanceof BaseCommandHandler;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private isQueryHandler(provider: any): provider is BaseQueryHandler<BaseQuery<any>> {
|
|
128
|
+
return provider instanceof BaseQueryHandler;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCommand,
|
|
3
|
+
BaseCommandHandler,
|
|
4
|
+
} from '../abstracts/index.js';
|
|
5
|
+
|
|
6
|
+
export const COMMAND_HANDLER_METADATA = Symbol('COMMAND_HANDLER_METADATA');
|
|
7
|
+
|
|
8
|
+
export function CommandHandler<Command extends BaseCommand<any>, Handler extends BaseCommandHandler<Command>>(
|
|
9
|
+
commandCtor: Class<Command, any[]>,
|
|
10
|
+
): TypedClassDecorator<Class<Handler>> {
|
|
11
|
+
return (ctor: Class<Handler>) => {
|
|
12
|
+
Reflect.defineMetadata(COMMAND_HANDLER_METADATA, commandCtor, ctor);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseQuery,
|
|
3
|
+
BaseQueryHandler,
|
|
4
|
+
} from '../abstracts/index.js';
|
|
5
|
+
|
|
6
|
+
export const QUERY_HANDLER_METADATA = Symbol('QUERY_HANDLER_METADATA');
|
|
7
|
+
|
|
8
|
+
export function QueryHandler<Query extends BaseQuery<any>, Handler extends BaseQueryHandler<Query>>(
|
|
9
|
+
queryCtor: Class<Query, any[]>,
|
|
10
|
+
): TypedClassDecorator<Class<Handler>> {
|
|
11
|
+
return (ctor: Class<Handler>) => {
|
|
12
|
+
Reflect.defineMetadata(QUERY_HANDLER_METADATA, queryCtor, ctor);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseException } from '@hg-ts/exception';
|
|
2
|
+
import { BaseCommandOrQuery } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export class HandlerAlreadyAddedException extends BaseException {
|
|
5
|
+
public constructor(command: Class<BaseCommandOrQuery>) {
|
|
6
|
+
super(`Handler for command "${command.name}" already added`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseException } from '@hg-ts/exception';
|
|
2
|
+
import { BaseCommandOrQuery } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export class HandlerNotFoundException extends BaseException {
|
|
5
|
+
public constructor(command: Class<BaseCommandOrQuery>) {
|
|
6
|
+
super(`Handler for command "${command.name}" not found`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCommand,
|
|
3
|
+
BaseExecutor,
|
|
4
|
+
} from '../abstracts/index.js';
|
|
5
|
+
import { CommonResult } from '../types.js';
|
|
6
|
+
|
|
7
|
+
export class CommandExecutor extends BaseExecutor<BaseCommand<any>> {
|
|
8
|
+
protected override async executeHandler<Command extends BaseCommand<any>>(
|
|
9
|
+
command: Command,
|
|
10
|
+
): Promise<CommonResult<Command>> {
|
|
11
|
+
const commandCtor = Object.getPrototypeOf(command)!.constructor as Class<Command>;
|
|
12
|
+
const handler = this.handlers.get(commandCtor) ?? null;
|
|
13
|
+
|
|
14
|
+
this.assertHandler(handler, commandCtor);
|
|
15
|
+
|
|
16
|
+
return handler.execute(command);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseExecutor,
|
|
3
|
+
BaseQuery,
|
|
4
|
+
} from '../abstracts/index.js';
|
|
5
|
+
import { CommonResult } from '../types.js';
|
|
6
|
+
|
|
7
|
+
export class QueryExecutor extends BaseExecutor<BaseQuery<any>> {
|
|
8
|
+
protected override async executeHandler<Query extends BaseQuery<any>>(
|
|
9
|
+
query: Query,
|
|
10
|
+
): Promise<CommonResult<Query>> {
|
|
11
|
+
const queryCtor = Object.getPrototypeOf(query)!.constructor as Class<Query>;
|
|
12
|
+
const handler = this.handlers.get(queryCtor) ?? null;
|
|
13
|
+
|
|
14
|
+
this.assertHandler(handler, queryCtor);
|
|
15
|
+
|
|
16
|
+
return handler.execute(query);
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
BaseQuery,
|
|
3
|
+
BaseQueryHandler,
|
|
4
|
+
BaseCommand,
|
|
5
|
+
BaseCommandHandler,
|
|
6
|
+
} from './abstracts/index.js';
|
|
7
|
+
export * from './exceptions/index.js';
|
|
8
|
+
export * from './executors/index.js';
|
|
9
|
+
export { CommandHandler, QueryHandler } from './decorators/index.js';
|
|
10
|
+
export { CqrsModule } from './cqrs.module.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Describe } from '@hg-ts/tests';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BaseCommand,
|
|
5
|
+
BaseCommandHandler,
|
|
6
|
+
BaseExecutor,
|
|
7
|
+
BaseExecutorTestSuite,
|
|
8
|
+
} from '../abstracts/index.js';
|
|
9
|
+
import { CommandExecutor } from '../executors/index.js';
|
|
10
|
+
import { TestCommand } from './commands/index.js';
|
|
11
|
+
|
|
12
|
+
import { TestCommandHandler } from './test-command.handler.js';
|
|
13
|
+
|
|
14
|
+
@Describe()
|
|
15
|
+
export class CommandExecutorTest extends BaseExecutorTestSuite<BaseCommand<any>> {
|
|
16
|
+
protected override getCommandOrQuery(input: string): TestCommand {
|
|
17
|
+
return new TestCommand(input);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected override getExecutor(): BaseExecutor<BaseCommand<any>> {
|
|
21
|
+
return new CommandExecutor();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected override getHandler(): BaseCommandHandler<BaseCommand<any>> {
|
|
25
|
+
return new TestCommandHandler();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected override getCommandOrQueryCtor(): Class<BaseCommand<any>, [string]> {
|
|
29
|
+
return TestCommand;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from '../../abstracts/index.js';
|
|
2
|
+
|
|
3
|
+
export class TestCommand extends BaseCommand<string> {
|
|
4
|
+
public readonly expectedResult: string;
|
|
5
|
+
|
|
6
|
+
public constructor(expectedResult: string) {
|
|
7
|
+
super();
|
|
8
|
+
|
|
9
|
+
this.expectedResult = expectedResult;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from '../../abstracts/index.js';
|
|
2
|
+
|
|
3
|
+
export class UnknownCommand extends BaseCommand<string> {
|
|
4
|
+
public readonly expectedResult: string;
|
|
5
|
+
|
|
6
|
+
public constructor(expectedResult: string) {
|
|
7
|
+
super();
|
|
8
|
+
|
|
9
|
+
this.expectedResult = expectedResult;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { CqrsModule } from '../cqrs.module.js';
|
|
3
|
+
import { TestCommandHandler } from './test-command.handler.js';
|
|
4
|
+
import { TestQueryHandler } from './test-query.handler.js';
|
|
5
|
+
|
|
6
|
+
@Module({ imports: [CqrsModule], providers: [TestCommandHandler, TestQueryHandler] })
|
|
7
|
+
export class CqrsTestModule {}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Describe,
|
|
3
|
+
expect,
|
|
4
|
+
ExpectException,
|
|
5
|
+
Suite,
|
|
6
|
+
Test,
|
|
7
|
+
} from '@hg-ts/tests';
|
|
8
|
+
import {
|
|
9
|
+
Test as TestFactory,
|
|
10
|
+
TestingModule,
|
|
11
|
+
} from '@nestjs/testing';
|
|
12
|
+
|
|
13
|
+
import { HandlerNotFoundException } from '../exceptions/index.js';
|
|
14
|
+
import {
|
|
15
|
+
CommandExecutor,
|
|
16
|
+
QueryExecutor,
|
|
17
|
+
} from '../executors/index.js';
|
|
18
|
+
import {
|
|
19
|
+
TestCommand,
|
|
20
|
+
UnknownCommand,
|
|
21
|
+
} from './commands/index.js';
|
|
22
|
+
import { CqrsTestModule } from './cqrs-test.module.js';
|
|
23
|
+
import {
|
|
24
|
+
TestQuery,
|
|
25
|
+
UnknownQuery,
|
|
26
|
+
} from './queries/index.js';
|
|
27
|
+
|
|
28
|
+
@Describe()
|
|
29
|
+
export class QueryExecutorTest extends Suite {
|
|
30
|
+
private container: TestingModule;
|
|
31
|
+
private commandExecutor: CommandExecutor;
|
|
32
|
+
private queryExecutor: QueryExecutor;
|
|
33
|
+
|
|
34
|
+
@Test()
|
|
35
|
+
public async executorProvides(): Promise<void> {
|
|
36
|
+
expect(this.commandExecutor).toBeInstanceOf(CommandExecutor);
|
|
37
|
+
expect(this.queryExecutor).toBeInstanceOf(QueryExecutor);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Test()
|
|
41
|
+
public async commandSuccess(): Promise<void> {
|
|
42
|
+
const value = String(Math.random());
|
|
43
|
+
const result = await this.commandExecutor.execute(new TestCommand(value));
|
|
44
|
+
|
|
45
|
+
expect(result).toBe(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Test()
|
|
49
|
+
@ExpectException(HandlerNotFoundException)
|
|
50
|
+
public async unknownCommand(): Promise<void> {
|
|
51
|
+
await this.commandExecutor.execute(new UnknownCommand(''));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Test()
|
|
55
|
+
public async querySuccess(): Promise<void> {
|
|
56
|
+
const value = String(Math.random());
|
|
57
|
+
const result = await this.queryExecutor.execute(new TestQuery(value));
|
|
58
|
+
|
|
59
|
+
expect(result).toBe(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test()
|
|
63
|
+
@ExpectException(HandlerNotFoundException)
|
|
64
|
+
public async unknownQuery(): Promise<void> {
|
|
65
|
+
await this.queryExecutor.execute(new UnknownQuery(''));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public override async beforeEach(): Promise<void> {
|
|
69
|
+
this.container = await TestFactory.createTestingModule({ imports: [CqrsTestModule] }).compile();
|
|
70
|
+
|
|
71
|
+
await this.container.init();
|
|
72
|
+
|
|
73
|
+
this.queryExecutor = this.container.get(QueryExecutor);
|
|
74
|
+
this.commandExecutor = this.container.get(CommandExecutor);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseQuery } from '../../abstracts/index.js';
|
|
2
|
+
|
|
3
|
+
export class TestQuery extends BaseQuery<string> {
|
|
4
|
+
public readonly expectedResult: string;
|
|
5
|
+
|
|
6
|
+
public constructor(expectedResult: string) {
|
|
7
|
+
super();
|
|
8
|
+
|
|
9
|
+
this.expectedResult = expectedResult;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseQuery } from '../../abstracts/index.js';
|
|
2
|
+
|
|
3
|
+
export class UnknownQuery extends BaseQuery<string> {
|
|
4
|
+
public readonly expectedResult: string;
|
|
5
|
+
|
|
6
|
+
public constructor(expectedResult: string) {
|
|
7
|
+
super();
|
|
8
|
+
|
|
9
|
+
this.expectedResult = expectedResult;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Describe } from '@hg-ts/tests';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BaseExecutor,
|
|
5
|
+
BaseExecutorTestSuite,
|
|
6
|
+
BaseQuery,
|
|
7
|
+
BaseQueryHandler,
|
|
8
|
+
} from '../abstracts/index.js';
|
|
9
|
+
import { QueryExecutor } from '../executors/index.js';
|
|
10
|
+
import { TestQuery } from './queries/index.js';
|
|
11
|
+
import { TestQueryHandler } from './test-query.handler.js';
|
|
12
|
+
|
|
13
|
+
@Describe()
|
|
14
|
+
export class QueryExecutorTest extends BaseExecutorTestSuite<BaseQuery<any>> {
|
|
15
|
+
protected override getCommandOrQuery(input: string): TestQuery {
|
|
16
|
+
return new TestQuery(input);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected override getExecutor(): BaseExecutor<BaseQuery<any>> {
|
|
20
|
+
return new QueryExecutor();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected override getHandler(): BaseQueryHandler<BaseQuery<any>> {
|
|
24
|
+
return new TestQueryHandler();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected override getCommandOrQueryCtor(): Class<BaseQuery<any>, [string]> {
|
|
28
|
+
return TestQuery;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { UnknownException } from '@hg-ts/exception';
|
|
2
|
+
import {
|
|
3
|
+
BaseCommandHandler,
|
|
4
|
+
INPUT_FOR_EXCEPTION,
|
|
5
|
+
} from '../abstracts/index.js';
|
|
6
|
+
import { CommandHandler } from '../decorators/index.js';
|
|
7
|
+
import { TestCommand } from './commands/index.js';
|
|
8
|
+
|
|
9
|
+
@CommandHandler(TestCommand)
|
|
10
|
+
export class TestCommandHandler extends BaseCommandHandler<TestCommand> {
|
|
11
|
+
public async execute(command: TestCommand): Promise<string> {
|
|
12
|
+
if (command.expectedResult === INPUT_FOR_EXCEPTION) {
|
|
13
|
+
throw new UnknownException(command.expectedResult);
|
|
14
|
+
}
|
|
15
|
+
return command.expectedResult;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { UnknownException } from '@hg-ts/exception';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BaseQueryHandler,
|
|
5
|
+
INPUT_FOR_EXCEPTION,
|
|
6
|
+
} from '../abstracts/index.js';
|
|
7
|
+
import { QueryHandler } from '../decorators/index.js';
|
|
8
|
+
import { TestQuery } from './queries/index.js';
|
|
9
|
+
|
|
10
|
+
@QueryHandler(TestQuery)
|
|
11
|
+
export class TestQueryHandler extends BaseQueryHandler<TestQuery> {
|
|
12
|
+
public async execute(command: TestQuery): Promise<string> {
|
|
13
|
+
if (command.expectedResult === INPUT_FOR_EXCEPTION) {
|
|
14
|
+
throw new UnknownException(command.expectedResult);
|
|
15
|
+
}
|
|
16
|
+
return command.expectedResult;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCommand,
|
|
3
|
+
BaseCommandHandler,
|
|
4
|
+
BaseQuery,
|
|
5
|
+
BaseQueryHandler,
|
|
6
|
+
} from './abstracts/index.js';
|
|
7
|
+
|
|
8
|
+
export type CommandResult<Command extends BaseCommand<any>> = Command extends BaseCommand<infer Result>
|
|
9
|
+
? Result
|
|
10
|
+
: never;
|
|
11
|
+
export type QueryResult<Command extends BaseQuery<any>> = Command extends BaseQuery<infer Result>
|
|
12
|
+
? Result
|
|
13
|
+
: never;
|
|
14
|
+
|
|
15
|
+
export type BaseCommandOrQuery = BaseQuery<any> | BaseCommand<any>;
|
|
16
|
+
|
|
17
|
+
export type CommonHandler<CommandOrQuery extends BaseCommandOrQuery> =
|
|
18
|
+
CommandOrQuery extends BaseQuery<any>
|
|
19
|
+
? BaseQueryHandler<CommandOrQuery>
|
|
20
|
+
: CommandOrQuery extends BaseCommand<any>
|
|
21
|
+
? BaseCommandHandler<CommandOrQuery>
|
|
22
|
+
: never;
|
|
23
|
+
|
|
24
|
+
export type CommonResult<CommandOrQuery extends BaseCommandOrQuery> =
|
|
25
|
+
CommandOrQuery extends BaseQuery<any>
|
|
26
|
+
? QueryResult<CommandOrQuery>
|
|
27
|
+
: CommandOrQuery extends BaseCommand<any>
|
|
28
|
+
? CommandResult<CommandOrQuery>
|
|
29
|
+
: never;
|