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.
@@ -1,632 +0,0 @@
1
- const expect = require('expect.js');
2
- const fs = require('fs-extra');
3
- const path = require('path');
4
- const EventStore = require('../src/EventStore');
5
-
6
- const storageDirectory = __dirname + '/data';
7
-
8
- describe('EventStore', function() {
9
-
10
- let eventstore;
11
-
12
- beforeEach(function () {
13
- fs.emptyDirSync(storageDirectory);
14
- });
15
-
16
- afterEach(function () {
17
- if (eventstore) {
18
- eventstore.close();
19
- }
20
- eventstore = null;
21
- });
22
-
23
- it('basically works', function(done) {
24
- eventstore = new EventStore({
25
- storageDirectory
26
- });
27
-
28
- let events = [{foo: 'bar'}, {foo: 'baz'}, {foo: 'quux'}];
29
- eventstore.on('ready', () => {
30
- eventstore.commit('foo-bar', events, () => {
31
- const stream = eventstore.getEventStream('foo-bar');
32
- let i = 0;
33
- for (let event of stream) {
34
- expect(event).to.eql(events[i++]);
35
- }
36
- done();
37
- });
38
- });
39
- });
40
-
41
- it('can be created with custom name', function(done) {
42
- eventstore = new EventStore('custom-store', {
43
- storageDirectory
44
- });
45
-
46
- eventstore.commit('foo-bar', [{ type: 'foo'}], () => {
47
- expect(fs.existsSync(path.join(storageDirectory, 'custom-store.foo-bar'))).to.be(true);
48
- done();
49
- });
50
- });
51
-
52
- it('throws when scanning of stream directory fails', function() {
53
- const fs = require('fs');
54
- const originalReaddir = fs.readdir;
55
- fs.readdir = (dir, callback) => callback(new Error('Something went wrong!'), null);
56
-
57
- expect(() => new EventStore({
58
- storageDirectory
59
- })).to.throwError(/Something went wrong!/);
60
- fs.readdir = originalReaddir;
61
- });
62
-
63
- it('repairs torn writes', function(done) {
64
- eventstore = new EventStore({
65
- storageDirectory
66
- });
67
-
68
- const events = [{foo: 'bar'.repeat(500)}];
69
- eventstore.on('ready', () => {
70
- eventstore.commit('foo-bar', events, () => {
71
- // Simulate a torn write (but indexes are still written)
72
- fs.truncateSync(eventstore.storage.getPartition('foo-bar').fileName, 512);
73
-
74
- // The previous instance was not closed, so the lock still exists
75
- eventstore = new EventStore({
76
- storageDirectory,
77
- storageConfig: {
78
- lock: EventStore.LOCK_RECLAIM
79
- }
80
- });
81
- eventstore.on('ready', () => {
82
- expect(eventstore.length).to.be(0);
83
- expect(eventstore.getStreamVersion('foo-bar')).to.be(0);
84
- done();
85
- });
86
- });
87
- });
88
- });
89
-
90
- it('throws when trying to open non-existing store read-only', function() {
91
- expect(() => new EventStore({
92
- storageDirectory,
93
- readOnly: true
94
- })).to.throwError();
95
- });
96
-
97
- it('can open read-only', function(done) {
98
- eventstore = new EventStore({
99
- storageDirectory
100
- });
101
-
102
- let events = [{foo: 'bar'}, {foo: 'baz'}, {foo: 'quux'}];
103
- eventstore.on('ready', () => {
104
- eventstore.commit('foo-bar', events, () => {
105
- let readstore = new EventStore({
106
- storageDirectory,
107
- readOnly: true
108
- });
109
-
110
- readstore.on('ready', () => {
111
- const stream = readstore.getEventStream('foo-bar');
112
- let i = 0;
113
- for (let event of stream) {
114
- expect(event).to.eql(events[i++]);
115
- }
116
- readstore.close();
117
- done();
118
- });
119
- });
120
- });
121
- });
122
-
123
- describe('commit', function() {
124
-
125
- it('throws when no stream name specified', function() {
126
- eventstore = new EventStore({
127
- storageDirectory
128
- });
129
-
130
- expect(() => eventstore.commit({ foo: 'bar' })).to.throwError();
131
- });
132
-
133
- it('throws when no events specified', function() {
134
- eventstore = new EventStore({
135
- storageDirectory
136
- });
137
-
138
- expect(() => eventstore.commit('foo-bar')).to.throwError();
139
- });
140
-
141
- it('throws when opened in read-only mode', function() {
142
- eventstore = new EventStore({
143
- storageDirectory
144
- });
145
- eventstore.close();
146
-
147
- eventstore = new EventStore({
148
- storageDirectory,
149
- readOnly: true
150
- });
151
-
152
- expect(() => eventstore.commit('foo-bar', { foo: 'bar' })).to.throwError();
153
- });
154
-
155
- it('can commit a single event', function() {
156
- eventstore = new EventStore({
157
- storageDirectory
158
- });
159
-
160
- eventstore.commit('foo-bar', { foo: 'bar' });
161
- expect(eventstore.length).to.be(1);
162
- });
163
-
164
- it('can commit multiple events at once', function() {
165
- eventstore = new EventStore({
166
- storageDirectory
167
- });
168
-
169
- eventstore.commit('foo-bar', [{ foo: 'bar' }, { bar: 'baz' }, { baz: 'quux' }]);
170
- expect(eventstore.length).to.be(3);
171
- });
172
-
173
- it('invokes callback when finished', function(done) {
174
- eventstore = new EventStore({
175
- storageDirectory
176
- });
177
-
178
- eventstore.commit('foo-bar', [{ foo: 'bar' }], (commit) => {
179
- expect(eventstore.length).to.be(1);
180
- expect(commit.streamName).to.be('foo-bar');
181
- expect(commit.streamVersion).to.be(0);
182
- expect(commit.events).to.eql([{ foo: 'bar' }]);
183
- done();
184
- });
185
- });
186
-
187
- it('invokes callback when finished with optimistic concurrency check', function(done) {
188
- eventstore = new EventStore({
189
- storageDirectory
190
- });
191
-
192
- eventstore.commit('foo-bar', [{ foo: 'bar' }], EventStore.ExpectedVersion.EmptyStream, (commit) => {
193
- expect(eventstore.length).to.be(1);
194
- expect(commit.streamName).to.be('foo-bar');
195
- expect(commit.streamVersion).to.be(0);
196
- expect(commit.events).to.eql([{ foo: 'bar' }]);
197
- done();
198
- });
199
- });
200
-
201
- it('invokes callback when finished with optimistic concurrency check and metdata', function(done) {
202
- eventstore = new EventStore({
203
- storageDirectory
204
- });
205
-
206
- eventstore.commit('foo-bar', [{ foo: 'bar' }], EventStore.ExpectedVersion.EmptyStream, {}, (commit) => {
207
- expect(eventstore.length).to.be(1);
208
- expect(commit.streamName).to.be('foo-bar');
209
- expect(commit.streamVersion).to.be(0);
210
- expect(commit.events).to.eql([{ foo: 'bar' }]);
211
- done();
212
- });
213
- });
214
-
215
- it('invokes "commit" event when finished', function(done) {
216
- eventstore = new EventStore({
217
- storageDirectory
218
- });
219
-
220
- eventstore.on('commit', (commit) => {
221
- expect(eventstore.length).to.be(1);
222
- expect(commit.streamName).to.be('foo-bar');
223
- expect(commit.streamVersion).to.be(0);
224
- expect(commit.events).to.eql([{ foo: 'bar' }]);
225
- done();
226
- });
227
- eventstore.commit('foo-bar', [{ foo: 'bar' }]);
228
- });
229
-
230
- it('throws an optimistic concurrency error if stream version does not match', function(done) {
231
- eventstore = new EventStore({
232
- storageDirectory
233
- });
234
-
235
- expect(() => eventstore.commit('foo-bar', { foo: 'bar' }, 1)).to.throwError(
236
- e => expect(e).to.be.a(EventStore.OptimisticConcurrencyError)
237
- );
238
-
239
- eventstore.commit('foo-bar', { foo: 'bar' }, () => {
240
- expect(() => eventstore.commit('foo-bar', { foo: 'baz' }, EventStore.ExpectedVersion.EmptyStream)).to.throwError(
241
- e => expect(e).to.be.a(EventStore.OptimisticConcurrencyError)
242
- );
243
- expect(() => eventstore.commit('foo-bar', { foo: 'baz' }, 2)).to.throwError(
244
- e => expect(e).to.be.a(EventStore.OptimisticConcurrencyError)
245
- );
246
- done();
247
- });
248
- });
249
-
250
- it('does not throw an optimistic concurrency error if stream version matches', function(done) {
251
- eventstore = new EventStore({
252
- storageDirectory
253
- });
254
-
255
- expect(() => eventstore.commit('foo-bar', { foo: 'bar' }, EventStore.ExpectedVersion.EmptyStream)).to.not.throwError();
256
-
257
- eventstore.commit('foo-bar', { foo: 'bar' }, () => {
258
- expect(() => eventstore.commit('foo-bar', {foo: 'baz'}, 2)).to.not.throwError();
259
- expect(() => eventstore.commit('foo-bar', {foo: 'baz'}, EventStore.ExpectedVersion.Any)).to.not.throwError();
260
- done();
261
- });
262
- });
263
-
264
- it('uses metadata from argument for commit', function(done) {
265
- eventstore = new EventStore({
266
- storageDirectory
267
- });
268
-
269
- eventstore.commit('foo-bar', [{ foo: 'bar' }], { commitId: 1, committedAt: 12345, quux: 'quux' }, (commit) => {
270
- expect(commit.commitId).to.be(1);
271
- expect(commit.committedAt).to.be(12345);
272
- expect(commit.quux).to.be('quux');
273
-
274
- const stream = eventstore.getEventStream('foo-bar');
275
- const storedEvent = stream.next();
276
- expect(storedEvent.metadata.commitId).to.be(1);
277
- expect(storedEvent.metadata.committedAt).to.be(12345);
278
- expect(storedEvent.metadata.quux).to.be('quux');
279
- done();
280
- });
281
- });
282
-
283
- });
284
-
285
- describe('createEventStream', function() {
286
-
287
- it('throws when trying to recreate existing stream', function(done) {
288
- eventstore = new EventStore({
289
- storageDirectory
290
- });
291
-
292
- eventstore.commit('foo-bar', [{ type: 'foo' }], () => {
293
- expect(() => eventstore.createEventStream('foo-bar', event => event.payload.type === 'foo')).to.throwError();
294
- done();
295
- });
296
- });
297
-
298
- it('can create new streams on existing events', function(done) {
299
- eventstore = new EventStore({
300
- storageDirectory
301
- });
302
-
303
- eventstore.commit('foo-bar', [{ type: 'foo' }], () => {
304
- const stream = eventstore.createEventStream('my-foo-bar', event => event.payload.type === 'foo');
305
- expect(stream.events.length).to.be(1);
306
- expect(stream.events[0]).to.eql({ type: 'foo' });
307
- done();
308
- });
309
- });
310
-
311
- });
312
-
313
- describe('getStreamVersion', function() {
314
-
315
- it('returns -1 if the stream does not exist', function() {
316
- eventstore = new EventStore({
317
- storageDirectory
318
- });
319
- expect(eventstore.getStreamVersion('foo')).to.be(-1);
320
- });
321
-
322
- it('returns 0 if the stream is empty', function() {
323
- eventstore = new EventStore({
324
- storageDirectory
325
- });
326
- eventstore.createEventStream('foo', () => true);
327
- expect(eventstore.getStreamVersion('foo')).to.be(0);
328
- });
329
-
330
- it('returns the version of the stream', function(done) {
331
- eventstore = new EventStore({
332
- storageDirectory
333
- });
334
- eventstore.commit('foo', [{ type: 'foo' }], () => {
335
- expect(eventstore.getStreamVersion('foo')).to.be(1);
336
- done();
337
- });
338
- });
339
-
340
- });
341
-
342
- describe('getEventStream', function() {
343
-
344
- it('can open existing streams', function(done) {
345
- eventstore = new EventStore({
346
- storageDirectory
347
- });
348
-
349
- eventstore.commit('foo-bar', [{ foo: 'bar' }]);
350
- eventstore.close();
351
-
352
- eventstore = new EventStore({
353
- storageDirectory
354
- });
355
- eventstore.on('ready', () => {
356
- const stream = eventstore.getEventStream('foo-bar');
357
- expect(stream.events.length).to.be(1);
358
- done();
359
- });
360
- });
361
-
362
- it('can iterate events in reverse order', function() {
363
- eventstore = new EventStore({
364
- storageDirectory
365
- });
366
-
367
- for (let i=1; i<=20; i++) {
368
- eventstore.commit('foo-bar', [{key: i}]);
369
- }
370
-
371
- let reverseStream = eventstore.getEventStream('foo-bar', -1, 0);
372
- let i = 20;
373
- for (let event of reverseStream) {
374
- expect(event).to.eql({ key: i-- });
375
- }
376
- });
377
-
378
- it('can open streams created in writer', function(done) {
379
- eventstore = new EventStore({
380
- storageDirectory
381
- });
382
-
383
- const readstore = new EventStore({
384
- storageDirectory,
385
- readOnly: true
386
- });
387
-
388
- expect(readstore.getStreamVersion('foo')).to.be(-1);
389
-
390
- readstore.on('stream-available', (streamName) => {
391
- if (streamName === 'foo') {
392
- expect(readstore.getStreamVersion('foo')).to.be(0);
393
- readstore.close();
394
- done();
395
- }
396
- });
397
-
398
- eventstore.createEventStream('foo', { type: 'foo' });
399
- });
400
-
401
- it('needs to be tested further.');
402
- });
403
-
404
- describe('getAllEvents', function() {
405
-
406
- it('returns stream for all events', function (done) {
407
- eventstore = new EventStore({
408
- storageDirectory
409
- });
410
-
411
- for (let i=1; i<=20; i++) {
412
- eventstore.commit('foo-bar', [{key: i}]);
413
- }
414
-
415
- eventstore.on('ready', () => {
416
- const stream = eventstore.getAllEvents();
417
- expect(stream.events.length).to.be(20);
418
- done();
419
- });
420
- });
421
-
422
- });
423
-
424
- describe('fromStreams', function() {
425
-
426
- it('throws when not specifying a join stream name', function() {
427
- eventstore = new EventStore({
428
- storageDirectory
429
- });
430
-
431
- expect(() => eventstore.fromStreams()).to.throwError();
432
- });
433
-
434
- it('throws when not specifying an array of stream names to join', function() {
435
- eventstore = new EventStore({
436
- storageDirectory
437
- });
438
-
439
- expect(() => eventstore.fromStreams('join-foo-bar')).to.throwError();
440
- });
441
-
442
- it('throws when specifying a non-existing stream to join', function() {
443
- eventstore = new EventStore({
444
- storageDirectory
445
- });
446
-
447
- expect(() => eventstore.fromStreams('join-foo-bar', ['foo-bar', 'baz'])).to.throwError(/does not exist/);
448
- });
449
-
450
- it('iterates events from multiple streams in correct order', function(done) {
451
- eventstore = new EventStore({
452
- storageDirectory
453
- });
454
-
455
- eventstore.commit('foo', { key: 1 }, () => {
456
- eventstore.commit('bar', { key: 2}, () => {
457
- eventstore.commit('foo', { key: 3 }, () => {
458
- eventstore.commit('bar', { key: 4}, () => {
459
- let joinStream = eventstore.fromStreams('foobar', ['foo','bar']);
460
- let key = 1;
461
- for (let event of joinStream) {
462
- expect(event.key).to.be(key);
463
- key++;
464
- }
465
- expect(key).to.be(5);
466
- done();
467
- });
468
- });
469
- });
470
- });
471
- });
472
-
473
- it('iterates events from multiple streams in reverse order', function() {
474
- eventstore = new EventStore({
475
- storageDirectory
476
- });
477
-
478
- for (let i=1; i<=20; i++) {
479
- eventstore.commit(i % 2 ? 'foo' : 'bar', [{key: i}]);
480
- }
481
-
482
- let reverseStream = eventstore.fromStreams('foo-bar', ['foo', 'bar'],-1, 0);
483
- let i = 20;
484
- for (let event of reverseStream) {
485
- expect(event).to.eql({ key: i-- });
486
- }
487
- expect(i).to.be(0);
488
- });
489
-
490
- });
491
-
492
- describe('getEventStreamForCategory', function() {
493
-
494
- it('throws when not specifying category without streams', function () {
495
- eventstore = new EventStore({
496
- storageDirectory
497
- });
498
-
499
- expect(() => eventstore.getEventStreamForCategory('non-existing-category')).to.throwError();
500
- });
501
-
502
- it('iterates events for all streams with a given category prefix', function () {
503
- eventstore = new EventStore({
504
- storageDirectory
505
- });
506
-
507
- eventstore.commit('bar', [{key: 0}]);
508
- for (let i=1; i<=20; i++) {
509
- eventstore.commit('foo-' + i, [{key: i}]);
510
- }
511
- eventstore.commit('foobar', [{key: 21}]);
512
-
513
- let categoryStream = eventstore.getEventStreamForCategory('foo');
514
- let i = 1;
515
- for (let event of categoryStream) {
516
- expect(event).to.eql({ key: i++ });
517
- }
518
- expect(i).to.be(21);
519
- });
520
-
521
- it('works with a dedicated stream for the category', function () {
522
- eventstore = new EventStore({
523
- storageDirectory
524
- });
525
-
526
- eventstore.createEventStream('foo', e => e.stream.startsWith('foo-'));
527
- for (let i=1; i<=20; i++) {
528
- eventstore.commit('foo-' + i, [{key: i}]);
529
- }
530
-
531
- let categoryStream = eventstore.getEventStreamForCategory('foo');
532
- let i = 1;
533
- for (let event of categoryStream) {
534
- expect(event).to.eql({ key: i++ });
535
- }
536
- expect(i).to.be(21);
537
- });
538
-
539
- });
540
-
541
- describe('createEventStream', function() {
542
-
543
- it('throws in read-only mode', function () {
544
- eventstore = new EventStore({
545
- storageDirectory
546
- });
547
-
548
- let readstore = new EventStore({
549
- storageDirectory,
550
- readOnly: true
551
- });
552
- expect(() => readstore.createEventStream('foo-bar', () => true)).to.throwError();
553
- readstore.close();
554
- });
555
-
556
- it('throws when trying to re-create stream', function () {
557
- eventstore = new EventStore({
558
- storageDirectory
559
- });
560
- eventstore.createEventStream('foo-bar', () => true)
561
-
562
- expect(() => eventstore.createEventStream('foo-bar', () => true)).to.throwError();
563
- });
564
- });
565
-
566
- describe('deleteEventStream', function() {
567
-
568
- it('throws in read-only mode', function(done) {
569
- eventstore = new EventStore({
570
- storageDirectory
571
- });
572
- eventstore.createEventStream('foo-bar', () => true);
573
-
574
- let readstore = new EventStore({
575
- storageDirectory,
576
- readOnly: true
577
- });
578
- readstore.on('ready', () => {
579
- expect(() => readstore.deleteEventStream('foo-bar')).to.throwError();
580
- readstore.close();
581
- done();
582
- });
583
- });
584
-
585
- it('removes the stream persistently', function(done) {
586
- eventstore = new EventStore({
587
- storageDirectory
588
- });
589
-
590
- eventstore.commit('foo-bar', [{ foo: 'bar' }], () => {
591
- expect(fs.existsSync(storageDirectory + '/streams/eventstore.stream-foo-bar.index')).to.be(true);
592
- eventstore.deleteEventStream('foo-bar');
593
- expect(eventstore.getEventStream('foo-bar')).to.be(false);
594
- expect(fs.existsSync(storageDirectory + '/streams/eventstore.stream-foo-bar.index')).to.be(false);
595
- done();
596
- });
597
- });
598
-
599
- it('is noop for non-existing stream', function(done) {
600
- eventstore = new EventStore({
601
- storageDirectory
602
- });
603
-
604
- eventstore.commit('foo-bar', [{ foo: 'bar' }], () => {
605
- eventstore.deleteEventStream('bar');
606
- expect(eventstore.getEventStream('foo-bar')).to.not.be(false);
607
- done();
608
- });
609
- });
610
-
611
- });
612
-
613
- describe('getConsumer', function() {
614
-
615
- it('returns a consumer for the given stream', function(done) {
616
- eventstore = new EventStore({
617
- storageDirectory
618
- });
619
- eventstore.createEventStream('foo-bar', event => event.payload.foo === 'bar');
620
-
621
- const consumer = eventstore.getConsumer('foo-bar', 'consumer1');
622
- consumer.on('data', event => {
623
- expect(event.id).to.be(2);
624
- done();
625
- });
626
- eventstore.commit('foo', { foo: 'baz', id: 1 });
627
- eventstore.commit('foo', { foo: 'bar', id: 2 });
628
- });
629
-
630
- });
631
-
632
- });