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.
- package/CHANGELOG.md +53 -0
- package/README.md +2 -1
- package/dist/AbstractAggregate.js +178 -0
- package/dist/AbstractAggregate.js.map +1 -0
- package/dist/AbstractProjection.js +121 -0
- package/dist/AbstractProjection.js.map +1 -0
- package/dist/AbstractSaga.js +99 -0
- package/dist/AbstractSaga.js.map +1 -0
- package/dist/AggregateCommandHandler.js +85 -0
- package/dist/AggregateCommandHandler.js.map +1 -0
- package/dist/CommandBus.js +77 -0
- package/dist/CommandBus.js.map +1 -0
- package/dist/CqrsContainerBuilder.js +77 -0
- package/dist/CqrsContainerBuilder.js.map +1 -0
- package/dist/Event.js +43 -0
- package/dist/Event.js.map +1 -0
- package/dist/EventStore.js +229 -0
- package/dist/EventStore.js.map +1 -0
- package/dist/SagaEventHandler.js +117 -0
- package/dist/SagaEventHandler.js.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/InMemoryEventStorage.js +53 -0
- package/dist/infrastructure/InMemoryEventStorage.js.map +1 -0
- package/dist/infrastructure/InMemoryLock.js +68 -0
- package/dist/infrastructure/InMemoryLock.js.map +1 -0
- package/dist/infrastructure/InMemoryMessageBus.js +95 -0
- package/dist/infrastructure/InMemoryMessageBus.js.map +1 -0
- package/dist/infrastructure/InMemorySnapshotStorage.js +26 -0
- package/dist/infrastructure/InMemorySnapshotStorage.js.map +1 -0
- package/dist/infrastructure/InMemoryView.js +173 -0
- package/dist/infrastructure/InMemoryView.js.map +1 -0
- package/dist/infrastructure/utils/Deferred.js +38 -0
- package/dist/infrastructure/utils/Deferred.js.map +1 -0
- package/dist/infrastructure/utils/index.js +19 -0
- package/dist/infrastructure/utils/index.js.map +1 -0
- package/dist/infrastructure/utils/nextCycle.js +9 -0
- package/dist/infrastructure/utils/nextCycle.js.map +1 -0
- package/dist/interfaces.js +4 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/utils/getClassName.js +10 -0
- package/dist/utils/getClassName.js.map +1 -0
- package/dist/utils/getHandledMessageTypes.js +18 -0
- package/dist/utils/getHandledMessageTypes.js.map +1 -0
- package/dist/utils/getHandler.js +20 -0
- package/dist/utils/getHandler.js.map +1 -0
- package/dist/utils/getMessageHandlerNames.js +38 -0
- package/dist/utils/getMessageHandlerNames.js.map +1 -0
- package/dist/utils/index.js +25 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/isClass.js +8 -0
- package/dist/utils/isClass.js.map +1 -0
- package/dist/utils/setupOneTimeEmitterSubscription.js +46 -0
- package/dist/utils/setupOneTimeEmitterSubscription.js.map +1 -0
- package/dist/utils/subscribe.js +39 -0
- package/dist/utils/subscribe.js.map +1 -0
- package/dist/utils/validateHandlers.js +21 -0
- package/dist/utils/validateHandlers.js.map +1 -0
- package/package.json +26 -17
- package/src/AbstractAggregate.ts +223 -0
- package/src/AbstractProjection.ts +172 -0
- package/src/AbstractSaga.ts +118 -0
- package/src/AggregateCommandHandler.ts +129 -0
- package/src/CommandBus.ts +98 -0
- package/src/CqrsContainerBuilder.ts +120 -0
- package/src/Event.ts +43 -0
- package/src/EventStore.ts +315 -0
- package/src/SagaEventHandler.ts +161 -0
- package/src/index.ts +26 -0
- package/src/infrastructure/InMemoryEventStorage.ts +68 -0
- package/src/infrastructure/InMemoryLock.ts +73 -0
- package/src/infrastructure/InMemoryMessageBus.ts +118 -0
- package/src/infrastructure/InMemorySnapshotStorage.ts +27 -0
- package/src/infrastructure/InMemoryView.ts +221 -0
- package/src/infrastructure/utils/Deferred.ts +41 -0
- package/src/infrastructure/utils/index.ts +2 -0
- package/src/infrastructure/utils/nextCycle.ts +4 -0
- package/src/interfaces.ts +328 -0
- package/src/utils/getClassName.ts +6 -0
- package/src/utils/{getHandledMessageTypes.js → getHandledMessageTypes.ts} +4 -8
- package/src/utils/{getHandler.js → getHandler.ts} +6 -7
- package/src/utils/{getMessageHandlerNames.js → getMessageHandlerNames.ts} +2 -9
- package/src/utils/index.ts +8 -0
- package/src/utils/{isClass.js → isClass.ts} +2 -4
- package/src/utils/setupOneTimeEmitterSubscription.ts +57 -0
- package/src/{subscribe.js → utils/subscribe.ts} +21 -18
- package/src/utils/{validateHandlers.js → validateHandlers.ts} +2 -8
- package/index.d.ts +0 -43
- package/index.js +0 -3
- package/jsconfig.json +0 -15
- package/src/AbstractAggregate.js +0 -277
- package/src/AbstractProjection.js +0 -192
- package/src/AbstractSaga.js +0 -171
- package/src/AggregateCommandHandler.js +0 -126
- package/src/CommandBus.js +0 -91
- package/src/CqrsContainerBuilder.js +0 -134
- package/src/EventStore.js +0 -457
- package/src/EventStream.js +0 -63
- package/src/SagaEventHandler.js +0 -141
- package/src/index.js +0 -21
- package/src/infrastructure/InMemoryEventStorage.js +0 -76
- package/src/infrastructure/InMemoryMessageBus.js +0 -132
- package/src/infrastructure/InMemorySnapshotStorage.js +0 -40
- package/src/infrastructure/InMemoryView.js +0 -265
- package/src/utils/getClassName.js +0 -11
- package/src/utils/index.js +0 -6
- package/src/utils/nullLogger.js +0 -8
- package/types/classes/AbstractAggregate.d.ts +0 -64
- package/types/classes/AbstractProjection.d.ts +0 -46
- package/types/classes/AbstractSaga.d.ts +0 -39
- package/types/classes/AggregateCommandHandler.d.ts +0 -21
- package/types/classes/CommandBus.d.ts +0 -17
- package/types/classes/CqrsContainerBuilder.d.ts +0 -26
- package/types/classes/EventStore.d.ts +0 -53
- package/types/classes/EventStream.d.ts +0 -18
- package/types/classes/InMemoryEventStorage.d.ts +0 -21
- package/types/classes/InMemoryMessageBus.d.ts +0 -30
- package/types/classes/InMemorySnapshotStorage.d.ts +0 -18
- package/types/classes/InMemoryView.d.ts +0 -57
- package/types/classes/SagaEventHandler.d.ts +0 -20
- package/types/interfaces/IAggregate.d.ts +0 -30
- package/types/interfaces/IAggregateSnapshotStorage.d.ts +0 -4
- package/types/interfaces/ICommandBus.d.ts +0 -6
- package/types/interfaces/ICommandHandler.d.ts +0 -3
- package/types/interfaces/IConcurrentView.d.ts +0 -22
- package/types/interfaces/IEventReceptor.d.ts +0 -3
- package/types/interfaces/IEventStorage.d.ts +0 -20
- package/types/interfaces/IEventStore.d.ts +0 -19
- package/types/interfaces/IEventStream.d.ts +0 -13
- package/types/interfaces/ILogger.d.ts +0 -3
- package/types/interfaces/IMessageBus.d.ts +0 -5
- package/types/interfaces/IObserver.d.ts +0 -11
- package/types/interfaces/IProjection.d.ts +0 -10
- package/types/interfaces/ISaga.d.ts +0 -27
- package/types/interfaces/Identifier.d.ts +0 -1
package/src/AbstractAggregate.js
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { validateHandlers, getHandler, getClassName } = require('./utils');
|
|
4
|
-
const EventStream = require('./EventStream');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Deep-clone simple JS object
|
|
8
|
-
* @template T
|
|
9
|
-
* @param {T} obj
|
|
10
|
-
* @returns {T}
|
|
11
|
-
*/
|
|
12
|
-
const clone = obj => JSON.parse(JSON.stringify(obj));
|
|
13
|
-
|
|
14
|
-
const SNAPSHOT_EVENT_TYPE = 'snapshot';
|
|
15
|
-
|
|
16
|
-
const _id = Symbol('id');
|
|
17
|
-
const _changes = Symbol('changes');
|
|
18
|
-
const _version = Symbol('version');
|
|
19
|
-
const _snapshotVersion = Symbol('snapshotVersion');
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Base class for Aggregate definition
|
|
23
|
-
*
|
|
24
|
-
* @class AbstractAggregate
|
|
25
|
-
* @implements {IAggregate}
|
|
26
|
-
*/
|
|
27
|
-
class AbstractAggregate {
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Optional list of commands handled by Aggregate.
|
|
31
|
-
* Can be overridden in the aggregate implementation
|
|
32
|
-
*
|
|
33
|
-
* @type {string[]}
|
|
34
|
-
* @readonly
|
|
35
|
-
* @static
|
|
36
|
-
* @example
|
|
37
|
-
* return ['createUser', 'changePassword'];
|
|
38
|
-
*/
|
|
39
|
-
static get handles() {
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Aggregate ID
|
|
45
|
-
*
|
|
46
|
-
* @type {string|number}
|
|
47
|
-
* @readonly
|
|
48
|
-
*/
|
|
49
|
-
get id() {
|
|
50
|
-
return this[_id];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Aggregate Version
|
|
55
|
-
*
|
|
56
|
-
* @type {number}
|
|
57
|
-
* @readonly
|
|
58
|
-
*/
|
|
59
|
-
get version() {
|
|
60
|
-
return this[_version];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Aggregate Snapshot Version
|
|
65
|
-
*
|
|
66
|
-
* @type {number|undefined}
|
|
67
|
-
* @readonly
|
|
68
|
-
*/
|
|
69
|
-
get snapshotVersion() {
|
|
70
|
-
return this[_snapshotVersion];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Events emitted by Aggregate
|
|
75
|
-
*
|
|
76
|
-
* @type {IEventStream}
|
|
77
|
-
* @readonly
|
|
78
|
-
*/
|
|
79
|
-
get changes() {
|
|
80
|
-
return new EventStream(this[_changes]);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Override to define whether an aggregate state snapshot should be taken
|
|
85
|
-
*
|
|
86
|
-
* @type {boolean}
|
|
87
|
-
* @readonly
|
|
88
|
-
* @example
|
|
89
|
-
* // create snapshot every 50 events
|
|
90
|
-
* return this.version % 50 === 0;
|
|
91
|
-
*/
|
|
92
|
-
get shouldTakeSnapshot() { // eslint-disable-line class-methods-use-this
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Creates an instance of AbstractAggregate.
|
|
98
|
-
*
|
|
99
|
-
* @param {TAggregateConstructorParams} options
|
|
100
|
-
*/
|
|
101
|
-
constructor(options) {
|
|
102
|
-
const { id, state, events } = options;
|
|
103
|
-
if (!id) throw new TypeError('id argument required');
|
|
104
|
-
if (state && typeof state !== 'object') throw new TypeError('state argument, when provided, must be an Object');
|
|
105
|
-
if (events && !Array.isArray(events)) throw new TypeError('events argument, when provided, must be an Array');
|
|
106
|
-
|
|
107
|
-
this[_id] = id;
|
|
108
|
-
this[_changes] = [];
|
|
109
|
-
this[_version] = 0;
|
|
110
|
-
this[_snapshotVersion] = 0;
|
|
111
|
-
|
|
112
|
-
validateHandlers(this);
|
|
113
|
-
|
|
114
|
-
if (state)
|
|
115
|
-
this.state = state;
|
|
116
|
-
|
|
117
|
-
if (events)
|
|
118
|
-
events.forEach(event => this.mutate(event));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Pass command to command handler
|
|
123
|
-
*
|
|
124
|
-
* @param {ICommand} command
|
|
125
|
-
* @returns {any}
|
|
126
|
-
*/
|
|
127
|
-
handle(command) {
|
|
128
|
-
if (!command) throw new TypeError('command argument required');
|
|
129
|
-
if (!command.type) throw new TypeError('command.type argument required');
|
|
130
|
-
|
|
131
|
-
const handler = getHandler(this, command.type);
|
|
132
|
-
if (!handler)
|
|
133
|
-
throw new Error(`'${command.type}' handler is not defined or not a function`);
|
|
134
|
-
|
|
135
|
-
this.command = command;
|
|
136
|
-
|
|
137
|
-
return handler.call(this, command.payload, command.context);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Mutate aggregate state and increment aggregate version
|
|
142
|
-
*
|
|
143
|
-
* @protected
|
|
144
|
-
* @param {IEvent} event
|
|
145
|
-
*/
|
|
146
|
-
mutate(event) {
|
|
147
|
-
if ('aggregateVersion' in event)
|
|
148
|
-
this[_version] = event.aggregateVersion;
|
|
149
|
-
|
|
150
|
-
if (event.type === SNAPSHOT_EVENT_TYPE) {
|
|
151
|
-
this[_snapshotVersion] = event.aggregateVersion;
|
|
152
|
-
this.restoreSnapshot(event);
|
|
153
|
-
}
|
|
154
|
-
else if (this.state) {
|
|
155
|
-
const handler = this.state.mutate || getHandler(this.state, event.type);
|
|
156
|
-
if (handler)
|
|
157
|
-
handler.call(this.state, event);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
this[_version] += 1;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Format and register aggregate event and mutate aggregate state
|
|
165
|
-
*
|
|
166
|
-
* @protected
|
|
167
|
-
* @param {string} type - event type
|
|
168
|
-
* @param {object} [payload] - event data
|
|
169
|
-
*/
|
|
170
|
-
emit(type, payload) {
|
|
171
|
-
if (typeof type !== 'string' || !type.length) throw new TypeError('type argument must be a non-empty string');
|
|
172
|
-
|
|
173
|
-
const event = this.makeEvent(type, payload, this.command);
|
|
174
|
-
|
|
175
|
-
this.emitRaw(event);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Format event based on a current aggregate state
|
|
180
|
-
* and a command being executed
|
|
181
|
-
*
|
|
182
|
-
* @protected
|
|
183
|
-
* @param {string} type
|
|
184
|
-
* @param {any} [payload]
|
|
185
|
-
* @param {ICommand} [sourceCommand]
|
|
186
|
-
* @returns {IEvent}
|
|
187
|
-
*/
|
|
188
|
-
makeEvent(type, payload, sourceCommand) {
|
|
189
|
-
/** @type {IEvent} */
|
|
190
|
-
const event = {
|
|
191
|
-
aggregateId: this.id,
|
|
192
|
-
aggregateVersion: this.version,
|
|
193
|
-
type,
|
|
194
|
-
payload
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
if (sourceCommand) {
|
|
198
|
-
// augment event with command context
|
|
199
|
-
const { context, sagaId, sagaVersion } = sourceCommand;
|
|
200
|
-
if (context !== undefined)
|
|
201
|
-
event.context = context;
|
|
202
|
-
if (sagaId !== undefined)
|
|
203
|
-
event.sagaId = sagaId;
|
|
204
|
-
if (sagaVersion !== undefined)
|
|
205
|
-
event.sagaVersion = sagaVersion;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return event;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Register aggregate event and mutate aggregate state
|
|
213
|
-
*
|
|
214
|
-
* @protected
|
|
215
|
-
* @param {IEvent} event
|
|
216
|
-
*/
|
|
217
|
-
emitRaw(event) {
|
|
218
|
-
if (!event) throw new TypeError('event argument required');
|
|
219
|
-
if (!event.aggregateId) throw new TypeError('event.aggregateId argument required');
|
|
220
|
-
if (typeof event.aggregateVersion !== 'number') throw new TypeError('event.aggregateVersion argument must be a Number');
|
|
221
|
-
if (typeof event.type !== 'string' || !event.type.length) throw new TypeError('event.type argument must be a non-empty String');
|
|
222
|
-
|
|
223
|
-
this.mutate(event);
|
|
224
|
-
|
|
225
|
-
this[_changes].push(event);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Take an aggregate state snapshot and add it to the changes queue
|
|
230
|
-
*/
|
|
231
|
-
takeSnapshot() {
|
|
232
|
-
this.emit(SNAPSHOT_EVENT_TYPE, this.makeSnapshot());
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Create an aggregate state snapshot
|
|
237
|
-
*
|
|
238
|
-
* @protected
|
|
239
|
-
* @returns {object}
|
|
240
|
-
*/
|
|
241
|
-
makeSnapshot() {
|
|
242
|
-
if (!this.state)
|
|
243
|
-
throw new Error('state property is empty, either define state or override makeSnapshot method');
|
|
244
|
-
|
|
245
|
-
return clone(this.state);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Restore aggregate state from a snapshot
|
|
250
|
-
*
|
|
251
|
-
* @protected
|
|
252
|
-
* @param {IEvent} snapshotEvent
|
|
253
|
-
*/
|
|
254
|
-
restoreSnapshot(snapshotEvent) {
|
|
255
|
-
if (!snapshotEvent) throw new TypeError('snapshotEvent argument required');
|
|
256
|
-
if (!snapshotEvent.type) throw new TypeError('snapshotEvent.type argument required');
|
|
257
|
-
if (!snapshotEvent.payload) throw new TypeError('snapshotEvent.payload argument required');
|
|
258
|
-
|
|
259
|
-
if (snapshotEvent.type !== SNAPSHOT_EVENT_TYPE)
|
|
260
|
-
throw new Error(`${SNAPSHOT_EVENT_TYPE} event type expected`);
|
|
261
|
-
if (!this.state)
|
|
262
|
-
throw new Error('state property is empty, either defined state or override restoreSnapshot method');
|
|
263
|
-
|
|
264
|
-
Object.assign(this.state, clone(snapshotEvent.payload));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Get human-readable aggregate identifier
|
|
269
|
-
*
|
|
270
|
-
* @returns {string}
|
|
271
|
-
*/
|
|
272
|
-
toString() {
|
|
273
|
-
return `${getClassName(this)} ${this.id} (v${this.version})`;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
module.exports = AbstractAggregate;
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const subscribe = require('./subscribe');
|
|
4
|
-
const InMemoryView = require('./infrastructure/InMemoryView');
|
|
5
|
-
const getHandledMessageTypes = require('./utils/getHandledMessageTypes');
|
|
6
|
-
const { validateHandlers, getHandler, getClassName } = require('./utils');
|
|
7
|
-
const nullLogger = require('./utils/nullLogger');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {any} view
|
|
11
|
-
*/
|
|
12
|
-
const isConcurrentView = view =>
|
|
13
|
-
typeof view.lock === 'function' &&
|
|
14
|
-
typeof view.unlock === 'function' &&
|
|
15
|
-
typeof view.once === 'function';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @param {any} view
|
|
19
|
-
* @returns {IConcurrentView}
|
|
20
|
-
*/
|
|
21
|
-
const asConcurrentView = view => (isConcurrentView(view) ? view : undefined);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Base class for Projection definition
|
|
25
|
-
*
|
|
26
|
-
* @class AbstractProjection
|
|
27
|
-
* @implements {IProjection}
|
|
28
|
-
*/
|
|
29
|
-
class AbstractProjection {
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Optional list of event types being handled by projection.
|
|
33
|
-
* Can be overridden in projection implementation.
|
|
34
|
-
* If not overridden, will detect event types from event handlers declared on the Projection class
|
|
35
|
-
*
|
|
36
|
-
* @type {string[]}
|
|
37
|
-
* @readonly
|
|
38
|
-
* @static
|
|
39
|
-
*/
|
|
40
|
-
static get handles() {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Default view associated with projection.
|
|
46
|
-
* If not defined, an instance of `NodeCqrs.InMemoryView` is created on first access.
|
|
47
|
-
*
|
|
48
|
-
* @readonly
|
|
49
|
-
*/
|
|
50
|
-
get view() {
|
|
51
|
-
return this._view || (this._view = new InMemoryView());
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Indicates if view should be restored from EventStore on start.
|
|
56
|
-
* Override for custom behavior.
|
|
57
|
-
*
|
|
58
|
-
* @type {boolean | Promise<boolean>}
|
|
59
|
-
* @readonly
|
|
60
|
-
*/
|
|
61
|
-
get shouldRestoreView() {
|
|
62
|
-
return (this.view instanceof Map)
|
|
63
|
-
|| (this.view instanceof InMemoryView);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Creates an instance of AbstractProjection
|
|
68
|
-
*
|
|
69
|
-
* @param {object} [options]
|
|
70
|
-
* @param {any} [options.view]
|
|
71
|
-
* @param {ILogger} [options.logger]
|
|
72
|
-
*/
|
|
73
|
-
constructor(options) {
|
|
74
|
-
validateHandlers(this);
|
|
75
|
-
|
|
76
|
-
if (options && options.view)
|
|
77
|
-
this._view = options.view;
|
|
78
|
-
|
|
79
|
-
this._logger = (options && options.logger) || nullLogger;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Subscribe to event store
|
|
84
|
-
*
|
|
85
|
-
* @param {IEventStore} eventStore
|
|
86
|
-
* @return {Promise<void>}
|
|
87
|
-
*/
|
|
88
|
-
async subscribe(eventStore) {
|
|
89
|
-
subscribe(eventStore, this, {
|
|
90
|
-
masterHandler: e => this.project(e)
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
await this.restore(eventStore);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Pass event to projection event handler
|
|
98
|
-
*
|
|
99
|
-
* @param {IEvent} event
|
|
100
|
-
* @returns {Promise<void>}
|
|
101
|
-
*/
|
|
102
|
-
async project(event) {
|
|
103
|
-
const concurrentView = asConcurrentView(this.view);
|
|
104
|
-
if (concurrentView && !concurrentView.ready)
|
|
105
|
-
await concurrentView.once('ready');
|
|
106
|
-
|
|
107
|
-
return this._project(event);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Pass event to projection event handler, without awaiting for restore operation to complete
|
|
112
|
-
* @protected
|
|
113
|
-
* @param {IEvent} event
|
|
114
|
-
* @returns {Promise<void>}
|
|
115
|
-
*/
|
|
116
|
-
async _project(event) {
|
|
117
|
-
const handler = getHandler(this, event.type);
|
|
118
|
-
if (!handler)
|
|
119
|
-
throw new Error(`'${event.type}' handler is not defined or not a function`);
|
|
120
|
-
|
|
121
|
-
return handler.call(this, event);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Restore projection view from event store
|
|
126
|
-
*
|
|
127
|
-
* @param {IEventStore} eventStore
|
|
128
|
-
* @return {Promise<void>}
|
|
129
|
-
*/
|
|
130
|
-
async restore(eventStore) {
|
|
131
|
-
// lock the view to ensure same restoring procedure
|
|
132
|
-
// won't be performed by another projection instance
|
|
133
|
-
const concurrentView = asConcurrentView(this.view);
|
|
134
|
-
if (concurrentView)
|
|
135
|
-
await concurrentView.lock();
|
|
136
|
-
|
|
137
|
-
const shouldRestore = await this.shouldRestoreView;
|
|
138
|
-
if (shouldRestore)
|
|
139
|
-
await this._restore(eventStore);
|
|
140
|
-
|
|
141
|
-
if (concurrentView)
|
|
142
|
-
concurrentView.unlock();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Restore projection view from event store
|
|
147
|
-
* @protected
|
|
148
|
-
* @param {IEventStore} eventStore
|
|
149
|
-
* @return {Promise<void>}
|
|
150
|
-
*/
|
|
151
|
-
async _restore(eventStore) {
|
|
152
|
-
/* istanbul ignore if */
|
|
153
|
-
if (!eventStore) throw new TypeError('eventStore argument required');
|
|
154
|
-
/* istanbul ignore if */
|
|
155
|
-
if (typeof eventStore.getAllEvents !== 'function') throw new TypeError('eventStore.getAllEvents must be a Function');
|
|
156
|
-
|
|
157
|
-
const service = getClassName(this);
|
|
158
|
-
this._logger.log('debug', 'retrieving events and restoring projection...', { service });
|
|
159
|
-
|
|
160
|
-
const messageTypes = getHandledMessageTypes(this);
|
|
161
|
-
const eventsIterable = eventStore.getAllEvents(messageTypes);
|
|
162
|
-
|
|
163
|
-
for await (const event of eventsIterable) {
|
|
164
|
-
try {
|
|
165
|
-
await this._project(event);
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
this._onRestoringError(err, event);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
this._logger.log('info', `view restored (${this.view})`, { service });
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Handle error on restoring
|
|
177
|
-
*
|
|
178
|
-
* @protected
|
|
179
|
-
* @param {Error} error
|
|
180
|
-
* @param {IEvent} event
|
|
181
|
-
*/
|
|
182
|
-
_onRestoringError(error, event) {
|
|
183
|
-
this._logger.log('error', `view restoring has failed: ${error.message}`, {
|
|
184
|
-
service: getClassName(this),
|
|
185
|
-
event,
|
|
186
|
-
stack: error.stack
|
|
187
|
-
});
|
|
188
|
-
throw error;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
module.exports = AbstractProjection;
|
package/src/AbstractSaga.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { validateHandlers, getHandler, getClassName } = require('./utils');
|
|
4
|
-
|
|
5
|
-
const _id = Symbol('id');
|
|
6
|
-
const _version = Symbol('version');
|
|
7
|
-
const _messages = Symbol('messages');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Base class for Saga definition
|
|
11
|
-
*
|
|
12
|
-
* @class AbstractSaga
|
|
13
|
-
* @implements {ISaga}
|
|
14
|
-
*/
|
|
15
|
-
class AbstractSaga {
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* List of events that start new saga, must be overridden in Saga implementation
|
|
19
|
-
*
|
|
20
|
-
* @type {string[]}
|
|
21
|
-
* @readonly
|
|
22
|
-
* @static
|
|
23
|
-
*/
|
|
24
|
-
static get startsWith() {
|
|
25
|
-
throw new Error('startsWith must be overridden to return a list of event types that start saga');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* List of event types being handled by Saga, must be overridden in Saga implementation
|
|
30
|
-
*
|
|
31
|
-
* @type {string[]}
|
|
32
|
-
* @readonly
|
|
33
|
-
* @static
|
|
34
|
-
*/
|
|
35
|
-
static get handles() {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Saga ID
|
|
41
|
-
*
|
|
42
|
-
* @type {string|number}
|
|
43
|
-
* @readonly
|
|
44
|
-
*/
|
|
45
|
-
get id() {
|
|
46
|
-
return this[_id];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Saga version
|
|
51
|
-
*
|
|
52
|
-
* @type {number}
|
|
53
|
-
* @readonly
|
|
54
|
-
*/
|
|
55
|
-
get version() {
|
|
56
|
-
return this[_version];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Command execution queue
|
|
61
|
-
*
|
|
62
|
-
* @type {ICommand[]}
|
|
63
|
-
* @readonly
|
|
64
|
-
*/
|
|
65
|
-
get uncommittedMessages() {
|
|
66
|
-
return Array.from(this[_messages]);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Creates an instance of AbstractSaga
|
|
71
|
-
*
|
|
72
|
-
* @param {TSagaConstructorParams} options
|
|
73
|
-
*/
|
|
74
|
-
constructor(options) {
|
|
75
|
-
if (!options) throw new TypeError('options argument required');
|
|
76
|
-
if (!options.id) throw new TypeError('options.id argument required');
|
|
77
|
-
|
|
78
|
-
this[_id] = options.id;
|
|
79
|
-
this[_version] = 0;
|
|
80
|
-
this[_messages] = [];
|
|
81
|
-
|
|
82
|
-
validateHandlers(this, 'startsWith');
|
|
83
|
-
validateHandlers(this, 'handles');
|
|
84
|
-
|
|
85
|
-
if (options.events) {
|
|
86
|
-
options.events.forEach(e => this.apply(e));
|
|
87
|
-
this.resetUncommittedMessages();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
Object.defineProperty(this, 'restored', { value: true });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Modify saga state by applying an event
|
|
95
|
-
*
|
|
96
|
-
* @param {IEvent} event
|
|
97
|
-
* @returns {void|Promise<void>}
|
|
98
|
-
*/
|
|
99
|
-
apply(event) {
|
|
100
|
-
if (!event) throw new TypeError('event argument required');
|
|
101
|
-
if (!event.type) throw new TypeError('event.type argument required');
|
|
102
|
-
|
|
103
|
-
const handler = getHandler(this, event.type);
|
|
104
|
-
if (!handler)
|
|
105
|
-
throw new Error(`'${event.type}' handler is not defined or not a function`);
|
|
106
|
-
|
|
107
|
-
const r = handler.call(this, event);
|
|
108
|
-
if (r instanceof Promise) {
|
|
109
|
-
return r.then(() => {
|
|
110
|
-
this[_version] += 1;
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this[_version] += 1;
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Format a command and put it to the execution queue
|
|
120
|
-
*
|
|
121
|
-
* @protected
|
|
122
|
-
* @param {string} commandType
|
|
123
|
-
* @param {string|number} aggregateId
|
|
124
|
-
* @param {object} payload
|
|
125
|
-
*/
|
|
126
|
-
enqueue(commandType, aggregateId, payload) {
|
|
127
|
-
if (typeof commandType !== 'string' || !commandType.length)
|
|
128
|
-
throw new TypeError('commandType argument must be a non-empty String');
|
|
129
|
-
if (!['string', 'number', 'undefined'].includes(typeof aggregateId))
|
|
130
|
-
throw new TypeError('aggregateId argument must be either string, number or undefined');
|
|
131
|
-
|
|
132
|
-
this.enqueueRaw({
|
|
133
|
-
aggregateId,
|
|
134
|
-
sagaId: this.id,
|
|
135
|
-
sagaVersion: this.version,
|
|
136
|
-
type: commandType,
|
|
137
|
-
payload
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Put a command to the execution queue
|
|
143
|
-
*
|
|
144
|
-
* @protected
|
|
145
|
-
* @param {ICommand} command
|
|
146
|
-
*/
|
|
147
|
-
enqueueRaw(command) {
|
|
148
|
-
if (typeof command !== 'object' || !command) throw new TypeError('command argument must be an Object');
|
|
149
|
-
if (typeof command.type !== 'string' || !command.type.length) throw new TypeError('command.type argument must be a non-empty String');
|
|
150
|
-
|
|
151
|
-
this[_messages].push(command);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Clear the execution queue
|
|
156
|
-
*/
|
|
157
|
-
resetUncommittedMessages() {
|
|
158
|
-
this[_messages].length = 0;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Get human-readable Saga name
|
|
163
|
-
*
|
|
164
|
-
* @returns {string}
|
|
165
|
-
*/
|
|
166
|
-
toString() {
|
|
167
|
-
return `${getClassName(this)} ${this.id} (v${this.version})`;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
module.exports = AbstractSaga;
|