event-storage 0.8.0 → 1.0.0
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 -540
- package/index.js +5 -6
- package/package.json +28 -31
- package/src/Clock.js +21 -9
- package/src/Consumer.js +6 -7
- package/src/EventStore.js +261 -67
- package/src/EventStream.js +172 -19
- package/src/Index/ReadOnlyIndex.js +3 -3
- package/src/Index/ReadableIndex.js +17 -13
- package/src/Index/WritableIndex.js +17 -11
- package/src/Index.js +7 -5
- package/src/IndexEntry.js +2 -3
- package/src/JoinEventStream.js +34 -32
- package/src/Partition/ReadOnlyPartition.js +3 -3
- package/src/Partition/ReadablePartition.js +110 -57
- package/src/Partition/WritablePartition.js +81 -23
- package/src/Partition.js +7 -4
- package/src/Storage/ReadOnlyStorage.js +4 -4
- package/src/Storage/ReadableStorage.js +144 -22
- package/src/Storage/WritableStorage.js +175 -33
- package/src/Storage.js +9 -4
- package/src/Watcher.js +6 -5
- package/src/WatchesFile.js +8 -7
- package/src/metadataUtil.js +79 -0
- package/src/util.js +74 -73
- 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/EventStream.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import stream from 'stream';
|
|
2
|
+
import { assert } from './util.js';
|
|
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,6 +214,7 @@ 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
|
*
|
|
217
|
+
* @api
|
|
78
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) {
|
|
@@ -94,13 +234,26 @@ 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
|
}
|
|
@@ -119,4 +272,4 @@ class EventStream extends stream.Readable {
|
|
|
119
272
|
|
|
120
273
|
}
|
|
121
274
|
|
|
122
|
-
|
|
275
|
+
export default EventStream;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import ReadableIndex from './ReadableIndex.js';
|
|
2
|
+
import watchesFile from '../WatchesFile.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A read-only index is a readable index instance that reacts on file changes and triggers events.
|
|
@@ -45,4 +45,4 @@ class ReadOnlyIndex extends watchesFile(ReadableIndex) {
|
|
|
45
45
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
export default ReadOnlyIndex;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import events from 'events';
|
|
4
|
+
import Entry, { assertValidEntryClass } from '../IndexEntry.js';
|
|
5
|
+
import { assert, wrapAndCheck, binarySearch } from '../util.js';
|
|
6
6
|
|
|
7
7
|
// node-event-store-index V01
|
|
8
8
|
const HEADER_MAGIC = "nesidx01";
|
|
@@ -53,7 +53,7 @@ class ReadableIndex extends events.EventEmitter {
|
|
|
53
53
|
EntryClass: Entry
|
|
54
54
|
};
|
|
55
55
|
options = Object.assign(defaults, options);
|
|
56
|
-
|
|
56
|
+
assertValidEntryClass(options.EntryClass);
|
|
57
57
|
|
|
58
58
|
this.name = name;
|
|
59
59
|
this.initialize(options);
|
|
@@ -72,7 +72,7 @@ class ReadableIndex extends events.EventEmitter {
|
|
|
72
72
|
this.EntryClass = options.EntryClass;
|
|
73
73
|
this.dataDirectory = options.dataDirectory;
|
|
74
74
|
this.fileName = path.resolve(options.dataDirectory, this.name);
|
|
75
|
-
this.readBuffer = Buffer.allocUnsafe(options.EntryClass.size);
|
|
75
|
+
this.readBuffer = Buffer.allocUnsafe(Math.max(options.EntryClass.size, options.writeBufferSize > 0 ? options.writeBufferSize : 4096));
|
|
76
76
|
|
|
77
77
|
if (options.metadata) {
|
|
78
78
|
this.metadata = Object.assign({entryClass: options.EntryClass.name, entrySize: options.EntryClass.size}, options.metadata);
|
|
@@ -281,8 +281,11 @@ class ReadableIndex extends events.EventEmitter {
|
|
|
281
281
|
const readFrom = Math.max(this.readUntil + 1, from);
|
|
282
282
|
const amount = (until - readFrom + 1);
|
|
283
283
|
|
|
284
|
-
const
|
|
285
|
-
|
|
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);
|
|
286
289
|
let index = 0;
|
|
287
290
|
while (index < amount && readSize > 0) {
|
|
288
291
|
this.data[index + readFrom] = this.EntryClass.fromBuffer(readBuffer, index * this.EntryClass.size);
|
|
@@ -388,13 +391,14 @@ class ReadableIndex extends events.EventEmitter {
|
|
|
388
391
|
* @returns {number} The last index entry position that is lower than or equal to the `number`. Returns 0 if no index matches.
|
|
389
392
|
*/
|
|
390
393
|
find(number, min = false) {
|
|
394
|
+
if (this.length < 1) {
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
391
397
|
// We only need to search until the searched number because entry.number is always >= position
|
|
392
398
|
const [low, high] = binarySearch(number, Math.min(this.length, number), index => this.get(index).number);
|
|
393
399
|
return min ? low : high;
|
|
394
400
|
}
|
|
395
401
|
}
|
|
396
402
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
module.exports.HEADER_MAGIC = HEADER_MAGIC;
|
|
400
|
-
module.exports.CorruptedIndexError = CorruptedIndexError;
|
|
403
|
+
export default ReadableIndex;
|
|
404
|
+
export { Entry, HEADER_MAGIC, CorruptedIndexError };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import ReadableIndex, { Entry, CorruptedIndexError, HEADER_MAGIC } from './ReadableIndex.js';
|
|
3
|
+
import { assertEqual, buildMetadataHeader, ensureDirectory } from '../util.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* An index is a simple append-only file that stores an ordered list of entry elements pointing to the actual file position
|
|
@@ -71,7 +71,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
71
71
|
}
|
|
72
72
|
return entries;
|
|
73
73
|
} catch (e) {
|
|
74
|
-
if (e instanceof
|
|
74
|
+
if (e instanceof CorruptedIndexError) {
|
|
75
75
|
this.truncate(e.size);
|
|
76
76
|
return e.size;
|
|
77
77
|
}
|
|
@@ -107,7 +107,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
107
107
|
if (!this.metadata) {
|
|
108
108
|
this.metadata = {entryClass: this.EntryClass.name, entrySize: this.EntryClass.size};
|
|
109
109
|
}
|
|
110
|
-
const metadataBuffer = buildMetadataHeader(
|
|
110
|
+
const metadataBuffer = buildMetadataHeader(HEADER_MAGIC, this.metadata);
|
|
111
111
|
fs.writeSync(this.fd, metadataBuffer, 0, metadataBuffer.byteLength, 0);
|
|
112
112
|
this.headerSize = metadataBuffer.byteLength;
|
|
113
113
|
}
|
|
@@ -156,8 +156,9 @@ class WritableIndex extends ReadableIndex {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
this.writeBufferCursor = 0;
|
|
159
|
-
this.flushCallbacks
|
|
159
|
+
const callbacks = this.flushCallbacks;
|
|
160
160
|
this.flushCallbacks = [];
|
|
161
|
+
for (let i = 0; i < callbacks.length; i += 2) callbacks[i](callbacks[i + 1]);
|
|
161
162
|
return true;
|
|
162
163
|
}
|
|
163
164
|
|
|
@@ -172,7 +173,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
172
173
|
if (typeof callback !== 'function') {
|
|
173
174
|
return;
|
|
174
175
|
}
|
|
175
|
-
this.flushCallbacks.push(
|
|
176
|
+
this.flushCallbacks.push(callback, position);
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
/**
|
|
@@ -187,10 +188,15 @@ class WritableIndex extends ReadableIndex {
|
|
|
187
188
|
assertEqual(entry.constructor.name, this.EntryClass.name, `Wrong entry object.`);
|
|
188
189
|
assertEqual(entry.constructor.size, this.EntryClass.size, `Invalid entry size.`);
|
|
189
190
|
|
|
190
|
-
|
|
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) {
|
|
191
197
|
this.readUntil++;
|
|
192
198
|
}
|
|
193
|
-
this.data[
|
|
199
|
+
this.data[dataLen] = entry;
|
|
194
200
|
|
|
195
201
|
if (this.writeBufferCursor === 0) {
|
|
196
202
|
this.flushTimeout = setTimeout(() => this.flush(), this.flushDelay);
|
|
@@ -230,5 +236,5 @@ class WritableIndex extends ReadableIndex {
|
|
|
230
236
|
}
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
|
|
234
|
-
|
|
239
|
+
export default WritableIndex;
|
|
240
|
+
export { Entry };
|
package/src/Index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import WritableIndex, { Entry } from './Index/WritableIndex.js';
|
|
2
|
+
import ReadOnlyIndex from './Index/ReadOnlyIndex.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
WritableIndex.ReadOnly = ReadOnlyIndex;
|
|
5
|
+
WritableIndex.Entry = Entry;
|
|
6
|
+
|
|
7
|
+
export default WritableIndex;
|
|
8
|
+
export { ReadOnlyIndex as ReadOnly, Entry };
|
package/src/IndexEntry.js
CHANGED
|
@@ -137,6 +137,5 @@ class Entry extends Array {
|
|
|
137
137
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
module.exports.assertValidEntryClass = assertValidEntryClass;
|
|
140
|
+
export default Entry;
|
|
141
|
+
export { EntryInterface, assertValidEntryClass };
|
package/src/JoinEventStream.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import EventStream from './EventStream.js';
|
|
2
|
+
import { wrapAndCheck } from './util.js';
|
|
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') {
|
|
@@ -99,4 +101,4 @@ class JoinEventStream extends EventStream {
|
|
|
99
101
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
export default JoinEventStream;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import ReadablePartition from './ReadablePartition.js';
|
|
2
|
+
import WatchesFile from '../WatchesFile.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A read-only partition is a readable partition that keeps it's size in sync with the underlying file.
|
|
@@ -42,4 +42,4 @@ class ReadOnlyPartition extends WatchesFile(ReadablePartition) {
|
|
|
42
42
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
export default ReadOnlyPartition;
|