evtstore
Version:
Event Sourcing with Node.JS
215 lines (214 loc) • 16.8 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',
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmVvNGouanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJuZW80ai50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxvQ0FBbUM7QUFFbkMsbUNBQXNDO0FBQ3RDLGlDQUFvRDtBQTJCcEQsU0FBZ0IsY0FBYyxDQUFrQixJQUFhO0lBQzNELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFBO0lBQ3BDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUE7SUFDMUIsTUFBTSxHQUFHLEdBQUcsQ0FBYyxLQUFhLEVBQUUsTUFBVyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUksTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUV6RixPQUFPO1FBQ0wsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO1FBQ2pCLE1BQU0sRUFBRSxPQUFPO1FBQ2YsT0FBTztRQUNQLFdBQVcsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLEVBQUU7WUFDeEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxHQUFHLE1BQU0sR0FBRyxDQUNyQixjQUFjLElBQUksQ0FBQyxTQUFTLCtCQUErQixFQUMzRCxFQUFFLEVBQUUsRUFBRSxDQUNQLENBQUE7WUFDRCxJQUFJLEdBQUcsS0FBSyxTQUFTO2dCQUFFLE9BQU8sQ0FBQyxDQUFBO1lBQy9CLE9BQU8sa0JBQWtCLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQ3pDLENBQUM7UUFDRCxXQUFXLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsRUFBRTtZQUM3QixNQUFNLFFBQVEsR0FBRyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUE7WUFDbkMsTUFBTSxHQUFHLENBQ1A7cUJBQ2EsSUFBSSxDQUFDLFNBQVM7OztPQUc1QixFQUNDLEVBQUUsRUFBRSxFQUFFLFFBQVEsRUFBRSxDQUNqQixDQUFBO1FBQ0gsQ0FBQztRQUNELFlBQVksRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsRUFBRTtZQUN2QyxNQUFNLE1BQU0sR0FBUSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLGFBQWEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFBO1lBQzdELElBQUksS0FBSyxHQUFHO3FCQUNHLElBQUksQ0FBQyxNQUFNOzs7O09BSXpCLENBQUE7WUFDRCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFBO1lBRXJELE1BQU0sTUFBTSxHQUFHLE1BQU0sR0FBRyxDQUFNLEdBQUcsS0FBSyx1Q0FBdUMsS0FBSyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUE7WUFFN0YsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDakMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxNQUFNO2dCQUNqQixRQUFRLEVBQUUsa0JBQWtCLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQztnQkFDekMsT0FBTyxFQUFFLFNBQVMsQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDO2dCQUM5QixTQUFTLEVBQUUsSUFBSSxJQUFJLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FBQztnQkFDakMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxXQUFXO2dCQUMzQixLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDO2FBQzVCLENBQUMsQ0FBQyxDQUFBO1lBRUgsT0FBTyxNQUFNLENBQUE7UUFDZixDQUFDO1FBQ0QsZUFBZSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLEVBQUU7WUFDcEMsTUFBTSxPQUFPLEdBQUcsSUFBQSxjQUFPLEVBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUE7WUFDOUQsTUFBTSxNQUFNLEdBQVEsRUFBRSxDQUFBO1lBRXRCLElBQUksS0FBSyxHQUFHO3FCQUNHLElBQUksQ0FBQyxNQUFNOzhCQUNGLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQTtZQUU3QyxJQUFJLEVBQUUsRUFBRTtnQkFDTixNQUFNLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQTtnQkFDZCxLQUFLLElBQUksMkJBQTJCLENBQUE7YUFDckM7WUFFRCxNQUFNLE1BQU0sR0FBRyxNQUFNLEdBQUcsQ0FBTSxHQUFHLEtBQUssOENBQThDLEVBQUUsTUFBTSxDQUFDLENBQUE7WUFFN0YsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDakMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxNQUFNO2dCQUNqQixRQUFRLEVBQUUsa0JBQWtCLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQztnQkFDekMsT0FBTyxFQUFFLFNBQVMsQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDO2dCQUM5QixTQUFTLEVBQUUsSUFBSSxJQUFJLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FBQztnQkFDakMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxXQUFXO2dCQUMzQixLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDO2FBQzVCLENBQUMsQ0FBQyxDQUFBO1lBRUgsT0FBTyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDbEIsQ0FBQztRQUNELGFBQWEsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRTtZQUN4QyxNQUFNLE9BQU8sR0FBRyxJQUFBLGNBQU8sRUFBQyxNQUFNLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLElBQUksTUFBTSxHQUFHLENBQUMsQ0FBQTtZQUM5RCxNQUFNLE1BQU0sR0FBUSxFQUFFLEdBQUcsRUFBRSxhQUFhLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQTtZQUMvQyxNQUFNLEtBQUssR0FBRztxQkFDQyxJQUFJLENBQUMsTUFBTTs4QkFDRixPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQzs7T0FFekMsQ0FBQTtZQUNELE1BQU0sS0FBSyxHQUFHLENBQUEsR0FBRyxhQUFILEdBQUcsY0FBSCxHQUFHLEdBQUksSUFBSSxDQUFDLEtBQUssRUFBQyxDQUFDLENBQUMsU0FBUyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtZQUU1RCxNQUFNLE1BQU0sR0FBRyxNQUFNLEdBQUcsQ0FBTSxHQUFHLEtBQUssdUNBQXVDLEtBQUssRUFBRSxFQUFFLE1BQU0sQ0FBQyxDQUFBO1lBRTdGLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7Z0JBQ2pDLE1BQU0sRUFBRSxFQUFFLENBQUMsTUFBTTtnQkFDakIsUUFBUSxFQUFFLGtCQUFrQixDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUM7Z0JBQ3pDLE9BQU8sRUFBRSxTQUFTLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQztnQkFDOUIsU0FBUyxFQUFFLElBQUksSUFBSSxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQUM7Z0JBQ2pDLFdBQVcsRUFBRSxFQUFFLENBQUMsV0FBVztnQkFDM0IsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQzthQUM1QixDQUFDLENBQUMsQ0FBQTtZQUVILE9BQU8sTUFBTSxDQUFBO1FBQ2YsQ0FBQztRQUNELFlBQVksRUFBRSxJQUFBLHlCQUFrQixFQUFJLENBQUMsQ0FBQztRQUN0QyxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxFQUFFO1lBQ2hELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQTtZQUNoQyxLQUFLLE1BQU0sS0FBSyxJQUFJLFNBQVMsRUFBRTtnQkFDN0IsSUFBSTtvQkFDRixNQUFNLE1BQU0sQ0FDVixNQUFNLEVBQ047OzBCQUVjLElBQUksQ0FBQyxNQUFNOzs7Ozs7Ozs7OztXQVcxQixFQUNDO3dCQUNFLE1BQU07d0JBQ04sRUFBRTt3QkFDRixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87d0JBQ3RCLFNBQVMsRUFBRSxLQUFLLENBQUMsU0FBUyxDQUFDLFdBQVcsRUFBRTt3QkFDeEMsS0FBSyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQzt3QkFDbEMsZUFBZSxFQUFFLEdBQUcsTUFBTSxJQUFJLEVBQUUsSUFBSSxLQUFLLENBQUMsT0FBTyxFQUFFO3FCQUNwRCxDQUNGLENBQUE7aUJBQ0Y7Z0JBQUMsT0FBTyxFQUFPLEVBQUU7b0JBQ2hCLElBQUksRUFBRSxZQUFZLEdBQUcsQ0FBQyxVQUFVLEtBQUssS0FBSzt3QkFBRSxNQUFNLEVBQUUsQ0FBQTtvQkFDcEQsSUFBSSxFQUFFLENBQUMsSUFBSSxLQUFLLG1EQUFtRCxFQUFFO3dCQUNuRSxNQUFNLElBQUksb0JBQVksQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLENBQUE7cUJBQ25DO29CQUNELE1BQU0sRUFBRSxDQUFBO2lCQUNUO2FBQ0Y7WUFDRCxPQUFPLFNBQVMsQ0FBQTtRQUNsQixDQUFDO0tBQ0YsQ0FBQTtBQUNILENBQUM7QUE3SUQsd0NBNklDO0FBRU0sS0FBSyxVQUFVLE9BQU8sQ0FBQyxJQUFvQjtJQUNoRCxNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUE7SUFDN0IsTUFBTSxPQUFPLEdBQUcsTUFBTSxHQUFHLENBQUMsT0FBTyxDQUFDLEVBQUUsaUJBQWlCLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQTtJQUNqRSxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQTtJQUV0QyxNQUFNLEdBQUcsQ0FBQyxHQUFHLENBQUM7bUJBQ0csSUFBSSxDQUFDLE1BQU07O2VBRWYsSUFBSSxDQUFDLE1BQU07O0dBRXZCLENBQUMsQ0FBQTtJQUVGLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQzttQkFDRyxJQUFJLENBQUMsTUFBTTs7ZUFFZixJQUFJLENBQUMsTUFBTTs7R0FFdkIsQ0FBQyxDQUFBO0lBRUYsTUFBTSxHQUFHLENBQUMsR0FBRyxDQUFDO3dCQUNRLElBQUksQ0FBQyxNQUFNOztjQUVyQixJQUFJLENBQUMsTUFBTTs7R0FFdEIsQ0FBQyxDQUFBO0lBRUYsTUFBTSxHQUFHLENBQUMsR0FBRyxDQUFDO3dCQUNRLElBQUksQ0FBQyxNQUFNOztjQUVyQixJQUFJLENBQUMsTUFBTTs7R0FFdEIsQ0FBQyxDQUFBO0lBRUYsTUFBTSxHQUFHLENBQUMsTUFBTSxFQUFFLENBQUE7SUFDbEIsTUFBTSxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUE7QUFDdkIsQ0FBQztBQW5DRCwwQkFtQ0M7QUFFTSxLQUFLLFVBQVUsTUFBTSxDQUMxQixNQUF3QyxFQUN4QyxLQUFhLEVBQ2IsTUFBVzs7SUFFWCxNQUFNLEdBQUcsR0FBRyxNQUFNLE1BQU0sQ0FBQTtJQUN4QixNQUFNLE9BQU8sR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLEVBQUUsaUJBQWlCLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQTtJQUMzRCxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFBO0lBQ2pELE1BQU0sT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFBO0lBRXJCLHdFQUF3RTtJQUN4RSxpQ0FBaUM7SUFDakMsTUFBTSxPQUFPLEdBQVUsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFBO0lBQzFFLE1BQU0sT0FBTyxHQUFRLEVBQUUsQ0FBQTtJQUV2QixLQUFLLE1BQU0sR0FBRyxJQUFJLE9BQU8sRUFBRTtRQUN6QixJQUFJLEdBQUcsR0FBUSxFQUFFLENBQUE7UUFDakIsS0FBSyxNQUFNLEdBQUcsSUFBSSxHQUFHLEVBQUU7WUFDckIsSUFBSSxDQUFBLE1BQUEsR0FBRyxDQUFDLEdBQUcsQ0FBQywwQ0FBRSxVQUFVLE1BQUssU0FBUyxFQUFFO2dCQUN0QyxHQUFHLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO2dCQUM3QixTQUFRO2FBQ1Q7WUFFRCxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUE7U0FDeEM7UUFDRCxPQUFPLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFBO0tBQ2xCO0lBRUQsT0FBTyxPQUFPLENBQUE7QUFDaEIsQ0FBQztBQTdCRCx3QkE2QkM7QUFFRCxTQUFTLFFBQVEsQ0FBQyxHQUFXO0lBQzNCLE1BQU0sSUFBSSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDeEMsT0FBTyxJQUFJLENBQUE7QUFDYixDQUFDO0FBRUQsU0FBUyxJQUFJLEtBQUksQ0FBQztBQUVsQixTQUFTLFNBQVMsQ0FBQyxLQUFVO0lBQzNCLE9BQU8sR0FBRyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUE7QUFDakQsQ0FBQztBQUVELFNBQVMsYUFBYSxDQUFDLFFBQWE7SUFDbEMsSUFBSSxDQUFDLFFBQVEsRUFBRTtRQUNiLE9BQU8sSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUE7S0FDakM7SUFFRCxJQUFJLE9BQU8sUUFBUSxLQUFLLFFBQVEsRUFBRTtRQUNoQyxPQUFPLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFBO0tBQ3hDO0lBRUQsSUFBSSxRQUFRLFlBQVksSUFBSSxFQUFFO1FBQzVCLE9BQU8sUUFBUSxDQUFDLFdBQVcsRUFBRSxDQUFBO0tBQzlCO0lBRUQsT0FBTyxRQUFRLENBQUE7QUFDakIsQ0FBQztBQUVELFNBQVMsa0JBQWtCLENBQUMsUUFBYTtJQUN2QyxJQUFJLEdBQUcsQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxFQUFFO1FBQzVELE9BQU8sSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUE7S0FDL0M7SUFFRCxJQUFJLEtBQUssQ0FBQyxRQUFRLENBQUM7UUFBRSxPQUFPLENBQUMsQ0FBQTtJQUU3QixPQUFPLFFBQVEsQ0FBQTtBQUNqQixDQUFDIn0=