evtstore
Version:
Event Sourcing with Node.JS
199 lines (198 loc) • 16.5 kB
JavaScript
;
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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