@dxos/hypercore 0.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -0
  3. package/dist/src/crypto.d.ts +13 -0
  4. package/dist/src/crypto.d.ts.map +1 -0
  5. package/dist/src/crypto.js +45 -0
  6. package/dist/src/crypto.js.map +1 -0
  7. package/dist/src/defaults.d.ts +18 -0
  8. package/dist/src/defaults.d.ts.map +1 -0
  9. package/dist/src/defaults.js +44 -0
  10. package/dist/src/defaults.js.map +1 -0
  11. package/dist/src/hypercore-factory.d.ts +23 -0
  12. package/dist/src/hypercore-factory.d.ts.map +1 -0
  13. package/dist/src/hypercore-factory.js +44 -0
  14. package/dist/src/hypercore-factory.js.map +1 -0
  15. package/dist/src/hypercore-factory.test.d.ts +2 -0
  16. package/dist/src/hypercore-factory.test.d.ts.map +1 -0
  17. package/dist/src/hypercore-factory.test.js +46 -0
  18. package/dist/src/hypercore-factory.test.js.map +1 -0
  19. package/dist/src/hypercore-protocol.test.d.ts +2 -0
  20. package/dist/src/hypercore-protocol.test.d.ts.map +1 -0
  21. package/dist/src/hypercore-protocol.test.js +191 -0
  22. package/dist/src/hypercore-protocol.test.js.map +1 -0
  23. package/dist/src/hypercore.test.d.ts +2 -0
  24. package/dist/src/hypercore.test.d.ts.map +1 -0
  25. package/dist/src/hypercore.test.js +79 -0
  26. package/dist/src/hypercore.test.js.map +1 -0
  27. package/dist/src/index.d.ts +5 -0
  28. package/dist/src/index.d.ts.map +1 -0
  29. package/dist/src/index.js +24 -0
  30. package/dist/src/index.js.map +1 -0
  31. package/dist/src/iterator.d.ts +26 -0
  32. package/dist/src/iterator.d.ts.map +1 -0
  33. package/dist/src/iterator.js +37 -0
  34. package/dist/src/iterator.js.map +1 -0
  35. package/dist/src/iterator.test.d.ts +2 -0
  36. package/dist/src/iterator.test.d.ts.map +1 -0
  37. package/dist/src/iterator.test.js +61 -0
  38. package/dist/src/iterator.test.js.map +1 -0
  39. package/dist/src/multiplexer.d.ts +21 -0
  40. package/dist/src/multiplexer.d.ts.map +1 -0
  41. package/dist/src/multiplexer.js +103 -0
  42. package/dist/src/multiplexer.js.map +1 -0
  43. package/dist/src/multiplexer.test.d.ts +2 -0
  44. package/dist/src/multiplexer.test.d.ts.map +1 -0
  45. package/dist/src/multiplexer.test.js +142 -0
  46. package/dist/src/multiplexer.test.js.map +1 -0
  47. package/dist/src/replicate.test.d.ts +2 -0
  48. package/dist/src/replicate.test.d.ts.map +1 -0
  49. package/dist/src/replicate.test.js +197 -0
  50. package/dist/src/replicate.test.js.map +1 -0
  51. package/dist/src/streams.test.d.ts +2 -0
  52. package/dist/src/streams.test.d.ts.map +1 -0
  53. package/dist/src/streams.test.js +65 -0
  54. package/dist/src/streams.test.js.map +1 -0
  55. package/dist/src/testing.d.ts +11 -0
  56. package/dist/src/testing.d.ts.map +1 -0
  57. package/dist/src/testing.js +36 -0
  58. package/dist/src/testing.js.map +1 -0
  59. package/package.json +50 -0
  60. package/src/crypto.ts +46 -0
  61. package/src/defaults.ts +46 -0
  62. package/src/hypercore-factory.test.ts +53 -0
  63. package/src/hypercore-factory.ts +44 -0
  64. package/src/hypercore-protocol.test.ts +236 -0
  65. package/src/hypercore.test.ts +98 -0
  66. package/src/index.ts +8 -0
  67. package/src/iterator.test.ts +66 -0
  68. package/src/iterator.ts +35 -0
  69. package/src/multiplexer.test.ts +172 -0
  70. package/src/multiplexer.ts +122 -0
  71. package/src/replicate.test.ts +241 -0
  72. package/src/streams.test.ts +75 -0
  73. package/src/testing.ts +40 -0
@@ -0,0 +1,236 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+ import faker from 'faker';
7
+ import ProtocolStream from 'hypercore-protocol';
8
+
9
+ import { latch } from '@dxos/async';
10
+ import { createKeyPair } from '@dxos/crypto';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { log } from '@dxos/log';
13
+
14
+ import { HypercoreFactory } from './hypercore-factory';
15
+
16
+ // TODO(burdon): Test encoding.
17
+ // TODO(burdon): Simulate multiple peers and broadcast.
18
+
19
+ describe('ProtocolStream', function () {
20
+ it('protocol stream handshake completes', async function () {
21
+ const [handshake, setHandshake] = latch({ count: 2 });
22
+
23
+ const stream1 = new ProtocolStream(true, {
24
+ onhandshake: () => {
25
+ log('onhandshake', { key: PublicKey.from(stream1.publicKey) });
26
+ setHandshake();
27
+ }
28
+ });
29
+
30
+ const stream2 = new ProtocolStream(false, {
31
+ onhandshake: () => {
32
+ log('onhandshake', { key: PublicKey.from(stream2.publicKey) });
33
+ setHandshake();
34
+ }
35
+ });
36
+
37
+ const [closed, setClosed] = latch({ count: 2 });
38
+ const pipeline = stream1.pipe(stream2, setClosed).pipe(stream1, setClosed);
39
+
40
+ await handshake();
41
+ pipeline.destroy();
42
+ await closed();
43
+ });
44
+
45
+ it('protocol stream handshake completes with feeds', async function () {
46
+ //
47
+ // Pipeline and handshake.
48
+ //
49
+
50
+ const [handshake, setHandshake] = latch({ count: 2 });
51
+
52
+ const stream1 = new ProtocolStream(true, {
53
+ onhandshake: () => {
54
+ log('onhandshake', { key: PublicKey.from(stream1.publicKey) });
55
+ setHandshake();
56
+ }
57
+ });
58
+
59
+ const stream2 = new ProtocolStream(false, {
60
+ onhandshake: () => {
61
+ log('onhandshake', { key: PublicKey.from(stream2.publicKey) });
62
+ setHandshake();
63
+ }
64
+ });
65
+
66
+ const [streamClosed, setStreamClosed] = latch({ count: 2 });
67
+ const pipeline = stream1.pipe(stream2, setStreamClosed).pipe(stream1, setStreamClosed);
68
+
69
+ await handshake();
70
+
71
+ //
72
+ // Hypercore replication.
73
+ // https://github.com/hypercore-protocol/hypercore-protocol#const-channel--streamopenkey-handlers
74
+ //
75
+
76
+ const factory = new HypercoreFactory<string>();
77
+ const { publicKey, secretKey } = createKeyPair();
78
+ const core1 = factory.createFeed(publicKey, { secretKey });
79
+ const core2 = factory.createFeed(publicKey);
80
+
81
+ const [feedsClosed, setFeedClosed] = latch({ count: 2 });
82
+ core1.once('close', setFeedClosed);
83
+ core2.once('close', setFeedClosed);
84
+
85
+ core1.replicate(true, { stream: stream1, live: true });
86
+ core2.replicate(false, { stream: stream2, live: true });
87
+
88
+ //
89
+ // Extensions.
90
+ //
91
+
92
+ const [received, incMessage] = latch({ count: 2 });
93
+ const extension1 = stream1.registerExtension('stream-extension', {
94
+ onmessage: (msg: any) => {
95
+ log('onmessage', {
96
+ key: PublicKey.from(stream1.publicKey),
97
+ msg: msg.toString()
98
+ });
99
+ incMessage();
100
+ }
101
+ });
102
+
103
+ const extension2 = stream2.registerExtension('stream-extension', {
104
+ onmessage: (msg: any) => {
105
+ log('onmessage', {
106
+ key: PublicKey.from(stream2.publicKey),
107
+ msg: msg.toString()
108
+ });
109
+ incMessage();
110
+ }
111
+ });
112
+
113
+ extension1.send(Buffer.from('message-1'));
114
+ extension2.send(Buffer.from('message-2'));
115
+ await received();
116
+
117
+ //
118
+ // Replication.
119
+ //
120
+
121
+ const [synced, setSynced] = latch({ count: 1 });
122
+
123
+ {
124
+ const numBlocks = 10;
125
+ Array.from(Array(numBlocks)).forEach((_, i) => {
126
+ core1.append(`block-${i}`, () => {});
127
+ });
128
+
129
+ core2.on('sync', () => {
130
+ log('sync', {
131
+ core: { key: PublicKey.from(core2.key), length: core2.length }
132
+ });
133
+ expect(core2.length).to.eq(numBlocks);
134
+ setSynced();
135
+ });
136
+ }
137
+
138
+ await synced();
139
+
140
+ //
141
+ // Close.
142
+ //
143
+
144
+ // Gracefully shutdown.
145
+ pipeline.finalize();
146
+ await streamClosed();
147
+
148
+ // TODO(burdon): Destroy and finalize don't seem to close the feeds.
149
+ expect(core1.closed).to.be.false;
150
+ expect(core2.closed).to.be.false;
151
+
152
+ core1.close(setFeedClosed);
153
+ core2.close(setFeedClosed);
154
+ await feedsClosed();
155
+
156
+ expect(core1.closed).to.be.true;
157
+ expect(core2.closed).to.be.true;
158
+ });
159
+
160
+ it('multi-feed multiplexing', async function () {
161
+ //
162
+ // Handshake.
163
+ //
164
+
165
+ const [handshake, setHandshake] = latch({ count: 2 });
166
+ const stream1 = new ProtocolStream(true, { onhandshake: setHandshake });
167
+ const stream2 = new ProtocolStream(false, { onhandshake: setHandshake });
168
+
169
+ const [streamClosed, setStreamClosed] = latch({ count: 2 });
170
+ const pipeline = stream1.pipe(stream2, setStreamClosed).pipe(stream1, setStreamClosed);
171
+ await handshake();
172
+
173
+ //
174
+ // Feeds.
175
+ //
176
+
177
+ const factory = new HypercoreFactory<string>();
178
+
179
+ const numFeeds = 5;
180
+ const [feedsClosed, setFeedClosed] = latch({ count: 2 * numFeeds });
181
+
182
+ // Create set of feed pairs.
183
+ const feeds = Array.from(Array(numFeeds)).map(() => {
184
+ const { publicKey, secretKey } = createKeyPair();
185
+ const core1 = factory.createFeed(publicKey, { secretKey });
186
+ const core2 = factory.createFeed(publicKey);
187
+
188
+ core1.once('close', setFeedClosed);
189
+ core2.once('close', setFeedClosed);
190
+
191
+ core1.replicate(true, { stream: stream1, live: true });
192
+ core2.replicate(false, { stream: stream2, live: true });
193
+
194
+ return [core1, core2];
195
+ });
196
+
197
+ //
198
+ // Replicate.
199
+ //
200
+
201
+ let total = 0;
202
+ const numBlocks = 100;
203
+ const [synced, setSynced] = latch({ count: numFeeds });
204
+ feeds.forEach(([, core2]) => {
205
+ core2.on('sync', () => {
206
+ core2.audit((_, data) => {
207
+ total += data.valid;
208
+ log('synced', { data });
209
+ setSynced();
210
+ });
211
+ });
212
+ });
213
+
214
+ Array.from(Array(numBlocks)).forEach((_, i) => {
215
+ const [core1] = faker.random.arrayElement(feeds);
216
+ core1.append(`block-${i}`, () => {});
217
+ });
218
+
219
+ await synced();
220
+ expect(total).to.eq(numBlocks);
221
+
222
+ //
223
+ // Close.
224
+ //
225
+
226
+ pipeline.finalize();
227
+ await streamClosed();
228
+
229
+ feeds.forEach(([core1, core2]) => {
230
+ core1.close();
231
+ core2.close();
232
+ });
233
+
234
+ await feedsClosed();
235
+ });
236
+ });
@@ -0,0 +1,98 @@
1
+ //
2
+ // Copyright 2019 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+ import { AbstractValueEncoding } from 'hypercore';
7
+ import util from 'node:util';
8
+
9
+ import { Codec } from '@dxos/codec-protobuf';
10
+ import { createKeyPair } from '@dxos/crypto';
11
+
12
+ import { createCodecEncoding } from './crypto';
13
+ import { HypercoreFactory } from './hypercore-factory';
14
+
15
+ type TestItem = {
16
+ key: string;
17
+ value: string;
18
+ };
19
+
20
+ const codec: Codec<TestItem> = {
21
+ encode: (obj: TestItem) => Buffer.from(JSON.stringify(obj)),
22
+ decode: (buffer: Uint8Array) => JSON.parse(buffer.toString())
23
+ };
24
+
25
+ const valueEncoding: AbstractValueEncoding<TestItem> = createCodecEncoding(codec);
26
+
27
+ describe('Hypercore', function () {
28
+ it('create, append, and close a feed', async function () {
29
+ const factory = new HypercoreFactory<string>();
30
+ const { publicKey, secretKey } = createKeyPair();
31
+ const core = factory.createFeed(publicKey, { secretKey });
32
+
33
+ {
34
+ // Check open is idempotent.
35
+ const open = util.promisify(core.open.bind(core));
36
+ await open();
37
+ await open();
38
+ }
39
+
40
+ {
41
+ // Append block.
42
+ expect(core.length).to.eq(0);
43
+ const append = util.promisify(core.append.bind(core));
44
+ const seq = await append('test');
45
+ expect(core.length).to.eq(1);
46
+ expect(seq).to.eq(0);
47
+ }
48
+
49
+ {
50
+ // Get block.
51
+ const get = util.promisify(core.get.bind(core));
52
+ const block: any = await get(0);
53
+ expect(block.toString()).to.eq('test');
54
+ }
55
+
56
+ {
57
+ // Check open is idempotent.
58
+ const close = util.promisify(core.close.bind(core));
59
+ await close();
60
+ await close();
61
+ }
62
+ });
63
+
64
+ it('encoding with typed hypercore', async function () {
65
+ const factory = new HypercoreFactory<TestItem>();
66
+ const { publicKey, secretKey } = createKeyPair();
67
+ const core = factory.createFeed(publicKey, { secretKey, valueEncoding });
68
+
69
+ {
70
+ const append = util.promisify(core.append.bind(core));
71
+
72
+ expect(core.length).to.eq(0);
73
+ const seq = await append({
74
+ key: 'test-1',
75
+ value: 'test'
76
+ });
77
+
78
+ expect(core.length).to.eq(1);
79
+ expect(seq).to.eq(0);
80
+ }
81
+
82
+ {
83
+ const head = util.promisify(core.head.bind(core));
84
+
85
+ const { key, value } = await head();
86
+ expect(key).to.eq('test-1');
87
+ expect(value).to.eq('test');
88
+ }
89
+
90
+ {
91
+ const get = util.promisify(core.get.bind(core));
92
+
93
+ const { key, value } = await get(0);
94
+ expect(key).to.eq('test-1');
95
+ expect(value).to.eq('test');
96
+ }
97
+ });
98
+ });
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2021 DXOS.org
3
+ //
4
+
5
+ export * from './crypto';
6
+ export * from './defaults';
7
+ export * from './hypercore-factory';
8
+ export * from './iterator';
@@ -0,0 +1,66 @@
1
+ //
2
+ // Copyright 2019 DXOS.org
3
+ //
4
+
5
+ import util from 'node:util';
6
+
7
+ import { latch } from '@dxos/async';
8
+ import { createKeyPair } from '@dxos/crypto';
9
+ import { log } from '@dxos/log';
10
+
11
+ import { HypercoreFactory } from './hypercore-factory';
12
+ import { createAsyncIterator, createReadable } from './iterator';
13
+
14
+ describe('AsyncIterator', function () {
15
+ it('iterates a feed until stopped', async function () {
16
+ const factory = new HypercoreFactory();
17
+ const { publicKey, secretKey } = createKeyPair();
18
+ const core = factory.createFeed(publicKey, { secretKey });
19
+
20
+ const numBlocks = 10;
21
+
22
+ // Write.
23
+ {
24
+ const append = util.promisify(core.append.bind(core));
25
+ for (let i = 0; i < numBlocks; i++) {
26
+ await append(`test-${i}`);
27
+ }
28
+ }
29
+
30
+ // Read until stopped.
31
+ {
32
+ // Read forever.
33
+ // NOTE: Read must be opened in live mode.
34
+ // NOTE: Uses wrapper to normalize stream; otherwise done isn't called after destroying the stream.
35
+ const stream = createReadable(core.createReadStream({ live: true }));
36
+
37
+ // Use util to create reliable iterators.
38
+ const iterator = createAsyncIterator(stream);
39
+
40
+ const [streamEnded, end] = latch();
41
+ const [readAll, inc] = latch({ count: numBlocks });
42
+ setTimeout(async () => {
43
+ while (true) {
44
+ try {
45
+ const { value, done } = await iterator.next();
46
+ if (done) {
47
+ end();
48
+ break;
49
+ }
50
+
51
+ log('received', { data: String(value) });
52
+ inc();
53
+ } catch (err) {
54
+ end();
55
+ break;
56
+ }
57
+ }
58
+ });
59
+
60
+ await readAll();
61
+
62
+ stream.destroy();
63
+ await streamEnded();
64
+ }
65
+ });
66
+ });
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { Readable } from 'readable-stream';
6
+ import { Readable as StreamXReadable } from 'streamx';
7
+
8
+ /**
9
+ * Wraps streamx.Readable (hypercore.createReadStream) to a standard Readable stream.
10
+ *
11
+ * The read-stream package is mirror of the streams implementations in Node.js 18.9.0.
12
+ * This function is here to standardize the cast in case there are incompatibilities
13
+ * across different platforms.
14
+ *
15
+ * Hypercore createReadStream returns a `streamx` Readable, which does not close properly on destroy.
16
+ *
17
+ * https://github.com/nodejs/readable-stream
18
+ * https://nodejs.org/api/stream.html#readable-streams
19
+ * https://nodejs.org/dist/v18.9.0/docs/api/stream.html#readablewrapstream
20
+ */
21
+ export const createReadable = (stream: StreamXReadable): Readable => {
22
+ return new Readable({ objectMode: true }).wrap(stream as any);
23
+ };
24
+
25
+ /**
26
+ * Converts streamx.Readable (hypercore.createReadStream) to an async iterator.
27
+ *
28
+ * https://github.com/tc39/proposal-async-iteration
29
+ * https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#async-iteration
30
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
31
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
32
+ */
33
+ export const createAsyncIterator = (stream: Readable): AsyncIterator<any> => {
34
+ return stream[Symbol.asyncIterator]();
35
+ };
@@ -0,0 +1,172 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+ import faker from 'faker';
7
+ import { PassThrough, Transform } from 'streamx';
8
+
9
+ import { latch } from '@dxos/async';
10
+ import { createKeyPair } from '@dxos/crypto';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { log } from '@dxos/log';
13
+
14
+ import { HypercoreFactory } from './hypercore-factory';
15
+ import { Multiplexer } from './multiplexer';
16
+ import { noop, py } from './testing';
17
+
18
+ describe('Multiplexing', function () {
19
+ it.only('multiplexes feeds', async function () {
20
+ const plex1 = new Multiplexer('A');
21
+ const plex2 = new Multiplexer('B');
22
+
23
+ await plex1.open();
24
+ await plex2.open();
25
+
26
+ const numFeeds = 1;
27
+ const factory = new HypercoreFactory();
28
+ const feeds = await Promise.all(
29
+ Array.from(Array(numFeeds)).map(async () => {
30
+ const { publicKey, secretKey } = createKeyPair();
31
+ const core1 = factory.createFeed(publicKey, { secretKey });
32
+ const core2 = factory.createFeed(publicKey);
33
+
34
+ await py(core1, core1.open)();
35
+ await py(core2, core2.open)();
36
+
37
+ plex1.addFeed(core1, true);
38
+ plex2.addFeed(core2);
39
+ return [core1, core2];
40
+ })
41
+ );
42
+
43
+ feeds.forEach(([core1, core2]) => {
44
+ expect(core1.stats.peers.length).to.eq(1);
45
+ expect(core2.stats.peers.length).to.eq(1);
46
+ });
47
+
48
+ const [closed, setClosed] = latch({ count: 2 });
49
+
50
+ // TODO(burdon): Test with additional PassThrough.
51
+ plex1.output.pipe(plex2.input, setClosed);
52
+ plex2.output.pipe(plex1.input, setClosed);
53
+
54
+ const numMessages = 1;
55
+ const written = new Set<string>();
56
+ Array.from(Array(numMessages)).forEach((_, i) => {
57
+ const [core] = faker.random.arrayElement(feeds);
58
+ written.add(core.key.toString());
59
+ core.append(`test-${i}: ${faker.lorem.sentence(10)}`, noop); // Long message.
60
+ });
61
+
62
+ {
63
+ const [synced, setSynced] = latch({ count: written.size });
64
+ feeds.forEach(([, core2]) => {
65
+ core2.on('sync', () => {
66
+ log('sync', { feedKey: PublicKey.from(core2.key) });
67
+ setSynced();
68
+ });
69
+ });
70
+
71
+ await synced();
72
+ }
73
+
74
+ await plex1.close();
75
+ await plex2.close();
76
+ await closed();
77
+
78
+ expect(plex1.isOpen).to.be.false;
79
+ expect(plex2.isOpen).to.be.false;
80
+
81
+ const count = feeds.reduce((total, [, core2]) => total + core2.length, 0);
82
+ expect(count).to.eq(numMessages);
83
+ });
84
+
85
+ it('pipelines', async function () {
86
+ const factory = new HypercoreFactory();
87
+ const { publicKey, secretKey } = createKeyPair();
88
+ const core1 = factory.createFeed(publicKey, { secretKey });
89
+ const core2 = factory.createFeed(publicKey);
90
+
91
+ await py(core1, core1.open)();
92
+ await py(core2, core2.open)();
93
+
94
+ const stream1 = core1.replicate(true, { live: true });
95
+ const stream2 = core2.replicate(false, { live: true });
96
+
97
+ //
98
+ // Pipeline
99
+ //
100
+
101
+ const createWireTap = (label: string) =>
102
+ new Transform({
103
+ transform: (data: Buffer, next: (err: Error | null, data: Buffer) => void) => {
104
+ log(label, { length: data.length });
105
+ next(null, data);
106
+ }
107
+ });
108
+
109
+ const [pipelineClosed, onStreamClose] = latch({ count: 2 });
110
+ const createPipeline = (type: number) => {
111
+ switch (type) {
112
+ case 1:
113
+ // prettier-ignore
114
+ return stream1
115
+ .pipe(stream2, onStreamClose)
116
+ .pipe(stream1, onStreamClose);
117
+
118
+ case 2:
119
+ // prettier-ignore
120
+ return stream1
121
+ .pipe(new PassThrough())
122
+ .pipe(stream2, onStreamClose)
123
+ .pipe(new PassThrough())
124
+ .pipe(stream1, onStreamClose);
125
+
126
+ case 3:
127
+ // prettier-ignore
128
+ return stream1
129
+ .pipe(createWireTap('send'))
130
+ .pipe(stream2, onStreamClose)
131
+ .pipe(createWireTap('recv'))
132
+ .pipe(stream1, onStreamClose);
133
+ }
134
+ };
135
+
136
+ const pipeline = createPipeline(3);
137
+ expect(core1.stats.peers).to.have.lengthOf(1);
138
+ expect(core2.stats.peers).to.have.lengthOf(1);
139
+
140
+ //
141
+ // Sync
142
+ //
143
+
144
+ const numMessages = 10;
145
+ const [synced, setSynced] = latch();
146
+ core2.on('sync', () => {
147
+ log('sync', { send: core1.stats.totals, recv: core1.stats.totals });
148
+ expect(core2.length).to.eq(10);
149
+ setSynced();
150
+ });
151
+
152
+ Array.from(Array(numMessages)).map((_, i) => core1.append(`test-${i}: ${faker.lorem.sentence()}`, noop));
153
+ await synced();
154
+
155
+ //
156
+ // Close
157
+ //
158
+
159
+ {
160
+ pipeline.finalize();
161
+ await pipelineClosed();
162
+
163
+ const [feedsClosed, done] = latch({ count: 2 });
164
+ core1.close(done);
165
+ core2.close(done);
166
+ await feedsClosed();
167
+
168
+ expect(core1.closed).to.be.true;
169
+ expect(core2.closed).to.be.true;
170
+ }
171
+ });
172
+ });