UNPKG

evtstore

Version:

Event Sourcing with Node.JS

215 lines (214 loc) 16.8 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', 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) { params.id = id; query += ` AND ev.aggregateId = $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 = await cli.session({ defaultAccessMode: 'WRITE' }); const trx = session.beginTransaction(); await trx.run(` CREATE INDEX ${opts.events}_stream_position IF NOT EXISTS FOR (ev: ${opts.events}) ON (ev.stream, ev.position) `); await trx.run(` CREATE INDEX ${opts.events}_stream_id_pos IF NOT EXISTS FOR (ev: ${opts.events}) ON (ev.stream, ev.aggregateId, ev.position) `); await trx.run(` CREATE CONSTRAINT ${opts.events}_streampos_unique IF NOT EXISTS ON (ev: ${opts.events}) ASSERT ev._streamPos IS UNIQUE `); await trx.run(` CREATE CONSTRAINT ${opts.events}_streamidversion_unique IF NOT EXISTS 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmVvNGouanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJuZW80ai50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxvQ0FBbUM7QUFFbkMsbUNBQXNDO0FBQ3RDLGlDQUFvRDtBQTJCcEQsU0FBZ0IsY0FBYyxDQUFrQixJQUFhO0lBQzNELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFBO0lBQ3BDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUE7SUFDMUIsTUFBTSxHQUFHLEdBQUcsQ0FBYyxLQUFhLEVBQUUsTUFBVyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUksTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUV6RixPQUFPO1FBQ0wsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1FBQ2pCLE1BQU0sRUFBRSxPQUFPO1FBQ2YsT0FBTztRQUNQLFdBQVcsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLEVBQUU7WUFDeEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxHQUFHLE1BQU0sR0FBRyxDQUNyQixjQUFjLElBQUksQ0FBQyxTQUFTLCtCQUErQixFQUMzRCxFQUFFLEVBQUUsRUFBRSxDQUNQLENBQUE7WUFDRCxJQUFJLEdBQUcsS0FBSyxTQUFTO2dCQUFFLE9BQU8sQ0FBQyxDQUFBO1lBQy9CLE9BQU8sa0JBQWtCLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQ3pDLENBQUM7UUFDRCxXQUFXLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsRUFBRTtZQUM3QixNQUFNLFFBQVEsR0FBRyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUE7WUFDbkMsTUFBTSxHQUFHLENBQ1A7cUJBQ2EsSUFBSSxDQUFDLFNBQVM7OztPQUc1QixFQUNDLEVBQUUsRUFBRSxFQUFFLFFBQVEsRUFBRSxDQUNqQixDQUFBO1FBQ0gsQ0FBQztRQUNELFlBQVksRUFBRSxLQUFLLEVBQUUs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