event-storage 0.9.1 → 1.1.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 +7 -2
- package/index.js +5 -6
- package/package.json +10 -13
- package/src/Clock.js +1 -1
- package/src/Consumer.js +7 -7
- package/src/EventStore.js +231 -56
- package/src/EventStream.js +31 -7
- package/src/Index/ReadOnlyIndex.js +3 -3
- package/src/Index/ReadableIndex.js +8 -10
- package/src/Index/WritableIndex.js +9 -7
- package/src/Index.js +7 -5
- package/src/IndexEntry.js +2 -3
- package/src/IndexMatcher.js +205 -0
- package/src/JoinEventStream.js +38 -24
- package/src/Partition/ReadOnlyPartition.js +3 -3
- package/src/Partition/ReadablePartition.js +6 -12
- package/src/Partition/WritablePartition.js +10 -9
- package/src/Partition.js +7 -4
- package/src/PartitionPool.js +149 -0
- package/src/Storage/ReadOnlyStorage.js +6 -6
- package/src/Storage/ReadableStorage.js +149 -58
- package/src/Storage/WritableStorage.js +55 -40
- package/src/Storage.js +9 -4
- package/src/Watcher.js +5 -5
- package/src/WatchesFile.js +2 -2
- package/src/fsUtil.js +123 -0
- package/src/metadataUtil.js +51 -4
- package/src/util.js +16 -67
package/src/EventStream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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.
|
|
@@ -33,13 +33,16 @@ class EventStream extends stream.Readable {
|
|
|
33
33
|
* @param {EventStore} eventStore The event store to get the stream from.
|
|
34
34
|
* @param {number} [minRevision] The minimum revision to include in the events (inclusive).
|
|
35
35
|
* @param {number} [maxRevision] The maximum revision to include in the events (inclusive).
|
|
36
|
+
* @param {function(object, object): boolean|null} [predicate] An optional filter function
|
|
37
|
+
* `(payload, metadata) => boolean`. Only events for which this returns truthy are yielded.
|
|
36
38
|
*/
|
|
37
|
-
constructor(name, eventStore, minRevision = 1, maxRevision = -1) {
|
|
39
|
+
constructor(name, eventStore, minRevision = 1, maxRevision = -1, predicate = null) {
|
|
38
40
|
super({ objectMode: true });
|
|
39
41
|
assert(typeof name === 'string' && name !== '', 'Need to specify a stream name.');
|
|
40
42
|
assert(typeof eventStore === 'object' && eventStore !== null, `Need to provide EventStore instance to create EventStream ${name}.`);
|
|
41
43
|
|
|
42
44
|
this.name = name;
|
|
45
|
+
this.predicate = predicate || null;
|
|
43
46
|
if (eventStore.streams[name]) {
|
|
44
47
|
this.streamIndex = eventStore.streams[name].index;
|
|
45
48
|
this.minRevision = normalizeVersion(minRevision, this.streamIndex.length);
|
|
@@ -244,6 +247,23 @@ class EventStream extends stream.Readable {
|
|
|
244
247
|
return this;
|
|
245
248
|
}
|
|
246
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Apply a filter predicate to this stream. Only events for which `predicate(payload, metadata)`
|
|
252
|
+
* returns a truthy value will be yielded. The predicate is stored as a first-class property
|
|
253
|
+
* of the stream and applied in {@link EventStream#next}.
|
|
254
|
+
*
|
|
255
|
+
* @api
|
|
256
|
+
* @param {function(object, object): boolean} predicate A function receiving `(payload, metadata)`.
|
|
257
|
+
* Events for which the predicate returns falsy are skipped.
|
|
258
|
+
* @returns {EventStream} `this`
|
|
259
|
+
*/
|
|
260
|
+
filter(predicate) {
|
|
261
|
+
this.predicate = predicate || null;
|
|
262
|
+
this._iterator = null;
|
|
263
|
+
this._events = null;
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
247
267
|
/**
|
|
248
268
|
* @returns {object|boolean} The next event or false if no more events in the stream.
|
|
249
269
|
*/
|
|
@@ -251,13 +271,17 @@ class EventStream extends stream.Readable {
|
|
|
251
271
|
if (!this._iterator) {
|
|
252
272
|
this._iterator = this.fetch();
|
|
253
273
|
}
|
|
254
|
-
let next;
|
|
255
274
|
try {
|
|
256
|
-
|
|
275
|
+
while (true) {
|
|
276
|
+
const result = this._iterator.next();
|
|
277
|
+
if (result.done) return false;
|
|
278
|
+
if (!this.predicate || this.predicate(result.value.payload, result.value.metadata)) {
|
|
279
|
+
return result.value;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
257
282
|
} catch(e) {
|
|
258
283
|
return false;
|
|
259
284
|
}
|
|
260
|
-
return next.done ? false : next.value;
|
|
261
285
|
}
|
|
262
286
|
|
|
263
287
|
// noinspection JSUnusedGlobalSymbols
|
|
@@ -272,4 +296,4 @@ class EventStream extends stream.Readable {
|
|
|
272
296
|
|
|
273
297
|
}
|
|
274
298
|
|
|
275
|
-
|
|
299
|
+
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);
|
|
@@ -400,7 +400,5 @@ class ReadableIndex extends events.EventEmitter {
|
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
module.exports.HEADER_MAGIC = HEADER_MAGIC;
|
|
406
|
-
module.exports.CorruptedIndexError = CorruptedIndexError;
|
|
403
|
+
export default ReadableIndex;
|
|
404
|
+
export { Entry, HEADER_MAGIC, CorruptedIndexError };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import ReadableIndex, { Entry, CorruptedIndexError, HEADER_MAGIC } from './ReadableIndex.js';
|
|
3
|
+
import { assertEqual } from '../util.js';
|
|
4
|
+
import { buildMetadataHeader } from '../metadataUtil.js';
|
|
5
|
+
import { ensureDirectory } from '../fsUtil.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* An index is a simple append-only file that stores an ordered list of entry elements pointing to the actual file position
|
|
@@ -71,7 +73,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
71
73
|
}
|
|
72
74
|
return entries;
|
|
73
75
|
} catch (e) {
|
|
74
|
-
if (e instanceof
|
|
76
|
+
if (e instanceof CorruptedIndexError) {
|
|
75
77
|
this.truncate(e.size);
|
|
76
78
|
return e.size;
|
|
77
79
|
}
|
|
@@ -107,7 +109,7 @@ class WritableIndex extends ReadableIndex {
|
|
|
107
109
|
if (!this.metadata) {
|
|
108
110
|
this.metadata = {entryClass: this.EntryClass.name, entrySize: this.EntryClass.size};
|
|
109
111
|
}
|
|
110
|
-
const metadataBuffer = buildMetadataHeader(
|
|
112
|
+
const metadataBuffer = buildMetadataHeader(HEADER_MAGIC, this.metadata);
|
|
111
113
|
fs.writeSync(this.fd, metadataBuffer, 0, metadataBuffer.byteLength, 0);
|
|
112
114
|
this.headerSize = metadataBuffer.byteLength;
|
|
113
115
|
}
|
|
@@ -236,5 +238,5 @@ class WritableIndex extends ReadableIndex {
|
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
+
export default WritableIndex;
|
|
242
|
+
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 };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { getPropertyAtPath } from './util.js';
|
|
2
|
+
import { matches } from './metadataUtil.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object|function(object):boolean} Matcher
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Classifies secondary-index matchers into a fast lookup table keyed on a
|
|
10
|
+
* configurable ordered list of "discriminant" property paths. This enables O(1)
|
|
11
|
+
* candidate resolution on write instead of evaluating every registered matcher.
|
|
12
|
+
*
|
|
13
|
+
* Object matchers that contain at least one of the discriminant property paths
|
|
14
|
+
* (in priority order) are stored in a nested Map keyed first by property path and
|
|
15
|
+
* then by the scalar value found at that path in the matcher. Function matchers
|
|
16
|
+
* and object matchers whose discriminant properties all resolve to undefined/object
|
|
17
|
+
* are kept in separate fallback sets and are always evaluated in full.
|
|
18
|
+
*
|
|
19
|
+
* When the discriminant property list is empty, `forEachMatch()` falls back to a
|
|
20
|
+
* full O(N) scan over all registered indexes.
|
|
21
|
+
*/
|
|
22
|
+
class IndexMatcher {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string[]} [properties] Ordered list of document property paths (dot-notation)
|
|
26
|
+
* used as discriminant keys. The first path that resolves to a non-null scalar inside
|
|
27
|
+
* a given object matcher is used as the key; remaining paths are ignored for that matcher.
|
|
28
|
+
* Pass an empty array (the default) to disable the fast path entirely.
|
|
29
|
+
*/
|
|
30
|
+
constructor(properties = []) {
|
|
31
|
+
this.properties = properties;
|
|
32
|
+
/** Map<indexName, Matcher> — stores every registered matcher for full match verification. */
|
|
33
|
+
this.matchers = new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Nested lookup table: Map<propPath, Map<discriminantValue, Set<indexName>>>.
|
|
36
|
+
* Populated only for object matchers that contain at least one discriminant property.
|
|
37
|
+
*/
|
|
38
|
+
this.table = new Map();
|
|
39
|
+
/** Set of index names whose matchers are functions (always evaluated in full). */
|
|
40
|
+
this.functionMatchers = new Set();
|
|
41
|
+
/**
|
|
42
|
+
* Set of index names whose object matchers contain none of the configured
|
|
43
|
+
* discriminant properties (evaluated in full against every incoming document).
|
|
44
|
+
*/
|
|
45
|
+
this.unclassifiedMatchers = new Set();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register an index name and its matcher in the lookup structures.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} indexName
|
|
52
|
+
* @param {Matcher} matcher
|
|
53
|
+
*/
|
|
54
|
+
add(indexName, matcher) {
|
|
55
|
+
this.matchers.set(indexName, matcher);
|
|
56
|
+
if (typeof matcher === 'function') {
|
|
57
|
+
this.functionMatchers.add(indexName);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!matcher || typeof matcher !== 'object') {
|
|
61
|
+
this.unclassifiedMatchers.add(indexName);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const discriminant = this.findDiscriminant(matcher);
|
|
66
|
+
if (!discriminant) {
|
|
67
|
+
this.unclassifiedMatchers.add(indexName);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let propMap = this.table.get(discriminant.propPath);
|
|
72
|
+
if (!propMap) {
|
|
73
|
+
propMap = new Map();
|
|
74
|
+
this.table.set(discriminant.propPath, propMap);
|
|
75
|
+
}
|
|
76
|
+
for (const v of discriminant.values) {
|
|
77
|
+
let indexSet = propMap.get(v);
|
|
78
|
+
if (!indexSet) {
|
|
79
|
+
indexSet = new Set();
|
|
80
|
+
propMap.set(v, indexSet);
|
|
81
|
+
}
|
|
82
|
+
indexSet.add(indexName);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Remove an index name from the lookup structures.
|
|
88
|
+
* The matcher is retrieved from the internal registry, so only the index name
|
|
89
|
+
* is required.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} indexName
|
|
92
|
+
*/
|
|
93
|
+
remove(indexName) {
|
|
94
|
+
if (!this.matchers.has(indexName)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const matcher = this.matchers.get(indexName);
|
|
98
|
+
this.matchers.delete(indexName);
|
|
99
|
+
if (typeof matcher === 'function') {
|
|
100
|
+
this.functionMatchers.delete(indexName);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!matcher || typeof matcher !== 'object') {
|
|
104
|
+
this.unclassifiedMatchers.delete(indexName);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const discriminant = this.findDiscriminant(matcher);
|
|
109
|
+
if (!discriminant) {
|
|
110
|
+
this.unclassifiedMatchers.delete(indexName);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const v of discriminant.values) {
|
|
115
|
+
this.table.get(discriminant.propPath)?.get(v)?.delete(indexName);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Iterate over every registered index whose matcher matches `document`, calling
|
|
121
|
+
* `iterationHandler` with the index name for each match.
|
|
122
|
+
*
|
|
123
|
+
* When `this.properties` is non-empty, an O(1) discriminant lookup narrows the
|
|
124
|
+
* candidate set before the full `matches()` check is applied. Each candidate's
|
|
125
|
+
* matcher is still verified with `matches()` to handle multi-property matchers
|
|
126
|
+
* where only the first property was used as the discriminant.
|
|
127
|
+
*
|
|
128
|
+
* When `this.properties` is empty the method falls back to a full O(N) scan.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} document
|
|
131
|
+
* @param {function(string): void} iterationHandler Called with the index name for each match.
|
|
132
|
+
*/
|
|
133
|
+
forEachMatch(document, iterationHandler) {
|
|
134
|
+
if (this.properties.length === 0) {
|
|
135
|
+
// Fast path disabled: full O(N) scan.
|
|
136
|
+
for (const [indexName, matcher] of this.matchers) {
|
|
137
|
+
if (matches(document, matcher)) {
|
|
138
|
+
iterationHandler(indexName);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const propPath of this.properties) {
|
|
145
|
+
const docValue = getPropertyAtPath(document, propPath);
|
|
146
|
+
if (docValue === undefined || docValue === null || typeof docValue === 'object') {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const indexSet = this.table.get(propPath)?.get(String(docValue));
|
|
151
|
+
if (!indexSet) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const indexName of indexSet) {
|
|
156
|
+
if (matches(document, this.matchers.get(indexName))) {
|
|
157
|
+
iterationHandler(indexName);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const indexName of this.unclassifiedMatchers) {
|
|
163
|
+
if (matches(document, this.matchers.get(indexName))) {
|
|
164
|
+
iterationHandler(indexName);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const indexName of this.functionMatchers) {
|
|
169
|
+
if (matches(document, this.matchers.get(indexName))) {
|
|
170
|
+
iterationHandler(indexName);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Find the first usable discriminant for an object matcher.
|
|
177
|
+
* Returns `{ propPath, values }` for the first entry in `this.properties` that
|
|
178
|
+
* resolves to a non-null, non-object scalar (or a non-empty array of scalars) inside
|
|
179
|
+
* `matcher`, or `null` if none.
|
|
180
|
+
*
|
|
181
|
+
* For a scalar discriminant `values` contains exactly one element.
|
|
182
|
+
* For an array-valued discriminant `values` contains all elements of the array.
|
|
183
|
+
*
|
|
184
|
+
* @param {object} matcher
|
|
185
|
+
* @returns {{ propPath: string, values: string[] }|null}
|
|
186
|
+
*/
|
|
187
|
+
findDiscriminant(matcher) {
|
|
188
|
+
for (const propPath of this.properties) {
|
|
189
|
+
const value = getPropertyAtPath(matcher, propPath);
|
|
190
|
+
if (value !== undefined && value !== null) {
|
|
191
|
+
if (typeof value !== 'object') {
|
|
192
|
+
return { propPath, values: [String(value)] };
|
|
193
|
+
}
|
|
194
|
+
if (Array.isArray(value) && value.length > 0 &&
|
|
195
|
+
value.every(v => v !== null && v !== undefined && typeof v !== 'object')) {
|
|
196
|
+
return { propPath, values: value.map(String) };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default IndexMatcher;
|
package/src/JoinEventStream.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import EventStream from './EventStream.js';
|
|
2
|
+
import { wrapAndCheck } from './util.js';
|
|
3
|
+
|
|
4
|
+
/** Reusable sentinel used for missing or empty per-stream iterators. */
|
|
5
|
+
const emptyIterator = Object.freeze({ next() { return { done: true }; } });
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Calculate the actual version number from a possibly relative (negative) version number.
|
|
@@ -24,9 +27,11 @@ class JoinEventStream extends EventStream {
|
|
|
24
27
|
* @param {EventStore} eventStore The event store to get the stream from.
|
|
25
28
|
* @param {number} [minRevision] The 1-based minimum revision to include in the events (inclusive).
|
|
26
29
|
* @param {number} [maxRevision] The 1-based maximum revision to include in the events (inclusive).
|
|
30
|
+
* @param {function(object, object): boolean|null} [predicate] An optional filter function
|
|
31
|
+
* `(payload, metadata) => boolean`. Only events for which this returns truthy are yielded.
|
|
27
32
|
*/
|
|
28
|
-
constructor(name, streams, eventStore, minRevision = 1, maxRevision = -1) {
|
|
29
|
-
super(name, eventStore, minRevision, maxRevision);
|
|
33
|
+
constructor(name, streams, eventStore, minRevision = 1, maxRevision = -1, predicate = null) {
|
|
34
|
+
super(name, eventStore, minRevision, maxRevision, predicate);
|
|
30
35
|
if (!(streams instanceof Array) || streams.length === 0) {
|
|
31
36
|
throw new Error(`Invalid list of streams supplied to JoinStream ${name}.`);
|
|
32
37
|
}
|
|
@@ -38,12 +43,17 @@ class JoinEventStream extends EventStream {
|
|
|
38
43
|
this.fetch = function() {
|
|
39
44
|
this._next = new Array(streams.length).fill(undefined);
|
|
40
45
|
return streams.map(streamName => {
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
const streamIndex = eventStore.streams[streamName]?.index;
|
|
47
|
+
if (!streamIndex || streamIndex.length === 0) {
|
|
48
|
+
return emptyIterator;
|
|
43
49
|
}
|
|
44
|
-
const streamIndex = eventStore.streams[streamName].index;
|
|
45
50
|
const from = streamIndex.find(this.minRevision, this.minRevision <= this.maxRevision);
|
|
46
51
|
const until = streamIndex.find(this.maxRevision, this.minRevision > this.maxRevision);
|
|
52
|
+
if (from === 0 || until === 0) {
|
|
53
|
+
// find() returns 0 when the requested revision is outside the stream's range
|
|
54
|
+
// (e.g. minRevision > all entries, or maxRevision < all entries).
|
|
55
|
+
return emptyIterator;
|
|
56
|
+
}
|
|
47
57
|
return eventStore.storage.readRange(from, until, streamIndex);
|
|
48
58
|
});
|
|
49
59
|
}
|
|
@@ -78,27 +88,31 @@ class JoinEventStream extends EventStream {
|
|
|
78
88
|
if (!this._iterator) {
|
|
79
89
|
this._iterator = this.fetch();
|
|
80
90
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
while (true) {
|
|
92
|
+
let nextIndex = -1;
|
|
93
|
+
this._next.forEach((value, index) => {
|
|
94
|
+
if (typeof value === 'undefined') {
|
|
95
|
+
value = this._next[index] = this.getValue(index);
|
|
96
|
+
}
|
|
97
|
+
if (value === false) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (nextIndex === -1 || this.follows(this._next[nextIndex].metadata.commitId, value.metadata.commitId)) {
|
|
101
|
+
nextIndex = index;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (nextIndex === -1) {
|
|
106
|
+
return false;
|
|
88
107
|
}
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
const next = this._next[nextIndex];
|
|
109
|
+
this._next[nextIndex] = undefined;
|
|
110
|
+
if (!this.predicate || this.predicate(next.payload, next.metadata)) {
|
|
111
|
+
return next;
|
|
91
112
|
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
if (nextIndex === -1) {
|
|
95
|
-
return false;
|
|
96
113
|
}
|
|
97
|
-
const next = this._next[nextIndex];
|
|
98
|
-
this._next[nextIndex] = undefined;
|
|
99
|
-
return next;
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
}
|
|
103
117
|
|
|
104
|
-
|
|
118
|
+
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;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import events from 'events';
|
|
4
|
+
import { assert, alignTo, hash, binarySearch } from '../util.js';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_READ_BUFFER_SIZE = 64 * 1024;
|
|
7
7
|
const DOCUMENT_HEADER_SIZE = 16;
|
|
@@ -453,11 +453,5 @@ class ReadablePartition extends events.EventEmitter {
|
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
module.exports.InvalidDataSizeError = InvalidDataSizeError;
|
|
459
|
-
module.exports.HEADER_MAGIC = HEADER_MAGIC;
|
|
460
|
-
module.exports.DOCUMENT_SEPARATOR = DOCUMENT_SEPARATOR;
|
|
461
|
-
module.exports.DOCUMENT_ALIGNMENT = DOCUMENT_ALIGNMENT;
|
|
462
|
-
module.exports.DOCUMENT_HEADER_SIZE = DOCUMENT_HEADER_SIZE;
|
|
463
|
-
module.exports.DOCUMENT_FOOTER_SIZE = DOCUMENT_FOOTER_SIZE;
|
|
456
|
+
export default ReadablePartition;
|
|
457
|
+
export { CorruptFileError, InvalidDataSizeError, HEADER_MAGIC, DOCUMENT_SEPARATOR, DOCUMENT_ALIGNMENT, DOCUMENT_HEADER_SIZE, DOCUMENT_FOOTER_SIZE };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import ReadablePartition, { CorruptFileError, HEADER_MAGIC, DOCUMENT_ALIGNMENT, DOCUMENT_SEPARATOR, DOCUMENT_HEADER_SIZE, DOCUMENT_FOOTER_SIZE } from './ReadablePartition.js';
|
|
3
|
+
import { assert, alignTo } from '../util.js';
|
|
4
|
+
import { buildMetadataHeader } from '../metadataUtil.js';
|
|
5
|
+
import { ensureDirectory } from '../fsUtil.js';
|
|
6
|
+
import Clock from '../Clock.js';
|
|
5
7
|
|
|
6
8
|
const DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
|
|
7
|
-
const { DOCUMENT_ALIGNMENT, DOCUMENT_SEPARATOR, DOCUMENT_HEADER_SIZE, DOCUMENT_FOOTER_SIZE } = ReadablePartition;
|
|
8
9
|
const DOCUMENT_PAD = ' '.repeat(DOCUMENT_ALIGNMENT);
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -111,7 +112,7 @@ class WritablePartition extends ReadablePartition {
|
|
|
111
112
|
* @returns void
|
|
112
113
|
*/
|
|
113
114
|
writeMetadata() {
|
|
114
|
-
const metadataBuffer = buildMetadataHeader(
|
|
115
|
+
const metadataBuffer = buildMetadataHeader(HEADER_MAGIC, this.metadata);
|
|
115
116
|
fs.writeFileSync(this.fileName, metadataBuffer);
|
|
116
117
|
this.headerSize = metadataBuffer.byteLength;
|
|
117
118
|
}
|
|
@@ -387,7 +388,7 @@ class WritablePartition extends ReadablePartition {
|
|
|
387
388
|
try {
|
|
388
389
|
this.readFrom(after);
|
|
389
390
|
} catch (e) {
|
|
390
|
-
if (!(e instanceof
|
|
391
|
+
if (!(e instanceof CorruptFileError)) {
|
|
391
392
|
throw new Error('Can only truncate on valid document boundaries.');
|
|
392
393
|
}
|
|
393
394
|
}
|
|
@@ -419,5 +420,5 @@ class WritablePartition extends ReadablePartition {
|
|
|
419
420
|
}
|
|
420
421
|
}
|
|
421
422
|
|
|
422
|
-
|
|
423
|
-
|
|
423
|
+
export default WritablePartition;
|
|
424
|
+
export { CorruptFileError };
|
package/src/Partition.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import WritablePartition, { CorruptFileError } from './Partition/WritablePartition.js';
|
|
2
|
+
import ReadOnlyPartition from './Partition/ReadOnlyPartition.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
WritablePartition.ReadOnly = ReadOnlyPartition;
|
|
5
|
+
WritablePartition.CorruptFileError = CorruptFileError;
|
|
6
|
+
|
|
7
|
+
export default WritablePartition;
|
|
8
|
+
export { ReadOnlyPartition as ReadOnly, CorruptFileError };
|