@nebulae/event-store
Version:
Event Store Lib for NebulaE Microservices
151 lines (128 loc) • 5.55 kB
JavaScript
'use strict';
const { Subject, Observable, defer, from, of } = require('rxjs');
const { filter, map, mapTo, mergeMap, last } = require('rxjs/operators');
const uuidv4 = require('uuid/v4');
class MqttBroker {
constructor({ eventsTopic, brokerUrl, disableListener = false, preParseFilter = null }) {
this.topicName = eventsTopic;
this.disableListener = disableListener;
this.preParseFilter = preParseFilter;
this.mqttServerUrl = brokerUrl;
this.senderId = uuidv4();
/**
* Rx Subject for Incoming events
*/
this.incomingEvents$ = new Subject();
this.orderedIncomingEvents$ = this.incomingEvents$.pipe(
filter(msg => msg)
);
/**
* MQTT Client
*/
this.mqtt = require("async-mqtt");
}
/**
* Starts Broker connections
* Returns an Obserable that resolves to each connection result
*/
start$() {
return Observable.create(observer => {
this.mqttClient = this.mqtt.connect(this.mqttServerUrl);
observer.next('MQTT broker connecting ...');
this.mqttClient.on('connect', () => {
observer.next('MQTT broker connected');
this.mqttClient.subscribe(this.topicName);
observer.next(`MQTT broker listening messages`);
observer.complete();
});
if (this.disableListener) {
return;
}
this.mqttClient.on('message', (topic, message) => {
if(this.aggregateEventsMap == null){
return;
}
const envelope = JSON.parse(message);
const hasEventSourcingAttributes = envelope.attributes.at != null && envelope.attributes.at != '' && envelope.attributes.et != null && envelope.attributes.et != ' ' && envelope.attributes.aid != null && envelope.attributes.aid != '';
//if the message already has the headers/attributes we can skip the message before parsing
if (hasEventSourcingAttributes) {
const atMap = this.aggregateEventsMap[envelope.attributes.at] || this.aggregateEventsMap.$ALL;
if (!atMap || (atMap[envelope.attributes.et] == null && atMap.$ALL == null)) {
return;
}
}
//if the developer assigned preParse filters using the attributes (Eg. ReplciaSet instance filter) the we can discard messages before parsing
if (envelope.attributes.aid != null && envelope.attributes.aid != '' && this.preParseFilter != null && !this.preParseFilter(envelope.attributes)) {
return;
}
this.incomingEvents$.next(
{
id: envelope.id,
data: envelope.data,
attributes: envelope.attributes,
correlationId: envelope.attributes.correlationId
}
);
});
});
}
/**
* Disconnect the broker and return an observable that completes when disconnected
*/
stop$() {
return defer(() => this.mqttClient.end());
}
/**
* Config aggregate event map
* @param {*} aggregateEventsMap
*/
configAggregateEventMap(aggregateEventsMap) {
this.aggregateEventsMap = aggregateEventsMap;
}
/**
* Publish data (or an array of data) throught the events topic
* Returns an Observable that resolves to the sent message ID
* @param {string} topicName
* @param {*} data Object or Array of objects to send
*/
publish$(data) {
return (Array.isArray(data) ? from(data) : of(data)).pipe(
mergeMap(d => defer(() => this.mqttClient.publish(
`${this.topicName}`,
JSON.stringify(
{
id: d.id || '',
data: d,
attributes: {
senderId: this.senderId,
id: d.id || '',
et: d.et || '',
at: d.at || '',
aid: String(d.aid || ''),
etv: String(d.etv || ''),
ephemeral: String(d.ephemeral || false)
}
}
),
{ qos: 1 })
)),
last(),
mapTo(data)
);
}
/**
* Returns an Observable that will emit any event related to the given aggregateType (or Array of aggregate types)
* @param {string} aggregateType aggregateType (or Array of aggregateType) to filter
*/
getEventListener$(aggregateType, ignoreSelfEvents = true) {
const isAggregateTypeAnArray = Array.isArray(aggregateType);
const allowAll = isAggregateTypeAnArray ? aggregateType.includes('$ALL') : aggregateType === '$ALL';
return this.orderedIncomingEvents$.pipe(
filter(msg => msg),
filter(msg => !ignoreSelfEvents || msg.attributes.senderId !== this.senderId),
map(msg => ({ ...msg.data, acknowledgeMsg: msg.acknowledgeMsg })),
filter(evt => allowAll || (isAggregateTypeAnArray ? aggregateType.includes(evt.at) : evt.at === aggregateType))
);
}
}
module.exports = MqttBroker;