node-cqrs 0.16.3 → 0.17.0

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +2 -1
  3. package/dist/AbstractAggregate.js +178 -0
  4. package/dist/AbstractAggregate.js.map +1 -0
  5. package/dist/AbstractProjection.js +121 -0
  6. package/dist/AbstractProjection.js.map +1 -0
  7. package/dist/AbstractSaga.js +99 -0
  8. package/dist/AbstractSaga.js.map +1 -0
  9. package/dist/AggregateCommandHandler.js +85 -0
  10. package/dist/AggregateCommandHandler.js.map +1 -0
  11. package/dist/CommandBus.js +77 -0
  12. package/dist/CommandBus.js.map +1 -0
  13. package/dist/CqrsContainerBuilder.js +77 -0
  14. package/dist/CqrsContainerBuilder.js.map +1 -0
  15. package/dist/Event.js +43 -0
  16. package/dist/Event.js.map +1 -0
  17. package/dist/EventStore.js +229 -0
  18. package/dist/EventStore.js.map +1 -0
  19. package/dist/SagaEventHandler.js +117 -0
  20. package/dist/SagaEventHandler.js.map +1 -0
  21. package/dist/index.js +39 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/infrastructure/InMemoryEventStorage.js +53 -0
  24. package/dist/infrastructure/InMemoryEventStorage.js.map +1 -0
  25. package/dist/infrastructure/InMemoryLock.js +68 -0
  26. package/dist/infrastructure/InMemoryLock.js.map +1 -0
  27. package/dist/infrastructure/InMemoryMessageBus.js +95 -0
  28. package/dist/infrastructure/InMemoryMessageBus.js.map +1 -0
  29. package/dist/infrastructure/InMemorySnapshotStorage.js +26 -0
  30. package/dist/infrastructure/InMemorySnapshotStorage.js.map +1 -0
  31. package/dist/infrastructure/InMemoryView.js +173 -0
  32. package/dist/infrastructure/InMemoryView.js.map +1 -0
  33. package/dist/infrastructure/utils/Deferred.js +38 -0
  34. package/dist/infrastructure/utils/Deferred.js.map +1 -0
  35. package/dist/infrastructure/utils/index.js +19 -0
  36. package/dist/infrastructure/utils/index.js.map +1 -0
  37. package/dist/infrastructure/utils/nextCycle.js +9 -0
  38. package/dist/infrastructure/utils/nextCycle.js.map +1 -0
  39. package/dist/interfaces.js +4 -0
  40. package/dist/interfaces.js.map +1 -0
  41. package/dist/utils/getClassName.js +10 -0
  42. package/dist/utils/getClassName.js.map +1 -0
  43. package/dist/utils/getHandledMessageTypes.js +18 -0
  44. package/dist/utils/getHandledMessageTypes.js.map +1 -0
  45. package/dist/utils/getHandler.js +20 -0
  46. package/dist/utils/getHandler.js.map +1 -0
  47. package/dist/utils/getMessageHandlerNames.js +38 -0
  48. package/dist/utils/getMessageHandlerNames.js.map +1 -0
  49. package/dist/utils/index.js +25 -0
  50. package/dist/utils/index.js.map +1 -0
  51. package/dist/utils/isClass.js +8 -0
  52. package/dist/utils/isClass.js.map +1 -0
  53. package/dist/utils/setupOneTimeEmitterSubscription.js +46 -0
  54. package/dist/utils/setupOneTimeEmitterSubscription.js.map +1 -0
  55. package/dist/utils/subscribe.js +39 -0
  56. package/dist/utils/subscribe.js.map +1 -0
  57. package/dist/utils/validateHandlers.js +21 -0
  58. package/dist/utils/validateHandlers.js.map +1 -0
  59. package/package.json +26 -17
  60. package/src/AbstractAggregate.ts +223 -0
  61. package/src/AbstractProjection.ts +172 -0
  62. package/src/AbstractSaga.ts +118 -0
  63. package/src/AggregateCommandHandler.ts +129 -0
  64. package/src/CommandBus.ts +98 -0
  65. package/src/CqrsContainerBuilder.ts +120 -0
  66. package/src/Event.ts +43 -0
  67. package/src/EventStore.ts +315 -0
  68. package/src/SagaEventHandler.ts +161 -0
  69. package/src/index.ts +26 -0
  70. package/src/infrastructure/InMemoryEventStorage.ts +68 -0
  71. package/src/infrastructure/InMemoryLock.ts +73 -0
  72. package/src/infrastructure/InMemoryMessageBus.ts +118 -0
  73. package/src/infrastructure/InMemorySnapshotStorage.ts +27 -0
  74. package/src/infrastructure/InMemoryView.ts +221 -0
  75. package/src/infrastructure/utils/Deferred.ts +41 -0
  76. package/src/infrastructure/utils/index.ts +2 -0
  77. package/src/infrastructure/utils/nextCycle.ts +4 -0
  78. package/src/interfaces.ts +328 -0
  79. package/src/utils/getClassName.ts +6 -0
  80. package/src/utils/{getHandledMessageTypes.js → getHandledMessageTypes.ts} +4 -8
  81. package/src/utils/{getHandler.js → getHandler.ts} +6 -7
  82. package/src/utils/{getMessageHandlerNames.js → getMessageHandlerNames.ts} +2 -9
  83. package/src/utils/index.ts +8 -0
  84. package/src/utils/{isClass.js → isClass.ts} +2 -4
  85. package/src/utils/setupOneTimeEmitterSubscription.ts +57 -0
  86. package/src/{subscribe.js → utils/subscribe.ts} +21 -18
  87. package/src/utils/{validateHandlers.js → validateHandlers.ts} +2 -8
  88. package/index.d.ts +0 -43
  89. package/index.js +0 -3
  90. package/jsconfig.json +0 -15
  91. package/src/AbstractAggregate.js +0 -277
  92. package/src/AbstractProjection.js +0 -192
  93. package/src/AbstractSaga.js +0 -171
  94. package/src/AggregateCommandHandler.js +0 -126
  95. package/src/CommandBus.js +0 -91
  96. package/src/CqrsContainerBuilder.js +0 -134
  97. package/src/EventStore.js +0 -457
  98. package/src/EventStream.js +0 -63
  99. package/src/SagaEventHandler.js +0 -141
  100. package/src/index.js +0 -21
  101. package/src/infrastructure/InMemoryEventStorage.js +0 -76
  102. package/src/infrastructure/InMemoryMessageBus.js +0 -132
  103. package/src/infrastructure/InMemorySnapshotStorage.js +0 -40
  104. package/src/infrastructure/InMemoryView.js +0 -265
  105. package/src/utils/getClassName.js +0 -11
  106. package/src/utils/index.js +0 -6
  107. package/src/utils/nullLogger.js +0 -8
  108. package/types/classes/AbstractAggregate.d.ts +0 -64
  109. package/types/classes/AbstractProjection.d.ts +0 -46
  110. package/types/classes/AbstractSaga.d.ts +0 -39
  111. package/types/classes/AggregateCommandHandler.d.ts +0 -21
  112. package/types/classes/CommandBus.d.ts +0 -17
  113. package/types/classes/CqrsContainerBuilder.d.ts +0 -26
  114. package/types/classes/EventStore.d.ts +0 -53
  115. package/types/classes/EventStream.d.ts +0 -18
  116. package/types/classes/InMemoryEventStorage.d.ts +0 -21
  117. package/types/classes/InMemoryMessageBus.d.ts +0 -30
  118. package/types/classes/InMemorySnapshotStorage.d.ts +0 -18
  119. package/types/classes/InMemoryView.d.ts +0 -57
  120. package/types/classes/SagaEventHandler.d.ts +0 -20
  121. package/types/interfaces/IAggregate.d.ts +0 -30
  122. package/types/interfaces/IAggregateSnapshotStorage.d.ts +0 -4
  123. package/types/interfaces/ICommandBus.d.ts +0 -6
  124. package/types/interfaces/ICommandHandler.d.ts +0 -3
  125. package/types/interfaces/IConcurrentView.d.ts +0 -22
  126. package/types/interfaces/IEventReceptor.d.ts +0 -3
  127. package/types/interfaces/IEventStorage.d.ts +0 -20
  128. package/types/interfaces/IEventStore.d.ts +0 -19
  129. package/types/interfaces/IEventStream.d.ts +0 -13
  130. package/types/interfaces/ILogger.d.ts +0 -3
  131. package/types/interfaces/IMessageBus.d.ts +0 -5
  132. package/types/interfaces/IObserver.d.ts +0 -11
  133. package/types/interfaces/IProjection.d.ts +0 -10
  134. package/types/interfaces/ISaga.d.ts +0 -27
  135. package/types/interfaces/Identifier.d.ts +0 -1
@@ -0,0 +1,118 @@
1
+ import { ICommand, Identifier, IEvent, ISaga, ISagaConstructorParams } from "./interfaces";
2
+
3
+ import { getClassName, validateHandlers, getHandler } from './utils';
4
+
5
+ /**
6
+ * Base class for Saga definition
7
+ */
8
+ export abstract class AbstractSaga implements ISaga {
9
+
10
+ /** List of events that start new saga, must be overridden in Saga implementation */
11
+ static get startsWith(): string[] {
12
+ throw new Error('startsWith must be overridden to return a list of event types that start saga');
13
+ }
14
+
15
+ /** List of event types being handled by Saga, must be overridden in Saga implementation */
16
+ static get handles(): string[] {
17
+ return [];
18
+ }
19
+
20
+ /** Saga ID */
21
+ get id(): Identifier {
22
+ return this.#id;
23
+ }
24
+
25
+ /** Saga version */
26
+ get version(): number {
27
+ return this.#version;
28
+ }
29
+
30
+ /** Command execution queue */
31
+ get uncommittedMessages(): ICommand[] {
32
+ return Array.from(this.#messages);
33
+ }
34
+
35
+ #id: Identifier;
36
+ #version = 0;
37
+ #messages: ICommand[] = [];
38
+
39
+ /**
40
+ * Creates an instance of AbstractSaga
41
+ */
42
+ constructor(options: ISagaConstructorParams) {
43
+ if (!options)
44
+ throw new TypeError('options argument required');
45
+ if (!options.id)
46
+ throw new TypeError('options.id argument required');
47
+
48
+ this.#id = options.id;
49
+
50
+ validateHandlers(this, 'startsWith');
51
+ validateHandlers(this, 'handles');
52
+
53
+ if (options.events) {
54
+ options.events.forEach(e => this.apply(e));
55
+ this.resetUncommittedMessages();
56
+ }
57
+
58
+ Object.defineProperty(this, 'restored', { value: true });
59
+ }
60
+
61
+ /** Modify saga state by applying an event */
62
+ apply(event: IEvent): Promise<void> | void {
63
+ if (!event)
64
+ throw new TypeError('event argument required');
65
+ if (!event.type)
66
+ throw new TypeError('event.type argument required');
67
+
68
+ const handler = getHandler(this, event.type);
69
+ if (!handler)
70
+ throw new Error(`'${event.type}' handler is not defined or not a function`);
71
+
72
+ const r = handler.call(this, event);
73
+ if (r instanceof Promise) {
74
+ return r.then(() => {
75
+ this.#version += 1;
76
+ });
77
+ }
78
+
79
+ this.#version += 1;
80
+ return undefined;
81
+ }
82
+
83
+ /** Format a command and put it to the execution queue */
84
+ protected enqueue(commandType: string, aggregateId: Identifier | undefined, payload: object) {
85
+ if (typeof commandType !== 'string' || !commandType.length)
86
+ throw new TypeError('commandType argument must be a non-empty String');
87
+ if (!['string', 'number', 'undefined'].includes(typeof aggregateId))
88
+ throw new TypeError('aggregateId argument must be either string, number or undefined');
89
+
90
+ this.enqueueRaw({
91
+ aggregateId,
92
+ sagaId: this.id,
93
+ sagaVersion: this.version,
94
+ type: commandType,
95
+ payload
96
+ });
97
+ }
98
+
99
+ /** Put a command to the execution queue */
100
+ protected enqueueRaw(command: ICommand) {
101
+ if (typeof command !== 'object' || !command)
102
+ throw new TypeError('command argument must be an Object');
103
+ if (typeof command.type !== 'string' || !command.type.length)
104
+ throw new TypeError('command.type argument must be a non-empty String');
105
+
106
+ this.#messages.push(command);
107
+ }
108
+
109
+ /** Clear the execution queue */
110
+ resetUncommittedMessages() {
111
+ this.#messages.length = 0;
112
+ }
113
+
114
+ /** Get human-readable Saga name */
115
+ toString(): string {
116
+ return `${getClassName(this)} ${this.id} (v${this.version})`;
117
+ }
118
+ }
@@ -0,0 +1,129 @@
1
+ import {
2
+ IAggregate,
3
+ IAggregateConstructor,
4
+ IAggregateFactory,
5
+ ICommand,
6
+ ICommandBus,
7
+ ICommandHandler,
8
+ Identifier,
9
+ IEventSet,
10
+ IEventStore,
11
+ IExtendableLogger,
12
+ ILogger
13
+ } from "./interfaces";
14
+
15
+ import {
16
+ getClassName,
17
+ getHandledMessageTypes,
18
+ subscribe
19
+ } from './utils';
20
+
21
+ /**
22
+ * Aggregate command handler.
23
+ *
24
+ * Subscribes to event store and awaits aggregate commands.
25
+ * Upon command receiving creates an instance of aggregate,
26
+ * restores its state, passes command and commits emitted events to event store.
27
+ */
28
+ export class AggregateCommandHandler implements ICommandHandler {
29
+
30
+ #eventStore: IEventStore;
31
+ #logger?: ILogger;
32
+
33
+ #aggregateFactory: IAggregateFactory<any>;
34
+ #handles: string[];
35
+
36
+ constructor({
37
+ eventStore,
38
+ aggregateType,
39
+ aggregateFactory,
40
+ handles,
41
+ logger
42
+ }: {
43
+ eventStore: IEventStore,
44
+ aggregateType?: IAggregateConstructor<any>,
45
+ aggregateFactory?: IAggregateFactory<any>,
46
+ handles?: string[],
47
+ logger?: ILogger | IExtendableLogger
48
+ }) {
49
+ if (!eventStore)
50
+ throw new TypeError('eventStore argument required');
51
+
52
+ this.#eventStore = eventStore;
53
+ this.#logger = logger && 'child' in logger ?
54
+ logger.child({ service: getClassName(this) }) :
55
+ logger;
56
+
57
+ if (aggregateType) {
58
+ const AggregateType = aggregateType;
59
+ this.#aggregateFactory = params => new AggregateType(params);
60
+ this.#handles = getHandledMessageTypes(AggregateType);
61
+ }
62
+ else if (aggregateFactory) {
63
+ if (!Array.isArray(handles) || !handles.length)
64
+ throw new TypeError('handles argument must be an non-empty Array');
65
+
66
+ this.#aggregateFactory = aggregateFactory;
67
+ this.#handles = handles;
68
+ }
69
+ else {
70
+ throw new TypeError('either aggregateType or aggregateFactory is required');
71
+ }
72
+ }
73
+
74
+ /** Subscribe to all command types handled by aggregateType */
75
+ subscribe(commandBus: ICommandBus) {
76
+ subscribe(commandBus, this, {
77
+ messageTypes: this.#handles,
78
+ masterHandler: (c: ICommand) => this.execute(c)
79
+ });
80
+ }
81
+
82
+ /** Restore aggregate from event store events */
83
+ async #restoreAggregate(id: Identifier): Promise<IAggregate> {
84
+ if (!id)
85
+ throw new TypeError('id argument required');
86
+
87
+ const events = await this.#eventStore.getAggregateEvents(id);
88
+ const aggregate = this.#aggregateFactory({ id, events });
89
+
90
+ this.#logger?.info(`${aggregate} state restored from ${events.length} event(s)`);
91
+
92
+ return aggregate;
93
+ }
94
+
95
+ /** Create new aggregate with new Id generated by event store */
96
+ async #createAggregate(): Promise<IAggregate> {
97
+ const id = await this.#eventStore.getNewId();
98
+ const aggregate = this.#aggregateFactory({ id });
99
+ this.#logger?.info(`${aggregate} created`);
100
+
101
+ return aggregate;
102
+ }
103
+
104
+ /** Pass a command to corresponding aggregate */
105
+ async execute(cmd: ICommand): Promise<IEventSet> {
106
+ if (!cmd) throw new TypeError('cmd argument required');
107
+ if (!cmd.type) throw new TypeError('cmd.type argument required');
108
+
109
+ const aggregate = cmd.aggregateId ?
110
+ await this.#restoreAggregate(cmd.aggregateId) :
111
+ await this.#createAggregate();
112
+
113
+ await aggregate.handle(cmd);
114
+
115
+ let events = aggregate.changes;
116
+ this.#logger?.info(`${aggregate} "${cmd.type}" command processed, ${events.length} event(s) produced`);
117
+ if (!events.length)
118
+ return events;
119
+
120
+ if (aggregate.shouldTakeSnapshot && this.#eventStore.snapshotsSupported) {
121
+ aggregate.takeSnapshot();
122
+ events = aggregate.changes;
123
+ }
124
+
125
+ await this.#eventStore.commit(events);
126
+
127
+ return events;
128
+ }
129
+ }
@@ -0,0 +1,98 @@
1
+ import { InMemoryMessageBus } from "./infrastructure/InMemoryMessageBus";
2
+ import {
3
+ ICommand,
4
+ ICommandBus,
5
+ IEventSet,
6
+ IExtendableLogger,
7
+ ILogger,
8
+ IMessageBus,
9
+ IMessageHandler
10
+ } from "./interfaces";
11
+
12
+ export class CommandBus implements ICommandBus {
13
+
14
+ #logger?: ILogger;
15
+ #bus: IMessageBus;
16
+
17
+ /**
18
+ * Creates an instance of CommandBus.
19
+ */
20
+ constructor({ messageBus, logger }: {
21
+ messageBus?: IMessageBus,
22
+ logger?: ILogger | IExtendableLogger
23
+ }) {
24
+ this.#bus = messageBus ?? new InMemoryMessageBus();
25
+
26
+ this.#logger = logger && 'child' in logger ?
27
+ logger.child({ service: 'CommandBus' }) :
28
+ logger;
29
+ }
30
+
31
+ /**
32
+ * Set up a command handler
33
+ */
34
+ on(commandType: string, handler: IMessageHandler) {
35
+ if (typeof commandType !== 'string' || !commandType.length)
36
+ throw new TypeError('commandType argument must be a non-empty String');
37
+ if (typeof handler !== 'function')
38
+ throw new TypeError('handler argument must be a Function');
39
+
40
+ return this.#bus.on(commandType, handler);
41
+ }
42
+
43
+ /**
44
+ * Remove previously installed command handler
45
+ */
46
+ off(commandType: string, handler: IMessageHandler) {
47
+ if (typeof commandType !== 'string' || !commandType.length)
48
+ throw new TypeError('commandType argument must be a non-empty String');
49
+ if (typeof handler !== 'function')
50
+ throw new TypeError('handler argument must be a Function');
51
+
52
+ return this.#bus.off(commandType, handler);
53
+ }
54
+
55
+ /**
56
+ * Format and send a command for execution
57
+ */
58
+ send<TPayload>(type: string, aggregateId: string, options: { payload: TPayload, context: object }, ...otherArgs: object[]): Promise<IEventSet> {
59
+ if (typeof type !== 'string' || !type.length)
60
+ throw new TypeError('type argument must be a non-empty String');
61
+ if (options && typeof options !== 'object')
62
+ throw new TypeError('options argument, when defined, must be an Object');
63
+ if (otherArgs.length > 1)
64
+ throw new TypeError('more than expected arguments supplied');
65
+
66
+ // obsolete. left for backward compatibility
67
+ const optionsContainContext = options && !('context' in options) && !('payload' in options);
68
+ if (otherArgs.length || optionsContainContext) {
69
+ const context = options;
70
+ const payload = otherArgs.length ? otherArgs[0] : undefined;
71
+ return this.sendRaw({ type, aggregateId, context, payload });
72
+ }
73
+
74
+ return this.sendRaw<TPayload>({ type, aggregateId, ...options });
75
+ }
76
+
77
+ /**
78
+ * Send a command for execution
79
+ */
80
+ sendRaw<TPayload>(command: ICommand<TPayload>): Promise<IEventSet> {
81
+ if (!command)
82
+ throw new TypeError('command argument required');
83
+ if (!command.type)
84
+ throw new TypeError('command.type argument required');
85
+
86
+ this.#logger?.debug(`sending '${command.type}' command${command.aggregateId ? ` to ${command.aggregateId}` : ''}...`);
87
+
88
+ return this.#bus.send(command).then(r => {
89
+ this.#logger?.debug(`'${command.type}' ${command.aggregateId ? `on ${command.aggregateId}` : ''} processed`);
90
+ return r;
91
+ }, error => {
92
+ this.#logger?.warn(`'${command.type}' ${command.aggregateId ? `on ${command.aggregateId}` : ''} processing has failed: ${error.message}`, {
93
+ stack: error.stack
94
+ });
95
+ throw error;
96
+ });
97
+ }
98
+ }
@@ -0,0 +1,120 @@
1
+ import { ContainerBuilder, Container, TypeConfig, TClassOrFactory } from 'di0';
2
+
3
+ import { AggregateCommandHandler } from './AggregateCommandHandler';
4
+ import { CommandBus } from './CommandBus';
5
+ import { EventStore } from './EventStore';
6
+ import { SagaEventHandler } from './SagaEventHandler';
7
+ import { InMemoryMessageBus } from './infrastructure/InMemoryMessageBus';
8
+
9
+ import {
10
+ getHandledMessageTypes,
11
+ isClass
12
+ } from './utils';
13
+
14
+ import {
15
+ IAggregateConstructor,
16
+ ICommandBus,
17
+ ICommandHandler,
18
+ IEventReceptor,
19
+ IEventStore,
20
+ IProjection,
21
+ IProjectionConstructor,
22
+ ISagaConstructor
23
+ } from './interfaces';
24
+
25
+ interface CqrsContainer extends Container {
26
+ eventStore: IEventStore;
27
+ commandBus: ICommandBus;
28
+ }
29
+
30
+ export class CqrsContainerBuilder extends ContainerBuilder {
31
+
32
+ constructor(options?: {
33
+ types: Readonly<TypeConfig<any>[]>,
34
+ singletones: object
35
+ }) {
36
+ super(options);
37
+ super.register(EventStore).as('eventStore');
38
+ super.register(CommandBus).as('commandBus');
39
+ }
40
+
41
+ /** Register command handler, which will be subscribed to commandBus upon instance creation */
42
+ registerCommandHandler(typeOrFactory: TClassOrFactory<ICommandHandler>) {
43
+ return super.register(
44
+ (container: CqrsContainer) => {
45
+ const handler = container.createInstance(typeOrFactory);
46
+ handler.subscribe(container.commandBus);
47
+ return handler;
48
+ })
49
+ .asSingleInstance();
50
+ }
51
+
52
+ /** Register event receptor, which will be subscribed to eventStore upon instance creation */
53
+ registerEventReceptor(typeOrFactory: TClassOrFactory<IEventReceptor>) {
54
+ return super.register(
55
+ (container: CqrsContainer) => {
56
+ const receptor = container.createInstance(typeOrFactory);
57
+ receptor.subscribe(container.eventStore);
58
+ return receptor;
59
+ })
60
+ .asSingleInstance();
61
+ }
62
+
63
+ /**
64
+ * Register projection, which will expose view and will be subscribed
65
+ * to eventStore and will restore its state upon instance creation
66
+ */
67
+ registerProjection(ProjectionType: IProjectionConstructor, exposedViewAlias?: string) {
68
+ if (!isClass(ProjectionType))
69
+ throw new TypeError('ProjectionType argument must be a constructor function');
70
+
71
+ const projectionFactory = (container: CqrsContainer): IProjection<any> => {
72
+ const projection = container.createInstance(ProjectionType);
73
+ projection.subscribe(container.eventStore);
74
+
75
+ if (exposedViewAlias)
76
+ return projection.view;
77
+
78
+ return projection;
79
+ };
80
+
81
+ const t = super.register(projectionFactory).asSingleInstance();
82
+
83
+ if (exposedViewAlias)
84
+ t.as(exposedViewAlias);
85
+
86
+ return t;
87
+ }
88
+
89
+ /** Register aggregate type in the container */
90
+ registerAggregate(AggregateType: IAggregateConstructor<any>) {
91
+ if (!isClass(AggregateType))
92
+ throw new TypeError('AggregateType argument must be a constructor function');
93
+
94
+ const commandHandlerFactory = (container: CqrsContainer): ICommandHandler =>
95
+ container.createInstance(AggregateCommandHandler, {
96
+ aggregateFactory: (options: any) =>
97
+ container.createInstance(AggregateType, options),
98
+ handles: getHandledMessageTypes(AggregateType)
99
+ });
100
+
101
+ return this.registerCommandHandler(commandHandlerFactory);
102
+ }
103
+
104
+
105
+ /** Register saga type in the container */
106
+ registerSaga(SagaType: ISagaConstructor) {
107
+ if (!isClass(SagaType))
108
+ throw new TypeError('SagaType argument must be a constructor function');
109
+
110
+ const eventReceptorFactory = (container: CqrsContainer): IEventReceptor =>
111
+ container.createInstance(SagaEventHandler, {
112
+ sagaFactory: (options: any) => container.createInstance(SagaType, options),
113
+ handles: SagaType.handles,
114
+ startsWith: SagaType.startsWith,
115
+ queueName: SagaType.name
116
+ });
117
+
118
+ return this.registerEventReceptor(eventReceptorFactory);
119
+ }
120
+ }
package/src/Event.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { IEvent } from "./interfaces";
2
+ import * as crypto from 'crypto';
3
+
4
+ const md5 = (data: object): string => crypto
5
+ .createHash('md5')
6
+ .update(JSON.stringify(data))
7
+ .digest('hex')
8
+ .replace(/==$/, '');
9
+
10
+ /**
11
+ * Get text description of an event for logging purposes
12
+ */
13
+ export function describe(event: IEvent): string {
14
+ return `'${event.type}' of ${event.aggregateId} (v${event.aggregateVersion})`;
15
+ }
16
+
17
+ /**
18
+ * Get text description of a set of events for logging purposes
19
+ */
20
+ export function describeMultiple(events: ReadonlyArray<IEvent>): string {
21
+ if (events.length === 1)
22
+ return describe(events[0]);
23
+
24
+ return `${events.length} events`;
25
+ }
26
+
27
+ /**
28
+ * Validate event structure
29
+ */
30
+ export function validate(event: IEvent) {
31
+ if (typeof event !== 'object' || !event)
32
+ throw new TypeError('event must be an Object');
33
+ if (typeof event.type !== 'string' || !event.type.length)
34
+ throw new TypeError('event.type must be a non-empty String');
35
+ if (!event.aggregateId && !event.sagaId)
36
+ throw new TypeError('either event.aggregateId or event.sagaId is required');
37
+ if (event.sagaId && typeof event.sagaVersion === 'undefined')
38
+ throw new TypeError('event.sagaVersion is required, when event.sagaId is defined');
39
+ }
40
+
41
+ export function getId(event: IEvent): string {
42
+ return event.id ?? md5(event);
43
+ }