UNPKG

evtstore

Version:

Event Sourcing with Node.JS

199 lines (198 loc) 16.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.cypher = exports.migrate = exports.createProvider = void 0; const neo = require("neo4j-driver"); const error_1 = require("./error"); const util_1 = require("./util"); function createProvider(opts) { const onError = opts.onError || noop; const client = opts.client; const run = (query, params) => cypher(client, query, params); return { limit: opts.limit, driver: 'neo4j-v3', onError, getPosition: async (bm) => { const [pos] = await run(`MATCH (bm: ${opts.bookmarks} { bookmark: $bm }) RETURN bm`, { bm }); if (pos === undefined) return 0; return toInternalPosition(pos.position); }, setPosition: async (bm, pos) => { const position = toNeoPosition(pos); await run(` MERGE (bm: ${opts.bookmarks} { bookmark: $bm }) ON CREATE SET bm.position = $position ON MATCH SET bm.position = $position `, { bm, position }); }, getEventsFor: async (stream, id, from) => { const params = { stream, id, from: toNeoPosition(from), }; let query = ` MATCH (ev: ${opts.events}) WHERE ev.aggregateId = $id AND ev.position > datetime($from) AND ev.stream = $stream `; const limit = opts.limit ? `LIMIT ${opts.limit}` : ''; const events = await run(`${query} RETURN ev ORDER BY ev.position ASC ${limit}`, params); const parsed = events.map((ev) => ({ stream: ev.stream, position: toInternalPosition(ev.position), version: toVersion(ev.version), timestamp: new Date(ev.timestamp), aggregateId: ev.aggregateId, event: JSON.parse(ev.event), })); return parsed; }, getLastEventFor: async (stream, id) => { const streams = (0, util_1.toArray)(stream).map((stream) => `'${stream}'`); const params = {}; let query = ` MATCH (ev: ${opts.events}) WHERE ev.stream IN [${streams.join(', ')}]`; if (id) { query += ` AND ev.aggregateId = $id`; params.id = id; } const events = await run(`${query} RETURN ev ORDER BY ev.position DESC LIMIT 1`, params); const parsed = events.map((ev) => ({ stream: ev.stream, position: toInternalPosition(ev.position), version: toVersion(ev.version), timestamp: new Date(ev.timestamp), aggregateId: ev.aggregateId, event: JSON.parse(ev.event), })); return parsed[0]; }, getEventsFrom: async (stream, pos, lim) => { const streams = (0, util_1.toArray)(stream).map((stream) => `'${stream}'`); const params = { pos: toNeoPosition(pos) }; const query = ` MATCH (ev: ${opts.events}) WHERE ev.stream IN [${streams.join(', ')}] AND ev.position > datetime($pos) `; const limit = (lim !== null && lim !== void 0 ? lim : opts.limit) ? `LIMIT ${opts.limit}` : ''; const events = await run(`${query} RETURN ev ORDER BY ev.position ASC ${limit}`, params); const parsed = events.map((ev) => ({ stream: ev.stream, position: toInternalPosition(ev.position), version: toVersion(ev.version), timestamp: new Date(ev.timestamp), aggregateId: ev.aggregateId, event: JSON.parse(ev.event), })); return parsed; }, createEvents: (0, util_1.createEventsMapper)(0), append: async (stream, id, _version, newEvents) => { const client = await opts.client; for (const event of newEvents) { try { await cypher(client, ` WITH datetime.transaction() as curr, $stream + "_" + toString(datetime.transaction()) as streampos CREATE (ev: ${opts.events} { stream: $stream, position: curr, version: $version, timestamp: datetime($timestamp), aggregateId: $id, event: $event, _streamPosition: streampos, _streamIdVersion: $streamIdVersion }) RETURN ev `, { stream, id, version: event.version, timestamp: event.timestamp.toISOString(), event: JSON.stringify(event.event), streamIdVersion: `${stream}_${id}_${event.version}`, }); } catch (ex) { if (ex instanceof neo.Neo4jError === false) throw ex; if (ex.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') { throw new error_1.VersionError(ex.message); } throw ex; } } return newEvents; }, }; } exports.createProvider = createProvider; async function migrate(opts) { const cli = await opts.client; const session = cli.session({ defaultAccessMode: 'WRITE' }); const trx = session.beginTransaction(); await trx.run(`CREATE INDEX ON :${opts.events}(stream, position)`); await trx.run(`CREATE INDEX ON :${opts.events}(stream, aggregateId, position)`); await trx.run(`CREATE CONSTRAINT ON (ev: ${opts.events}) ASSERT ev._streamPos IS UNIQUE`); await trx.run(`CREATE CONSTRAINT ON (ev: ${opts.events}) ASSERT ev._streamIdVersion IS UNIQUE`); await trx.commit(); await session.close(); } exports.migrate = migrate; async function cypher(client, query, params) { var _a; const cli = await client; const session = cli.session({ defaultAccessMode: 'WRITE' }); const response = await session.run(query, params); await session.close(); // Unfortunately the type definitions in neo4j-driver are weak and don't // allow us to do any better here const objects = response.records.map((record) => record.toObject()); const results = []; for (const row of objects) { let obj = {}; for (const key in row) { if (((_a = row[key]) === null || _a === void 0 ? void 0 : _a.properties) === undefined) { obj[sanitise(key)] = row[key]; continue; } Object.assign(obj, row[key].properties); } results.push(obj); } return results; } exports.cypher = cypher; function sanitise(key) { const last = key.split('.').slice(-1)[0]; return last; } function noop() { } function toVersion(value) { return neo.isInt(value) ? value.toInt() : value; } function toNeoPosition(position) { if (!position) { return new Date(0).toISOString(); } if (typeof position === 'number') { return new Date(position).toISOString(); } if (position instanceof Date) { return position.toISOString(); } return position; } function toInternalPosition(position) { if (neo.isDateTime(position) || typeof position === 'string') { return new Date(position.toString()).valueOf(); } if (isNaN(position)) return 0; return position; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmVvNGotdjMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJuZW80ai12My50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxvQ0FBbUM7QUFFbkMsbUNBQXNDO0FBQ3RDLGlDQUFvRDtBQTJCcEQsU0FBZ0IsY0FBYyxDQUFrQixJQUFhO0lBQzNELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFBO0lBQ3BDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUE7SUFDMUIsTUFBTSxHQUFHLEdBQUcsQ0FBYyxLQUFhLEVBQUUsTUFBVyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUksTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUV6RixPQUFPO1FBQ0wsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1FBQ2pCLE1BQU0sRUFBRSxVQUFVO1FBQ2xCLE9BQU87UUFDUCxXQUFXLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxFQUFFO1lBQ3hCLE1BQU0sQ0FBQyxHQUFHLENBQUMsR0FBRyxNQUFNLEdBQUcsQ0FDckIsY0FBYyxJQUFJLENBQUMsU0FBUywrQkFBK0IsRUFDM0QsRUFBRSxFQUFFLEVBQUUsQ0FDUCxDQUFBO1lBQ0QsSUFBSSxHQUFHLEtBQUssU0FBUztnQkFBRSxPQUFPLENBQUMsQ0FBQTtZQUMvQixPQUFPLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQTtRQUN6QyxDQUFDO1FBQ0QsV0FBVyxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUUsR0FBRyxFQUFFLEVBQUU7WUFDN0IsTUFBTSxRQUFRLEdBQUcsYUFBYSxDQUFDLEdBQUcsQ0FBQyxDQUFBO1lBQ25DLE1BQU0sR0FBRyxDQUNQO3FCQUNhLElBQUksQ0FBQyxTQUFTOzs7T0FHNUIsRUFDQyxFQUFFLEVBQUUsRUFBRSxRQUFRLEVBQUUsQ0FDakIsQ0FBQTtRQUNILENBQUM7UUFDRCxZQUFZLEVBQUUsS0FB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