@getanthill/event-source
Advanced tools
Comparing version 0.8.0 to 0.9.0
@@ -5,3 +5,2 @@ "use strict"; | ||
const assert_1 = require("assert"); | ||
const date = require("./utils/date"); | ||
/** | ||
@@ -102,7 +101,3 @@ * Events class to manage events for EventSourced instances | ||
static store(collection, events) { | ||
const datedEvents = events.map((event) => ({ | ||
created_at: date.getNow(), | ||
...event, | ||
})); | ||
return collection.insertMany(datedEvents); | ||
return collection.insertMany(events); | ||
} | ||
@@ -109,0 +104,0 @@ } |
@@ -11,5 +11,12 @@ import * as ERRORS from './errors'; | ||
CORRELATION_FIELD: string; | ||
CREATED_AT_FIELD: string; | ||
CURRENT_HASH_FIELD: string; | ||
PREVIOUS_HASH_FIELD: string; | ||
NONCE_FIELD: string; | ||
EVENTS_COLLECTION_NAME: string; | ||
SNAPSHOTS_COLLECTION_NAME: string; | ||
WITH_GLOBAL_VERSION: boolean; | ||
WITH_BLOCKCHAIN_HASH: boolean; | ||
BLOCKCHAIN_HASH_DIFFICULTY: number; | ||
BLOCKCHAIN_HASH_GENESIS: string; | ||
} | ||
@@ -22,2 +29,6 @@ /** | ||
* @param {string} [options.CORRELATION_FIELD='correlation_id'] The correlation id | ||
* @param {string} [options.CREATED_AT_FIELD='created_at'] The event created_at field | ||
* @param {string} [options.CURRENT_HASH_FIELD='hash'] The blockchain hash field | ||
* @param {string} [options.PREVIOUS_HASH_FIELD='prev'] The blockchain previous hash field | ||
* @param {string} [options.NONCE_FIELD='nonce'] The blockchain hash field | ||
* @param {string} [options.EVENTS_COLLECTION_NAME=`${collectionName}_events`] | ||
@@ -29,2 +40,9 @@ * The events collection name | ||
* of a entity-local one | ||
* @param {number} [options.BLOCKCHAIN_HASH_DIFFICULTY=0] | ||
* Blockchain hash generation difficulty | ||
* @param {boolean} [options.WITH_BLOCKCHAIN_HASH=false] | ||
* Does the model have a blockchain hash generated version | ||
* after version | ||
* @param {boolean} [options.BLOCKCHAIN_HASH_GENESIS=0000000000000000000000000000000000000000000000000000000000000000] | ||
* Genesis hash value | ||
* | ||
@@ -143,2 +161,24 @@ * The snapshots collection name | ||
/** | ||
* Compute the hash for the blockchain mode | ||
* @param {number} version | ||
* @param {Date} createdAt | ||
* @param {object} state | ||
* @param {string} [precedingHash=' '] | ||
* @param {number} [nonce=0] | ||
* @returns {string} | ||
*/ | ||
computeHash(version: number, createdAt: Date | string, state: any, nonce: number, precedingHash: string): string; | ||
/** | ||
* Computes a difficult blockchain hash | ||
* @param {number} version | ||
* @param {Date} createdAt | ||
* @param {object} state | ||
* @param {string} [precedingHash] | ||
* @returns {string} | ||
*/ | ||
computeDifficultHash(version: number, createdAt: Date | string, state: any, precedingHash: string): { | ||
hash: string; | ||
nonce: number; | ||
}; | ||
/** | ||
* Reduce events on the given state | ||
@@ -145,0 +185,0 @@ * @param {object[]} events Array of events |
@@ -5,2 +5,3 @@ "use strict"; | ||
const assert_1 = require("assert"); | ||
const crypto = require("crypto"); | ||
const lodash_1 = require("lodash"); | ||
@@ -11,2 +12,3 @@ const deep_freeze_1 = require("./utils/deep-freeze"); | ||
const ERRORS = require("./errors"); | ||
const date = require("./utils/date"); | ||
/** | ||
@@ -18,2 +20,6 @@ * Factory of the EventSourced class | ||
* @param {string} [options.CORRELATION_FIELD='correlation_id'] The correlation id | ||
* @param {string} [options.CREATED_AT_FIELD='created_at'] The event created_at field | ||
* @param {string} [options.CURRENT_HASH_FIELD='hash'] The blockchain hash field | ||
* @param {string} [options.PREVIOUS_HASH_FIELD='prev'] The blockchain previous hash field | ||
* @param {string} [options.NONCE_FIELD='nonce'] The blockchain hash field | ||
* @param {string} [options.EVENTS_COLLECTION_NAME=`${collectionName}_events`] | ||
@@ -25,2 +31,9 @@ * The events collection name | ||
* of a entity-local one | ||
* @param {number} [options.BLOCKCHAIN_HASH_DIFFICULTY=0] | ||
* Blockchain hash generation difficulty | ||
* @param {boolean} [options.WITH_BLOCKCHAIN_HASH=false] | ||
* Does the model have a blockchain hash generated version | ||
* after version | ||
* @param {boolean} [options.BLOCKCHAIN_HASH_GENESIS=0000000000000000000000000000000000000000000000000000000000000000] | ||
* Genesis hash value | ||
* | ||
@@ -58,5 +71,12 @@ * The snapshots collection name | ||
CORRELATION_FIELD: 'correlation_id', | ||
CURRENT_HASH_FIELD: 'hash', | ||
PREVIOUS_HASH_FIELD: 'prev', | ||
NONCE_FIELD: 'nonce', | ||
CREATED_AT_FIELD: 'created_at', | ||
EVENTS_COLLECTION_NAME: `${collectionName}_events`, | ||
SNAPSHOTS_COLLECTION_NAME: `${collectionName}_snapshots`, | ||
WITH_GLOBAL_VERSION: false, | ||
WITH_BLOCKCHAIN_HASH: false, | ||
BLOCKCHAIN_HASH_DIFFICULTY: 0, | ||
BLOCKCHAIN_HASH_GENESIS: '0000000000000000000000000000000000000000000000000000000000000000', | ||
}, options); | ||
@@ -147,2 +167,38 @@ } | ||
/** | ||
* Compute the hash for the blockchain mode | ||
* @param {number} version | ||
* @param {Date} createdAt | ||
* @param {object} state | ||
* @param {string} [precedingHash=' '] | ||
* @param {number} [nonce=0] | ||
* @returns {string} | ||
*/ | ||
static computeHash(version, createdAt, state, nonce, precedingHash) { | ||
const hash = crypto.createHash('sha256'); | ||
hash.update(`${version}${precedingHash}${createdAt}${JSON.stringify((0, lodash_1.omit)(state, [ | ||
EventSourced.options.CURRENT_HASH_FIELD, | ||
EventSourced.options.PREVIOUS_HASH_FIELD, | ||
EventSourced.options.NONCE_FIELD, | ||
]))}${nonce}`); | ||
return hash.digest('hex'); | ||
} | ||
/** | ||
* Computes a difficult blockchain hash | ||
* @param {number} version | ||
* @param {Date} createdAt | ||
* @param {object} state | ||
* @param {string} [precedingHash] | ||
* @returns {string} | ||
*/ | ||
static computeDifficultHash(version, createdAt, state, precedingHash) { | ||
const difficulty = EventSourced.options.BLOCKCHAIN_HASH_DIFFICULTY; | ||
let nonce = 0; | ||
let hash = EventSourced.computeHash(version, createdAt, state, nonce, precedingHash); | ||
while (hash.substring(0, difficulty) !== Array(difficulty + 1).join('0')) { | ||
nonce += 1; | ||
hash = EventSourced.computeHash(version, createdAt, state, nonce, precedingHash); | ||
} | ||
return { hash, nonce }; | ||
} | ||
/** | ||
* Reduce events on the given state | ||
@@ -166,2 +222,9 @@ * @param {object[]} events Array of events | ||
} | ||
const precedingHash = (options.WITH_GLOBAL_VERSION === true && globalVersion > stateVersion | ||
? lastEvent?.[EventSourced.options.CURRENT_HASH_FIELD] | ||
: updated?.[EventSourced.options.CURRENT_HASH_FIELD]) || | ||
EventSourced.options.BLOCKCHAIN_HASH_GENESIS; | ||
// Apply a timestamp to the event: | ||
event[EventSourced.options.CREATED_AT_FIELD] = | ||
event[EventSourced.options.CREATED_AT_FIELD] || date.getNow(); | ||
updated = reducer(updated, event); | ||
@@ -178,2 +241,13 @@ // Force the event version tagging | ||
event[EventSourced.options.CORRELATION_FIELD] = correlationId; | ||
// Blockchain logic | ||
if (options.WITH_BLOCKCHAIN_HASH === true) { | ||
const { hash, nonce } = EventSourced.computeDifficultHash(event.version, event.created_at, event, precedingHash); | ||
updated[EventSourced.options.CURRENT_HASH_FIELD] = hash; | ||
updated[EventSourced.options.NONCE_FIELD] = nonce; | ||
event[EventSourced.options.PREVIOUS_HASH_FIELD] = precedingHash; | ||
event[EventSourced.options.CURRENT_HASH_FIELD] = | ||
updated[EventSourced.options.CURRENT_HASH_FIELD]; | ||
event[EventSourced.options.NONCE_FIELD] = | ||
updated[EventSourced.options.NONCE_FIELD]; | ||
} | ||
return (0, deep_freeze_1.deepFreeze)(updated); | ||
@@ -180,0 +254,0 @@ }, (0, deep_freeze_1.deepFreeze)((0, lodash_1.cloneDeep)(state))); |
{ | ||
"name": "@getanthill/event-source", | ||
"version": "0.8.0", | ||
"version": "0.9.0", | ||
"description": "Event-sourced data store handling library", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -118,9 +118,4 @@ import { strict as assert } from 'assert'; | ||
static store(collection, events) { | ||
const datedEvents = events.map((event) => ({ | ||
created_at: date.getNow(), | ||
...event, | ||
})); | ||
return collection.insertMany(datedEvents); | ||
return collection.insertMany(events); | ||
} | ||
} |
import { strict as assert } from 'assert'; | ||
import * as crypto from 'crypto'; | ||
import { cloneDeep } from 'lodash'; | ||
import { cloneDeep, omit } from 'lodash'; | ||
@@ -13,2 +14,4 @@ import { deepFreeze } from './utils/deep-freeze'; | ||
import * as date from './utils/date'; | ||
interface Logger { | ||
@@ -23,5 +26,12 @@ trace: Function; | ||
CORRELATION_FIELD: string; | ||
CREATED_AT_FIELD: string; | ||
CURRENT_HASH_FIELD: string; | ||
PREVIOUS_HASH_FIELD: string; | ||
NONCE_FIELD: string; | ||
EVENTS_COLLECTION_NAME: string; | ||
SNAPSHOTS_COLLECTION_NAME: string; | ||
WITH_GLOBAL_VERSION: boolean; | ||
WITH_BLOCKCHAIN_HASH: boolean; | ||
BLOCKCHAIN_HASH_DIFFICULTY: number; | ||
BLOCKCHAIN_HASH_GENESIS: string; | ||
} | ||
@@ -35,2 +45,6 @@ | ||
* @param {string} [options.CORRELATION_FIELD='correlation_id'] The correlation id | ||
* @param {string} [options.CREATED_AT_FIELD='created_at'] The event created_at field | ||
* @param {string} [options.CURRENT_HASH_FIELD='hash'] The blockchain hash field | ||
* @param {string} [options.PREVIOUS_HASH_FIELD='prev'] The blockchain previous hash field | ||
* @param {string} [options.NONCE_FIELD='nonce'] The blockchain hash field | ||
* @param {string} [options.EVENTS_COLLECTION_NAME=`${collectionName}_events`] | ||
@@ -42,2 +56,9 @@ * The events collection name | ||
* of a entity-local one | ||
* @param {number} [options.BLOCKCHAIN_HASH_DIFFICULTY=0] | ||
* Blockchain hash generation difficulty | ||
* @param {boolean} [options.WITH_BLOCKCHAIN_HASH=false] | ||
* Does the model have a blockchain hash generated version | ||
* after version | ||
* @param {boolean} [options.BLOCKCHAIN_HASH_GENESIS=0000000000000000000000000000000000000000000000000000000000000000] | ||
* Genesis hash value | ||
* | ||
@@ -57,5 +78,13 @@ * The snapshots collection name | ||
CORRELATION_FIELD: 'correlation_id', | ||
CURRENT_HASH_FIELD: 'hash', | ||
PREVIOUS_HASH_FIELD: 'prev', | ||
NONCE_FIELD: 'nonce', | ||
CREATED_AT_FIELD: 'created_at', | ||
EVENTS_COLLECTION_NAME: `${collectionName}_events`, | ||
SNAPSHOTS_COLLECTION_NAME: `${collectionName}_snapshots`, | ||
WITH_GLOBAL_VERSION: false, | ||
WITH_BLOCKCHAIN_HASH: false, | ||
BLOCKCHAIN_HASH_DIFFICULTY: 0, | ||
BLOCKCHAIN_HASH_GENESIS: | ||
'0000000000000000000000000000000000000000000000000000000000000000', | ||
}, | ||
@@ -185,2 +214,74 @@ options, | ||
/** | ||
* Compute the hash for the blockchain mode | ||
* @param {number} version | ||
* @param {Date} createdAt | ||
* @param {object} state | ||
* @param {string} [precedingHash=' '] | ||
* @param {number} [nonce=0] | ||
* @returns {string} | ||
*/ | ||
static computeHash( | ||
version: number, | ||
createdAt: Date | string, | ||
state: any, | ||
nonce: number, | ||
precedingHash: string, | ||
): string { | ||
const hash = crypto.createHash('sha256'); | ||
hash.update( | ||
`${version}${precedingHash}${createdAt}${JSON.stringify( | ||
omit(state, [ | ||
EventSourced.options.CURRENT_HASH_FIELD, | ||
EventSourced.options.PREVIOUS_HASH_FIELD, | ||
EventSourced.options.NONCE_FIELD, | ||
]), | ||
)}${nonce}`, | ||
); | ||
return hash.digest('hex'); | ||
} | ||
/** | ||
* Computes a difficult blockchain hash | ||
* @param {number} version | ||
* @param {Date} createdAt | ||
* @param {object} state | ||
* @param {string} [precedingHash] | ||
* @returns {string} | ||
*/ | ||
static computeDifficultHash( | ||
version: number, | ||
createdAt: Date | string, | ||
state: any, | ||
precedingHash: string, | ||
): { hash: string; nonce: number } { | ||
const difficulty: number = | ||
EventSourced.options.BLOCKCHAIN_HASH_DIFFICULTY; | ||
let nonce: number = 0; | ||
let hash = EventSourced.computeHash( | ||
version, | ||
createdAt, | ||
state, | ||
nonce, | ||
precedingHash, | ||
); | ||
while ( | ||
hash.substring(0, difficulty) !== Array(difficulty + 1).join('0') | ||
) { | ||
nonce += 1; | ||
hash = EventSourced.computeHash( | ||
version, | ||
createdAt, | ||
state, | ||
nonce, | ||
precedingHash, | ||
); | ||
} | ||
return { hash, nonce }; | ||
} | ||
/** | ||
* Reduce events on the given state | ||
@@ -210,2 +311,12 @@ * @param {object[]} events Array of events | ||
const precedingHash = | ||
(options.WITH_GLOBAL_VERSION === true && globalVersion > stateVersion | ||
? lastEvent?.[EventSourced.options.CURRENT_HASH_FIELD] | ||
: updated?.[EventSourced.options.CURRENT_HASH_FIELD]) || | ||
EventSourced.options.BLOCKCHAIN_HASH_GENESIS; | ||
// Apply a timestamp to the event: | ||
event[EventSourced.options.CREATED_AT_FIELD] = | ||
event[EventSourced.options.CREATED_AT_FIELD] || date.getNow(); | ||
updated = reducer(updated, event); | ||
@@ -226,2 +337,20 @@ | ||
// Blockchain logic | ||
if (options.WITH_BLOCKCHAIN_HASH === true) { | ||
const { hash, nonce } = EventSourced.computeDifficultHash( | ||
event.version, | ||
event.created_at, | ||
event, | ||
precedingHash, | ||
); | ||
updated[EventSourced.options.CURRENT_HASH_FIELD] = hash; | ||
updated[EventSourced.options.NONCE_FIELD] = nonce; | ||
event[EventSourced.options.PREVIOUS_HASH_FIELD] = precedingHash; | ||
event[EventSourced.options.CURRENT_HASH_FIELD] = | ||
updated[EventSourced.options.CURRENT_HASH_FIELD]; | ||
event[EventSourced.options.NONCE_FIELD] = | ||
updated[EventSourced.options.NONCE_FIELD]; | ||
} | ||
return deepFreeze(updated); | ||
@@ -228,0 +357,0 @@ }, deepFreeze(cloneDeep(state))); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
96857
1954