@dxos/hypercore 0.1.51-main.e7215ae → 0.1.51-next.03aa1a8
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/package.json +11 -11
- package/dist/types/src/multiplexer.d.ts +0 -21
- package/dist/types/src/multiplexer.d.ts.map +0 -1
- package/dist/types/src/multiplexer.test.d.ts +0 -2
- package/dist/types/src/multiplexer.test.d.ts.map +0 -1
- package/src/multiplexer.test.ts +0 -174
- package/src/multiplexer.ts +0 -122
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/hypercore",
|
|
3
|
-
"version": "0.1.51-
|
|
3
|
+
"version": "0.1.51-next.03aa1a8",
|
|
4
4
|
"description": "Hypercore wrapper and utils.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"sodium-native": "^3.2.0",
|
|
21
21
|
"sodium-universal": "^3.0.2",
|
|
22
22
|
"streamx": "^2.12.5",
|
|
23
|
-
"@dxos/async": "0.1.51-
|
|
24
|
-
"@dxos/codec-protobuf": "0.1.51-
|
|
25
|
-
"@dxos/crypto": "0.1.51-
|
|
26
|
-
"@dxos/debug": "0.1.51-
|
|
27
|
-
"@dxos/keys": "0.1.51-
|
|
28
|
-
"@dxos/node-std": "0.1.51-
|
|
29
|
-
"@dxos/random-access-storage": "0.1.51-
|
|
30
|
-
"@dxos/typings": "0.1.51-
|
|
23
|
+
"@dxos/async": "0.1.51-next.03aa1a8",
|
|
24
|
+
"@dxos/codec-protobuf": "0.1.51-next.03aa1a8",
|
|
25
|
+
"@dxos/crypto": "0.1.51-next.03aa1a8",
|
|
26
|
+
"@dxos/debug": "0.1.51-next.03aa1a8",
|
|
27
|
+
"@dxos/keys": "0.1.51-next.03aa1a8",
|
|
28
|
+
"@dxos/node-std": "0.1.51-next.03aa1a8",
|
|
29
|
+
"@dxos/random-access-storage": "0.1.51-next.03aa1a8",
|
|
30
|
+
"@dxos/typings": "0.1.51-next.03aa1a8"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@faker-js/faker": "^7.3.0",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"hypercore-protocol": "^8.0.7",
|
|
41
41
|
"noise-protocol": "3.0.1",
|
|
42
42
|
"random-access-memory": "^4.1.0",
|
|
43
|
-
"@dxos/log": "0.1.51-
|
|
44
|
-
"@dxos/util": "0.1.51-
|
|
43
|
+
"@dxos/log": "0.1.51-next.03aa1a8",
|
|
44
|
+
"@dxos/util": "0.1.51-next.03aa1a8"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Hypercore } from 'hypercore';
|
|
2
|
-
import { PassThrough } from 'streamx';
|
|
3
|
-
/**
|
|
4
|
-
* Hypercore multiplexer.
|
|
5
|
-
*/
|
|
6
|
-
export declare class Multiplexer {
|
|
7
|
-
private readonly _id;
|
|
8
|
-
private readonly _feeds;
|
|
9
|
-
private readonly _streams;
|
|
10
|
-
private _isOpen;
|
|
11
|
-
private _closedTrigger;
|
|
12
|
-
readonly input: PassThrough;
|
|
13
|
-
readonly output: PassThrough;
|
|
14
|
-
constructor(_id: string);
|
|
15
|
-
get isOpen(): boolean;
|
|
16
|
-
get feeds(): Hypercore<any>[];
|
|
17
|
-
addFeed(core: Hypercore, initiator?: boolean): this;
|
|
18
|
-
open(): Promise<void>;
|
|
19
|
-
close(): Promise<void>;
|
|
20
|
-
}
|
|
21
|
-
//# sourceMappingURL=multiplexer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"multiplexer.d.ts","sourceRoot":"","sources":["../../../src/multiplexer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,WAAW,EAAuB,MAAM,SAAS,CAAC;AAO3D;;GAEG;AAEH,qBAAa,WAAW;IAYpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAXtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwD;IAC/E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6D;IAEtF,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAoC;IAE1D,SAAgB,KAAK,cAAqB;IAC1C,SAAgB,MAAM,cAAqB;gBAIxB,GAAG,EAAE,MAAM;IAG9B,IAAI,MAAM,YAET;IAED,IAAI,KAAK,qBAER;IAED,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,UAAQ;IA6BpC,IAAI;IAuBJ,KAAK;CA4BZ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"multiplexer.test.d.ts","sourceRoot":"","sources":["../../../src/multiplexer.test.ts"],"names":[],"mappings":""}
|
package/src/multiplexer.test.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2022 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { faker } from '@faker-js/faker';
|
|
6
|
-
import { expect } from 'chai';
|
|
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
|
-
import { describe, test } from '@dxos/test';
|
|
14
|
-
|
|
15
|
-
import { HypercoreFactory } from './hypercore-factory';
|
|
16
|
-
import { Multiplexer } from './multiplexer';
|
|
17
|
-
import { noop } from './testing';
|
|
18
|
-
import { py } from './util';
|
|
19
|
-
|
|
20
|
-
describe('Multiplexing', () => {
|
|
21
|
-
test('multiplexes feeds', async () => {
|
|
22
|
-
const plex1 = new Multiplexer('A');
|
|
23
|
-
const plex2 = new Multiplexer('B');
|
|
24
|
-
|
|
25
|
-
await plex1.open();
|
|
26
|
-
await plex2.open();
|
|
27
|
-
|
|
28
|
-
const numFeeds = 1;
|
|
29
|
-
const factory = new HypercoreFactory();
|
|
30
|
-
const feeds = await Promise.all(
|
|
31
|
-
Array.from(Array(numFeeds)).map(async () => {
|
|
32
|
-
const { publicKey, secretKey } = createKeyPair();
|
|
33
|
-
const core1 = factory.createFeed(publicKey, { secretKey });
|
|
34
|
-
const core2 = factory.createFeed(publicKey);
|
|
35
|
-
|
|
36
|
-
await py(core1, core1.open)();
|
|
37
|
-
await py(core2, core2.open)();
|
|
38
|
-
|
|
39
|
-
plex1.addFeed(core1, true);
|
|
40
|
-
plex2.addFeed(core2);
|
|
41
|
-
return [core1, core2];
|
|
42
|
-
}),
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
feeds.forEach(([core1, core2]) => {
|
|
46
|
-
expect(core1.stats.peers.length).to.eq(1);
|
|
47
|
-
expect(core2.stats.peers.length).to.eq(1);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const [closed, setClosed] = latch({ count: 2 });
|
|
51
|
-
|
|
52
|
-
// TODO(burdon): Test with additional PassThrough.
|
|
53
|
-
plex1.output.pipe(plex2.input, setClosed);
|
|
54
|
-
plex2.output.pipe(plex1.input, setClosed);
|
|
55
|
-
|
|
56
|
-
const numMessages = 1;
|
|
57
|
-
const written = new Set<string>();
|
|
58
|
-
Array.from(Array(numMessages)).forEach((_, i) => {
|
|
59
|
-
const [core] = faker.helpers.arrayElement(feeds);
|
|
60
|
-
written.add(core.key.toString());
|
|
61
|
-
core.append(`test-${i}: ${faker.lorem.sentence(10)}`, noop); // Long message.
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
const [synced, setSynced] = latch({ count: written.size });
|
|
66
|
-
feeds.forEach(([, core2]) => {
|
|
67
|
-
core2.on('sync', () => {
|
|
68
|
-
log('sync', { feedKey: PublicKey.from(core2.key) });
|
|
69
|
-
setSynced();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
await synced();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
await plex1.close();
|
|
77
|
-
await plex2.close();
|
|
78
|
-
await closed();
|
|
79
|
-
|
|
80
|
-
expect(plex1.isOpen).to.be.false;
|
|
81
|
-
expect(plex2.isOpen).to.be.false;
|
|
82
|
-
|
|
83
|
-
const count = feeds.reduce((total, [, core2]) => total + core2.length, 0);
|
|
84
|
-
expect(count).to.eq(numMessages);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('pipelines', async () => {
|
|
88
|
-
const factory = new HypercoreFactory();
|
|
89
|
-
const { publicKey, secretKey } = createKeyPair();
|
|
90
|
-
const core1 = factory.createFeed(publicKey, { secretKey });
|
|
91
|
-
const core2 = factory.createFeed(publicKey);
|
|
92
|
-
|
|
93
|
-
await py(core1, core1.open)();
|
|
94
|
-
await py(core2, core2.open)();
|
|
95
|
-
|
|
96
|
-
const stream1 = core1.replicate(true, { live: true });
|
|
97
|
-
const stream2 = core2.replicate(false, { live: true });
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
// Pipeline
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
const createWireTap = (label: string) =>
|
|
104
|
-
new Transform({
|
|
105
|
-
transform: (data: Buffer, next: (err: Error | null, data: Buffer) => void) => {
|
|
106
|
-
log(label, { length: data.length });
|
|
107
|
-
next(null, data);
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const [pipelineClosed, onStreamClose] = latch({ count: 2 });
|
|
112
|
-
const createPipeline = (type: number) => {
|
|
113
|
-
switch (type) {
|
|
114
|
-
case 1:
|
|
115
|
-
// prettier-ignore
|
|
116
|
-
return stream1
|
|
117
|
-
.pipe(stream2, onStreamClose)
|
|
118
|
-
.pipe(stream1, onStreamClose);
|
|
119
|
-
|
|
120
|
-
case 2:
|
|
121
|
-
// prettier-ignore
|
|
122
|
-
return stream1
|
|
123
|
-
.pipe(new PassThrough())
|
|
124
|
-
.pipe(stream2, onStreamClose)
|
|
125
|
-
.pipe(new PassThrough())
|
|
126
|
-
.pipe(stream1, onStreamClose);
|
|
127
|
-
|
|
128
|
-
case 3:
|
|
129
|
-
// prettier-ignore
|
|
130
|
-
return stream1
|
|
131
|
-
.pipe(createWireTap('send'))
|
|
132
|
-
.pipe(stream2, onStreamClose)
|
|
133
|
-
.pipe(createWireTap('recv'))
|
|
134
|
-
.pipe(stream1, onStreamClose);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const pipeline = createPipeline(3);
|
|
139
|
-
expect(core1.stats.peers).to.have.lengthOf(1);
|
|
140
|
-
expect(core2.stats.peers).to.have.lengthOf(1);
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
// Sync
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
const numMessages = 10;
|
|
147
|
-
const [synced, setSynced] = latch();
|
|
148
|
-
core2.on('sync', () => {
|
|
149
|
-
log('sync', { send: core1.stats.totals, recv: core1.stats.totals });
|
|
150
|
-
expect(core2.length).to.eq(10);
|
|
151
|
-
setSynced();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
Array.from(Array(numMessages)).map((_, i) => core1.append(`test-${i}: ${faker.lorem.sentence()}`, noop));
|
|
155
|
-
await synced();
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
// Close
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
{
|
|
162
|
-
pipeline.finalize();
|
|
163
|
-
await pipelineClosed();
|
|
164
|
-
|
|
165
|
-
const [feedsClosed, done] = latch({ count: 2 });
|
|
166
|
-
core1.close(done);
|
|
167
|
-
core2.close(done);
|
|
168
|
-
await feedsClosed();
|
|
169
|
-
|
|
170
|
-
expect(core1.closed).to.be.true;
|
|
171
|
-
expect(core2.closed).to.be.true;
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
package/src/multiplexer.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2022 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { Hypercore } from 'hypercore';
|
|
6
|
-
import { ProtocolStream } from 'hypercore-protocol';
|
|
7
|
-
import assert from 'node:assert';
|
|
8
|
-
import { PassThrough, Transform, Writable } from 'streamx';
|
|
9
|
-
|
|
10
|
-
import { latch, Trigger } from '@dxos/async';
|
|
11
|
-
import { PublicKey } from '@dxos/keys';
|
|
12
|
-
import { log } from '@dxos/log';
|
|
13
|
-
import { ComplexMap } from '@dxos/util';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Hypercore multiplexer.
|
|
17
|
-
*/
|
|
18
|
-
// TODO(burdon): Add encoding.
|
|
19
|
-
export class Multiplexer {
|
|
20
|
-
private readonly _feeds = new ComplexMap<PublicKey, Hypercore>(PublicKey.hash);
|
|
21
|
-
private readonly _streams = new ComplexMap<PublicKey, ProtocolStream>(PublicKey.hash);
|
|
22
|
-
|
|
23
|
-
private _isOpen = false;
|
|
24
|
-
private _closedTrigger = new Trigger({ autoReset: true });
|
|
25
|
-
|
|
26
|
-
public readonly input = new PassThrough();
|
|
27
|
-
public readonly output = new PassThrough();
|
|
28
|
-
|
|
29
|
-
// prettier-ignore
|
|
30
|
-
constructor(
|
|
31
|
-
private readonly _id: string
|
|
32
|
-
) {}
|
|
33
|
-
|
|
34
|
-
get isOpen() {
|
|
35
|
-
return this._isOpen;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get feeds() {
|
|
39
|
-
return Array.from(this._feeds.values());
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
addFeed(core: Hypercore, initiator = false) {
|
|
43
|
-
const stream = core.replicate(initiator, { live: true });
|
|
44
|
-
|
|
45
|
-
const feedKey = PublicKey.from(core.key);
|
|
46
|
-
this._feeds.set(feedKey, core);
|
|
47
|
-
this._streams.set(feedKey, stream);
|
|
48
|
-
log('added', { feedKey: PublicKey.from(feedKey) });
|
|
49
|
-
|
|
50
|
-
stream
|
|
51
|
-
.pipe(
|
|
52
|
-
new Transform({
|
|
53
|
-
transform: (data: Buffer, next: (err: Error | null, data: any) => void) => {
|
|
54
|
-
log('send', { id: this._id, feedKey: PublicKey.from(feedKey), payload: data.length });
|
|
55
|
-
next(null, {
|
|
56
|
-
feedKey: core.key,
|
|
57
|
-
payload: data,
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
62
|
-
.pipe(this.output, () => {
|
|
63
|
-
log('pipeline closed', { id: this._id });
|
|
64
|
-
this._isOpen = false;
|
|
65
|
-
this._closedTrigger.wake();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return this;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async open() {
|
|
72
|
-
if (this._isOpen) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
this.input.pipe(
|
|
77
|
-
new Writable({
|
|
78
|
-
write: (data: any, next: () => void) => {
|
|
79
|
-
const { feedKey, payload } = data;
|
|
80
|
-
const feedStream = this._streams.get(PublicKey.from(feedKey));
|
|
81
|
-
assert(feedStream, `invalid stream: ${PublicKey.from(feedKey).truncate()}`);
|
|
82
|
-
log('recv', { id: this._id, feedKey: PublicKey.from(feedKey), payload: (payload as Buffer).length });
|
|
83
|
-
feedStream.write(payload);
|
|
84
|
-
next();
|
|
85
|
-
},
|
|
86
|
-
}),
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
log('opened');
|
|
90
|
-
this._isOpen = true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// TODO(burdon): Test if re-entrant?
|
|
94
|
-
async close() {
|
|
95
|
-
if (!this._isOpen) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
log('closing...');
|
|
100
|
-
const [closed, setClosed] = latch({ count: this._streams.size });
|
|
101
|
-
Array.from(this._streams.values()).forEach((stream) => {
|
|
102
|
-
stream.once('end', () => {
|
|
103
|
-
log('stream closed');
|
|
104
|
-
setClosed();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
stream.finalize();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
await closed();
|
|
111
|
-
|
|
112
|
-
// NOTE: Depending on environment, pipeline may close before or after the stream above.
|
|
113
|
-
if (this._isOpen) {
|
|
114
|
-
await this._closedTrigger.wait();
|
|
115
|
-
assert(!this._isOpen);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
this._feeds.clear();
|
|
119
|
-
this._streams.clear();
|
|
120
|
-
log('closed');
|
|
121
|
-
}
|
|
122
|
-
}
|