Skip to content

Commit b6f8d9b

Browse files
author
Mateusz Kołecki
committed
First working implementation with tests
1 parent d0eaa82 commit b6f8d9b

20 files changed

Lines changed: 661 additions & 85 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
node_modules/
3+
package-lock.json

command-bus.ts

Lines changed: 0 additions & 85 deletions
This file was deleted.

package.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "commander",
3+
"version": "0.1.0",
4+
"description": "Command Bus For TypeScript",
5+
"license": "MIT",
6+
"main": "dist/src/index.js",
7+
"directories": {
8+
"test": "test"
9+
},
10+
"scripts": {
11+
"test": "npm run build && npm run run-test",
12+
"build": "rm -rf dist && tsc",
13+
"run-test": "mocha --file dist/test/setup.js 'dist/test/**/*.test.js'",
14+
"watch-test": "mocha --require ts-node/register --watch --watch-extensions ts --file test/setup.ts 'test/**/*.test.ts'"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+ssh://git@github.com/snapshotpl/commander.git"
19+
},
20+
"keywords": [
21+
"command",
22+
"bus",
23+
"typescript",
24+
"commander"
25+
],
26+
"devDependencies": {
27+
"@types/chai": "^4.1.3",
28+
"@types/chai-as-promised": "^7.1.0",
29+
"@types/mocha": "^5.2.0",
30+
"@types/node": "^10.0.10",
31+
"@types/sinon": "^4.3.3",
32+
"chai": "^4.1.2",
33+
"chai-as-promised": "^7.1.1",
34+
"mocha": "^5.1.1",
35+
"sinon": "^5.0.7",
36+
"ts-node": "^6.0.3",
37+
"typescript": "^2.8.3"
38+
}
39+
}

src/commandBus.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
export interface Command { } // marking interface
3+
4+
export interface Handler<C extends Command> {
5+
handle(command: C): Promise<any>;
6+
}
7+
8+
export interface CommandBus {
9+
handle(command: Command): Promise<any>
10+
}

src/commander.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Middleware } from './middlewares';
2+
import { CommandBus, Command } from './commandBus';
3+
4+
type ExecutionChain = (command: Command) => Promise<any>;
5+
6+
export class Commander implements CommandBus {
7+
8+
private executionChain: ExecutionChain;
9+
10+
constructor(middlewares: Middleware[]) {
11+
this.executionChain = this.createExecutionChain(middlewares);
12+
}
13+
14+
public async handle(command: Command): Promise<any> {
15+
return await this.executionChain(command);
16+
}
17+
18+
private createExecutionChain(middlewares: Middleware[]): ExecutionChain {
19+
const last = () => {
20+
// last callback in chain is no-op
21+
return Promise.resolve();
22+
};
23+
24+
const reducer = (
25+
next: ExecutionChain,
26+
middleware: Middleware
27+
): ExecutionChain => {
28+
return (command: Command): Promise<any> => {
29+
return middleware.run(command, () => next(command));
30+
}
31+
};
32+
33+
return middlewares.reverse().reduce(reducer, last);
34+
}
35+
}

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
export * from './commander';
3+
export * from './commandBus';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
import { Middleware, NextMiddleware } from './index';
3+
import { Command, Handler } from '../commandBus';
4+
import { HandlerResolver } from '../resolvers'
5+
6+
export class CommandExecutionMiddleware implements Middleware {
7+
constructor(
8+
private handlerResolver: HandlerResolver
9+
) { }
10+
11+
public async run(command: Command, next: NextMiddleware): Promise<any> {
12+
const handler: Handler<Command> = this.handlerResolver.resolve(command);
13+
const result = await handler.handle(command);
14+
await next();
15+
return result;
16+
}
17+
}

src/middlewares/commandLogger.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
import { Middleware, NextMiddleware } from './index';
3+
import { Command } from '../commandBus';
4+
5+
6+
export interface CommandLogger {
7+
log(level: string, msg: string): CommandLogger;
8+
log(level: string, msg: string, meta: any): CommandLogger;
9+
log(level: string, msg: string, ...meta: any[]): CommandLogger;
10+
}
11+
12+
export interface CommandLoggerOptions {
13+
level?: string;
14+
errorLevel?: string;
15+
}
16+
17+
export class CommandLoggerMiddleware implements Middleware {
18+
private logger: CommandLogger;
19+
private level: string;
20+
private errorLevel: string;
21+
22+
constructor(logger: CommandLogger, options: CommandLoggerOptions = {}) {
23+
this.logger = logger;
24+
this.level = options.level || 'info';
25+
this.errorLevel = options.errorLevel || 'error';
26+
}
27+
28+
public async run(command: Command, next: NextMiddleware): Promise<any> {
29+
const start = Date.now();
30+
const commandName = command.constructor.name;
31+
32+
this.logger.log(this.level, 'Command %s dispatched', commandName);
33+
34+
try {
35+
return await next();
36+
} catch (err) {
37+
this.logger.log(this.errorLevel, 'Command %s error. %s', commandName, err);
38+
throw err;
39+
} finally {
40+
const time = Date.now() - start;
41+
this.logger.log(this.level, 'Command %s time %dms', commandName, time);
42+
}
43+
}
44+
}

src/middlewares/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
import { Command } from '../commandBus';
3+
4+
export type NextMiddleware = () => Promise<any>;
5+
6+
export interface Middleware {
7+
run(command: Command, next: NextMiddleware): Promise<any>;
8+
}
9+
10+
export * from './commandLogger';
11+
export * from './commandExecution';

src/resolvers/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
import { Handler, Command } from '../commandBus';
3+
4+
export interface HandlerResolver {
5+
resolve<C extends Command>(command: C): Handler<C>
6+
}
7+
8+
export * from './memory';

0 commit comments

Comments
 (0)