@eventstore.net/event.store
Version:
A simple and fast EventStore that support multiple persistence and notification providers
126 lines (108 loc) • 4.03 kB
text/typescript
import * as _ from 'lodash';
import { Collection, MongoClient } from 'mongodb';
import { Event } from '../model/event';
import { Stream } from '../model/stream';
import { PersistenceProvider } from './provider';
/**
* A Persistence Provider that handle all the data in mongodb.
*/
export class MongoProvider implements PersistenceProvider {
private mongoURL: string;
private eventCollection: Collection;
private countersCollection: Collection;
private mongoClient: MongoClient;
constructor(url: string) {
this.mongoURL = url;
}
public async addEvent(stream: Stream, data: any, type = '') {
const events = await this.events();
const sequence = await this.getNextSequenceValue(this.getKey(stream.aggregation, stream.id)) - 1;
const commitTimestamp = new Date().getTime();
const event: Event = {
commitTimestamp: commitTimestamp,
payload: data,
sequence: sequence,
type: type
};
const result = await events.insertOne(_.merge(event, { stream: stream }));
if (!result.result.ok) {
throw new Error('Error saving event into the store');
}
return event;
}
public async getEvents(stream: Stream, offset = 0, limit = 0) {
const events = await this.events();
const cursor = events.find({ 'stream.id': stream.id, 'stream.aggregation': stream.aggregation });
if (offset > 0) {
cursor.skip(offset);
}
if (limit > 0) {
cursor.limit(limit);
}
return await cursor.toArray();
}
public async getAggregations(offset = 0, limit = 0): Promise<Array<string>> {
const events = await this.events();
const cursor = events.aggregate().group({ _id: '$stream.aggregation' });
if (offset > 0) {
cursor.skip(offset);
}
if (limit > 0) {
cursor.limit(limit);
}
const aggregations: Array<string> = await cursor.toArray();
return aggregations;
}
public async getStreams(aggregation: string, offset = 0, limit = 0): Promise<Array<string>> {
const events = await this.events();
const cursor = events.aggregate()
.match({ 'stream.aggregation': aggregation })
.group({ _id: '$stream.id' });
if (offset > 0) {
cursor.skip(offset);
}
if (limit > 0) {
cursor.limit(limit);
}
const streams: Array<string> = await cursor.toArray();
return streams;
}
private getKey(aggregation: string, streamId: string): string {
return `${aggregation}:${streamId}`;
}
private async events() {
if (!this.eventCollection) {
const mongoClient = await this.getMongoClient();
this.eventCollection = mongoClient.db().collection('events');
}
return this.eventCollection;
}
private async counters() {
if (!this.countersCollection) {
const mongoClient = await this.getMongoClient();
this.countersCollection = mongoClient.db().collection('counters');
}
return this.countersCollection;
}
private async getMongoClient() {
if (!this.mongoClient) {
this.mongoClient = await MongoClient.connect(this.mongoURL, { useNewUrlParser: true });
}
return this.mongoClient;
}
private async getNextSequenceValue(sequenceName: string) {
const counters = await this.counters();
const result = await counters.findOneAndUpdate(
{ _id: sequenceName },
// eslint-disable-next-line @typescript-eslint/camelcase
{ $inc: { sequence_value: 1 } },
{
returnOriginal: false,
upsert: true
});
if (!result.ok) {
throw new Error('Error reading next sequence value');
}
return result.value.sequence_value;
}
}