event-storage 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -541
- package/package.json +22 -21
- package/src/Clock.js +20 -8
- package/src/EventStore.js +244 -51
- package/src/EventStream.js +169 -16
- package/src/Index/ReadableIndex.js +9 -3
- package/src/Index/WritableIndex.js +10 -4
- package/src/JoinEventStream.js +32 -30
- package/src/Partition/ReadablePartition.js +105 -46
- package/src/Partition/WritablePartition.js +73 -14
- package/src/Storage/ReadableStorage.js +136 -13
- package/src/Storage/WritableStorage.js +169 -24
- package/src/Watcher.js +1 -0
- package/src/WatchesFile.js +6 -5
- package/src/metadataUtil.js +79 -0
- package/src/util.js +71 -70
- package/test/Consumer.spec.js +0 -455
- package/test/EventStore.spec.js +0 -632
- package/test/EventStream.spec.js +0 -120
- package/test/Index.spec.js +0 -591
- package/test/JoinEventStream.spec.js +0 -113
- package/test/Partition.spec.js +0 -488
- package/test/Storage.spec.js +0 -1017
- package/test/Watcher.spec.js +0 -131
package/src/Watcher.js
CHANGED
|
@@ -28,6 +28,7 @@ class DirectoryWatcher extends events.EventEmitter {
|
|
|
28
28
|
assert(fs.existsSync(directory), `Can not watch a non-existing directory "${directory}".`);
|
|
29
29
|
assert(fs.statSync(directory).isDirectory(), `Can only watch directories, but "${directory}" is none.`);
|
|
30
30
|
super();
|
|
31
|
+
this.setMaxListeners(1000);
|
|
31
32
|
directoryWatchers.set(directory, this);
|
|
32
33
|
this.directory = directory;
|
|
33
34
|
this.watcher = fs.watch(directory, Object.assign({ persistent: false }, options), this.emit.bind(this));
|
package/src/WatchesFile.js
CHANGED
|
@@ -34,12 +34,13 @@ const WatchesFile = Base => class extends Base {
|
|
|
34
34
|
* @returns {boolean}
|
|
35
35
|
*/
|
|
36
36
|
open() {
|
|
37
|
-
if (
|
|
38
|
-
|
|
37
|
+
if (super.open()) {
|
|
38
|
+
if (!this.watcher) {
|
|
39
|
+
this.watchFile();
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
this.watchFile();
|
|
42
|
-
return super.open();
|
|
43
|
+
return false;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} secret The secret to use for calculating further HMACs
|
|
5
|
+
* @returns {function(string)} A function that calculates the HMAC for a given string
|
|
6
|
+
*/
|
|
7
|
+
const createHmac = secret => string => {
|
|
8
|
+
const hmac = crypto.createHmac('sha256', secret);
|
|
9
|
+
hmac.update(string);
|
|
10
|
+
return hmac.digest('hex');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object|function(object):boolean} Matcher
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} document The document to check against the matcher.
|
|
19
|
+
* @param {Matcher} matcher An object of properties and their values that need to match in the object or a function that checks if the document matches.
|
|
20
|
+
* @returns {boolean} True if the document matches the matcher or false otherwise.
|
|
21
|
+
*/
|
|
22
|
+
function matches(document, matcher) {
|
|
23
|
+
if (typeof document === 'undefined') return false;
|
|
24
|
+
if (typeof matcher === 'undefined') return true;
|
|
25
|
+
|
|
26
|
+
if (typeof matcher === 'function') return matcher(document);
|
|
27
|
+
|
|
28
|
+
for (let prop of Object.getOwnPropertyNames(matcher)) {
|
|
29
|
+
if (typeof matcher[prop] === 'object') {
|
|
30
|
+
if (!matches(document[prop], matcher[prop])) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
} else if (typeof matcher[prop] !== 'undefined' && document[prop] !== matcher[prop]) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Matcher} matcher The matcher object or function that should be serialized.
|
|
42
|
+
* @param {function(string)} hmac A function that calculates a HMAC of the given string.
|
|
43
|
+
* @returns {{matcher: string|object, hmac?: string}}
|
|
44
|
+
*/
|
|
45
|
+
function buildMetadataForMatcher(matcher, hmac) {
|
|
46
|
+
if (!matcher) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (typeof matcher === 'object') {
|
|
50
|
+
return { matcher };
|
|
51
|
+
}
|
|
52
|
+
const matcherString = matcher.toString();
|
|
53
|
+
return { matcher: matcherString, hmac: hmac(matcherString) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {{matcher: string|object, hmac: string}} matcherMetadata The serialized matcher and its HMAC
|
|
58
|
+
* @param {function(string)} hmac A function that calculates a HMAC of the given string.
|
|
59
|
+
* @returns {Matcher} The matcher object or function.
|
|
60
|
+
*/
|
|
61
|
+
function buildMatcherFromMetadata(matcherMetadata, hmac) {
|
|
62
|
+
let matcher;
|
|
63
|
+
if (typeof matcherMetadata.matcher === 'object') {
|
|
64
|
+
matcher = matcherMetadata.matcher;
|
|
65
|
+
} else {
|
|
66
|
+
if (matcherMetadata.hmac !== hmac(matcherMetadata.matcher)) {
|
|
67
|
+
throw new Error('Invalid HMAC for matcher.');
|
|
68
|
+
}
|
|
69
|
+
matcher = eval('(' + matcherMetadata.matcher + ')').bind({}); // jshint ignore:line
|
|
70
|
+
}
|
|
71
|
+
return matcher;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
createHmac,
|
|
76
|
+
matches,
|
|
77
|
+
buildMetadataForMatcher,
|
|
78
|
+
buildMatcherFromMetadata
|
|
79
|
+
};
|
package/src/util.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
1
|
const fs = require('fs');
|
|
3
2
|
const mkdirpSync = require('mkdirp').sync;
|
|
4
3
|
|
|
@@ -40,74 +39,27 @@ function alignTo(value, alignment) {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const hmac = crypto.createHmac('sha256', secret);
|
|
48
|
-
hmac.update(string);
|
|
49
|
-
return hmac.digest('hex');
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @typedef {object|function(object):boolean} Matcher
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* @param {object} document The document to check against the matcher.
|
|
58
|
-
* @param {Matcher} matcher An object of properties and their values that need to match in the object or a function that checks if the document matches.
|
|
59
|
-
* @returns {boolean} True if the document matches the matcher or false otherwise.
|
|
42
|
+
* Method for hashing a string (e.g. a partition name) to a 32-bit unsigned integer.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} str
|
|
45
|
+
* @returns {number}
|
|
60
46
|
*/
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
if (typeof matcher === 'function') return matcher(document);
|
|
66
|
-
|
|
67
|
-
for (let prop of Object.getOwnPropertyNames(matcher)) {
|
|
68
|
-
if (typeof matcher[prop] === 'object') {
|
|
69
|
-
if (!matches(document[prop], matcher[prop])) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
} else if (typeof matcher[prop] !== 'undefined' && document[prop] !== matcher[prop]) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
47
|
+
function hash(str) {
|
|
48
|
+
/* istanbul ignore if */
|
|
49
|
+
if (str.length === 0) {
|
|
50
|
+
return 0;
|
|
75
51
|
}
|
|
76
|
-
|
|
77
|
-
|
|
52
|
+
let hash = 5381,
|
|
53
|
+
i = str.length;
|
|
78
54
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* @param {function(string)} hmac A function that calculates a HMAC of the given string.
|
|
82
|
-
* @returns {{matcher: string|object, hmac?: string}}
|
|
83
|
-
*/
|
|
84
|
-
function buildMetadataForMatcher(matcher, hmac) {
|
|
85
|
-
if (!matcher) {
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
if (typeof matcher === 'object') {
|
|
89
|
-
return { matcher };
|
|
55
|
+
while(i) {
|
|
56
|
+
hash = ((hash << 5) + hash) ^ str.charCodeAt(--i); // jshint ignore:line
|
|
90
57
|
}
|
|
91
|
-
const matcherString = matcher.toString();
|
|
92
|
-
return { matcher: matcherString, hmac: hmac(matcherString) };
|
|
93
|
-
}
|
|
94
58
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*/
|
|
100
|
-
function buildMatcherFromMetadata(matcherMetadata, hmac) {
|
|
101
|
-
let matcher;
|
|
102
|
-
if (typeof matcherMetadata.matcher === 'object') {
|
|
103
|
-
matcher = matcherMetadata.matcher;
|
|
104
|
-
} else {
|
|
105
|
-
if (matcherMetadata.hmac !== hmac(matcherMetadata.matcher)) {
|
|
106
|
-
throw new Error('Invalid HMAC for matcher.');
|
|
107
|
-
}
|
|
108
|
-
matcher = eval('(' + matcherMetadata.matcher + ')').bind({}); // jshint ignore:line
|
|
109
|
-
}
|
|
110
|
-
return matcher;
|
|
59
|
+
/* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
|
|
60
|
+
* integers. Since we want the results to be always positive, convert the
|
|
61
|
+
* signed int to an unsigned by doing an unsigned bitshift. */
|
|
62
|
+
return hash >>> 0; // jshint ignore:line
|
|
111
63
|
}
|
|
112
64
|
|
|
113
65
|
/**
|
|
@@ -169,7 +121,7 @@ function binarySearch(number, length, get) {
|
|
|
169
121
|
/**
|
|
170
122
|
* @param {number} index The 1-based index position to wrap around if < 0 and check against the bounds.
|
|
171
123
|
* @param {number} length The length of the index and upper bound.
|
|
172
|
-
* @returns {number} The wrapped index or -1 if index out of bounds.
|
|
124
|
+
* @returns {number} The wrapped index position or -1 if index out of bounds.
|
|
173
125
|
*/
|
|
174
126
|
function wrapAndCheck(index, length) {
|
|
175
127
|
if (typeof index !== 'number') {
|
|
@@ -201,17 +153,66 @@ function ensureDirectory(dirName) {
|
|
|
201
153
|
return true;
|
|
202
154
|
}
|
|
203
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Perform a k-way merge over multiple streams, invoking a callback for each item in ascending key order.
|
|
158
|
+
* Each stream object is mutated in place by the `advance` function.
|
|
159
|
+
*
|
|
160
|
+
* @param {object[]} streams Array of stream state objects; entries are removed when exhausted.
|
|
161
|
+
* @param {function(object): number} getKey Returns the current sort key for a stream state.
|
|
162
|
+
* @param {function(object): boolean} advance Advances the stream to its next item.
|
|
163
|
+
* Returns true if the stream has more items within range, false if exhausted.
|
|
164
|
+
* @param {function(object): void} visit Called for each stream state in merged order.
|
|
165
|
+
*/
|
|
166
|
+
function kWayMerge(streams, getKey, advance, visit) {
|
|
167
|
+
while (streams.length > 0) {
|
|
168
|
+
let minIdx = 0;
|
|
169
|
+
for (let i = 1; i < streams.length; i++) {
|
|
170
|
+
if (getKey(streams[i]) < getKey(streams[minIdx])) {
|
|
171
|
+
minIdx = i;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
visit(streams[minIdx]);
|
|
175
|
+
if (!advance(streams[minIdx])) {
|
|
176
|
+
streams.splice(minIdx, 1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Scan a directory for files whose names match a regex pattern, calling a callback for each match.
|
|
183
|
+
* The `onEach` callback receives the first capturing group of the match (`match[1]`), or the full
|
|
184
|
+
* match (`match[0]`) when no capturing group is defined in the pattern.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} directory The directory to scan.
|
|
187
|
+
* @param {RegExp} regexPattern The pattern to match file names against.
|
|
188
|
+
* @param {function(string)} onEach Called with the first capturing group (or full match) for each matching file name.
|
|
189
|
+
* @param {function(Error?)} onDone Called when the scan is complete, or with an error if one occurred.
|
|
190
|
+
*/
|
|
191
|
+
function scanForFiles(directory, regexPattern, onEach, onDone) {
|
|
192
|
+
fs.readdir(directory, (err, files) => {
|
|
193
|
+
if (err) {
|
|
194
|
+
return onDone(err);
|
|
195
|
+
}
|
|
196
|
+
let match;
|
|
197
|
+
for (let file of files) {
|
|
198
|
+
if ((match = file.match(regexPattern)) !== null) {
|
|
199
|
+
onEach(match[1] !== undefined ? match[1] : match[0]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
onDone(null);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
204
206
|
|
|
205
207
|
module.exports = {
|
|
206
208
|
assert,
|
|
207
209
|
assertEqual,
|
|
210
|
+
hash,
|
|
208
211
|
wrapAndCheck,
|
|
209
212
|
binarySearch,
|
|
210
|
-
createHmac,
|
|
211
|
-
matches,
|
|
212
|
-
buildMetadataForMatcher,
|
|
213
|
-
buildMatcherFromMetadata,
|
|
214
213
|
buildMetadataHeader,
|
|
215
214
|
alignTo,
|
|
216
|
-
ensureDirectory
|
|
215
|
+
ensureDirectory,
|
|
216
|
+
scanForFiles,
|
|
217
|
+
kWayMerge
|
|
217
218
|
};
|