@dxos/echo-pipeline 0.1.52 → 0.1.53-main.01cbc6b
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-AXGWKSM3.mjs → chunk-RFRZMQ5T.mjs} +444 -392
- package/dist/lib/browser/chunk-RFRZMQ5T.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +2 -2
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +3 -3
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node/index.cjs +512 -460
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +502 -450
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/types/src/dbhost/data-service-host.d.ts +3 -0
- package/dist/types/src/dbhost/data-service-host.d.ts.map +1 -1
- package/dist/types/src/dbhost/data-service.d.ts +2 -2
- package/dist/types/src/dbhost/data-service.d.ts.map +1 -1
- package/dist/types/src/dbhost/snapshot-store.d.ts +3 -0
- package/dist/types/src/dbhost/snapshot-store.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts +4 -0
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +0 -1
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/space/data-pipeline.d.ts +3 -3
- package/dist/types/src/space/data-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +3 -0
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +0 -1
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/package.json +31 -32
- package/src/common/feeds.ts +3 -3
- package/src/dbhost/data-service-host.ts +17 -4
- package/src/dbhost/data-service.ts +10 -6
- package/src/dbhost/snapshot-store.ts +3 -0
- package/src/metadata/metadata-store.ts +20 -6
- package/src/pipeline/message-selector.ts +3 -5
- package/src/pipeline/pipeline.ts +24 -12
- package/src/space/auth.ts +3 -3
- package/src/space/data-pipeline.ts +36 -29
- package/src/space/space-protocol.browser.test.ts +2 -2
- package/src/space/space-protocol.test.ts +2 -2
- package/src/space/space-protocol.ts +10 -4
- package/src/space/space.test.ts +46 -5
- package/src/space/space.ts +8 -17
- package/src/testing/util.ts +1 -1
- package/src/tests/database.test.ts +1 -1
- package/dist/lib/browser/chunk-AXGWKSM3.mjs.map +0 -7
package/src/pipeline/pipeline.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import invariant from 'tiny-invariant';
|
|
6
6
|
|
|
7
7
|
import { Event, sleep, synchronized, Trigger } from '@dxos/async';
|
|
8
8
|
import { Context, rejectOnDispose } from '@dxos/context';
|
|
@@ -36,6 +36,11 @@ export type WaitUntilReachedTargetParams = {
|
|
|
36
36
|
* External state accessor.
|
|
37
37
|
*/
|
|
38
38
|
export class PipelineState {
|
|
39
|
+
/**
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
_ctx = new Context();
|
|
43
|
+
|
|
39
44
|
// TODO(dmaretskyi): Remove?.
|
|
40
45
|
public readonly timeframeUpdate = this._timeframeClock.update;
|
|
41
46
|
|
|
@@ -51,7 +56,10 @@ export class PipelineState {
|
|
|
51
56
|
*/
|
|
52
57
|
private _targetTimeframe: Timeframe | undefined;
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
/**
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
_reachedTargetPromise: Promise<void> | undefined;
|
|
55
63
|
|
|
56
64
|
// prettier-ignore
|
|
57
65
|
constructor(
|
|
@@ -133,6 +141,7 @@ export class PipelineState {
|
|
|
133
141
|
if (timeout) {
|
|
134
142
|
return Promise.race([
|
|
135
143
|
rejectOnDispose(ctx),
|
|
144
|
+
rejectOnDispose(this._ctx),
|
|
136
145
|
this._reachedTargetPromise.then(() => {
|
|
137
146
|
done = true;
|
|
138
147
|
}),
|
|
@@ -224,7 +233,7 @@ export class Pipeline implements PipelineAccessor {
|
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
get writer(): FeedWriter<FeedMessage.Payload> {
|
|
227
|
-
|
|
236
|
+
invariant(this._writer, 'Writer not set.');
|
|
228
237
|
return this._writer;
|
|
229
238
|
}
|
|
230
239
|
|
|
@@ -247,8 +256,8 @@ export class Pipeline implements PipelineAccessor {
|
|
|
247
256
|
}
|
|
248
257
|
|
|
249
258
|
setWriteFeed(feed: FeedWrapper<FeedMessage>) {
|
|
250
|
-
|
|
251
|
-
|
|
259
|
+
invariant(!this._writer, 'Writer already set.');
|
|
260
|
+
invariant(feed.properties.writable, 'Feed must be writable.');
|
|
252
261
|
|
|
253
262
|
this._writer = createMappedFeedWriter<FeedMessage.Payload, FeedMessage>(
|
|
254
263
|
(payload: FeedMessage.Payload) => ({
|
|
@@ -274,6 +283,9 @@ export class Pipeline implements PipelineAccessor {
|
|
|
274
283
|
this._isStopping = true;
|
|
275
284
|
await this._feedSetIterator?.close();
|
|
276
285
|
await this._processingTrigger.wait(); // Wait for the in-flight message to be processed.
|
|
286
|
+
await this._state._ctx.dispose();
|
|
287
|
+
this._state._ctx = new Context();
|
|
288
|
+
this._state._reachedTargetPromise = undefined;
|
|
277
289
|
log('stopped');
|
|
278
290
|
this._isStarted = false;
|
|
279
291
|
}
|
|
@@ -283,7 +295,7 @@ export class Pipeline implements PipelineAccessor {
|
|
|
283
295
|
*/
|
|
284
296
|
@synchronized
|
|
285
297
|
async setCursor(timeframe: Timeframe) {
|
|
286
|
-
|
|
298
|
+
invariant(!this._isStarted || this._isPaused, 'Invalid state.');
|
|
287
299
|
|
|
288
300
|
this._state._startTimeframe = timeframe;
|
|
289
301
|
this._timeframeClock.setTimeframe(timeframe);
|
|
@@ -305,7 +317,7 @@ export class Pipeline implements PipelineAccessor {
|
|
|
305
317
|
*/
|
|
306
318
|
@synchronized
|
|
307
319
|
async pause() {
|
|
308
|
-
|
|
320
|
+
invariant(this._isStarted, 'Pipeline is not open.');
|
|
309
321
|
if (this._isPaused) {
|
|
310
322
|
return;
|
|
311
323
|
}
|
|
@@ -317,8 +329,8 @@ export class Pipeline implements PipelineAccessor {
|
|
|
317
329
|
|
|
318
330
|
@synchronized
|
|
319
331
|
async unpause() {
|
|
320
|
-
|
|
321
|
-
|
|
332
|
+
invariant(this._isStarted, 'Pipeline is not open.');
|
|
333
|
+
invariant(this._isPaused, 'Pipeline is not paused.');
|
|
322
334
|
|
|
323
335
|
this._pauseTrigger.wake();
|
|
324
336
|
this._isPaused = false;
|
|
@@ -329,10 +341,10 @@ export class Pipeline implements PipelineAccessor {
|
|
|
329
341
|
* Updates the timeframe clock after the message has bee processed.
|
|
330
342
|
*/
|
|
331
343
|
async *consume(): AsyncIterable<FeedMessageBlock> {
|
|
332
|
-
|
|
344
|
+
invariant(!this._isOpen, 'Pipeline is already being consumed.');
|
|
333
345
|
this._isOpen = true;
|
|
334
346
|
|
|
335
|
-
|
|
347
|
+
invariant(this._feedSetIterator, 'Iterator not initialized.');
|
|
336
348
|
let lastFeedSetIterator = this._feedSetIterator;
|
|
337
349
|
let iterable = lastFeedSetIterator[Symbol.asyncIterator]();
|
|
338
350
|
|
|
@@ -341,7 +353,7 @@ export class Pipeline implements PipelineAccessor {
|
|
|
341
353
|
|
|
342
354
|
// Iterator might have been changed while we were waiting for the processing to complete.
|
|
343
355
|
if (lastFeedSetIterator !== this._feedSetIterator) {
|
|
344
|
-
|
|
356
|
+
invariant(this._feedSetIterator, 'Iterator not initialized.');
|
|
345
357
|
lastFeedSetIterator = this._feedSetIterator;
|
|
346
358
|
iterable = lastFeedSetIterator[Symbol.asyncIterator]();
|
|
347
359
|
}
|
package/src/space/auth.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2019 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import invariant from 'tiny-invariant';
|
|
6
6
|
|
|
7
7
|
import { runInContext, scheduleTask } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
@@ -67,9 +67,9 @@ export class AuthExtension extends RpcExtension<Services, Services> {
|
|
|
67
67
|
try {
|
|
68
68
|
const challenge = randomBytes(32);
|
|
69
69
|
const { credential } = await this.rpc.AuthService.authenticate({ challenge });
|
|
70
|
-
|
|
70
|
+
invariant(credential?.length > 0, 'invalid credential');
|
|
71
71
|
const success = await this._authParams.verifier(challenge, credential);
|
|
72
|
-
|
|
72
|
+
invariant(success, 'credential not verified');
|
|
73
73
|
runInContext(this._ctx, () => this._authParams.onAuthSuccess());
|
|
74
74
|
} catch (err) {
|
|
75
75
|
log('auth failed', err);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import invariant from 'tiny-invariant';
|
|
6
6
|
|
|
7
7
|
import { Event, scheduleTask, synchronized, trackLeaks } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
@@ -65,10 +65,10 @@ const TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
|
|
|
65
65
|
* Reacts to new epochs to restart the pipeline.
|
|
66
66
|
*/
|
|
67
67
|
@trackLeaks('open', 'close')
|
|
68
|
-
export class DataPipeline {
|
|
68
|
+
export class DataPipeline implements CredentialProcessor {
|
|
69
69
|
private _ctx = new Context();
|
|
70
|
-
private _pipeline?: Pipeline;
|
|
71
|
-
private _targetTimeframe?: Timeframe;
|
|
70
|
+
private _pipeline?: Pipeline = undefined;
|
|
71
|
+
private _targetTimeframe?: Timeframe = undefined;
|
|
72
72
|
|
|
73
73
|
private _lastAutomaticSnapshotTimeframe = new Timeframe();
|
|
74
74
|
private _isOpen = false;
|
|
@@ -84,17 +84,18 @@ export class DataPipeline {
|
|
|
84
84
|
/**
|
|
85
85
|
* Current epoch. Might be still processing.
|
|
86
86
|
*/
|
|
87
|
-
public currentEpoch?: SpecificCredential<Epoch
|
|
87
|
+
public currentEpoch?: SpecificCredential<Epoch> = undefined;
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Epoch currently applied.
|
|
91
91
|
*/
|
|
92
|
-
public appliedEpoch?: SpecificCredential<Epoch
|
|
92
|
+
public appliedEpoch?: SpecificCredential<Epoch> = undefined;
|
|
93
93
|
|
|
94
94
|
private _lastProcessedEpoch = -1;
|
|
95
|
-
public onNewEpoch = new Event<Credential>();
|
|
96
95
|
private _epochCtx?: Context;
|
|
97
96
|
|
|
97
|
+
public onNewEpoch = new Event<Credential>();
|
|
98
|
+
|
|
98
99
|
get isOpen() {
|
|
99
100
|
return this._isOpen;
|
|
100
101
|
}
|
|
@@ -112,20 +113,16 @@ export class DataPipeline {
|
|
|
112
113
|
this._pipeline?.state.setTargetTimeframe(timeframe);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
116
|
+
async processCredential(credential: Credential) {
|
|
117
|
+
if (!checkCredentialType(credential, 'dxos.halo.credentials.Epoch')) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
},
|
|
128
|
-
};
|
|
121
|
+
this.currentEpoch = credential;
|
|
122
|
+
if (this._isOpen) {
|
|
123
|
+
// process epoch
|
|
124
|
+
await this._processEpochInSeparateTask(credential);
|
|
125
|
+
}
|
|
129
126
|
}
|
|
130
127
|
|
|
131
128
|
@synchronized
|
|
@@ -146,8 +143,8 @@ export class DataPipeline {
|
|
|
146
143
|
// Create database backend.
|
|
147
144
|
const feedWriter: FeedWriter<DataMessage> = {
|
|
148
145
|
write: (data, options) => {
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
invariant(this._pipeline, 'Pipeline is not initialized.');
|
|
147
|
+
invariant(this.currentEpoch, 'Epoch is not initialized.');
|
|
151
148
|
return this._pipeline.writer.write({ data }, options);
|
|
152
149
|
},
|
|
153
150
|
};
|
|
@@ -189,6 +186,15 @@ export class DataPipeline {
|
|
|
189
186
|
|
|
190
187
|
await this.databaseHost?.close();
|
|
191
188
|
await this.itemManager?.destroy();
|
|
189
|
+
|
|
190
|
+
this._ctx = new Context();
|
|
191
|
+
this._pipeline = undefined;
|
|
192
|
+
this._targetTimeframe = undefined;
|
|
193
|
+
this._lastAutomaticSnapshotTimeframe = new Timeframe();
|
|
194
|
+
this.currentEpoch = undefined;
|
|
195
|
+
this.appliedEpoch = undefined;
|
|
196
|
+
this._lastProcessedEpoch = -1;
|
|
197
|
+
this._epochCtx = undefined;
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
private async _consumePipeline() {
|
|
@@ -198,7 +204,7 @@ export class DataPipeline {
|
|
|
198
204
|
await waitForOneEpoch;
|
|
199
205
|
}
|
|
200
206
|
|
|
201
|
-
|
|
207
|
+
invariant(this._pipeline, 'Pipeline is not initialized.');
|
|
202
208
|
for await (const msg of this._pipeline.consume()) {
|
|
203
209
|
const { feedKey, seq, data } = msg;
|
|
204
210
|
log('processing message', { feedKey, seq });
|
|
@@ -237,7 +243,7 @@ export class DataPipeline {
|
|
|
237
243
|
}
|
|
238
244
|
|
|
239
245
|
private _createSnapshot(): SpaceSnapshot {
|
|
240
|
-
|
|
246
|
+
invariant(this.databaseHost, 'Database backend is not initialized.');
|
|
241
247
|
return {
|
|
242
248
|
spaceKey: this._params.spaceKey.asUint8Array(),
|
|
243
249
|
timeframe: this._pipeline!.state.timeframe,
|
|
@@ -259,6 +265,7 @@ export class DataPipeline {
|
|
|
259
265
|
const propertiesItem = this.itemManager.items.find(
|
|
260
266
|
(item) =>
|
|
261
267
|
item.modelMeta?.type === 'dxos:model/document' &&
|
|
268
|
+
// TODO(burdon): Document?
|
|
262
269
|
(getStateMachineFromItem(item)?.snapshot() as ObjectSnapshot).type === 'dxos.sdk.client.Properties',
|
|
263
270
|
);
|
|
264
271
|
if (propertiesItem) {
|
|
@@ -319,8 +326,8 @@ export class DataPipeline {
|
|
|
319
326
|
|
|
320
327
|
@synchronized
|
|
321
328
|
private async _processEpoch(ctx: Context, epoch: Epoch) {
|
|
322
|
-
|
|
323
|
-
|
|
329
|
+
invariant(this._isOpen, 'Space is closed.');
|
|
330
|
+
invariant(this._pipeline);
|
|
324
331
|
this._lastProcessedEpoch = epoch.number;
|
|
325
332
|
|
|
326
333
|
log('Processing epoch', { epoch });
|
|
@@ -339,14 +346,14 @@ export class DataPipeline {
|
|
|
339
346
|
}
|
|
340
347
|
|
|
341
348
|
async waitUntilTimeframe(timeframe: Timeframe) {
|
|
342
|
-
|
|
349
|
+
invariant(this._pipeline, 'Pipeline is not initialized.');
|
|
343
350
|
await this._pipeline.state.waitUntilTimeframe(timeframe);
|
|
344
351
|
}
|
|
345
352
|
|
|
346
353
|
@synchronized
|
|
347
354
|
async createEpoch(): Promise<Epoch> {
|
|
348
|
-
|
|
349
|
-
|
|
355
|
+
invariant(this._pipeline);
|
|
356
|
+
invariant(this.currentEpoch);
|
|
350
357
|
|
|
351
358
|
await this._pipeline.pause();
|
|
352
359
|
|
|
@@ -43,8 +43,8 @@ describe('space/space-protocol', () => {
|
|
|
43
43
|
afterTest(() => protocol2.stop());
|
|
44
44
|
|
|
45
45
|
await waitForExpect(() => {
|
|
46
|
-
expect(presence1.getPeersOnline().
|
|
47
|
-
expect(presence2.getPeersOnline().
|
|
46
|
+
expect(presence1.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer2.identityKey))).toBeTruthy();
|
|
47
|
+
expect(presence2.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer1.identityKey))).toBeTruthy();
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -39,8 +39,8 @@ describe('space/space-protocol', () => {
|
|
|
39
39
|
afterTest(() => protocol2.stop());
|
|
40
40
|
|
|
41
41
|
await waitForExpect(() => {
|
|
42
|
-
expect(presence1.getPeersOnline().
|
|
43
|
-
expect(presence2.getPeersOnline().
|
|
42
|
+
expect(presence1.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer2.identityKey))).toBeTruthy();
|
|
43
|
+
expect(presence2.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer1.identityKey))).toBeTruthy();
|
|
44
44
|
}, 1_000);
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Event } from '@dxos/async';
|
|
6
|
+
import { discoveryKey, subtleCrypto } from '@dxos/crypto';
|
|
6
7
|
import { FeedWrapper } from '@dxos/feed-store';
|
|
7
8
|
import { PublicKey } from '@dxos/keys';
|
|
8
9
|
import { log, logInfo } from '@dxos/log';
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
WireProtocolParams,
|
|
15
16
|
WireProtocolProvider,
|
|
16
17
|
} from '@dxos/network-manager';
|
|
18
|
+
import { ConnectionInfo } from '@dxos/protocols/proto/dxos/devtools/swarm';
|
|
17
19
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
18
20
|
import { Teleport } from '@dxos/teleport';
|
|
19
21
|
import { BlobStore, BlobSync } from '@dxos/teleport-extension-object-sync';
|
|
@@ -59,7 +61,7 @@ export class SpaceProtocol {
|
|
|
59
61
|
public readonly blobSync: BlobSync;
|
|
60
62
|
|
|
61
63
|
@logInfo
|
|
62
|
-
private readonly _topic: PublicKey
|
|
64
|
+
private readonly _topic: Promise<PublicKey>;
|
|
63
65
|
|
|
64
66
|
private readonly _feeds = new Set<FeedWrapper<FeedMessage>>();
|
|
65
67
|
private readonly _sessions = new ComplexMap<PublicKey, SpaceProtocolSession>(PublicKey.hash);
|
|
@@ -86,7 +88,7 @@ export class SpaceProtocol {
|
|
|
86
88
|
this._onAuthFailure = onAuthFailure;
|
|
87
89
|
this.blobSync = new BlobSync({ blobStore });
|
|
88
90
|
|
|
89
|
-
this._topic =
|
|
91
|
+
this._topic = subtleCrypto.digest('SHA-256', topic.asBuffer()).then(discoveryKey).then(PublicKey.from);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
// TODO(burdon): Create abstraction for Space (e.g., add keys and have provider).
|
|
@@ -120,7 +122,7 @@ export class SpaceProtocol {
|
|
|
120
122
|
this._connection = await this._networkManager.joinSwarm({
|
|
121
123
|
protocolProvider: this._createProtocolProvider(credentials),
|
|
122
124
|
peerId: this._swarmIdentity.peerKey,
|
|
123
|
-
topic: this._topic,
|
|
125
|
+
topic: await this._topic,
|
|
124
126
|
topology: new MMSTTopology(topologyConfig),
|
|
125
127
|
label: `Protocol swarm: ${this._topic}`,
|
|
126
128
|
});
|
|
@@ -204,6 +206,10 @@ export class SpaceProtocolSession implements WireProtocol {
|
|
|
204
206
|
return this._authStatus;
|
|
205
207
|
}
|
|
206
208
|
|
|
209
|
+
get stats(): Event<ConnectionInfo.StreamStats[]> {
|
|
210
|
+
return this._teleport.stats;
|
|
211
|
+
}
|
|
212
|
+
|
|
207
213
|
// TODO(dmaretskyi): Allow to pass in extra extensions.
|
|
208
214
|
constructor({ wireParams, swarmIdentity, onSessionAuth, onAuthFailure, blobSync }: SpaceProtocolSessionParams) {
|
|
209
215
|
this._wireParams = wireParams;
|
package/src/space/space.test.ts
CHANGED
|
@@ -22,7 +22,6 @@ describe('space/space', () => {
|
|
|
22
22
|
const space = await agent.createSpace();
|
|
23
23
|
|
|
24
24
|
await space.open();
|
|
25
|
-
|
|
26
25
|
expect(space.isOpen).toBeTruthy();
|
|
27
26
|
afterTest(() => space.close());
|
|
28
27
|
|
|
@@ -130,7 +129,6 @@ describe('space/space', () => {
|
|
|
130
129
|
const space1 = await agent.createSpace();
|
|
131
130
|
|
|
132
131
|
await space1.open();
|
|
133
|
-
|
|
134
132
|
expect(space1.isOpen).toBeTruthy();
|
|
135
133
|
afterTest(() => space1.close());
|
|
136
134
|
|
|
@@ -152,7 +150,6 @@ describe('space/space', () => {
|
|
|
152
150
|
const space2 = await agent.createSpace(agent.identityKey, space1.key, space1.genesisFeedKey, space1.dataFeedKey);
|
|
153
151
|
|
|
154
152
|
await space2.open();
|
|
155
|
-
|
|
156
153
|
await space2.controlPipeline.state!.waitUntilTimeframe(space2.controlPipeline.state!.endTimeframe);
|
|
157
154
|
await space2.initializeDataPipeline();
|
|
158
155
|
await space2.dataPipeline.ensureEpochInitialized();
|
|
@@ -166,6 +163,52 @@ describe('space/space', () => {
|
|
|
166
163
|
await testLocalDatabase(space2.dataPipeline);
|
|
167
164
|
});
|
|
168
165
|
|
|
166
|
+
test('re-open', async () => {
|
|
167
|
+
const builder = new TestAgentBuilder();
|
|
168
|
+
afterTest(async () => await builder.close());
|
|
169
|
+
const agent = await builder.createPeer();
|
|
170
|
+
const space = await agent.createSpace();
|
|
171
|
+
|
|
172
|
+
let objectCount: number;
|
|
173
|
+
{
|
|
174
|
+
await space.open();
|
|
175
|
+
afterTest(() => space.close());
|
|
176
|
+
expect(space.isOpen).toBeTruthy();
|
|
177
|
+
|
|
178
|
+
await agent.spaceGenesis(space);
|
|
179
|
+
|
|
180
|
+
await space.controlPipeline.state!.waitUntilTimeframe(space.controlPipeline.state!.endTimeframe);
|
|
181
|
+
await space.initializeDataPipeline();
|
|
182
|
+
await space.dataPipeline.ensureEpochInitialized();
|
|
183
|
+
|
|
184
|
+
assert(space.dataPipeline.databaseHost);
|
|
185
|
+
await testLocalDatabase(space.dataPipeline);
|
|
186
|
+
|
|
187
|
+
objectCount = space.dataPipeline.itemManager.entities.size;
|
|
188
|
+
|
|
189
|
+
await space.close();
|
|
190
|
+
expect(space.isOpen).toBeFalsy();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Re-open.
|
|
194
|
+
{
|
|
195
|
+
await space.open();
|
|
196
|
+
expect(space.isOpen).toBeTruthy();
|
|
197
|
+
|
|
198
|
+
await space.controlPipeline.state!.waitUntilTimeframe(space.controlPipeline.state!.endTimeframe);
|
|
199
|
+
await space.initializeDataPipeline();
|
|
200
|
+
await space.dataPipeline.ensureEpochInitialized();
|
|
201
|
+
|
|
202
|
+
space.dataPipeline.setTargetTimeframe(space.dataPipeline.pipelineState!.endTimeframe);
|
|
203
|
+
await space.dataPipeline.pipelineState!.waitUntilReachedTargetTimeframe();
|
|
204
|
+
|
|
205
|
+
assert(space.dataPipeline.databaseHost);
|
|
206
|
+
expect(space.dataPipeline.itemManager.entities.size).toEqual(objectCount);
|
|
207
|
+
|
|
208
|
+
await testLocalDatabase(space.dataPipeline);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
169
212
|
test('create epoch', async () => {
|
|
170
213
|
const builder = new TestAgentBuilder();
|
|
171
214
|
afterTest(async () => await builder.close());
|
|
@@ -173,7 +216,6 @@ describe('space/space', () => {
|
|
|
173
216
|
const space1 = await agent.createSpace();
|
|
174
217
|
|
|
175
218
|
await space1.open();
|
|
176
|
-
|
|
177
219
|
expect(space1.isOpen).toBeTruthy();
|
|
178
220
|
afterTest(() => space1.close());
|
|
179
221
|
|
|
@@ -214,7 +256,6 @@ describe('space/space', () => {
|
|
|
214
256
|
const space2 = await agent.createSpace(agent.identityKey, space1.key, space1.genesisFeedKey, space1.dataFeedKey);
|
|
215
257
|
|
|
216
258
|
await space2.open();
|
|
217
|
-
|
|
218
259
|
await space2.controlPipeline.state!.waitUntilTimeframe(space2.controlPipeline.state!.endTimeframe);
|
|
219
260
|
await space2.initializeDataPipeline();
|
|
220
261
|
await space2.dataPipeline.ensureEpochInitialized();
|
package/src/space/space.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import invariant from 'tiny-invariant';
|
|
6
6
|
|
|
7
7
|
import { Event, synchronized, trackLeaks, Lock } from '@dxos/async';
|
|
8
|
-
import {
|
|
8
|
+
import { FeedInfo } from '@dxos/credentials';
|
|
9
9
|
import { FeedOptions, FeedWrapper } from '@dxos/feed-store';
|
|
10
10
|
import { PublicKey } from '@dxos/keys';
|
|
11
11
|
import { log, logInfo } from '@dxos/log';
|
|
@@ -63,15 +63,12 @@ export class Space {
|
|
|
63
63
|
private readonly _dataPipeline: DataPipeline;
|
|
64
64
|
private readonly _snapshotManager: SnapshotManager;
|
|
65
65
|
|
|
66
|
-
// Processes epoch credentials.
|
|
67
|
-
private _dataPipelineCredentialConsumer?: CredentialConsumer<any> = undefined;
|
|
68
|
-
|
|
69
66
|
private _isOpen = false;
|
|
70
67
|
private _controlFeed?: FeedWrapper<FeedMessage>;
|
|
71
68
|
private _dataFeed?: FeedWrapper<FeedMessage>;
|
|
72
69
|
|
|
73
70
|
constructor(params: SpaceParams) {
|
|
74
|
-
|
|
71
|
+
invariant(params.spaceKey && params.feedProvider);
|
|
75
72
|
this._key = params.spaceKey;
|
|
76
73
|
this._genesisFeedKey = params.genesisFeed.key;
|
|
77
74
|
this._feedProvider = params.feedProvider;
|
|
@@ -181,14 +178,14 @@ export class Space {
|
|
|
181
178
|
}
|
|
182
179
|
|
|
183
180
|
setControlFeed(feed: FeedWrapper<FeedMessage>) {
|
|
184
|
-
|
|
181
|
+
invariant(!this._controlFeed, 'Control feed already set.');
|
|
185
182
|
this._controlFeed = feed;
|
|
186
183
|
this._controlPipeline.setWriteFeed(feed);
|
|
187
184
|
return this;
|
|
188
185
|
}
|
|
189
186
|
|
|
190
187
|
setDataFeed(feed: FeedWrapper<FeedMessage>) {
|
|
191
|
-
|
|
188
|
+
invariant(!this._dataFeed, 'Data feed already set.');
|
|
192
189
|
this._dataFeed = feed;
|
|
193
190
|
this._dataPipeline.pipeline?.setWriteFeed(feed);
|
|
194
191
|
return this;
|
|
@@ -217,12 +214,7 @@ export class Space {
|
|
|
217
214
|
// Order is important.
|
|
218
215
|
await this._controlPipeline.start();
|
|
219
216
|
await this.protocol.start();
|
|
220
|
-
|
|
221
|
-
this._dataPipelineCredentialConsumer = this._controlPipeline.spaceState.registerProcessor(
|
|
222
|
-
this._dataPipeline.createCredentialProcessor(),
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
await this._dataPipelineCredentialConsumer.open();
|
|
217
|
+
await this._controlPipeline.spaceState.addCredentialProcessor(this._dataPipeline);
|
|
226
218
|
|
|
227
219
|
this._isOpen = true;
|
|
228
220
|
log('opened');
|
|
@@ -234,8 +226,7 @@ export class Space {
|
|
|
234
226
|
if (!this._isOpen) {
|
|
235
227
|
return;
|
|
236
228
|
}
|
|
237
|
-
await this.
|
|
238
|
-
this._dataPipelineCredentialConsumer = undefined;
|
|
229
|
+
await this._controlPipeline.spaceState.removeCredentialProcessor(this._dataPipeline);
|
|
239
230
|
|
|
240
231
|
await this._dataPipeline.close();
|
|
241
232
|
|
|
@@ -250,7 +241,7 @@ export class Space {
|
|
|
250
241
|
@synchronized
|
|
251
242
|
async initializeDataPipeline() {
|
|
252
243
|
log('initializeDataPipeline');
|
|
253
|
-
|
|
244
|
+
invariant(this._isOpen, 'Space must be open to initialize data pipeline.');
|
|
254
245
|
await this._dataPipeline.open();
|
|
255
246
|
}
|
|
256
247
|
}
|
package/src/testing/util.ts
CHANGED
|
@@ -45,7 +45,7 @@ export const createRemoteDatabaseFromDataServiceHost = async (
|
|
|
45
45
|
const dataService = new DataServiceImpl(dataServiceSubscriptions);
|
|
46
46
|
|
|
47
47
|
const spaceKey = PublicKey.random();
|
|
48
|
-
dataServiceSubscriptions.registerSpace(spaceKey, dataServiceHost);
|
|
48
|
+
await dataServiceSubscriptions.registerSpace(spaceKey, dataServiceHost);
|
|
49
49
|
|
|
50
50
|
const itemManager = new ItemManager(modelFactory);
|
|
51
51
|
const backend = new DatabaseProxy(dataService, itemManager, spaceKey);
|
|
@@ -42,7 +42,7 @@ const createDatabaseWithFeeds = async () => {
|
|
|
42
42
|
const dataService = new DataServiceImpl(dataServiceSubscriptions);
|
|
43
43
|
|
|
44
44
|
const spaceKey = PublicKey.random();
|
|
45
|
-
dataServiceSubscriptions.registerSpace(spaceKey, host.createDataServiceHost());
|
|
45
|
+
await dataServiceSubscriptions.registerSpace(spaceKey, host.createDataServiceHost());
|
|
46
46
|
|
|
47
47
|
const proxy = new DatabaseProxy(dataService, new ItemManager(modelFactory), spaceKey);
|
|
48
48
|
await proxy.open(new ModelFactory().registerModel(DocumentModel));
|