UNPKG

evtstore

Version:

Event Sourcing with Node.JS

123 lines (102 loc) 3.58 kB
import { Collection, Timestamp, MongoError, Filter } from 'mongodb' import { Event, StoreEvent, Provider, ErrorCallback } from '../src/types' import { VersionError } from './error' import { createEventsMapper, toArray } from './util' export type Bookmark = { bookmark: string position: Timestamp } export type Options<E extends Event> = { limit?: number events: Collection<StoreEvent<E>> | Promise<Collection<StoreEvent<E>>> bookmarks: Collection<Bookmark> | Promise<Collection<Bookmark>> onError?: ErrorCallback } export function createProvider<E extends Event>(opts: Options<E>): Provider<E> { const events = Promise.resolve(opts.events) const bookmarks = Promise.resolve(opts.bookmarks) const onError = opts.onError || (() => { /* NOOP */ }) const createEvents = createEventsMapper<E>(new Timestamp({ t: 0, i: 0 })) return { limit: opts.limit, driver: 'mongo', onError, getPosition: (bm) => getPos(bm, bookmarks), setPosition: (bm, pos) => setPos(bm, pos, bookmarks), getEventsFor: async (stream, id, fromPosition) => { const query: Filter<StoreEvent<E>> = { stream, aggregateId: id, } if (fromPosition !== undefined) { query.position = { $gt: fromPosition } } const results = await events.then((coll) => coll.find(query).sort({ position: 1 }).toArray()) return results }, getLastEventFor: async (stream, id) => { const query: Filter<StoreEvent<E>> = {} query.stream = { $in: toArray(stream) } if (id) { query.aggregateId = id } const results = await events.then((coll) => coll.find(query).sort({ position: -1 }).limit(1).toArray() ) return results[0] }, getEventsFrom: async (stream, position, lim) => events.then((coll) => { const filter = { stream: { $in: toArray(stream) }, position: { $gt: position }, } as Filter<StoreEvent<E>> const query = coll.find(filter).sort({ position: 1 }) const limit = lim ?? opts.limit if (limit) { query.limit(limit) } return query.toArray() }), createEvents, append: async (_stream, _aggId, _version, newEvents) => { try { await events.then((coll) => coll.insertMany(newEvents)) return newEvents } catch (ex) { if (ex instanceof MongoError && ex.code === 11000) throw new VersionError() throw ex } }, } } export async function migrate( events: Collection<StoreEvent<any>> | Promise<Collection<StoreEvent<any>>>, bookmarks: Collection<Bookmark> | Promise<Collection<Bookmark>> ) { const eventColl = await events const bookmarkColl = await bookmarks await bookmarkColl.createIndex({ bookmark: 1 }, { name: 'bookmark-index', unique: true }) await eventColl.createIndex( { stream: 1, position: 1 }, { name: 'stream-position-index', unique: true } ) await eventColl.createIndex( { stream: 1, aggregateId: 1, version: 1 }, { name: 'stream-id-version-index', unique: true } ) } async function getPos(bm: string, bookmarks: Promise<Collection<Bookmark>>) { const record = await bookmarks.then((coll) => coll.findOne({ bookmark: bm })) if (record) return record.position return new Timestamp({ t: 1, i: 0 }) } async function setPos(bm: string, pos: Timestamp, bookmarks: Promise<Collection<Bookmark>>) { await bookmarks.then((coll) => coll.updateOne({ bookmark: bm }, { $set: { position: pos } }, { upsert: true }) ) }