@nebulae/event-store-tpi-rx6 1.1.2

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/.env ADDED
File without changes
package/.jshintrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "esversion": 11,
3
+ "node":true
4
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "cSpell.words": [
3
+ "APAR",
4
+ "BPAR"
5
+ ]
6
+ }
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Event Store
2
+
3
+
4
+ # Installation
5
+
6
+ Assuming that Node.js is already installed & running,
7
+
8
+ ## Install as dependency
9
+ ```sh
10
+ $ npm install @nebulae/event-store-tpi-rx6
11
+ ```
12
+
13
+ # usage
14
+ ## Author & Contributors
15
+
16
+ Developed &amp; maintained by authors: <b>Sebastian Molano & Esteban Zapata on behalf of Nebula Engineering SAS</b><br>
17
+ Follow Nebula Engineering at: <a href="https://github.com/NebulaEngineering" target="_blank">github</a>
18
+
19
+ ## License
20
+
21
+ The MIT License (MIT)
22
+
23
+ Copyright (c) 2018 Nebula Engineering SAS
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const EventStore = require('./lib/EventStore');
4
+ const Event = require('./lib/entities/Event');
5
+
6
+ module.exports = {
7
+ EventStore,
8
+ Event,
9
+ };
@@ -0,0 +1,172 @@
1
+ 'use strict'
2
+
3
+ const Rx = require('rxjs');
4
+ const { filter, map, concatMap, switchMap } = require('rxjs/operators');
5
+ const EventResult = require('./entities/Event');
6
+
7
+ class EventStore {
8
+
9
+
10
+ /**
11
+ * Create a new EventStore
12
+ *
13
+ * @param {Object} brokerConfig
14
+ * {
15
+ * type,
16
+ * eventsTopic,
17
+ * brokerUrl,
18
+ * eventsTopicSubscription,
19
+ * projectId,
20
+ * }
21
+ * @param {Object} storeConfig
22
+ * {
23
+ * type,
24
+ * url,
25
+ * eventStoreDbName,
26
+ * aggregatesDbName
27
+ * }
28
+ */
29
+ constructor(brokerConfig, storeConfig) {
30
+ switch (brokerConfig.type) {
31
+ case "PUBSUB":
32
+ const PubSubBroker = require('./broker/PubSubBroker');
33
+ this.broker = new PubSubBroker(brokerConfig);
34
+ break;
35
+ case "MQTT":
36
+ const MqttBroker = require('./broker/MqttBroker');
37
+ this.broker = new MqttBroker(brokerConfig);
38
+ break;
39
+ default:
40
+ throw new Error(`Invalid EventStore broker type: ${brokerConfig.type} `);
41
+ }
42
+
43
+ switch (storeConfig.type) {
44
+ case "MONGO":
45
+ const MongoStore = require('./store/MongoStore');
46
+ this.storeDB = new MongoStore(storeConfig);
47
+ break;
48
+ default:
49
+ throw new Error(`Invalid EventStore store type: ${storeConfig.type} `);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Starts Event Broker + Store
55
+ */
56
+ start$() {
57
+ return Rx.merge(
58
+ this.broker.start$(),
59
+ this.storeDB.start$()
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Stops Event Broker + Store
65
+ */
66
+ stop$() {
67
+ return Rx.merge(
68
+ this.broker.stop$(),
69
+ this.storeDB.stop$()
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Appends and emit a new Event
75
+ *
76
+ * @param {Event} event Event to store and emit
77
+ *
78
+ * Returns an obseravable that resolves to:
79
+ * {
80
+ * storeResult : {aggregate,event,versionTimeStr},
81
+ * brokerResult: { messageId }
82
+ * }
83
+ *
84
+ * where:
85
+ * - aggregate = current aggregate state
86
+ * - event = persisted event
87
+ * - versionTimeStr = EventStore date index where the event was store
88
+ * - messageId: sent message ID
89
+ *
90
+ */
91
+ emitEvent$(event) {
92
+ return this.storeDB.pushEvent$(event)
93
+ .pipe(
94
+ concatMap((storeResult) =>
95
+ this.broker.publish$(storeResult.event)
96
+ .pipe(map(messageId => {
97
+ return { storeResult, brokerResult: { messageId } };
98
+ }))
99
+ )
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Find all events of an especific aggregate
105
+ * @param {String} aggregateType Aggregate type
106
+ * @param {String} aggregateId Aggregate Id
107
+ * @param {number} version version to recover from (exclusive), defualt = 0
108
+ * @param {limit} limit max number of events to return, default = 20
109
+ *
110
+ * Returns an Observable that emits each found event one by one
111
+ */
112
+ retrieveEvents$(aggregateType, aggregateId, version = 0, limit = 20) {
113
+ return this.storeDB.getEvents$(aggregateType, aggregateId, version, limit)
114
+ }
115
+
116
+ /**
117
+ * Find all events of an especific aggregate having taken place but not acknowledged,
118
+ * @param {String} aggregateType Aggregate type
119
+ * @param {string} key process key (eg. microservice name) that acknowledged the events
120
+ *
121
+ * Returns an Observable that emits each found event one by one
122
+ */
123
+ retrieveUnacknowledgedEvents$(aggregateType, key) {
124
+ return this.storeDB.retrieveUnacknowledgedEvents$(aggregateType, key);
125
+ }
126
+
127
+ /**
128
+ * Find Aggregates that were created after the given date
129
+ *
130
+ * @param {string} type
131
+ * @param {number} createTimestamp
132
+ * @param {Object} ops {offset,pageSize}
133
+ *
134
+ * Returns an observable that publish every found aggregate
135
+ */
136
+ findAgregatesCreatedAfter$(type, createTimestamp = 0) {
137
+ return this.storeDB.findAgregatesCreatedAfter$(type, createTimestamp);
138
+ }
139
+
140
+ /**
141
+ * Returns an Observable that will emit any event related to the given aggregateType
142
+ * @param {string} aggregateType
143
+ */
144
+ getEventListener$(aggregateType, key, ignoreSelfEvents = true) {
145
+ return this.storeDB.findLatestAcknowledgedTimestamp$(aggregateType, key).pipe(
146
+ switchMap(latestTimeStamp => {
147
+ //this will ignore all queued messages that were already processed
148
+ return this.broker.getEventListener$(aggregateType, ignoreSelfEvents)
149
+ .pipe(filter(evt => evt.timestamp > latestTimeStamp))
150
+ })
151
+ );
152
+ }
153
+
154
+ /**
155
+ * @param {Event} event event to acknowledge
156
+ * @param {string} key process key (eg. microservice name) that is acknowledging the event
157
+ */
158
+ acknowledgeEvent$(event, key) {
159
+ return this.storeDB.acknowledgeEvent$(event, key);
160
+ }
161
+
162
+ /**
163
+ * Ensure the existence of a registry on the ack database for an aggregate type
164
+ * @param {string} aggregateType
165
+ * @param {string} key backend key
166
+ */
167
+ ensureAcknowledgeRegistry$(aggregateType, key) {
168
+ return this.storeDB.ensureAcknowledgeRegistry$(aggregateType, key);
169
+ }
170
+ }
171
+
172
+ module.exports = EventStore;
@@ -0,0 +1,116 @@
1
+ 'use strict'
2
+
3
+ const Rx = require('rxjs');
4
+ const { filter, mapTo, map} = require('rxjs/operators');
5
+ const uuidv4 = require('uuid/v4');
6
+
7
+
8
+
9
+ class MqttBroker {
10
+
11
+ constructor({ eventsTopic, brokerUrl }) {
12
+
13
+ this.topicName = eventsTopic;
14
+ this.mqttServerUrl = brokerUrl;
15
+ this.senderId = uuidv4();
16
+ /**
17
+ * Rx Subject for Incoming events
18
+ */
19
+ this.incomingEvents$ = new Rx.BehaviorSubject();
20
+ this.orderedIncomingEvents$ = this.incomingEvents$.pipe(
21
+ filter(msg => msg)
22
+ )
23
+ // .groupBy(msg => msg.data.at)
24
+ // .mergeMap(groupStream =>
25
+ // groupStream.bufferWhen(() => groupStream.debounceTime(250))
26
+ // .filter(bufferedArray => bufferedArray && bufferedArray.length > 0)
27
+ // .map(bufferedArray => bufferedArray.sort((o1, o2) => { return o1.data.av - o2.data.av }))
28
+ // .mergeMap(bufferedArray => Rx.Observable.from(bufferedArray))
29
+ // )
30
+ ;
31
+
32
+ /**
33
+ * MQTT Client
34
+ */
35
+ this.mqtt = require("async-mqtt");
36
+ }
37
+
38
+ /**
39
+ * Starts Broker connections
40
+ * Returns an Obserable that resolves to each connection result
41
+ */
42
+ start$() {
43
+ return Rx.Observable.create(observer => {
44
+ this.mqttClient = this.mqtt.connect(this.mqttServerUrl);
45
+ observer.next('MQTT broker connecting ...');
46
+ this.mqttClient.on('connect', () => {
47
+ observer.next('MQTT broker connected');
48
+ this.mqttClient.subscribe(this.topicName);
49
+ observer.next(`MQTT broker listening messages`);
50
+ observer.complete();
51
+ });
52
+ this.mqttClient.on('message', (topic, message) => {
53
+ const envelope = JSON.parse(message);
54
+ //console.log(`**************** Received message id: ${envelope.id}`);
55
+ // message is Buffer
56
+ this.incomingEvents$.next(
57
+ {
58
+ id: envelope.id,
59
+ data: envelope.data,
60
+ attributes: envelope.attributes,
61
+ correlationId: envelope.attributes.correlationId
62
+ }
63
+ );
64
+ });
65
+
66
+
67
+ });
68
+
69
+
70
+ }
71
+
72
+ /**
73
+ * Disconnect the broker and return an observable that completes when disconnected
74
+ */
75
+ stop$() {
76
+ return Rx.defer(() => this.mqttClient.end());
77
+ }
78
+
79
+
80
+ /**
81
+ * Publish data throught the events topic
82
+ * Returns an Observable that resolves to the sent message ID
83
+ * @param {string} topicName
84
+ * @param {Object} data
85
+ */
86
+ publish$(data) {
87
+ const uuid = uuidv4();
88
+ const dataBuffer = JSON.stringify(
89
+ {
90
+ id: uuid,
91
+ data,
92
+ attributes: {
93
+ senderId: this.senderId
94
+ }
95
+ }
96
+ );
97
+ return Rx.defer(() => this.mqttClient.publish(`${this.topicName}`, dataBuffer, { qos: 1 }))
98
+ .pipe(mapTo(uuid));
99
+ }
100
+
101
+ /**
102
+ * Returns an Observable that will emit any event related to the given aggregateType
103
+ * @param {string} aggregateType
104
+ */
105
+ getEventListener$(aggregateType, ignoreSelfEvents = true) {
106
+ return this.orderedIncomingEvents$.pipe(
107
+ filter(msg => msg)
108
+ ,filter(msg => !ignoreSelfEvents || msg.attributes.senderId !== this.senderId)
109
+ ,map(msg => msg.data)
110
+ ,filter(evt => evt.at === aggregateType || aggregateType == "*" )
111
+ )
112
+ }
113
+
114
+ }
115
+
116
+ module.exports = MqttBroker;
@@ -0,0 +1,144 @@
1
+ 'use strict'
2
+
3
+ const Rx = require('rxjs');
4
+ const { filter, map} = require('rxjs/operators');
5
+ // Imports the Google Cloud client library
6
+ const uuidv4 = require('uuid/v4');
7
+ const PubSub = require('@google-cloud/pubsub');
8
+
9
+ class PubSubBroker {
10
+
11
+ constructor({ eventsTopic, eventsTopicSubscription }) {
12
+ //this.projectId = projectId;
13
+ this.eventsTopic = eventsTopic;
14
+ this.eventsTopicSubscription = eventsTopicSubscription;
15
+
16
+ /**
17
+ * Rx Subject for every incoming event
18
+ */
19
+ this.incomingEvents$ = new Rx.BehaviorSubject();
20
+ this.orderedIncomingEvents$ = this.incomingEvents$.pipe(
21
+ filter(msg => msg)
22
+ )
23
+ // .groupBy(msg => msg.data.at)
24
+ // .mergeMap(groupStream =>
25
+ // groupStream.bufferWhen(() => groupStream.debounceTime(250))
26
+ // .filter(bufferedArray => bufferedArray && bufferedArray.length > 0)
27
+ // .map(bufferedArray => bufferedArray.sort((o1, o2) => { return o1.data.av - o2.data.av }))
28
+ // .mergeMap(bufferedArray => Rx.Observable.from(bufferedArray))
29
+ // );
30
+ this.senderId = uuidv4();
31
+
32
+ this.pubsubClient = new PubSub({
33
+ //projectId: projectId,
34
+ });
35
+
36
+ this.topic = this.pubsubClient.topic(eventsTopic);
37
+ }
38
+
39
+ /**
40
+ * Starts Broker connections
41
+ * Returns an Obserable that resolves to each connection result
42
+ */
43
+ start$() {
44
+ return Rx.Observable.create(observer => {
45
+ this.startMessageListener();
46
+ observer.next(`Event Store PubSub Broker listening: Topic=${this.eventsTopic}, subscriptionName=${this.eventsTopicSubscription}`);
47
+ observer.complete();
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Disconnect the broker and return an observable that completes when disconnected
53
+ */
54
+ stop$() {
55
+ return Rx.Observable.create(observer => {
56
+ this.getSubscription$().subscribe(
57
+ (subscription) => {
58
+ subscription.removeListener(`message`, this.onMessage);
59
+ observer.next(`Event Store PubSub Broker removed listener: Topic=${this.eventsTopic}, subscriptionName=${subscription}`);
60
+ },
61
+ (error) => observer.error(error),
62
+ () => {
63
+ this.messageListenerSubscription.unsubscribe();
64
+ observer.complete();
65
+ }
66
+ );
67
+
68
+ });
69
+
70
+ }
71
+
72
+ /**
73
+ * Publish data throught the events topic
74
+ * Returns an Observable that resolves to the sent message ID
75
+ * @param {string} topicName
76
+ * @param {Object} data
77
+ */
78
+ publish$(data) {
79
+ const dataBuffer = Buffer.from(JSON.stringify(data));
80
+ return Rx.defer(() =>
81
+ this.topic.publisher().publish(
82
+ dataBuffer,
83
+ { senderId: this.senderId }))
84
+ //.do(messageId => console.log(`PubSub Message published through ${this.topic.name}, Message=${JSON.stringify(data)}`))
85
+ ;
86
+ }
87
+
88
+ /**
89
+ * Returns an Observable that will emit any event related to the given aggregateType
90
+ * @param {string} aggregateType
91
+ */
92
+ getEventListener$(aggregateType, ignoreSelfEvents = true) {
93
+ return this.orderedIncomingEvents$.pipe(
94
+ filter(msg => msg)
95
+ ,filter(msg => !ignoreSelfEvents || msg.attributes.senderId !== this.senderId)
96
+ ,map(msg => msg.data)
97
+ ,filter(evt => evt.at === aggregateType || aggregateType == "*")
98
+ )
99
+ }
100
+
101
+
102
+ /**
103
+ * Returns an Observable that resolves to the subscription
104
+ */
105
+ getSubscription$() {
106
+ return Rx.defer(() =>
107
+ this.topic.subscription(this.eventsTopicSubscription)
108
+ .get({ autoCreate: true })).pipe(
109
+ map(results => results[0])
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Starts to listen messages
115
+ */
116
+ startMessageListener() {
117
+ this.messageListenerSubscription = this.getSubscription$()
118
+ .subscribe(
119
+ (pubSubSubscription) => {
120
+ this.onMessage = message => {
121
+ message.ack();
122
+ //console.log(`Received message ${message.id}:`);
123
+ this.incomingEvents$.next({
124
+ data: JSON.parse(message.data),
125
+ id: message.id,
126
+ attributes: message.attributes,
127
+ correlationId: message.attributes.correlationId
128
+ });
129
+ //console.log(`Execute ack message ${message.id}:`);
130
+ };
131
+ pubSubSubscription.on(`message`, this.onMessage);
132
+ },
133
+ (err) => {
134
+ console.error('Failed to obtain EventStore subscription', err);
135
+ },
136
+ () => {
137
+ console.log('GatewayEvents listener has completed!');
138
+ }
139
+ );
140
+ }
141
+
142
+ }
143
+
144
+ module.exports = PubSubBroker;
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ const Rx = require('rxjs');
4
+
5
+ class Event {
6
+ constructor({ eventType, eventTypeVersion, aggregateType, aggregateId, data, user, aggregateVersion, ephemeral = false }) {
7
+
8
+ /**
9
+ * Event type
10
+ */
11
+ this.et = eventType;
12
+ /**
13
+ * Event type version
14
+ */
15
+ this.etv = eventTypeVersion;
16
+ /**
17
+ * Aggregate Type
18
+ */
19
+ this.at = aggregateType;
20
+ /**
21
+ * Aggregate ID
22
+ */
23
+ this.aid = aggregateId;
24
+ /**
25
+ * Event data
26
+ */
27
+ this.data = data;
28
+ /**
29
+ * Responsible user
30
+ */
31
+ this.user = user;
32
+ /**
33
+ * TimeStamp
34
+ */
35
+ this.timestamp = (new Date).getTime();
36
+ /**
37
+ * Aggregate version
38
+ */
39
+ this.av = aggregateVersion;
40
+ /**
41
+ * if ephemeral is true, this event will not be stored
42
+ */
43
+ this.ephemeral = ephemeral;
44
+ }
45
+ }
46
+
47
+ module.exports = Event;