@dxos/echo-pipeline 0.1.41 → 0.1.45-next.45667d4
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/dist/lib/browser/{chunk-4YAT2XJW.mjs → chunk-2NGLSBL4.mjs} +1128 -1017
- package/dist/lib/browser/chunk-2NGLSBL4.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -3
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +72 -48
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +1048 -938
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +735 -540
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/types/src/dbhost/database-host.d.ts +2 -4
- package/dist/types/src/dbhost/database-host.d.ts.map +1 -1
- package/dist/types/src/dbhost/snapshot-store.d.ts +2 -0
- package/dist/types/src/dbhost/snapshot-store.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts +3 -2
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +27 -11
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/pipeline/timeframe-clock.d.ts +1 -0
- package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
- package/dist/types/src/space/auth.d.ts +4 -4
- package/dist/types/src/space/auth.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts +2 -4
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/data-pipeline.d.ts +19 -10
- package/dist/types/src/space/data-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/index.d.ts +0 -1
- package/dist/types/src/space/index.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +14 -14
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +2 -2
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +20 -7
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/dist/types/src/testing/database-test-rig.d.ts.map +1 -1
- package/dist/types/src/testing/test-agent-builder.d.ts +15 -5
- package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
- package/package.json +30 -30
- package/src/common/feeds.ts +2 -2
- package/src/dbhost/data-service-host.ts +10 -10
- package/src/dbhost/data-service.ts +1 -1
- package/src/dbhost/database-host.ts +1 -9
- package/src/dbhost/snapshot-store.test.ts +2 -2
- package/src/dbhost/snapshot-store.ts +16 -1
- package/src/metadata/metadata-store.ts +14 -16
- package/src/pipeline/message-selector.ts +1 -1
- package/src/pipeline/pipeline.test.ts +68 -44
- package/src/pipeline/pipeline.ts +118 -35
- package/src/pipeline/timeframe-clock.ts +8 -2
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +19 -20
- package/src/space/control-pipeline.ts +3 -5
- package/src/space/data-pipeline.ts +131 -69
- package/src/space/index.ts +0 -1
- package/src/space/replication.browser.test.ts +8 -8
- package/src/space/space-manager.browser.test.ts +3 -3
- package/src/space/space-manager.ts +25 -20
- package/src/space/space-protocol.browser.test.ts +1 -1
- package/src/space/space-protocol.test.ts +6 -6
- package/src/space/space-protocol.ts +7 -7
- package/src/space/space.test.ts +137 -48
- package/src/space/space.ts +82 -40
- package/src/testing/database-test-rig.ts +25 -24
- package/src/testing/test-agent-builder.ts +88 -49
- package/src/testing/test-feed-builder.ts +1 -1
- package/src/testing/util.ts +15 -15
- package/src/tests/database-unit.test.ts +4 -4
- package/src/tests/database.test.ts +1 -1
- package/dist/lib/browser/chunk-4YAT2XJW.mjs.map +0 -7
- package/dist/types/src/space/genesis.d.ts +0 -5
- package/dist/types/src/space/genesis.d.ts.map +0 -1
- package/src/space/genesis.ts +0 -69
|
@@ -4,19 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
import assert from 'node:assert';
|
|
6
6
|
|
|
7
|
-
import { scheduleTask, synchronized, trackLeaks } from '@dxos/async';
|
|
7
|
+
import { Event, scheduleTask, synchronized, trackLeaks } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
|
-
import { FeedInfo } from '@dxos/credentials';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { CredentialProcessor, FeedInfo, getCredentialAssertion } from '@dxos/credentials';
|
|
10
|
+
import { getStateMachineFromItem, ItemManager } from '@dxos/echo-db';
|
|
11
|
+
import { FeedWriter } from '@dxos/feed-store';
|
|
12
12
|
import { PublicKey } from '@dxos/keys';
|
|
13
13
|
import { log } from '@dxos/log';
|
|
14
14
|
import { ModelFactory } from '@dxos/model-factory';
|
|
15
|
-
import { DataMessage
|
|
15
|
+
import { DataMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
16
|
+
import { SpaceCache } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
17
|
+
import { ObjectSnapshot } from '@dxos/protocols/proto/dxos/echo/model/document';
|
|
16
18
|
import { SpaceSnapshot } from '@dxos/protocols/proto/dxos/echo/snapshot';
|
|
19
|
+
import { Credential, Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
17
20
|
import { Timeframe } from '@dxos/timeframe';
|
|
18
21
|
|
|
19
|
-
import { createMappedFeedWriter } from '../common';
|
|
20
22
|
import { DatabaseHost, SnapshotManager } from '../dbhost';
|
|
21
23
|
import { MetadataStore } from '../metadata';
|
|
22
24
|
import { Pipeline } from '../pipeline';
|
|
@@ -33,6 +35,11 @@ export type DataPipelineParams = {
|
|
|
33
35
|
spaceKey: PublicKey;
|
|
34
36
|
feedInfoProvider: (feedKey: PublicKey) => FeedInfo | undefined;
|
|
35
37
|
snapshotId: string | undefined;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Called once.
|
|
41
|
+
*/
|
|
42
|
+
onPipelineCreated: (pipeline: Pipeline) => Promise<void>;
|
|
36
43
|
};
|
|
37
44
|
|
|
38
45
|
/**
|
|
@@ -50,11 +57,6 @@ const AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL = 5000;
|
|
|
50
57
|
*/
|
|
51
58
|
const TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
|
|
52
59
|
|
|
53
|
-
/**
|
|
54
|
-
* Flag to disable automatic local snapshots.
|
|
55
|
-
*/
|
|
56
|
-
const DISABLE_SNAPSHOT_CACHE = true;
|
|
57
|
-
|
|
58
60
|
/**
|
|
59
61
|
* Controls data pipeline in the space.
|
|
60
62
|
* Consumes the pipeline and updates the database.
|
|
@@ -63,9 +65,7 @@ const DISABLE_SNAPSHOT_CACHE = true;
|
|
|
63
65
|
@trackLeaks('open', 'close')
|
|
64
66
|
export class DataPipeline {
|
|
65
67
|
private _ctx = new Context();
|
|
66
|
-
private _spaceContext!: PipelineFactory;
|
|
67
68
|
private _pipeline?: Pipeline;
|
|
68
|
-
private _snapshot?: SpaceSnapshot;
|
|
69
69
|
private _targetTimeframe?: Timeframe;
|
|
70
70
|
|
|
71
71
|
private _lastAutomaticSnapshotTimeframe = new Timeframe();
|
|
@@ -76,23 +76,22 @@ export class DataPipeline {
|
|
|
76
76
|
|
|
77
77
|
constructor(private readonly _params: DataPipelineParams) {}
|
|
78
78
|
|
|
79
|
-
public
|
|
80
|
-
public
|
|
79
|
+
public itemManager!: ItemManager;
|
|
80
|
+
public databaseHost?: DatabaseHost;
|
|
81
|
+
|
|
82
|
+
public currentEpoch?: Credential;
|
|
83
|
+
public onNewEpoch = new Event<Credential>();
|
|
81
84
|
|
|
82
85
|
get isOpen() {
|
|
83
86
|
return this._isOpen;
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
get
|
|
87
|
-
return this._pipeline
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
get snapshotTimeframe() {
|
|
91
|
-
return this._snapshot?.timeframe;
|
|
89
|
+
get pipeline() {
|
|
90
|
+
return this._pipeline;
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
return
|
|
93
|
+
get pipelineState() {
|
|
94
|
+
return this._pipeline?.state;
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
setTargetTimeframe(timeframe: Timeframe) {
|
|
@@ -100,34 +99,52 @@ export class DataPipeline {
|
|
|
100
99
|
this._pipeline?.state.setTargetTimeframe(timeframe);
|
|
101
100
|
}
|
|
102
101
|
|
|
102
|
+
createCredentialProcessor(): CredentialProcessor {
|
|
103
|
+
return {
|
|
104
|
+
process: async (credential) => {
|
|
105
|
+
const assertion = getCredentialAssertion(credential);
|
|
106
|
+
if (assertion['@type'] !== 'dxos.halo.credentials.Epoch') {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
log('new epoch', { credential });
|
|
111
|
+
await this._processEpoch(assertion);
|
|
112
|
+
|
|
113
|
+
this.currentEpoch = credential;
|
|
114
|
+
this.onNewEpoch.emit(credential);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
103
119
|
@synchronized
|
|
104
|
-
async open(
|
|
120
|
+
async open() {
|
|
105
121
|
if (this._isOpen) {
|
|
106
122
|
return;
|
|
107
123
|
}
|
|
108
124
|
|
|
109
|
-
this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
125
|
+
this._pipeline = new Pipeline();
|
|
126
|
+
await this._params.onPipelineCreated(this._pipeline);
|
|
127
|
+
await this._pipeline.start();
|
|
128
|
+
await this._pipeline.pause(); // Start paused until we have the first epoch.
|
|
114
129
|
|
|
115
|
-
this._pipeline = await this._spaceContext.openPipeline(this.getStartTimeframe());
|
|
116
130
|
if (this._targetTimeframe) {
|
|
117
131
|
this._pipeline.state.setTargetTimeframe(this._targetTimeframe);
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
// Create database backend.
|
|
121
|
-
const feedWriter
|
|
122
|
-
(data
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
const feedWriter: FeedWriter<DataMessage> = {
|
|
136
|
+
write: (data, options) => {
|
|
137
|
+
assert(this._pipeline, 'Pipeline is not initialized.');
|
|
138
|
+
assert(this.currentEpoch, 'Epoch is not initialized.');
|
|
139
|
+
return this._pipeline.writer.write({ data }, options);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
125
142
|
|
|
126
|
-
this.
|
|
127
|
-
this.
|
|
143
|
+
this.databaseHost = new DatabaseHost(feedWriter);
|
|
144
|
+
this.itemManager = new ItemManager(this._params.modelFactory);
|
|
128
145
|
|
|
129
146
|
// Connect pipeline to the database.
|
|
130
|
-
await this.
|
|
147
|
+
await this.databaseHost.open(this.itemManager, this._params.modelFactory);
|
|
131
148
|
|
|
132
149
|
// Start message processing loop.
|
|
133
150
|
scheduleTask(this._ctx, async () => {
|
|
@@ -150,18 +167,16 @@ export class DataPipeline {
|
|
|
150
167
|
|
|
151
168
|
// NOTE: Make sure the processing is stopped BEFORE we save the snapshot.
|
|
152
169
|
try {
|
|
170
|
+
await this._saveCache();
|
|
153
171
|
if (this._pipeline) {
|
|
154
172
|
await this._saveTargetTimeframe(this._pipeline.state.timeframe);
|
|
155
|
-
if (!DISABLE_SNAPSHOT_CACHE) {
|
|
156
|
-
await this._saveSnapshot(this._pipeline.state.timeframe);
|
|
157
|
-
}
|
|
158
173
|
}
|
|
159
174
|
} catch (err) {
|
|
160
175
|
log.catch(err);
|
|
161
176
|
}
|
|
162
177
|
|
|
163
|
-
await this.
|
|
164
|
-
await this.
|
|
178
|
+
await this.databaseHost?.close();
|
|
179
|
+
await this.itemManager?.destroy();
|
|
165
180
|
await this._params.snapshotManager.close();
|
|
166
181
|
}
|
|
167
182
|
|
|
@@ -169,7 +184,7 @@ export class DataPipeline {
|
|
|
169
184
|
assert(this._pipeline, 'Pipeline is not initialized.');
|
|
170
185
|
for await (const msg of this._pipeline.consume()) {
|
|
171
186
|
const { feedKey, seq, data } = msg;
|
|
172
|
-
log('processing message', {
|
|
187
|
+
log('processing message', { feedKey, seq });
|
|
173
188
|
|
|
174
189
|
try {
|
|
175
190
|
if (data.payload.data) {
|
|
@@ -179,14 +194,14 @@ export class DataPipeline {
|
|
|
179
194
|
continue;
|
|
180
195
|
}
|
|
181
196
|
|
|
182
|
-
await this.
|
|
197
|
+
await this.databaseHost!.echoProcessor({
|
|
183
198
|
batch: data.payload.data.batch,
|
|
184
199
|
meta: {
|
|
185
200
|
feedKey,
|
|
186
201
|
seq,
|
|
187
202
|
timeframe: data.timeframe,
|
|
188
|
-
memberKey: feedInfo.assertion.identityKey
|
|
189
|
-
}
|
|
203
|
+
memberKey: feedInfo.assertion.identityKey,
|
|
204
|
+
},
|
|
190
205
|
});
|
|
191
206
|
|
|
192
207
|
// Timeframe clock is not updated yet
|
|
@@ -198,29 +213,45 @@ export class DataPipeline {
|
|
|
198
213
|
}
|
|
199
214
|
}
|
|
200
215
|
|
|
201
|
-
private _createSnapshot(
|
|
202
|
-
assert(this.
|
|
216
|
+
private _createSnapshot(): SpaceSnapshot {
|
|
217
|
+
assert(this.databaseHost, 'Database backend is not initialized.');
|
|
203
218
|
return {
|
|
204
219
|
spaceKey: this._params.spaceKey.asUint8Array(),
|
|
205
|
-
timeframe,
|
|
206
|
-
database: this.
|
|
220
|
+
timeframe: this._pipeline!.state.timeframe,
|
|
221
|
+
database: this.databaseHost!.createSnapshot(),
|
|
207
222
|
};
|
|
208
223
|
}
|
|
209
224
|
|
|
210
|
-
private async _saveSnapshot(timeframe: Timeframe) {
|
|
211
|
-
const snapshot = await this._createSnapshot(timeframe);
|
|
212
|
-
const snapshotKey = await this._params.snapshotManager.store(snapshot);
|
|
213
|
-
await this._params.metadataStore.setSpaceSnapshot(this._params.spaceKey, snapshotKey);
|
|
214
|
-
return snapshot;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
225
|
private async _saveTargetTimeframe(timeframe: Timeframe) {
|
|
218
226
|
const newTimeframe = Timeframe.merge(this._targetTimeframe ?? new Timeframe(), timeframe);
|
|
219
227
|
await this._params.metadataStore.setSpaceLatestTimeframe(this._params.spaceKey, newTimeframe);
|
|
220
228
|
this._targetTimeframe = newTimeframe;
|
|
221
229
|
}
|
|
222
230
|
|
|
231
|
+
private async _saveCache() {
|
|
232
|
+
const cache: SpaceCache = {};
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
// Add properties to cache.
|
|
236
|
+
const propertiesItem = this.itemManager.items.find(
|
|
237
|
+
(item) =>
|
|
238
|
+
item.modelMeta?.type === 'dxos:model/document' &&
|
|
239
|
+
(getStateMachineFromItem(item)?.snapshot() as ObjectSnapshot).type === 'dxos.sdk.client.Properties',
|
|
240
|
+
);
|
|
241
|
+
if (propertiesItem) {
|
|
242
|
+
cache.properties = getStateMachineFromItem(propertiesItem)?.snapshot() as ObjectSnapshot;
|
|
243
|
+
}
|
|
244
|
+
} catch (err) {
|
|
245
|
+
log.warn('Failed to cache properties', err);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Save cache.
|
|
249
|
+
await this._params.metadataStore.setCache(this._params.spaceKey, cache);
|
|
250
|
+
}
|
|
251
|
+
|
|
223
252
|
private async _noteTargetStateIfNeeded(timeframe: Timeframe) {
|
|
253
|
+
// TODO(dmaretskyi): Replace this with a proper debounce/throttle.
|
|
254
|
+
|
|
224
255
|
if (Date.now() - this._lastTimeframeSaveTime > TIMEFRAME_SAVE_DEBOUNCE_INTERVAL) {
|
|
225
256
|
this._lastTimeframeSaveTime = Date.now();
|
|
226
257
|
|
|
@@ -228,27 +259,58 @@ export class DataPipeline {
|
|
|
228
259
|
}
|
|
229
260
|
|
|
230
261
|
if (
|
|
231
|
-
!DISABLE_SNAPSHOT_CACHE &&
|
|
232
262
|
Date.now() - this._lastSnapshotSaveTime > AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL &&
|
|
233
263
|
timeframe.totalMessages() - this._lastAutomaticSnapshotTimeframe.totalMessages() > MESSAGES_PER_SNAPSHOT
|
|
234
264
|
) {
|
|
235
|
-
this.
|
|
265
|
+
await this._saveCache();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
236
268
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
269
|
+
@synchronized
|
|
270
|
+
private async _processEpoch(epoch: Epoch) {
|
|
271
|
+
assert(this._isOpen); // TODO: In the future we might process epochs before we are open so that data pipeline starts from the last one.
|
|
272
|
+
assert(this._pipeline);
|
|
273
|
+
|
|
274
|
+
if (epoch.snapshotCid) {
|
|
275
|
+
const snapshot = await this._params.snapshotManager.load(epoch.snapshotCid);
|
|
276
|
+
|
|
277
|
+
// TODO(dmaretskyi): Clearing old items + events.
|
|
278
|
+
this.databaseHost!._itemDemuxer.restoreFromSnapshot(snapshot.database);
|
|
240
279
|
}
|
|
280
|
+
|
|
281
|
+
await this._pipeline.pause();
|
|
282
|
+
await this._pipeline.setCursor(epoch.timeframe);
|
|
283
|
+
await this._pipeline.unpause();
|
|
241
284
|
}
|
|
242
285
|
|
|
243
286
|
async waitUntilTimeframe(timeframe: Timeframe) {
|
|
244
287
|
assert(this._pipeline, 'Pipeline is not initialized.');
|
|
245
288
|
await this._pipeline.state.waitUntilTimeframe(timeframe);
|
|
246
289
|
}
|
|
247
|
-
}
|
|
248
290
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
291
|
+
@synchronized
|
|
292
|
+
async createEpoch(): Promise<Epoch> {
|
|
293
|
+
assert(this._pipeline);
|
|
294
|
+
assert(this.currentEpoch);
|
|
295
|
+
|
|
296
|
+
await this._pipeline.pause();
|
|
297
|
+
|
|
298
|
+
const snapshot = await this._createSnapshot();
|
|
299
|
+
const snapshotCid = await this._params.snapshotManager.store(snapshot);
|
|
300
|
+
|
|
301
|
+
const epoch: Epoch = {
|
|
302
|
+
previousId: this.currentEpoch.id,
|
|
303
|
+
timeframe: this._pipeline.state.timeframe,
|
|
304
|
+
number: (this.currentEpoch.subject.assertion as Epoch).number + 1,
|
|
305
|
+
snapshotCid,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
await this._pipeline.unpause();
|
|
309
|
+
|
|
310
|
+
return epoch;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async ensureEpochInitialized() {
|
|
314
|
+
await this.onNewEpoch.waitForCondition(() => !!this.currentEpoch);
|
|
315
|
+
}
|
|
316
|
+
}
|
package/src/space/index.ts
CHANGED
|
@@ -28,9 +28,9 @@ describe('replication', () => {
|
|
|
28
28
|
root: storage.createDirectory('feeds1'),
|
|
29
29
|
signer: keyring1,
|
|
30
30
|
hypercore: {
|
|
31
|
-
valueEncoding
|
|
32
|
-
}
|
|
33
|
-
})
|
|
31
|
+
valueEncoding,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
const keyring2 = new Keyring();
|
|
@@ -39,13 +39,13 @@ describe('replication', () => {
|
|
|
39
39
|
root: storage.createDirectory('feeds2'),
|
|
40
40
|
signer: keyring2,
|
|
41
41
|
hypercore: {
|
|
42
|
-
valueEncoding
|
|
43
|
-
}
|
|
44
|
-
})
|
|
42
|
+
valueEncoding,
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
const feed1 = await feedStore1.openFeed(await keyring1.createKey(), {
|
|
48
|
-
writable: true
|
|
48
|
+
writable: true,
|
|
49
49
|
});
|
|
50
50
|
const feed2 = await feedStore2.openFeed(feed1.key);
|
|
51
51
|
|
|
@@ -54,7 +54,7 @@ describe('replication', () => {
|
|
|
54
54
|
stream1.pipe(stream2).pipe(stream1);
|
|
55
55
|
|
|
56
56
|
await feed1.append({
|
|
57
|
-
timeframe: new Timeframe([[feed1.key, 123]])
|
|
57
|
+
timeframe: new Timeframe([[feed1.key, 123]]),
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
await waitForExpect(() => {
|
|
@@ -17,16 +17,16 @@ describe('space-manager', () => {
|
|
|
17
17
|
test.skip('invitations', async () => {
|
|
18
18
|
const builder = new TestAgentBuilder({
|
|
19
19
|
storage: createStorage(),
|
|
20
|
-
networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL)
|
|
20
|
+
networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL),
|
|
21
21
|
});
|
|
22
22
|
afterTest(async () => await builder.close());
|
|
23
23
|
|
|
24
24
|
const peer1 = await builder.createPeer();
|
|
25
|
-
const spaceManager1 = peer1.
|
|
25
|
+
const spaceManager1 = peer1.spaceManager;
|
|
26
26
|
await spaceManager1.open();
|
|
27
27
|
|
|
28
28
|
const peer2 = await builder.createPeer();
|
|
29
|
-
const spaceManager2 = peer2.
|
|
29
|
+
const spaceManager2 = peer2.spaceManager;
|
|
30
30
|
await spaceManager2.open();
|
|
31
31
|
|
|
32
32
|
afterTest(() => spaceManager1.close());
|
|
@@ -3,39 +3,35 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { synchronized, trackLeaks } from '@dxos/async';
|
|
6
|
-
import { CredentialSigner } from '@dxos/credentials';
|
|
7
6
|
import { failUndefined } from '@dxos/debug';
|
|
8
7
|
import { FeedStore } from '@dxos/feed-store';
|
|
9
8
|
import { PublicKey } from '@dxos/keys';
|
|
10
9
|
import { log } from '@dxos/log';
|
|
10
|
+
import { ModelFactory } from '@dxos/model-factory';
|
|
11
11
|
import { NetworkManager } from '@dxos/network-manager';
|
|
12
12
|
import { trace } from '@dxos/protocols';
|
|
13
13
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
14
14
|
import { SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
15
|
-
import { Credential, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
16
15
|
import { Teleport } from '@dxos/teleport';
|
|
17
16
|
import { ComplexMap } from '@dxos/util';
|
|
18
17
|
|
|
18
|
+
import { SnapshotManager, SnapshotStore } from '../dbhost';
|
|
19
|
+
import { MetadataStore } from '../metadata';
|
|
19
20
|
import { Space } from './space';
|
|
20
21
|
import { SpaceProtocol, SwarmIdentity } from './space-protocol';
|
|
21
22
|
|
|
22
|
-
// TODO(burdon): Factor out to DataSpaceManager
|
|
23
|
-
export interface SigningContext {
|
|
24
|
-
identityKey: PublicKey;
|
|
25
|
-
deviceKey: PublicKey;
|
|
26
|
-
credentialSigner: CredentialSigner; // TODO(burdon): Already has keyring.
|
|
27
|
-
recordCredential: (credential: Credential) => Promise<void>;
|
|
28
|
-
profile?: ProfileDocument;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
export type SpaceManagerParams = {
|
|
32
24
|
feedStore: FeedStore<FeedMessage>;
|
|
33
25
|
networkManager: NetworkManager;
|
|
26
|
+
modelFactory: ModelFactory;
|
|
27
|
+
metadataStore: MetadataStore;
|
|
28
|
+
snapshotStore: SnapshotStore;
|
|
34
29
|
};
|
|
35
30
|
|
|
36
31
|
export type ConstructSpaceParams = {
|
|
37
32
|
metadata: SpaceMetadata;
|
|
38
33
|
swarmIdentity: SwarmIdentity;
|
|
34
|
+
memberKey: PublicKey;
|
|
39
35
|
onNetworkConnection: (session: Teleport) => void;
|
|
40
36
|
};
|
|
41
37
|
|
|
@@ -47,13 +43,18 @@ export class SpaceManager {
|
|
|
47
43
|
private readonly _spaces = new ComplexMap<PublicKey, Space>(PublicKey.hash);
|
|
48
44
|
private readonly _feedStore: FeedStore<FeedMessage>;
|
|
49
45
|
private readonly _networkManager: NetworkManager;
|
|
46
|
+
private readonly _modelFactory: ModelFactory;
|
|
47
|
+
private readonly _metadataStore: MetadataStore;
|
|
48
|
+
private readonly _snapshotStore: SnapshotStore;
|
|
50
49
|
private readonly _instanceId = PublicKey.random().toHex();
|
|
51
|
-
public _traceParent?: string;
|
|
52
50
|
|
|
53
|
-
constructor({ feedStore, networkManager }: SpaceManagerParams) {
|
|
51
|
+
constructor({ feedStore, networkManager, modelFactory, metadataStore, snapshotStore }: SpaceManagerParams) {
|
|
54
52
|
// TODO(burdon): Assert.
|
|
55
53
|
this._feedStore = feedStore;
|
|
56
54
|
this._networkManager = networkManager;
|
|
55
|
+
this._modelFactory = modelFactory;
|
|
56
|
+
this._metadataStore = metadataStore;
|
|
57
|
+
this._snapshotStore = snapshotStore;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// TODO(burdon): Remove.
|
|
@@ -62,17 +63,15 @@ export class SpaceManager {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
@synchronized
|
|
65
|
-
async open() {
|
|
66
|
-
log.trace('dxos.echo.space-manager', trace.begin({ id: this._instanceId, parentId: this._traceParent }));
|
|
67
|
-
}
|
|
66
|
+
async open() {}
|
|
68
67
|
|
|
69
68
|
@synchronized
|
|
70
69
|
async close() {
|
|
71
70
|
await Promise.all([...this._spaces.values()].map((space) => space.close()));
|
|
72
|
-
log.trace('dxos.echo.space-manager', trace.end({ id: this._instanceId }));
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
async constructSpace({ metadata, swarmIdentity, onNetworkConnection }: ConstructSpaceParams) {
|
|
73
|
+
async constructSpace({ metadata, swarmIdentity, onNetworkConnection, memberKey }: ConstructSpaceParams) {
|
|
74
|
+
log.trace('dxos.echo.space-manager.construct-space', trace.begin({ id: this._instanceId }));
|
|
76
75
|
log('constructing space...', { spaceKey: metadata.genesisFeedKey });
|
|
77
76
|
|
|
78
77
|
// The genesis feed will be the same as the control feed if the space was created by the local agent.
|
|
@@ -83,16 +82,22 @@ export class SpaceManager {
|
|
|
83
82
|
topic: spaceKey,
|
|
84
83
|
swarmIdentity,
|
|
85
84
|
networkManager: this._networkManager,
|
|
86
|
-
onSessionAuth: onNetworkConnection
|
|
85
|
+
onSessionAuth: onNetworkConnection,
|
|
87
86
|
});
|
|
87
|
+
const snapshotManager = new SnapshotManager(this._snapshotStore);
|
|
88
88
|
|
|
89
89
|
const space = new Space({
|
|
90
90
|
spaceKey,
|
|
91
91
|
protocol,
|
|
92
92
|
genesisFeed,
|
|
93
|
-
feedProvider: (feedKey) => this._feedStore.openFeed(feedKey)
|
|
93
|
+
feedProvider: (feedKey) => this._feedStore.openFeed(feedKey),
|
|
94
|
+
modelFactory: this._modelFactory,
|
|
95
|
+
metadataStore: this._metadataStore,
|
|
96
|
+
snapshotManager,
|
|
97
|
+
memberKey,
|
|
94
98
|
});
|
|
95
99
|
this._spaces.set(space.key, space);
|
|
100
|
+
log.trace('dxos.echo.space-manager.construct-space', trace.end({ id: this._instanceId }));
|
|
96
101
|
return space;
|
|
97
102
|
}
|
|
98
103
|
}
|
|
@@ -93,7 +93,7 @@ describe('space/space-protocol', () => {
|
|
|
93
93
|
test('replicates a feed through a webrtc connection', async () => {
|
|
94
94
|
const builder = new TestAgentBuilder({
|
|
95
95
|
storage: createStorage(),
|
|
96
|
-
networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL)
|
|
96
|
+
networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL),
|
|
97
97
|
});
|
|
98
98
|
afterTest(async () => await builder.close());
|
|
99
99
|
|
|
@@ -51,12 +51,12 @@ describe('space/space-protocol', () => {
|
|
|
51
51
|
swarmIdentity: {
|
|
52
52
|
peerKey: peerId1,
|
|
53
53
|
credentialProvider: MOCK_AUTH_PROVIDER,
|
|
54
|
-
credentialAuthenticator: async () => false // Reject everyone.
|
|
54
|
+
credentialAuthenticator: async () => false, // Reject everyone.
|
|
55
55
|
},
|
|
56
56
|
networkManager: new NetworkManager({
|
|
57
57
|
signalManager: new MemorySignalManager(signalContext),
|
|
58
|
-
transportFactory: MemoryTransportFactory
|
|
59
|
-
})
|
|
58
|
+
transportFactory: MemoryTransportFactory,
|
|
59
|
+
}),
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
const protocol2 = new SpaceProtocol({
|
|
@@ -64,12 +64,12 @@ describe('space/space-protocol', () => {
|
|
|
64
64
|
swarmIdentity: {
|
|
65
65
|
peerKey: peerId2,
|
|
66
66
|
credentialProvider: MOCK_AUTH_PROVIDER,
|
|
67
|
-
credentialAuthenticator: MOCK_AUTH_VERIFIER
|
|
67
|
+
credentialAuthenticator: MOCK_AUTH_VERIFIER,
|
|
68
68
|
},
|
|
69
69
|
networkManager: new NetworkManager({
|
|
70
70
|
signalManager: new MemorySignalManager(signalContext),
|
|
71
|
-
transportFactory: MemoryTransportFactory
|
|
72
|
-
})
|
|
71
|
+
transportFactory: MemoryTransportFactory,
|
|
72
|
+
}),
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
await protocol1.start();
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
SwarmConnection,
|
|
13
13
|
WireProtocol,
|
|
14
14
|
WireProtocolParams,
|
|
15
|
-
WireProtocolProvider
|
|
15
|
+
WireProtocolProvider,
|
|
16
16
|
} from '@dxos/network-manager';
|
|
17
17
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
18
18
|
import { Teleport } from '@dxos/teleport';
|
|
@@ -101,7 +101,7 @@ export class SpaceProtocol {
|
|
|
101
101
|
const topologyConfig = {
|
|
102
102
|
originateConnections: 4,
|
|
103
103
|
maxPeers: 10,
|
|
104
|
-
sampleSize: 20
|
|
104
|
+
sampleSize: 20,
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
log('starting...');
|
|
@@ -110,7 +110,7 @@ export class SpaceProtocol {
|
|
|
110
110
|
peerId: this._swarmIdentity.peerKey,
|
|
111
111
|
topic: this._topic,
|
|
112
112
|
topology: new MMSTTopology(topologyConfig),
|
|
113
|
-
label: `Protocol swarm: ${this._topic}
|
|
113
|
+
label: `Protocol swarm: ${this._topic}`,
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
log('started');
|
|
@@ -129,7 +129,7 @@ export class SpaceProtocol {
|
|
|
129
129
|
const session = new SpaceProtocolSession({
|
|
130
130
|
wireParams,
|
|
131
131
|
swarmIdentity: this._swarmIdentity,
|
|
132
|
-
onSessionAuth: this._onSessionAuth
|
|
132
|
+
onSessionAuth: this._onSessionAuth,
|
|
133
133
|
});
|
|
134
134
|
this._sessions.set(wireParams.remotePeerId, session);
|
|
135
135
|
|
|
@@ -155,7 +155,7 @@ export type SpaceProtocolSessionParams = {
|
|
|
155
155
|
export enum AuthStatus {
|
|
156
156
|
INITIAL = 'INITIAL',
|
|
157
157
|
SUCCESS = 'SUCCESS',
|
|
158
|
-
FAILURE = 'FAILURE'
|
|
158
|
+
FAILURE = 'FAILURE',
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
// TODO(dmaretskyi): Move to a separate file.
|
|
@@ -210,8 +210,8 @@ export class SpaceProtocolSession implements WireProtocol {
|
|
|
210
210
|
onAuthFailure: () => {
|
|
211
211
|
log.warn('Auth failed');
|
|
212
212
|
this._authStatus = AuthStatus.FAILURE;
|
|
213
|
-
}
|
|
214
|
-
})
|
|
213
|
+
},
|
|
214
|
+
}),
|
|
215
215
|
);
|
|
216
216
|
this._teleport.addExtension('dxos.mesh.teleport.replicator', this.replicator);
|
|
217
217
|
}
|