event-storage 0.7.2 → 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 +51 -392
- package/index.js +2 -1
- package/package.json +28 -19
- package/src/Clock.js +20 -8
- package/src/Consumer.js +68 -18
- package/src/EventStore.js +305 -94
- package/src/EventStream.js +171 -17
- package/src/Index/ReadableIndex.js +33 -13
- package/src/Index/WritableIndex.js +33 -17
- package/src/IndexEntry.js +5 -1
- package/src/JoinEventStream.js +32 -30
- package/src/Partition/ReadOnlyPartition.js +1 -0
- package/src/Partition/ReadablePartition.js +201 -49
- package/src/Partition/WritablePartition.js +134 -61
- package/src/Storage/ReadOnlyStorage.js +6 -3
- package/src/Storage/ReadableStorage.js +147 -19
- package/src/Storage/WritableStorage.js +205 -27
- package/src/Watcher.js +38 -29
- package/src/WatchesFile.js +9 -8
- package/src/metadataUtil.js +79 -0
- package/src/util.js +102 -65
- package/test/Consumer.spec.js +0 -268
- package/test/EventStore.spec.js +0 -591
- package/test/EventStream.spec.js +0 -120
- package/test/Index.spec.js +0 -590
- package/test/JoinEventStream.spec.js +0 -113
- package/test/Partition.spec.js +0 -384
- package/test/Storage.spec.js +0 -955
- package/test/Watcher.spec.js +0 -131
package/src/EventStream.js
CHANGED
|
@@ -2,16 +2,14 @@ const stream = require('stream');
|
|
|
2
2
|
const { assert } = require('./util');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Calculate the actual version number from a possibly relative (negative) version number.
|
|
6
6
|
*
|
|
7
|
-
* @param {number}
|
|
8
|
-
* @
|
|
7
|
+
* @param {number} version The version to normalize.
|
|
8
|
+
* @param {number} length The maximum version number
|
|
9
|
+
* @returns {number} The absolute version number.
|
|
9
10
|
*/
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
return rev + 1;
|
|
13
|
-
}
|
|
14
|
-
return rev;
|
|
11
|
+
function normalizeVersion(version, length) {
|
|
12
|
+
return version < 0 ? version + length + 1 : version;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
/**
|
|
@@ -36,22 +34,163 @@ class EventStream extends stream.Readable {
|
|
|
36
34
|
* @param {number} [minRevision] The minimum revision to include in the events (inclusive).
|
|
37
35
|
* @param {number} [maxRevision] The maximum revision to include in the events (inclusive).
|
|
38
36
|
*/
|
|
39
|
-
constructor(name, eventStore, minRevision =
|
|
37
|
+
constructor(name, eventStore, minRevision = 1, maxRevision = -1) {
|
|
40
38
|
super({ objectMode: true });
|
|
41
39
|
assert(typeof name === 'string' && name !== '', 'Need to specify a stream name.');
|
|
42
40
|
assert(typeof eventStore === 'object' && eventStore !== null, `Need to provide EventStore instance to create EventStream ${name}.`);
|
|
43
41
|
|
|
44
42
|
this.name = name;
|
|
45
43
|
if (eventStore.streams[name]) {
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.
|
|
44
|
+
this.streamIndex = eventStore.streams[name].index;
|
|
45
|
+
this.minRevision = normalizeVersion(minRevision, this.streamIndex.length);
|
|
46
|
+
this.maxRevision = normalizeVersion(maxRevision, this.streamIndex.length);
|
|
47
|
+
this.version = minVersion(this.streamIndex.length, maxRevision);
|
|
48
|
+
this._iterator = null;
|
|
49
|
+
this.fetch = function() {
|
|
50
|
+
return eventStore.storage.readRange(this.minRevision, this.maxRevision, this.streamIndex);
|
|
51
|
+
}
|
|
51
52
|
} else {
|
|
53
|
+
this.streamIndex = { length: 0 };
|
|
52
54
|
this.version = -1;
|
|
53
|
-
this.
|
|
55
|
+
this._iterator = { next() { return { done: true }; } };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @api
|
|
61
|
+
* @param {number} revision The event revision to start reading from (inclusive).
|
|
62
|
+
* @returns {EventStream}
|
|
63
|
+
*/
|
|
64
|
+
from(revision) {
|
|
65
|
+
this.minRevision = normalizeVersion(revision, this.streamIndex.length);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @api
|
|
71
|
+
* @param {number} revision The event revision to read until (inclusive).
|
|
72
|
+
* @returns {EventStream}
|
|
73
|
+
*/
|
|
74
|
+
until(revision) {
|
|
75
|
+
this.maxRevision = normalizeVersion(revision, this.streamIndex.length);
|
|
76
|
+
this.version = minVersion(this.streamIndex.length, this.maxRevision);
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @api
|
|
82
|
+
* @param {number} amount The amount of events at the start of the stream to return in chronological order.
|
|
83
|
+
* @returns {EventStream}
|
|
84
|
+
*/
|
|
85
|
+
first(amount) {
|
|
86
|
+
return this.fromStart().following(amount);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @api
|
|
91
|
+
* @param {number} amount The amount of events at the end of the stream to return in chronological order.
|
|
92
|
+
* @returns {EventStream}
|
|
93
|
+
*/
|
|
94
|
+
last(amount) {
|
|
95
|
+
return this.fromEnd().previous(amount).forwards();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @api
|
|
100
|
+
* @returns {EventStream}
|
|
101
|
+
*/
|
|
102
|
+
fromStart() {
|
|
103
|
+
this.minRevision = 1;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @api
|
|
109
|
+
* @returns {EventStream}
|
|
110
|
+
*/
|
|
111
|
+
fromEnd() {
|
|
112
|
+
this.minRevision = this.streamIndex.length;
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {number} amount The amount of events to return in reverse chronological order.
|
|
118
|
+
* @returns {EventStream}
|
|
119
|
+
*/
|
|
120
|
+
previous(amount) {
|
|
121
|
+
this.maxRevision = Math.max(1, this.minRevision - amount + 1);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {number} amount The amount of events to return in chronological order.
|
|
127
|
+
* @returns {EventStream}
|
|
128
|
+
*/
|
|
129
|
+
following(amount) {
|
|
130
|
+
this.maxRevision = Math.min(this.streamIndex.length, this.minRevision + amount - 1);
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @api
|
|
136
|
+
* @returns {EventStream}
|
|
137
|
+
*/
|
|
138
|
+
toEnd() {
|
|
139
|
+
this.maxRevision = this.version = this.streamIndex.length;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @api
|
|
145
|
+
* @returns {EventStream}
|
|
146
|
+
*/
|
|
147
|
+
toStart() {
|
|
148
|
+
this.maxRevision = 1;
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Reverse the current range of events, no matter which direction it currently has.
|
|
154
|
+
* @returns {EventStream}
|
|
155
|
+
*/
|
|
156
|
+
reverse() {
|
|
157
|
+
let tmp = this.maxRevision;
|
|
158
|
+
this.maxRevision = this.minRevision;
|
|
159
|
+
this.minRevision = tmp;
|
|
160
|
+
this.version = minVersion(this.streamIndex.length, this.maxRevision);
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Make the current range of events read in forward chronological order.
|
|
166
|
+
* @api
|
|
167
|
+
* @param {number} [amount] Amount of events to read forward. If not specified, will read forward until the previously set limit.
|
|
168
|
+
* @returns {EventStream}
|
|
169
|
+
*/
|
|
170
|
+
forwards(amount = 0) {
|
|
171
|
+
if (amount > 0) {
|
|
172
|
+
this.following(amount);
|
|
173
|
+
}
|
|
174
|
+
if (this.maxRevision < this.minRevision) {
|
|
175
|
+
this.reverse();
|
|
54
176
|
}
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Make the current range of events read in backward chronological order.
|
|
182
|
+
* @api
|
|
183
|
+
* @param {number} [amount] Amount of events to read backward. If not specified, will read backward until the previously set limit.
|
|
184
|
+
* @returns {EventStream}
|
|
185
|
+
*/
|
|
186
|
+
backwards(amount = 0) {
|
|
187
|
+
if (amount > 0) {
|
|
188
|
+
this.previous(amount);
|
|
189
|
+
}
|
|
190
|
+
if (this.maxRevision > this.minRevision) {
|
|
191
|
+
this.reverse();
|
|
192
|
+
}
|
|
193
|
+
return this;
|
|
55
194
|
}
|
|
56
195
|
|
|
57
196
|
/**
|
|
@@ -75,7 +214,8 @@ class EventStream extends stream.Readable {
|
|
|
75
214
|
* Iterate over the events in this stream with a callback.
|
|
76
215
|
* This method is useful to gain access to the event metadata.
|
|
77
216
|
*
|
|
78
|
-
* @
|
|
217
|
+
* @api
|
|
218
|
+
* @param {function(object, object, string)} callback A callback function that will receive the event, the storage metadata and the original stream name for every event in this stream.
|
|
79
219
|
*/
|
|
80
220
|
forEach(callback) {
|
|
81
221
|
let next;
|
|
@@ -94,19 +234,33 @@ class EventStream extends stream.Readable {
|
|
|
94
234
|
}
|
|
95
235
|
}
|
|
96
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Reset this stream to the start so it can be iterated again.
|
|
239
|
+
* @returns {EventStream}
|
|
240
|
+
*/
|
|
241
|
+
reset() {
|
|
242
|
+
this._iterator = null;
|
|
243
|
+
this._events = null;
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
97
247
|
/**
|
|
98
248
|
* @returns {object|boolean} The next event or false if no more events in the stream.
|
|
99
249
|
*/
|
|
100
250
|
next() {
|
|
251
|
+
if (!this._iterator) {
|
|
252
|
+
this._iterator = this.fetch();
|
|
253
|
+
}
|
|
101
254
|
let next;
|
|
102
255
|
try {
|
|
103
|
-
next = this.
|
|
256
|
+
next = this._iterator.next();
|
|
104
257
|
} catch(e) {
|
|
105
258
|
return false;
|
|
106
259
|
}
|
|
107
260
|
return next.done ? false : next.value;
|
|
108
261
|
}
|
|
109
262
|
|
|
263
|
+
// noinspection JSUnusedGlobalSymbols
|
|
110
264
|
/**
|
|
111
265
|
* Readable stream implementation.
|
|
112
266
|
* @private
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const
|
|
3
|
+
const events = require('events');
|
|
4
4
|
const Entry = require('../IndexEntry');
|
|
5
5
|
const { assert, wrapAndCheck, binarySearch } = require('../util');
|
|
6
6
|
|
|
7
7
|
// node-event-store-index V01
|
|
8
8
|
const HEADER_MAGIC = "nesidx01";
|
|
9
9
|
|
|
10
|
+
class CorruptedIndexError extends Error {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns a constructor for a CorruptedIndexError with the given size property.
|
|
14
|
+
*/
|
|
15
|
+
function CorruptedIndexErrorFactory(size) {
|
|
16
|
+
return function (...args) {
|
|
17
|
+
let error = new CorruptedIndexError(...args);
|
|
18
|
+
error.size = size;
|
|
19
|
+
return error;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
/**
|
|
11
24
|
* An index is a simple append-only file that stores an ordered list of entry elements pointing to the actual file position
|
|
12
25
|
* where the matching document is found in the storage file.
|
|
@@ -18,7 +31,7 @@ const HEADER_MAGIC = "nesidx01";
|
|
|
18
31
|
*
|
|
19
32
|
* The index basically functions like a simplified LSM list.
|
|
20
33
|
*/
|
|
21
|
-
class ReadableIndex extends EventEmitter {
|
|
34
|
+
class ReadableIndex extends events.EventEmitter {
|
|
22
35
|
|
|
23
36
|
/**
|
|
24
37
|
* @param {string} [name] The name of the file to use for storing the index.
|
|
@@ -52,13 +65,14 @@ class ReadableIndex extends EventEmitter {
|
|
|
52
65
|
* @param {object} options
|
|
53
66
|
*/
|
|
54
67
|
initialize(options) {
|
|
68
|
+
/* @type Array<Entry> */
|
|
55
69
|
this.data = [];
|
|
56
70
|
this.fd = null;
|
|
57
71
|
this.fileMode = 'r';
|
|
58
72
|
this.EntryClass = options.EntryClass;
|
|
59
73
|
this.dataDirectory = options.dataDirectory;
|
|
60
74
|
this.fileName = path.resolve(options.dataDirectory, this.name);
|
|
61
|
-
this.readBuffer = Buffer.allocUnsafe(options.EntryClass.size);
|
|
75
|
+
this.readBuffer = Buffer.allocUnsafe(Math.max(options.EntryClass.size, options.writeBufferSize > 0 ? options.writeBufferSize : 4096));
|
|
62
76
|
|
|
63
77
|
if (options.metadata) {
|
|
64
78
|
this.metadata = Object.assign({entryClass: options.EntryClass.name, entrySize: options.EntryClass.size}, options.metadata);
|
|
@@ -110,14 +124,13 @@ class ReadableIndex extends EventEmitter {
|
|
|
110
124
|
const stat = fs.fstatSync(this.fd);
|
|
111
125
|
if (stat.size === 0) {
|
|
112
126
|
return -1;
|
|
113
|
-
} else {
|
|
114
|
-
stat.size -= this.readMetadata();
|
|
115
|
-
assert(stat.size >= 0, 'Invalid index file!');
|
|
116
127
|
}
|
|
117
128
|
|
|
129
|
+
stat.size -= this.readMetadata();
|
|
130
|
+
assert(stat.size >= 0, 'Invalid index file!');
|
|
131
|
+
|
|
118
132
|
const length = Math.floor(stat.size / this.EntryClass.size);
|
|
119
|
-
|
|
120
|
-
assert(stat.size === length * this.EntryClass.size, 'Index file is corrupt!');
|
|
133
|
+
assert(stat.size === length * this.EntryClass.size, 'Index file is corrupt!', CorruptedIndexErrorFactory(length));
|
|
121
134
|
|
|
122
135
|
return length;
|
|
123
136
|
}
|
|
@@ -237,7 +250,7 @@ class ReadableIndex extends EventEmitter {
|
|
|
237
250
|
* @returns {Entry} The index entry at the given position.
|
|
238
251
|
*/
|
|
239
252
|
read(index) {
|
|
240
|
-
index
|
|
253
|
+
index = Number(index) - 1;
|
|
241
254
|
|
|
242
255
|
fs.readSync(this.fd, this.readBuffer, 0, this.EntryClass.size, this.headerSize + index * this.EntryClass.size);
|
|
243
256
|
if (index === this.readUntil + 1) {
|
|
@@ -268,8 +281,11 @@ class ReadableIndex extends EventEmitter {
|
|
|
268
281
|
const readFrom = Math.max(this.readUntil + 1, from);
|
|
269
282
|
const amount = (until - readFrom + 1);
|
|
270
283
|
|
|
271
|
-
const
|
|
272
|
-
|
|
284
|
+
const bufferSize = amount * this.EntryClass.size;
|
|
285
|
+
const readBuffer = bufferSize <= this.readBuffer.byteLength
|
|
286
|
+
? this.readBuffer
|
|
287
|
+
: Buffer.allocUnsafe(bufferSize);
|
|
288
|
+
let readSize = fs.readSync(this.fd, readBuffer, 0, bufferSize, this.headerSize + readFrom * this.EntryClass.size);
|
|
273
289
|
let index = 0;
|
|
274
290
|
while (index < amount && readSize > 0) {
|
|
275
291
|
this.data[index + readFrom] = this.EntryClass.fromBuffer(readBuffer, index * this.EntryClass.size);
|
|
@@ -306,7 +322,7 @@ class ReadableIndex extends EventEmitter {
|
|
|
306
322
|
*/
|
|
307
323
|
get(index) {
|
|
308
324
|
index = wrapAndCheck(index, this.length);
|
|
309
|
-
if (index
|
|
325
|
+
if (index <= 0) {
|
|
310
326
|
return false;
|
|
311
327
|
}
|
|
312
328
|
|
|
@@ -344,7 +360,7 @@ class ReadableIndex extends EventEmitter {
|
|
|
344
360
|
from = wrapAndCheck(from, this.length);
|
|
345
361
|
until = wrapAndCheck(until, this.length);
|
|
346
362
|
|
|
347
|
-
if (from
|
|
363
|
+
if (from <= 0 || until < from) {
|
|
348
364
|
return false;
|
|
349
365
|
}
|
|
350
366
|
|
|
@@ -375,6 +391,9 @@ class ReadableIndex extends EventEmitter {
|
|
|
375
391
|
* @returns {number} The last index entry position that is lower than or equal to the `number`. Returns 0 if no index matches.
|
|
376
392
|
*/
|
|
377
393
|
find(number, min = false) {
|
|
394
|
+
if (this.length < 1) {
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
378
397
|
// We only need to search until the searched number because entry.number is always >= position
|
|
379
398
|
const [low, high] = binarySearch(number, Math.min(this.length, number), index => this.get(index).number);
|
|
380
399
|
return min ? low : high;
|
|
@@ -384,3 +403,4 @@ class ReadableIndex extends EventEmitter {
|
|
|
384
403
|
module.exports = ReadableIndex;
|
|
385
404
|
module.exports.Entry = Entry;
|
|
386
405
|
module.exports.HEADER_MAGIC = HEADER_MAGIC;
|
|
406
|
+
module.exports.CorruptedIndexError = CorruptedIndexError;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const mkdirpSync = require('mkdirp').sync;
|
|
3
2
|
const ReadableIndex = require('./ReadableIndex');
|
|
4
|
-
const { assertEqual, buildMetadataHeader } = require('../util');
|
|
3
|
+
const { assertEqual, buildMetadataHeader, ensureDirectory } = require('../util');
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* An index is a simple append-only file that stores an ordered list of entry elements pointing to the actual file position
|
|
@@ -45,9 +44,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
45
44
|
*/
|
|
46
45
|
initialize(options) {
|
|
47
46
|
super.initialize(options);
|
|
48
|
-
|
|
49
|
-
mkdirpSync(options.dataDirectory);
|
|
50
|
-
}
|
|
47
|
+
ensureDirectory(options.dataDirectory);
|
|
51
48
|
|
|
52
49
|
this.fileMode = 'a+';
|
|
53
50
|
this.writeBuffer = Buffer.allocUnsafe(options.writeBufferSize >>> 0); // jshint ignore:line
|
|
@@ -65,13 +62,21 @@ class WritableIndex extends ReadableIndex {
|
|
|
65
62
|
* @throws {Error} If the file is corrupt or can not be read correctly.
|
|
66
63
|
*/
|
|
67
64
|
checkFile() {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
try {
|
|
66
|
+
const entries = super.checkFile();
|
|
67
|
+
if (entries < 0) {
|
|
68
|
+
// Freshly created index... write metadata initially.
|
|
69
|
+
this.writeMetadata();
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
return entries;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
if (e instanceof ReadableIndex.CorruptedIndexError) {
|
|
75
|
+
this.truncate(e.size);
|
|
76
|
+
return e.size;
|
|
77
|
+
}
|
|
78
|
+
throw e;
|
|
73
79
|
}
|
|
74
|
-
return entries;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
/**
|
|
@@ -151,8 +156,9 @@ class WritableIndex extends ReadableIndex {
|
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
this.writeBufferCursor = 0;
|
|
154
|
-
this.flushCallbacks
|
|
159
|
+
const callbacks = this.flushCallbacks;
|
|
155
160
|
this.flushCallbacks = [];
|
|
161
|
+
for (let i = 0; i < callbacks.length; i += 2) callbacks[i](callbacks[i + 1]);
|
|
156
162
|
return true;
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -167,7 +173,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
167
173
|
if (typeof callback !== 'function') {
|
|
168
174
|
return;
|
|
169
175
|
}
|
|
170
|
-
this.flushCallbacks.push(
|
|
176
|
+
this.flushCallbacks.push(callback, position);
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
/**
|
|
@@ -182,10 +188,15 @@ class WritableIndex extends ReadableIndex {
|
|
|
182
188
|
assertEqual(entry.constructor.name, this.EntryClass.name, `Wrong entry object.`);
|
|
183
189
|
assertEqual(entry.constructor.size, this.EntryClass.size, `Invalid entry size.`);
|
|
184
190
|
|
|
185
|
-
|
|
191
|
+
const dataLen = this.data.length;
|
|
192
|
+
if (dataLen > 0 && this.data[dataLen - 1].number >= entry.number) {
|
|
193
|
+
throw new Error('Consistency error. Tried to add an index that should come before existing last entry.');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (this.readUntil === dataLen - 1) {
|
|
186
197
|
this.readUntil++;
|
|
187
198
|
}
|
|
188
|
-
this.data[
|
|
199
|
+
this.data[dataLen] = entry;
|
|
189
200
|
|
|
190
201
|
if (this.writeBufferCursor === 0) {
|
|
191
202
|
this.flushTimeout = setTimeout(() => this.flush(), this.flushDelay);
|
|
@@ -206,7 +217,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
206
217
|
* @param {number} after The index entry number to truncate after.
|
|
207
218
|
*/
|
|
208
219
|
truncate(after) {
|
|
209
|
-
if (
|
|
220
|
+
if (!this.fd) {
|
|
210
221
|
return;
|
|
211
222
|
}
|
|
212
223
|
if (after < 0) {
|
|
@@ -214,7 +225,12 @@ class WritableIndex extends ReadableIndex {
|
|
|
214
225
|
}
|
|
215
226
|
this.flush();
|
|
216
227
|
|
|
217
|
-
fs.
|
|
228
|
+
const stat = fs.statSync(this.fileName);
|
|
229
|
+
const truncatePosition = this.headerSize + after * this.EntryClass.size;
|
|
230
|
+
if (truncatePosition >= stat.size) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
fs.truncateSync(this.fileName, truncatePosition);
|
|
218
234
|
this.data.splice(after);
|
|
219
235
|
this.readUntil = Math.min(this.readUntil, after);
|
|
220
236
|
}
|
package/src/IndexEntry.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
class EntryInterface {
|
|
7
7
|
/**
|
|
8
|
+
* @abstract
|
|
8
9
|
* @returns {number} The byte size of this Entry.
|
|
9
10
|
*/
|
|
10
11
|
static get size() {}
|
|
@@ -12,6 +13,7 @@ class EntryInterface {
|
|
|
12
13
|
/**
|
|
13
14
|
* Read a new Entry from a Buffer object at the given offset.
|
|
14
15
|
*
|
|
16
|
+
* @abstract
|
|
15
17
|
* @param {Buffer} buffer The buffer object to read the index data from.
|
|
16
18
|
* @param {number} [offset] The buffer offset to start reading from. Default 0.
|
|
17
19
|
* @returns {EntryInterface} A new entry matching the values from the Buffer.
|
|
@@ -21,6 +23,7 @@ class EntryInterface {
|
|
|
21
23
|
/**
|
|
22
24
|
* Write this Entry into a Buffer object at the given offset.
|
|
23
25
|
*
|
|
26
|
+
* @abstract
|
|
24
27
|
* @param {Buffer} buffer The buffer object to write the index entry data to.
|
|
25
28
|
* @param {number} offset The offset to start writing into the buffer.
|
|
26
29
|
* @returns {number} The size of the data written.
|
|
@@ -48,7 +51,8 @@ function assertValidEntryClass(EntryClass) {
|
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
53
|
* Default Entry item contains information about the sequence number, the file position, the document size and the partition number.
|
|
51
|
-
* @
|
|
54
|
+
* @extends Array<number>
|
|
55
|
+
* @implements EntryInterface
|
|
52
56
|
*/
|
|
53
57
|
class Entry extends Array {
|
|
54
58
|
|
package/src/JoinEventStream.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
const EventStream = require('./EventStream');
|
|
2
|
+
const { wrapAndCheck } = require('./util');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Calculate the actual version number from a possibly relative (negative) version number.
|
|
5
6
|
*
|
|
6
|
-
* @param {number}
|
|
7
|
-
* @param {number} length The
|
|
8
|
-
* @returns {number} The
|
|
7
|
+
* @param {number} version The version to normalize.
|
|
8
|
+
* @param {number} length The maximum version number
|
|
9
|
+
* @returns {number} The absolute version number.
|
|
9
10
|
*/
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
if (rev <= 0) {
|
|
13
|
-
rev += length;
|
|
14
|
-
}
|
|
15
|
-
return rev;
|
|
11
|
+
function normalizeVersion(version, length) {
|
|
12
|
+
return version < 0 ? version + length + 1 : version;
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
/**
|
|
@@ -25,30 +22,32 @@ class JoinEventStream extends EventStream {
|
|
|
25
22
|
* @param {string} name The name of the stream.
|
|
26
23
|
* @param {Array<string>} streams The name of the streams to join together.
|
|
27
24
|
* @param {EventStore} eventStore The event store to get the stream from.
|
|
28
|
-
* @param {number} [minRevision] The minimum revision to include in the events (inclusive).
|
|
29
|
-
* @param {number} [maxRevision] The maximum revision to include in the events (inclusive).
|
|
25
|
+
* @param {number} [minRevision] The 1-based minimum revision to include in the events (inclusive).
|
|
26
|
+
* @param {number} [maxRevision] The 1-based maximum revision to include in the events (inclusive).
|
|
30
27
|
*/
|
|
31
|
-
constructor(name, streams, eventStore, minRevision =
|
|
28
|
+
constructor(name, streams, eventStore, minRevision = 1, maxRevision = -1) {
|
|
32
29
|
super(name, eventStore, minRevision, maxRevision);
|
|
33
30
|
if (!(streams instanceof Array) || streams.length === 0) {
|
|
34
31
|
throw new Error(`Invalid list of streams supplied to JoinStream ${name}.`);
|
|
35
32
|
}
|
|
36
|
-
this._next = new Array(streams.length).fill(undefined);
|
|
37
33
|
|
|
34
|
+
this.streamIndex = eventStore.storage.index;
|
|
38
35
|
// Translate revisions to index numbers (1-based) and wrap around negatives
|
|
39
|
-
minRevision =
|
|
40
|
-
maxRevision =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
36
|
+
this.minRevision = normalizeVersion(minRevision, eventStore.length);
|
|
37
|
+
this.maxRevision = normalizeVersion(maxRevision, eventStore.length);
|
|
38
|
+
this.fetch = function() {
|
|
39
|
+
this._next = new Array(streams.length).fill(undefined);
|
|
40
|
+
return streams.map(streamName => {
|
|
41
|
+
if (!eventStore.streams[streamName]) {
|
|
42
|
+
return { next() { return { done: true }; } };
|
|
43
|
+
}
|
|
44
|
+
const streamIndex = eventStore.streams[streamName].index;
|
|
45
|
+
const from = streamIndex.find(this.minRevision, this.minRevision <= this.maxRevision);
|
|
46
|
+
const until = streamIndex.find(this.maxRevision, this.minRevision > this.maxRevision);
|
|
47
|
+
return eventStore.storage.readRange(from, until, streamIndex);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
this._iterator = null;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
/**
|
|
@@ -57,7 +56,7 @@ class JoinEventStream extends EventStream {
|
|
|
57
56
|
* @returns {*}
|
|
58
57
|
*/
|
|
59
58
|
getValue(index) {
|
|
60
|
-
const next = this.
|
|
59
|
+
const next = this._iterator[index].next();
|
|
61
60
|
return next.done ? false : next.value;
|
|
62
61
|
}
|
|
63
62
|
|
|
@@ -65,10 +64,10 @@ class JoinEventStream extends EventStream {
|
|
|
65
64
|
* @private
|
|
66
65
|
* @param {number} first
|
|
67
66
|
* @param {number} second
|
|
68
|
-
* @returns {boolean} If the first item follows after the second in the given read order determined by this.
|
|
67
|
+
* @returns {boolean} If the first item follows after the second in the given read order determined by this.minRevision and this.maxRevision.
|
|
69
68
|
*/
|
|
70
69
|
follows(first, second) {
|
|
71
|
-
return (this.
|
|
70
|
+
return (this.minRevision > this.maxRevision ? first < second : first > second);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
@@ -76,6 +75,9 @@ class JoinEventStream extends EventStream {
|
|
|
76
75
|
* @returns {object|boolean} The next event or false if no more events in the stream.
|
|
77
76
|
*/
|
|
78
77
|
next() {
|
|
78
|
+
if (!this._iterator) {
|
|
79
|
+
this._iterator = this.fetch();
|
|
80
|
+
}
|
|
79
81
|
let nextIndex = -1;
|
|
80
82
|
this._next.forEach((value, index) => {
|
|
81
83
|
if (typeof value === 'undefined') {
|