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.
@@ -1,590 +0,0 @@
1
- const expect = require('expect.js');
2
- const fs = require('fs-extra');
3
- const Index = require('../src/Index');
4
-
5
- const dataDirectory = __dirname + '/data';
6
-
7
- describe('Index', function() {
8
-
9
- let index, counter = 1, readers = [];
10
-
11
- beforeEach(function() {
12
- fs.emptyDirSync(dataDirectory);
13
- });
14
-
15
- afterEach(function() {
16
- if (index) index.close();
17
- for (let reader of readers) reader.close();
18
- readers = [];
19
- index = null;
20
- });
21
-
22
- function createIndex(name = 'test.index', options = {}) {
23
- return new Index(name, Object.assign({ dataDirectory }, options));
24
- }
25
-
26
- function setupIndexWithEntries(num, indexMapper, options) {
27
- if (typeof indexMapper === 'object') {
28
- options = indexMapper;
29
- indexMapper = null;
30
- }
31
- index = createIndex('test' + (counter++) + '.index', options);
32
- for (let i = 1; i <= num; i++) {
33
- index.add(new Index.Entry(indexMapper && indexMapper(i) || i, i));
34
- }
35
- index.flush();
36
- return index;
37
- }
38
-
39
- function createReader(name, options) {
40
- let reader = new Index.ReadOnly(name, Object.assign({ dataDirectory }, options));
41
- readers[readers.length] = reader;
42
- return reader;
43
- }
44
-
45
- it('is opened on instantiation', function() {
46
- index = setupIndexWithEntries();
47
- expect(index.isOpen()).to.be(true);
48
- });
49
-
50
- it('defaults name to ".index"', function() {
51
- index = new Index({ dataDirectory });
52
- expect(index.name).to.be('.index');
53
- });
54
-
55
- it('recovers metadata on reopening', function() {
56
- index = createIndex('.index', { metadata: { test: 'valueStays' } });
57
- expect(index.metadata.test).to.be('valueStays');
58
- index.close();
59
- index = createIndex(index.name);
60
- expect(index.metadata.test).to.be('valueStays');
61
- });
62
-
63
- it('throws on opening an non-index file', function() {
64
- const indexFile = dataDirectory + '/.index';
65
- fs.writeFileSync(indexFile, 'foo');
66
- expect(() => index = new Index(indexFile)).to.throwError(/Invalid file header/);
67
- });
68
-
69
- it('throws on opening an index file with different version', function() {
70
- const indexFile = dataDirectory + '/.index';
71
- fs.writeFileSync(indexFile, 'nesidx00');
72
- expect(() => index = new Index(indexFile)).to.throwError(/Invalid file version/);
73
- });
74
-
75
- it('throws on opening an index file with wrong metadata size', function() {
76
- const indexFile = dataDirectory + '/.index';
77
- const metadataBuffer = Buffer.allocUnsafe(8 + 4);
78
- metadataBuffer.write("nesidx01", 0, 8, 'utf8');
79
- metadataBuffer.writeUInt32BE(0, 8);
80
- fs.writeFileSync(indexFile, metadataBuffer);
81
-
82
- expect(() => index = new Index(indexFile)).to.throwError(/Invalid metadata size/);
83
- });
84
-
85
- it('throws on opening an index file with too large metadata size', function() {
86
- const indexFile = dataDirectory + '/.index';
87
- const metadataBuffer = Buffer.allocUnsafe(8 + 4 + 3);
88
- metadataBuffer.write("nesidx01", 0, 8, 'utf8');
89
- metadataBuffer.writeUInt32BE(255, 8);
90
- metadataBuffer.write("{}\n", 12, 3, 'utf8');
91
- fs.writeFileSync(indexFile, metadataBuffer);
92
-
93
- expect(() => index = new Index(indexFile)).to.throwError(/Invalid index file/);
94
- });
95
-
96
- it('throws on opening an index file with invalid metadata', function() {
97
- const indexFile = dataDirectory + '/.index';
98
- const metadataBuffer = Buffer.allocUnsafe(8 + 4 + 3);
99
- metadataBuffer.write("nesidx01", 0, 8, 'utf8');
100
- metadataBuffer.writeUInt32BE(255, 8);
101
- metadataBuffer.write("{x$", 12, 3, 'utf8');
102
- fs.writeFileSync(indexFile, metadataBuffer);
103
-
104
- expect(() => index = new Index(indexFile)).to.throwError(/Invalid metadata/);
105
- });
106
-
107
- it('throws on reopening with altered metadata', function() {
108
- index = createIndex('.index', { metadata: { test: 'valueStays' } });
109
- expect(() => index = createIndex(index.name, { metadata: { test: 'anotherValue' } })).to.throwError(/Index metadata mismatch/);
110
- });
111
-
112
- it('throws on opening with altered file', function() {
113
- index = setupIndexWithEntries(5);
114
- index.close();
115
- fs.appendFileSync(index.fileName, 'foo');
116
- expect(() => index = createIndex(index.name)).to.throwError(/Index file is corrupt/);
117
- });
118
-
119
- describe('Entry', function() {
120
-
121
- it('stores data correctly', function() {
122
- let entry = new Index.Entry(1, 2, 3, 4);
123
- expect(entry.number).to.be(1);
124
- expect(entry.position).to.be(2);
125
- expect(entry.size).to.be(3);
126
- expect(entry.partition).to.be(4);
127
- });
128
-
129
- it('correctly validates custom entry classes', function() {
130
- class CustomEntryClassWithMissingFromBuffer {
131
- static get size() { return 4; }
132
- }
133
- class CustomEntryClassWithMissingToBuffer {
134
- static get size() { return 4; }
135
- static fromBuffer(buffer, offset = 0) {}
136
- }
137
- class CustomZeroSizeEntryClass {
138
- static get size() { return 0; }
139
- static fromBuffer(buffer, offset = 0) {}
140
- toBuffer(buffer, offset) {}
141
- }
142
- function CustomEs5Entry() {}
143
- CustomEs5Entry.size = 4;
144
- CustomEs5Entry.fromBuffer = function(buffer, offset) {};
145
- CustomEs5Entry.prototype.toBuffer = function(buffer, offset) {};
146
- expect(() => Index.Entry.assertValidEntryClass({})).to.throwError(/Invalid index entry class/);
147
- expect(() => Index.Entry.assertValidEntryClass(CustomEntryClassWithMissingFromBuffer)).to.throwError(/Invalid index entry class/);
148
- expect(() => Index.Entry.assertValidEntryClass(CustomEntryClassWithMissingToBuffer)).to.throwError(/Invalid index entry class/);
149
- expect(() => Index.Entry.assertValidEntryClass(CustomZeroSizeEntryClass)).to.throwError(/size must be positive/);
150
- expect(() => Index.Entry.assertValidEntryClass(CustomEs5Entry)).to.not.throwError();
151
- });
152
-
153
- });
154
-
155
- describe('add', function() {
156
-
157
- it('appends entries sequentially', function() {
158
- index = setupIndexWithEntries(25);
159
- index.close();
160
- index.open();
161
- let entries = index.all();
162
- expect(entries.length).to.be(25);
163
- for (let i = 1; i <= entries.length; i++) {
164
- expect(entries[i - 1].number).to.be(i);
165
- }
166
- });
167
-
168
- it('appends entries to reopened index correctly', function() {
169
- index = setupIndexWithEntries(5);
170
- index.close();
171
- index.open();
172
- index.add(new Index.Entry(6, 6));
173
- let entries = index.all();
174
- expect(entries.length).to.be(6);
175
- for (let i = 1; i <= entries.length; i++) {
176
- expect(entries[i - 1].number).to.be(i);
177
- }
178
- });
179
-
180
- it('calls callback eventually', function(done) {
181
- index = createIndex('.index', { flushDelay: 1 });
182
- let position = index.add(new Index.Entry(1, 0), (number) => {
183
- expect(number).to.be(position);
184
- done();
185
- });
186
- });
187
-
188
- it('flushes automatically when writeBuffer full', function() {
189
- index = setupIndexWithEntries(5, { writeBufferSize: 5 * Index.Entry.size });
190
- expect(index.flush()).to.be(false);
191
- });
192
-
193
- it('throws with invalid entry object', function() {
194
- index = createIndex();
195
- expect(() => index.add([1,2,3,4])).to.throwError(/Wrong entry object/);
196
- });
197
-
198
- it('throws with invalid entry size', function() {
199
- index = createIndex();
200
- class Entry extends Index.Entry {
201
- static get size() {
202
- return 20;
203
- }
204
- }
205
- expect(() => index.add(new Entry(1, 0))).to.throwError(/Invalid entry size/);
206
- });
207
-
208
- });
209
-
210
- describe('get', function() {
211
-
212
- it('returns false on out of bounds position', function() {
213
- index = setupIndexWithEntries(5);
214
- index.close();
215
- index.open();
216
- expect(index.get(0)).to.be(false);
217
- expect(index.get(index.length+1)).to.be(false);
218
- });
219
-
220
- it('returns false on closed index', function() {
221
- index = setupIndexWithEntries(5);
222
- index.close();
223
- expect(index.get(1)).to.be(false);
224
- });
225
-
226
- it('can read entry from the end', function() {
227
- setupIndexWithEntries(5);
228
- index.close();
229
- index.open();
230
- let entry = index.get(-1);
231
- expect(entry.number).to.be(index.length);
232
- });
233
-
234
- it('can random read entries', function() {
235
- index = setupIndexWithEntries(10);
236
- index.close();
237
- index.open();
238
- let entry = index.get(5);
239
- expect(entry.number).to.be(5);
240
- });
241
-
242
- it('can read entries multiple times', function() {
243
- index = setupIndexWithEntries(10);
244
- index.close();
245
- index.open();
246
- for (let i = 0; i < 5; i++) {
247
- let entry = index.get(5);
248
- expect(entry.number).to.be(5);
249
- }
250
- });
251
-
252
- });
253
-
254
- describe('range', function() {
255
-
256
- it('returns false on out of bounds range position', function() {
257
- index = setupIndexWithEntries(50);
258
- index.close();
259
- index.open();
260
- expect(index.range(0)).to.be(false);
261
- expect(index.range(51, 55)).to.be(false);
262
- expect(index.range(1, 51)).to.be(false);
263
- expect(index.range(15, 10)).to.be(false);
264
- });
265
-
266
- it('returns false on closed index', function() {
267
- index = setupIndexWithEntries(5);
268
- index.close();
269
- expect(index.range(1)).to.be(false);
270
- expect(index.range(1,5)).to.be(false);
271
- });
272
-
273
- it('can read an arbitrary range of entries', function() {
274
- index = setupIndexWithEntries(50);
275
- index.close();
276
- index.open();
277
- let entries = index.range(21, 37);
278
- for (let i = 0; i < entries.length; i++) {
279
- expect(entries[i].number).to.be(21 + i);
280
- }
281
- });
282
-
283
- it('can read a range of entries from the end', function() {
284
- index = setupIndexWithEntries(50);
285
- index.close();
286
- index.open();
287
- let entries = index.range(-15);
288
- expect(entries.length).to.be(15);
289
- for (let i = 0; i < entries.length; i++) {
290
- expect(entries[i].number).to.be(36 + i);
291
- }
292
- });
293
-
294
- it('can read a range of entries until a distance from the end', function() {
295
- index = setupIndexWithEntries(50);
296
- index.close();
297
- index.open();
298
- let entries = index.range(1, -15);
299
- expect(entries.length).to.be(36); // 36 because end is inclusive
300
- for (let i = 0; i < entries.length; i++) {
301
- expect(entries[i].number).to.be(1 + i);
302
- }
303
- });
304
-
305
- it('can read a single item range of entries', function() {
306
- index = setupIndexWithEntries(50);
307
- index.close();
308
- index.open();
309
- let entries = index.range(21, 21);
310
- expect(entries.length).to.be(1);
311
- expect(entries[0].number).to.be(21);
312
- });
313
-
314
- it('returns false with a non-numeric range', function() {
315
- index = setupIndexWithEntries(5);
316
- index.close();
317
- index.open();
318
- let entries = index.range('foo');
319
- expect(entries).to.be(false);
320
- });
321
-
322
- });
323
-
324
- describe('lastEntry', function() {
325
-
326
- it('returns the last entry', function() {
327
- index = setupIndexWithEntries(5);
328
- expect(index.lastEntry.number).to.be(5);
329
- });
330
-
331
- it('returns false on empty index', function() {
332
- index = setupIndexWithEntries(0);
333
- expect(index.lastEntry).to.be(false);
334
- });
335
-
336
- });
337
-
338
- describe('find', function() {
339
-
340
- it('returns 0 if no entry is lower or equal searched number', function() {
341
- index = setupIndexWithEntries(5, i => 5 + i);
342
- expect(index.find(index.length)).to.be(0);
343
- });
344
-
345
- it('returns last entry if all entries are lower searched number', function() {
346
- index = setupIndexWithEntries(5);
347
- expect(index.find(index.length+1)).to.be(index.length);
348
- });
349
-
350
- it('returns 0 if all entries are lower searched number with min=true', function() {
351
- index = setupIndexWithEntries(5);
352
- expect(index.find(index.length+1, true)).to.be(0);
353
- });
354
-
355
- it('returns the entry number on exact match', function() {
356
- index = setupIndexWithEntries(5);
357
- for (let i = 1; i <= 5; i++) {
358
- expect(index.find(i)).to.be(i);
359
- }
360
- });
361
-
362
- it('returns the highest entry number lower than the searched number', function() {
363
- index = setupIndexWithEntries(50, i => 2*i);
364
- expect(index.find(25)).to.be(12);
365
- });
366
-
367
- it('returns the lowest entry number higher than the searched number with min=true', function() {
368
- index = setupIndexWithEntries(50, i => 2*i);
369
- expect(index.find(25, true)).to.be(13);
370
- });
371
-
372
- });
373
-
374
- describe('truncate', function() {
375
-
376
- it('truncates after the given index position', function() {
377
- index = setupIndexWithEntries(5);
378
- index.close();
379
- index.open();
380
-
381
- index.truncate(2);
382
- expect(index.length).to.be(2);
383
-
384
- index.close();
385
- index.open();
386
- expect(index.length).to.be(2);
387
- });
388
-
389
- it('correctly truncates after unflushed entries', function() {
390
- index = setupIndexWithEntries(5);
391
-
392
- index.truncate(2);
393
- expect(index.length).to.be(2);
394
-
395
- index.close();
396
- index.open();
397
- expect(index.length).to.be(2);
398
- });
399
-
400
- it('does not truncate closed index', function() {
401
- index = setupIndexWithEntries(5);
402
- index.close();
403
-
404
- index.truncate(2);
405
-
406
- index.open();
407
- expect(index.length).to.be(5);
408
- });
409
-
410
- it('does nothing if truncating after index length', function() {
411
- index = setupIndexWithEntries(5);
412
- index.close();
413
- index.open();
414
-
415
- index.truncate(6);
416
- expect(index.length).to.be(5);
417
-
418
- index.close();
419
- index.open();
420
- expect(index.length).to.be(5);
421
- });
422
-
423
- it('truncates whole index if given negative position', function() {
424
- index = setupIndexWithEntries(5);
425
- index.close();
426
- index.open();
427
-
428
- index.truncate(-5);
429
- expect(index.length).to.be(0);
430
-
431
- index.close();
432
- index.open();
433
- expect(index.length).to.be(0);
434
- });
435
-
436
- });
437
-
438
- describe('validRange', function(){
439
-
440
- it('returns false for out of range from positions', function(){
441
- index = setupIndexWithEntries(5);
442
- expect(index.validRange(0, 1)).to.be(false);
443
- expect(index.validRange(-1, 1)).to.be(false);
444
- expect(index.validRange(index.length + 1, index.length + 2)).to.be(false);
445
- });
446
-
447
- it('returns false when from greater until', function(){
448
- index = setupIndexWithEntries(5);
449
- expect(index.validRange(2, 1)).to.be(false);
450
- expect(index.validRange(1, 0)).to.be(false);
451
- });
452
-
453
- it('returns false for out of range until positions', function(){
454
- index = setupIndexWithEntries(5);
455
- expect(index.validRange(1, -1)).to.be(false);
456
- expect(index.validRange(1, index.length +1)).to.be(false);
457
- });
458
-
459
- it('returns true for valid range positions', function(){
460
- index = setupIndexWithEntries(5);
461
- expect(index.validRange(1, 1)).to.be(true);
462
- expect(index.validRange(1, index.length)).to.be(true);
463
- expect(index.validRange(index.length, index.length)).to.be(true);
464
- });
465
-
466
- });
467
-
468
- describe('destroy', function(){
469
-
470
- it('completely deletes the file', function(){
471
- index = setupIndexWithEntries(5);
472
- const fileName = index.fileName;
473
- index.destroy();
474
- expect(fs.existsSync(fileName)).to.be(false);
475
- });
476
-
477
- });
478
-
479
- describe('flush', function(){
480
-
481
- it('returns false on a closed index', function(){
482
- index = setupIndexWithEntries(1);
483
- index.close();
484
- expect(index.flush()).to.be(false);
485
- });
486
-
487
- it('returns false if nothing to flush', function(){
488
- index = setupIndexWithEntries(1);
489
- index.flush();
490
- expect(index.flush()).to.be(false);
491
- });
492
-
493
- });
494
-
495
- describe('ReadOnly', function(){
496
-
497
- it('can be created without explicit name', function(){
498
- expect(() => {
499
- index = createIndex('.index');
500
- let reader = new Index.ReadOnly({ dataDirectory });
501
- reader.close();
502
- }).to.not.throwError();
503
- });
504
-
505
- it('can be opened and closed multiple times', function(){
506
- index = createIndex('.index');
507
- let reader = new Index.ReadOnly({ dataDirectory });
508
- expect(reader.open()).to.be(false);
509
- reader.close();
510
- reader.close();
511
- });
512
-
513
- it('throws when opening an empty file', function(){
514
- index = createIndex('.index');
515
- index.close();
516
- fs.truncateSync(index.fileName, 0);
517
- expect(() => createReader(index.name)).to.throwError(/empty/);
518
- });
519
-
520
- it('allows multiple readers for a single index', function(){
521
- index = setupIndexWithEntries(5);
522
-
523
- let reader1 = createReader(index.name);
524
- expect(reader1.isOpen()).to.be(true);
525
- expect(reader1.length).to.be(index.length);
526
- expect(reader1.lastEntry.number).to.be(index.lastEntry.number);
527
-
528
- let reader2 = createReader(index.name);
529
- expect(reader2.isOpen()).to.be(true);
530
- expect(reader2.length).to.be(index.length);
531
- expect(reader2.lastEntry.number).to.be(index.lastEntry.number);
532
- });
533
-
534
- it('updates when writer flushes', function(done){
535
- index = setupIndexWithEntries(5);
536
- let reader1 = createReader(index.name);
537
-
538
- reader1.on('append', (prev, next) => {
539
- expect(prev).to.be(5);
540
- expect(next).to.be(6);
541
- expect(reader1.get(next).number).to.be(index.get(next).number);
542
- done();
543
- });
544
-
545
- index.add(new Index.Entry(6, 6));
546
- index.flush();
547
- fs.fdatasync(index.fd);
548
- });
549
-
550
- it('updates when writer truncates', function(done){
551
- index = setupIndexWithEntries(5);
552
- let reader1 = createReader(index.name);
553
-
554
- reader1.on('truncate', (prev, next) => {
555
- expect(prev).to.be(5);
556
- expect(next).to.be(0);
557
- expect(reader1.length).to.be(0);
558
- done();
559
- });
560
-
561
- index.truncate(0);
562
- fs.fdatasync(index.fd);
563
- });
564
-
565
- it('closes when file renamed', function(done){
566
- index = setupIndexWithEntries(5);
567
- index.close();
568
- let reader = createReader(index.name);
569
- expect(reader.isOpen()).to.be(true);
570
-
571
- fs.rename(reader.fileName, reader.fileName + '2', () => {
572
- setTimeout(() => {
573
- expect(reader.isOpen()).to.be(false);
574
- done();
575
- }, 1);
576
- });
577
- });
578
-
579
- it('does not trigger handler when index closed', function(done){
580
- index = setupIndexWithEntries(5);
581
-
582
- let reader = createReader(index.name);
583
- reader.on('truncate', () => expect(this).to.be(false));
584
-
585
- index.truncate(0);
586
- reader.close();
587
- fs.fdatasync(index.fd, () => done());
588
- });
589
- });
590
- });
@@ -1,113 +0,0 @@
1
- const expect = require('expect.js');
2
- const fs = require('fs-extra');
3
- const JoinEventStream = require('../src/JoinEventStream');
4
- const EventStore = require('../src/EventStore');
5
-
6
- describe('JoinEventStream', function() {
7
-
8
- let stream, eventstore;
9
- const events = [{ type: 'foo' }, { type: 'bar' }, { type: 'baz' }];
10
-
11
- before(function (done) {
12
- fs.emptyDirSync('test/data');
13
- eventstore = new EventStore({
14
- storageDirectory: 'test/data'
15
- });
16
- eventstore.commit('foo', events[0], () => {
17
- eventstore.commit('bar', events[1], () => {
18
- eventstore.commit('foo', events[2], () => {
19
- done();
20
- });
21
- });
22
- });
23
- });
24
-
25
- after(function () {
26
- eventstore.close();
27
- eventstore = null;
28
- });
29
-
30
- it('makes the name available', function(){
31
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore);
32
- expect(stream.name).to.be('foo-bar');
33
- });
34
-
35
- it('throws if no name specified in constructor', function(){
36
- expect(() => new JoinEventStream()).to.throwError();
37
- });
38
-
39
- it('throws if no or invalid stream list specified in constructor', function(){
40
- expect(() => new JoinEventStream('foo-bar', 'foo', eventstore)).to.throwError();
41
- expect(() => new JoinEventStream('foo-bar', [], eventstore)).to.throwError();
42
- });
43
-
44
- it('throws if no EventStore specified in constructor', function(){
45
- expect(() => new JoinEventStream('foo-bar', ['foo', 'bar'])).to.throwError();
46
- });
47
-
48
- it('makes all events accessible as array', function(){
49
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore);
50
- expect(stream.events).to.eql(events);
51
- });
52
-
53
- it('returns all events consistently', function(){
54
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore);
55
- expect(stream.events).to.eql(stream.events);
56
- });
57
-
58
- it('can be iterated with for .. of', function(){
59
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore);
60
- let i = 0;
61
- for (let event of stream) {
62
- expect(event).to.eql(events[i++]);
63
- }
64
- });
65
-
66
- it('is a readable stream', function(done){
67
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore);
68
- let i = 0;
69
- stream.on('data', (event) => {
70
- expect(event).to.eql(events[i++]);
71
- if (i === events.length) {
72
- done();
73
- }
74
- });
75
- });
76
-
77
- it('can limit events fetched with min and max revision', function(){
78
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore, 0, 1);
79
- const fetchedEvents = stream.events;
80
-
81
- expect(fetchedEvents.length).to.be(2);
82
- expect(fetchedEvents[0]).to.eql(events[0]);
83
- expect(fetchedEvents[1]).to.eql(events[1]);
84
- });
85
-
86
- it('can fetch events from the end only', function(){
87
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore, -2, -1);
88
- const fetchedEvents = stream.events;
89
-
90
- expect(fetchedEvents.length).to.be(2);
91
- expect(fetchedEvents[0]).to.eql(events[1]);
92
- expect(fetchedEvents[1]).to.eql(events[2]);
93
- });
94
-
95
- it('is empty when stream does not exist', function(){
96
- stream = new JoinEventStream('foo-bar', ['baz'], eventstore);
97
- expect(stream.events).to.be.eql([]);
98
- });
99
-
100
- describe('forEach', function(){
101
-
102
- it('invokes a callback with payload, metadata and stream name', function(){
103
- stream = new JoinEventStream('foo-bar', ['foo', 'bar'], eventstore);
104
- let i = 0;
105
- stream.forEach((event, metadata, stream) => {
106
- expect(event).to.eql(events[i++]);
107
- expect(stream).to.be(i === 2 ? 'bar' : 'foo');
108
- });
109
- });
110
-
111
- });
112
-
113
- });