@dxos/client-services 0.4.9-main.76223f7 → 0.4.9-main.7750cb2
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-IVUA3ZSF.mjs → chunk-D6LYSGMU.mjs} +193 -254
- package/dist/lib/browser/chunk-D6LYSGMU.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +4 -10
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-XLFW2D2Q.cjs → chunk-FSN25IYL.cjs} +212 -273
- package/dist/lib/node/chunk-FSN25IYL.cjs.map +7 -0
- package/dist/lib/node/index.cjs +38 -38
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +10 -17
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/services/diagnostics.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +2 -3
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/automerge-space-state.d.ts +3 -0
- package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +2 -3
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +5 -3
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -3
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +2 -6
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/package.json +37 -37
- package/src/packlets/indexing/util.ts +2 -2
- package/src/packlets/invitations/space-invitation-protocol.test.ts +3 -12
- package/src/packlets/invitations/space-invitation-protocol.ts +0 -1
- package/src/packlets/services/automerge-host.test.ts +2 -5
- package/src/packlets/services/diagnostics.ts +2 -41
- package/src/packlets/services/service-context.test.ts +3 -76
- package/src/packlets/services/service-context.ts +5 -15
- package/src/packlets/services/service-host.ts +1 -5
- package/src/packlets/spaces/automerge-space-state.ts +9 -0
- package/src/packlets/spaces/data-space-manager.test.ts +3 -145
- package/src/packlets/spaces/data-space-manager.ts +3 -18
- package/src/packlets/spaces/data-space.ts +11 -43
- package/src/packlets/spaces/spaces-service.test.ts +4 -9
- package/src/packlets/spaces/spaces-service.ts +11 -15
- package/src/packlets/testing/test-builder.ts +2 -22
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-IVUA3ZSF.mjs.map +0 -7
- package/dist/lib/node/chunk-XLFW2D2Q.cjs.map +0 -7
|
@@ -6,11 +6,8 @@ import { type ClientServices } from '@dxos/client-protocol';
|
|
|
6
6
|
import { getFirstStreamValue } from '@dxos/codec-protobuf';
|
|
7
7
|
import { type Config, type ConfigProto } from '@dxos/config';
|
|
8
8
|
import { credentialTypeFilter } from '@dxos/credentials';
|
|
9
|
-
import { DocumentModel, type DocumentModelState } from '@dxos/document-model';
|
|
10
|
-
import { TYPE_PROPERTIES } from '@dxos/echo-db';
|
|
11
9
|
import { invariant } from '@dxos/invariant';
|
|
12
10
|
import { type PublicKey } from '@dxos/keys';
|
|
13
|
-
import { log } from '@dxos/log';
|
|
14
11
|
import { STORAGE_VERSION } from '@dxos/protocols';
|
|
15
12
|
import {
|
|
16
13
|
type Device,
|
|
@@ -180,25 +177,6 @@ export const createDiagnostics = async (
|
|
|
180
177
|
return diagnostics;
|
|
181
178
|
};
|
|
182
179
|
|
|
183
|
-
const getProperties = (space: DataSpace) => {
|
|
184
|
-
let properties: any = {};
|
|
185
|
-
try {
|
|
186
|
-
// Add properties to cache.
|
|
187
|
-
const propertiesItem = space.dataPipeline.itemManager.items.find(
|
|
188
|
-
(item) =>
|
|
189
|
-
item.modelMeta?.type === DocumentModel.meta.type &&
|
|
190
|
-
(item.state as DocumentModelState)?.type?.itemId === TYPE_PROPERTIES,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const state = propertiesItem?.state as DocumentModelState;
|
|
194
|
-
properties = state?.data;
|
|
195
|
-
} catch (err: any) {
|
|
196
|
-
log.warn(err.message);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return properties;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
180
|
const getSpaceStats = async (space: DataSpace): Promise<SpaceStats> => {
|
|
203
181
|
const stats: SpaceStats = {
|
|
204
182
|
key: space.key,
|
|
@@ -226,33 +204,16 @@ const getSpaceStats = async (space: DataSpace): Promise<SpaceStats> => {
|
|
|
226
204
|
|
|
227
205
|
pipeline: {
|
|
228
206
|
// TODO(burdon): Pick properties from credentials if needed.
|
|
229
|
-
|
|
230
|
-
|
|
207
|
+
currentEpoch: space.automergeSpaceState.lastEpoch,
|
|
208
|
+
appliedEpoch: space.automergeSpaceState.lastEpoch,
|
|
231
209
|
|
|
232
210
|
controlFeeds: space.inner.controlPipeline.state.feeds.map((feed) => feed.key),
|
|
233
211
|
currentControlTimeframe: space.inner.controlPipeline.state.timeframe,
|
|
234
212
|
targetControlTimeframe: space.inner.controlPipeline.state.targetTimeframe,
|
|
235
213
|
totalControlTimeframe: space.inner.controlPipeline.state.endTimeframe,
|
|
236
|
-
|
|
237
|
-
// TODO(burdon): Empty?
|
|
238
|
-
dataFeeds: space.dataPipeline.pipelineState?.feeds.map((feed) => feed.key) ?? [],
|
|
239
|
-
startDataTimeframe: space.dataPipeline.pipelineState?.startTimeframe,
|
|
240
|
-
currentDataTimeframe: space.dataPipeline.pipelineState?.timeframe,
|
|
241
|
-
targetDataTimeframe: space.dataPipeline.pipelineState?.targetTimeframe,
|
|
242
|
-
totalDataTimeframe: space.dataPipeline.pipelineState?.endTimeframe,
|
|
243
214
|
},
|
|
244
215
|
};
|
|
245
216
|
|
|
246
|
-
// TODO(burdon): May not be open?
|
|
247
|
-
if (space.dataPipeline.itemManager) {
|
|
248
|
-
Object.assign(stats, {
|
|
249
|
-
properties: getProperties(space),
|
|
250
|
-
db: {
|
|
251
|
-
objects: space.dataPipeline.itemManager.entities.size,
|
|
252
|
-
},
|
|
253
|
-
} as SpaceStats);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
217
|
// TODO(burdon): Factor out.
|
|
257
218
|
if (stats.metrics) {
|
|
258
219
|
const { open, ready } = stats.metrics;
|
|
@@ -2,87 +2,14 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from 'chai';
|
|
6
|
-
|
|
7
|
-
import { DocumentModel, type DocumentModelState, MutationBuilder } from '@dxos/document-model';
|
|
8
|
-
import { createModelMutation, encodeModelMutation, genesisMutation } from '@dxos/echo-db';
|
|
9
|
-
import { type WriteReceipt } from '@dxos/feed-store';
|
|
10
|
-
import { PublicKey } from '@dxos/keys';
|
|
11
|
-
import { log } from '@dxos/log';
|
|
12
5
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
13
6
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
14
7
|
import { describe, test } from '@dxos/test';
|
|
15
|
-
import { Timeframe } from '@dxos/timeframe';
|
|
16
|
-
import { range } from '@dxos/util';
|
|
17
8
|
|
|
18
|
-
import { createServiceContext
|
|
9
|
+
import { createServiceContext } from '../testing';
|
|
19
10
|
import { performInvitation } from '../testing/invitation-utils';
|
|
20
11
|
|
|
21
12
|
describe('services/ServiceContext', () => {
|
|
22
|
-
test('existing space is synchronized on device invitations', async () => {
|
|
23
|
-
const networkContext = new MemorySignalManagerContext();
|
|
24
|
-
const device1 = createServiceContext({ signalContext: networkContext });
|
|
25
|
-
await device1.createIdentity();
|
|
26
|
-
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
27
|
-
|
|
28
|
-
const itemId = PublicKey.random().toHex();
|
|
29
|
-
await space1.dataPipeline.databaseHost!.getWriteStream()?.write({
|
|
30
|
-
batch: genesisMutation(itemId, DocumentModel.meta.type),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
let counter = 0;
|
|
34
|
-
|
|
35
|
-
for (const _ in range(5)) {
|
|
36
|
-
const receipts: WriteReceipt[] = [];
|
|
37
|
-
for (const _ in range(50)) {
|
|
38
|
-
receipts.push(
|
|
39
|
-
await space1.dataPipeline.databaseHost!.getWriteStream()!.write({
|
|
40
|
-
batch: createModelMutation(
|
|
41
|
-
itemId,
|
|
42
|
-
encodeModelMutation(DocumentModel.meta, new MutationBuilder().set('counter', ++counter).build()),
|
|
43
|
-
),
|
|
44
|
-
}),
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
await space1.dataPipeline.pipelineState!.waitUntilTimeframe(
|
|
49
|
-
Timeframe.merge(...receipts.map((receipt) => new Timeframe([[receipt.feedKey, receipt.seq]]))),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
await space1.createEpoch();
|
|
53
|
-
log('epoch', { number: space1.dataPipeline.currentEpoch?.subject.assertion.number });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const device2 = createServiceContext({ signalContext: networkContext });
|
|
57
|
-
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
58
|
-
|
|
59
|
-
await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
|
|
60
|
-
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
61
|
-
|
|
62
|
-
log('peer 2', {
|
|
63
|
-
currentEpoch: space2!.dataPipeline.currentEpoch?.subject.assertion.number,
|
|
64
|
-
appliedEpoch: space2!.dataPipeline.appliedEpoch?.subject.assertion.number,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await space2?.dataPipeline.onNewEpoch.waitForCondition(
|
|
68
|
-
() =>
|
|
69
|
-
space2!.dataPipeline.appliedEpoch?.subject.assertion.number ===
|
|
70
|
-
space1.dataPipeline.currentEpoch?.subject.assertion.number,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
expect(space2!.dataPipeline.currentEpoch!.subject.assertion.number).to.equal(
|
|
74
|
-
space1.dataPipeline.currentEpoch!.subject.assertion.number,
|
|
75
|
-
);
|
|
76
|
-
expect(space2!.dataPipeline.appliedEpoch!.subject.assertion.number).to.equal(
|
|
77
|
-
space1.dataPipeline.appliedEpoch!.subject.assertion.number,
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
const item2 = space2!.dataPipeline.itemManager.entities.get(itemId);
|
|
81
|
-
expect((item2!.state as DocumentModelState).data.counter).to.equal(counter);
|
|
82
|
-
|
|
83
|
-
await syncItemsLocal(space1.dataPipeline, space2!.dataPipeline);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
13
|
test('new space is synchronized on device invitations', async () => {
|
|
87
14
|
const networkContext = new MemorySignalManagerContext();
|
|
88
15
|
const device1 = createServiceContext({ signalContext: networkContext });
|
|
@@ -94,7 +21,7 @@ describe('services/ServiceContext', () => {
|
|
|
94
21
|
const space1 = await device1.dataSpaceManager!.createSpace();
|
|
95
22
|
await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
|
|
96
23
|
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
97
|
-
await
|
|
24
|
+
await space2!.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.timeframe);
|
|
98
25
|
}).tag('flaky');
|
|
99
26
|
|
|
100
27
|
test('joined space is synchronized on device invitations', async () => {
|
|
@@ -118,6 +45,6 @@ describe('services/ServiceContext', () => {
|
|
|
118
45
|
|
|
119
46
|
await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
|
|
120
47
|
const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
|
|
121
|
-
await
|
|
48
|
+
await space2!.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.timeframe);
|
|
122
49
|
});
|
|
123
50
|
});
|
|
@@ -4,16 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import { Trigger } from '@dxos/async';
|
|
6
6
|
import { Context } from '@dxos/context';
|
|
7
|
-
import { type CredentialProcessor
|
|
7
|
+
import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
|
|
8
8
|
import { failUndefined } from '@dxos/debug';
|
|
9
|
-
import {
|
|
10
|
-
valueEncoding,
|
|
11
|
-
MetadataStore,
|
|
12
|
-
SpaceManager,
|
|
13
|
-
DataServiceSubscriptions,
|
|
14
|
-
SnapshotStore,
|
|
15
|
-
AutomergeHost,
|
|
16
|
-
} from '@dxos/echo-pipeline';
|
|
9
|
+
import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
|
|
17
10
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
18
11
|
import { IndexMetadataStore, IndexStore, Indexer } from '@dxos/indexing';
|
|
19
12
|
import { invariant } from '@dxos/invariant';
|
|
@@ -26,15 +19,15 @@ import { type NetworkManager } from '@dxos/network-manager';
|
|
|
26
19
|
import { InvalidStorageVersionError, STORAGE_VERSION, trace } from '@dxos/protocols';
|
|
27
20
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
28
21
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
29
|
-
import { type
|
|
22
|
+
import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
30
23
|
import { type Storage } from '@dxos/random-access-storage';
|
|
31
24
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
32
25
|
import { trace as Trace } from '@dxos/tracing';
|
|
33
26
|
import { safeInstanceof } from '@dxos/util';
|
|
34
27
|
|
|
35
28
|
import {
|
|
36
|
-
type CreateIdentityOptions,
|
|
37
29
|
IdentityManager,
|
|
30
|
+
type CreateIdentityOptions,
|
|
38
31
|
type IdentityManagerRuntimeParams,
|
|
39
32
|
type JoinIdentityParams,
|
|
40
33
|
} from '../identity';
|
|
@@ -42,8 +35,8 @@ import { createGetAllDocuments, createLoadDocuments } from '../indexing';
|
|
|
42
35
|
import {
|
|
43
36
|
DeviceInvitationProtocol,
|
|
44
37
|
InvitationsHandler,
|
|
45
|
-
type InvitationProtocol,
|
|
46
38
|
SpaceInvitationProtocol,
|
|
39
|
+
type InvitationProtocol,
|
|
47
40
|
} from '../invitations';
|
|
48
41
|
import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
|
|
49
42
|
|
|
@@ -57,7 +50,6 @@ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpa
|
|
|
57
50
|
@Trace.resource()
|
|
58
51
|
export class ServiceContext {
|
|
59
52
|
public readonly initialized = new Trigger();
|
|
60
|
-
public readonly dataServiceSubscriptions = new DataServiceSubscriptions();
|
|
61
53
|
public readonly metadataStore: MetadataStore;
|
|
62
54
|
/**
|
|
63
55
|
* @deprecated
|
|
@@ -186,7 +178,6 @@ export class ServiceContext {
|
|
|
186
178
|
await this.feedStore.close();
|
|
187
179
|
await this.networkManager.close();
|
|
188
180
|
await this.signalManager.close();
|
|
189
|
-
this.dataServiceSubscriptions.clear();
|
|
190
181
|
await this.metadataStore.close();
|
|
191
182
|
await this.indexer.destroy();
|
|
192
183
|
log('closed');
|
|
@@ -246,7 +237,6 @@ export class ServiceContext {
|
|
|
246
237
|
this.dataSpaceManager = new DataSpaceManager(
|
|
247
238
|
this.spaceManager,
|
|
248
239
|
this.metadataStore,
|
|
249
|
-
this.dataServiceSubscriptions,
|
|
250
240
|
this.keyring,
|
|
251
241
|
signingContext,
|
|
252
242
|
this.feedStore,
|
|
@@ -268,17 +268,13 @@ export class ClientServicesHost {
|
|
|
268
268
|
SpacesService: new SpacesServiceImpl(
|
|
269
269
|
this._serviceContext.identityManager,
|
|
270
270
|
this._serviceContext.spaceManager,
|
|
271
|
-
this._serviceContext.dataServiceSubscriptions,
|
|
272
271
|
async () => {
|
|
273
272
|
await this._serviceContext.initialized.wait();
|
|
274
273
|
return this._serviceContext.dataSpaceManager!;
|
|
275
274
|
},
|
|
276
275
|
),
|
|
277
276
|
|
|
278
|
-
DataService: new DataServiceImpl(
|
|
279
|
-
this._serviceContext.dataServiceSubscriptions,
|
|
280
|
-
this._serviceContext.automergeHost,
|
|
281
|
-
),
|
|
277
|
+
DataService: new DataServiceImpl(this._serviceContext.automergeHost),
|
|
282
278
|
|
|
283
279
|
IndexService: new IndexServiceImpl({
|
|
284
280
|
indexer: this._serviceContext.indexer,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Event } from '@dxos/async';
|
|
5
6
|
import { type CredentialProcessor, type SpecificCredential, checkCredentialType } from '@dxos/credentials';
|
|
6
7
|
import { type Credential, type Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
7
8
|
|
|
@@ -9,6 +10,8 @@ export class AutomergeSpaceState implements CredentialProcessor {
|
|
|
9
10
|
public rootUrl: string | undefined = undefined;
|
|
10
11
|
public lastEpoch: SpecificCredential<Epoch> | undefined = undefined;
|
|
11
12
|
|
|
13
|
+
public readonly onNewEpoch = new Event<SpecificCredential<Epoch>>();
|
|
14
|
+
|
|
12
15
|
private _isProcessingRootDocs = false;
|
|
13
16
|
|
|
14
17
|
constructor(private readonly _onNewRoot: (rootUrl: string) => void) {}
|
|
@@ -26,6 +29,8 @@ export class AutomergeSpaceState implements CredentialProcessor {
|
|
|
26
29
|
this._onNewRoot(this.rootUrl);
|
|
27
30
|
}
|
|
28
31
|
}
|
|
32
|
+
|
|
33
|
+
this.onNewEpoch.emit(credential);
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
startProcessingRootDocs() {
|
|
@@ -38,4 +43,8 @@ export class AutomergeSpaceState implements CredentialProcessor {
|
|
|
38
43
|
}
|
|
39
44
|
this._isProcessingRootDocs = true;
|
|
40
45
|
}
|
|
46
|
+
|
|
47
|
+
async ensureEpochInitialized() {
|
|
48
|
+
await this.onNewEpoch.waitForCondition(() => !!this.lastEpoch);
|
|
49
|
+
}
|
|
41
50
|
}
|
|
@@ -3,24 +3,16 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
|
-
import path from 'node:path';
|
|
7
6
|
|
|
8
7
|
import { asyncTimeout, latch } from '@dxos/async';
|
|
9
|
-
import {
|
|
8
|
+
import { createAdmissionCredentials } from '@dxos/credentials';
|
|
10
9
|
import { AuthStatus } from '@dxos/echo-pipeline';
|
|
11
|
-
import { testLocalDatabase } from '@dxos/echo-pipeline/testing';
|
|
12
10
|
import { writeMessages } from '@dxos/feed-store';
|
|
13
|
-
import { type PublicKey } from '@dxos/keys';
|
|
14
11
|
import { log } from '@dxos/log';
|
|
15
12
|
import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
16
|
-
import {
|
|
17
|
-
import { type Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
18
|
-
import { StorageType } from '@dxos/random-access-storage';
|
|
19
|
-
import { afterTest, describe, openAndClose, test } from '@dxos/test';
|
|
20
|
-
import { Timeframe } from '@dxos/timeframe';
|
|
21
|
-
import { range } from '@dxos/util';
|
|
13
|
+
import { describe, openAndClose, test } from '@dxos/test';
|
|
22
14
|
|
|
23
|
-
import { TestBuilder
|
|
15
|
+
import { TestBuilder } from '../testing';
|
|
24
16
|
|
|
25
17
|
describe('DataSpaceManager', () => {
|
|
26
18
|
test('create space', async () => {
|
|
@@ -103,8 +95,6 @@ describe('DataSpaceManager', () => {
|
|
|
103
95
|
});
|
|
104
96
|
log.break();
|
|
105
97
|
|
|
106
|
-
await syncItemsLocal(space1.dataPipeline, space2.dataPipeline);
|
|
107
|
-
|
|
108
98
|
expect(space1.inner.protocol.sessions.get(peer2.identity.deviceKey)).to.exist;
|
|
109
99
|
expect(space1.inner.protocol.sessions.get(peer2.identity.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
|
|
110
100
|
expect(space2.inner.protocol.sessions.get(peer1.identity.deviceKey)).to.exist;
|
|
@@ -157,137 +147,6 @@ describe('DataSpaceManager', () => {
|
|
|
157
147
|
await receivedMessage();
|
|
158
148
|
});
|
|
159
149
|
|
|
160
|
-
describe('Epochs', () => {
|
|
161
|
-
test
|
|
162
|
-
.skip('Epoch truncates feeds', async () => {
|
|
163
|
-
const builder = new TestBuilder();
|
|
164
|
-
afterTest(async () => builder.destroy());
|
|
165
|
-
|
|
166
|
-
const peer = builder.createPeer({
|
|
167
|
-
dataStore: typeof window === 'undefined' ? StorageType.NODE : StorageType.WEBFS,
|
|
168
|
-
});
|
|
169
|
-
await peer.createIdentity();
|
|
170
|
-
await openAndClose(peer.dataSpaceManager);
|
|
171
|
-
const space = await peer.dataSpaceManager.createSpace();
|
|
172
|
-
await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
|
|
173
|
-
|
|
174
|
-
const feedDataPath = path.join(space.inner.dataPipeline.pipelineState!.feeds[0].key.toHex(), 'data');
|
|
175
|
-
const directory = peer.storage.createDirectory('feeds');
|
|
176
|
-
const file = directory.getOrCreateFile(feedDataPath);
|
|
177
|
-
afterTest(() => file.close());
|
|
178
|
-
|
|
179
|
-
expect((await file.stat()).size === 0).to.be.true;
|
|
180
|
-
|
|
181
|
-
for (const _ in range(10)) {
|
|
182
|
-
await testLocalDatabase(space.dataPipeline);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
expect((await file.stat()).size !== 0).to.be.true;
|
|
186
|
-
await space.createEpoch();
|
|
187
|
-
expect((await file.stat()).size === 0).to.be.true;
|
|
188
|
-
})
|
|
189
|
-
.onlyEnvironments('nodejs', 'chromium', 'firefox')
|
|
190
|
-
.tag('flaky');
|
|
191
|
-
|
|
192
|
-
test('Loads only last epoch', async () => {
|
|
193
|
-
const builder = new TestBuilder();
|
|
194
|
-
afterTest(async () => builder.destroy());
|
|
195
|
-
|
|
196
|
-
const peer = builder.createPeer();
|
|
197
|
-
await peer.createIdentity();
|
|
198
|
-
const epochsNumber = 10;
|
|
199
|
-
let spaceKey: PublicKey;
|
|
200
|
-
{
|
|
201
|
-
// Create space and create epochs in it.s
|
|
202
|
-
await openAndClose(peer.dataSpaceManager);
|
|
203
|
-
|
|
204
|
-
const space = await peer.dataSpaceManager.createSpace();
|
|
205
|
-
spaceKey = space.key;
|
|
206
|
-
await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
|
|
207
|
-
|
|
208
|
-
for (const _ of range(epochsNumber)) {
|
|
209
|
-
await testLocalDatabase(space.dataPipeline);
|
|
210
|
-
await space.createEpoch();
|
|
211
|
-
}
|
|
212
|
-
await space.close();
|
|
213
|
-
await peer.dataSpaceManager.close();
|
|
214
|
-
peer.props.dataSpaceManager = undefined;
|
|
215
|
-
}
|
|
216
|
-
{
|
|
217
|
-
// Load same space and check if it loads only last epoch.s
|
|
218
|
-
await openAndClose(peer.dataSpaceManager);
|
|
219
|
-
|
|
220
|
-
const space = peer.dataSpaceManager.spaces.get(spaceKey)!;
|
|
221
|
-
|
|
222
|
-
const epochs: number[] = [];
|
|
223
|
-
space.dataPipeline.onNewEpoch.on((epoch: SpecificCredential<Epoch>) => {
|
|
224
|
-
epochs.push(epoch.subject.assertion.number);
|
|
225
|
-
});
|
|
226
|
-
const processedFirstEpoch = space.dataPipeline.onNewEpoch.waitFor(() => true);
|
|
227
|
-
|
|
228
|
-
await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
|
|
229
|
-
|
|
230
|
-
await processedFirstEpoch;
|
|
231
|
-
expect(epochs).to.deep.equal([epochsNumber]);
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('Items are cleared before epoch applied', async () => {
|
|
236
|
-
const builder = new TestBuilder();
|
|
237
|
-
afterTest(async () => builder.destroy());
|
|
238
|
-
const peer = builder.createPeer();
|
|
239
|
-
await peer.createIdentity();
|
|
240
|
-
await openAndClose(peer.dataSpaceManager);
|
|
241
|
-
|
|
242
|
-
// Create space and fill it with Items.
|
|
243
|
-
const space = await peer.dataSpaceManager.createSpace();
|
|
244
|
-
await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
|
|
245
|
-
const itemsNumber = 2;
|
|
246
|
-
for (const _ of range(itemsNumber)) {
|
|
247
|
-
await testLocalDatabase(space.dataPipeline);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Create empty Epoch and check if it clears items.
|
|
251
|
-
{
|
|
252
|
-
const processedFirstEpoch = space.dataPipeline.onNewEpoch.waitFor(
|
|
253
|
-
(epoch) => epoch.subject.assertion.number === 1,
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
// Empty snapshot.
|
|
257
|
-
const snapshot: SpaceSnapshot = {
|
|
258
|
-
spaceKey: space.key.asUint8Array(),
|
|
259
|
-
timeframe: space.inner.dataPipeline.pipelineState!.timeframe,
|
|
260
|
-
database: {},
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const snapshotCid = await peer.snapshotStore.saveSnapshot(snapshot);
|
|
264
|
-
|
|
265
|
-
const epoch: Epoch = {
|
|
266
|
-
previousId: space.dataPipeline.currentEpoch?.id,
|
|
267
|
-
timeframe: space.inner.dataPipeline.pipelineState!.timeframe,
|
|
268
|
-
number: (space.dataPipeline.currentEpoch?.subject.assertion as Epoch).number + 1,
|
|
269
|
-
snapshotCid,
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const receipt = await space.inner.controlPipeline.writer.write({
|
|
273
|
-
credential: {
|
|
274
|
-
credential: await peer.props.signingContext!.credentialSigner.createCredential({
|
|
275
|
-
subject: space.key,
|
|
276
|
-
assertion: {
|
|
277
|
-
'@type': 'dxos.halo.credentials.Epoch',
|
|
278
|
-
...epoch,
|
|
279
|
-
},
|
|
280
|
-
}),
|
|
281
|
-
},
|
|
282
|
-
});
|
|
283
|
-
await space.inner.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
|
|
284
|
-
await processedFirstEpoch;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
expect(Array.from(space.dataPipeline.itemManager.entities.keys()).length).to.equal(0);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
150
|
describe('activation', () => {
|
|
292
151
|
test('can activate and deactivate a space', async () => {
|
|
293
152
|
const builder = new TestBuilder();
|
|
@@ -308,7 +167,6 @@ describe('DataSpaceManager', () => {
|
|
|
308
167
|
space.stateUpdate.waitForCondition(() => space.state === SpaceState.READY),
|
|
309
168
|
500,
|
|
310
169
|
);
|
|
311
|
-
await testLocalDatabase(space.dataPipeline);
|
|
312
170
|
});
|
|
313
171
|
});
|
|
314
172
|
});
|
|
@@ -3,15 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
6
|
-
import {
|
|
7
|
-
import { type CredentialSigner
|
|
8
|
-
import {
|
|
9
|
-
type AutomergeHost,
|
|
10
|
-
type DataServiceSubscriptions,
|
|
11
|
-
type MetadataStore,
|
|
12
|
-
type Space,
|
|
13
|
-
type SpaceManager,
|
|
14
|
-
} from '@dxos/echo-pipeline';
|
|
6
|
+
import { Context, cancelWithContext } from '@dxos/context';
|
|
7
|
+
import { getCredentialAssertion, type CredentialSigner } from '@dxos/credentials';
|
|
8
|
+
import { type AutomergeHost, type MetadataStore, type Space, type SpaceManager } from '@dxos/echo-pipeline';
|
|
15
9
|
import { type FeedStore } from '@dxos/feed-store';
|
|
16
10
|
import { invariant } from '@dxos/invariant';
|
|
17
11
|
import { type Keyring } from '@dxos/keyring';
|
|
@@ -80,7 +74,6 @@ export class DataSpaceManager {
|
|
|
80
74
|
constructor(
|
|
81
75
|
private readonly _spaceManager: SpaceManager,
|
|
82
76
|
private readonly _metadataStore: MetadataStore,
|
|
83
|
-
private readonly _dataServiceSubscriptions: DataServiceSubscriptions,
|
|
84
77
|
private readonly _keyring: Keyring,
|
|
85
78
|
private readonly _signingContext: SigningContext,
|
|
86
79
|
private readonly _feedStore: FeedStore<FeedMessage>,
|
|
@@ -270,10 +263,6 @@ export class DataSpaceManager {
|
|
|
270
263
|
callbacks: {
|
|
271
264
|
beforeReady: async () => {
|
|
272
265
|
log('before space ready', { space: space.key });
|
|
273
|
-
await this._dataServiceSubscriptions.registerSpace(
|
|
274
|
-
space.key,
|
|
275
|
-
dataSpace.dataPipeline.databaseHost!.createDataServiceHost(),
|
|
276
|
-
);
|
|
277
266
|
},
|
|
278
267
|
afterReady: async () => {
|
|
279
268
|
log('after space ready', { space: space.key, open: this._isOpen });
|
|
@@ -283,7 +272,6 @@ export class DataSpaceManager {
|
|
|
283
272
|
},
|
|
284
273
|
beforeClose: async () => {
|
|
285
274
|
log('before space close', { space: space.key });
|
|
286
|
-
await this._dataServiceSubscriptions.unregisterSpace(space.key);
|
|
287
275
|
},
|
|
288
276
|
},
|
|
289
277
|
cache: metadata.cache,
|
|
@@ -297,9 +285,6 @@ export class DataSpaceManager {
|
|
|
297
285
|
if (metadata.controlTimeframe) {
|
|
298
286
|
dataSpace.inner.controlPipeline.state.setTargetTimeframe(metadata.controlTimeframe);
|
|
299
287
|
}
|
|
300
|
-
if (metadata.dataTimeframe) {
|
|
301
|
-
dataSpace.dataPipeline.setTargetTimeframe(metadata.dataTimeframe);
|
|
302
|
-
}
|
|
303
288
|
|
|
304
289
|
this._spaces.set(metadata.key, dataSpace);
|
|
305
290
|
return dataSpace;
|
|
@@ -6,14 +6,7 @@ import { Event, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } fr
|
|
|
6
6
|
import { AUTH_TIMEOUT } from '@dxos/client-protocol';
|
|
7
7
|
import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
|
|
8
8
|
import { timed, warnAfterTimeout } from '@dxos/debug';
|
|
9
|
-
import {
|
|
10
|
-
type MetadataStore,
|
|
11
|
-
type Space,
|
|
12
|
-
createMappedFeedWriter,
|
|
13
|
-
type DataPipeline,
|
|
14
|
-
type CreateEpochOptions,
|
|
15
|
-
type AutomergeHost,
|
|
16
|
-
} from '@dxos/echo-pipeline';
|
|
9
|
+
import { type MetadataStore, type Space, createMappedFeedWriter, type AutomergeHost } from '@dxos/echo-pipeline';
|
|
17
10
|
import { type FeedStore } from '@dxos/feed-store';
|
|
18
11
|
import { failedInvariant, invariant } from '@dxos/invariant';
|
|
19
12
|
import { type Keyring } from '@dxos/keyring';
|
|
@@ -71,11 +64,9 @@ export type DataSpaceParams = {
|
|
|
71
64
|
automergeHost: AutomergeHost;
|
|
72
65
|
};
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// TODO(dmaretskyi): Disabled till better times https://github.com/dxos/dxos/issues/3949.
|
|
78
|
-
const ENABLE_FEED_PURGE = false;
|
|
67
|
+
export type CreateEpochOptions = {
|
|
68
|
+
migration?: CreateEpochRequest.Migration;
|
|
69
|
+
};
|
|
79
70
|
|
|
80
71
|
@trackLeaks('open', 'close')
|
|
81
72
|
@trace.resource()
|
|
@@ -160,10 +151,6 @@ export class DataSpace {
|
|
|
160
151
|
return this._inner;
|
|
161
152
|
}
|
|
162
153
|
|
|
163
|
-
get dataPipeline(): DataPipeline {
|
|
164
|
-
return this._inner.dataPipeline;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
154
|
get presence() {
|
|
168
155
|
return this._presence;
|
|
169
156
|
}
|
|
@@ -278,20 +265,8 @@ export class DataSpace {
|
|
|
278
265
|
|
|
279
266
|
this._automergeSpaceState.startProcessingRootDocs();
|
|
280
267
|
|
|
281
|
-
await this._inner.initializeDataPipeline();
|
|
282
|
-
|
|
283
|
-
this.metrics.dataPipelineOpen = new Date();
|
|
284
|
-
|
|
285
268
|
// Wait for the first epoch.
|
|
286
|
-
await cancelWithContext(this._ctx, this.
|
|
287
|
-
|
|
288
|
-
log('waiting for data pipeline to reach target timeframe');
|
|
289
|
-
// Wait for the data pipeline to catch up to its desired timeframe.
|
|
290
|
-
await this._inner.dataPipeline.pipelineState!.waitUntilReachedTargetTimeframe({
|
|
291
|
-
ctx: this._ctx,
|
|
292
|
-
breakOnStall: false,
|
|
293
|
-
});
|
|
294
|
-
this.metrics.dataPipelineReady = new Date();
|
|
269
|
+
await cancelWithContext(this._ctx, this.automergeSpaceState.ensureEpochInitialized());
|
|
295
270
|
|
|
296
271
|
log('data pipeline ready');
|
|
297
272
|
await this._callbacks.beforeReady?.();
|
|
@@ -426,7 +401,12 @@ export class DataSpace {
|
|
|
426
401
|
case CreateEpochRequest.Migration.NONE:
|
|
427
402
|
{
|
|
428
403
|
// TODO(dmaretskyi): Unify epoch construction.
|
|
429
|
-
epoch =
|
|
404
|
+
epoch = {
|
|
405
|
+
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
406
|
+
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
407
|
+
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
408
|
+
automergeRoot: this._automergeSpaceState.lastEpoch?.subject.assertion?.automergeRoot,
|
|
409
|
+
};
|
|
430
410
|
}
|
|
431
411
|
break;
|
|
432
412
|
case CreateEpochRequest.Migration.INIT_AUTOMERGE:
|
|
@@ -476,18 +456,6 @@ export class DataSpace {
|
|
|
476
456
|
});
|
|
477
457
|
|
|
478
458
|
await this.inner.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
|
|
479
|
-
|
|
480
|
-
// Clear feed blocks before epoch.
|
|
481
|
-
if (ENABLE_FEED_PURGE) {
|
|
482
|
-
for (const feed of this.inner.dataPipeline.pipelineState?.feeds ?? []) {
|
|
483
|
-
const indexBeforeEpoch = epoch.timeframe.get(feed.key);
|
|
484
|
-
if (indexBeforeEpoch === undefined) {
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
await feed.safeClear(0, indexBeforeEpoch + 1);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
459
|
}
|
|
492
460
|
|
|
493
461
|
@synchronized
|
|
@@ -24,15 +24,10 @@ describe('SpacesService', () => {
|
|
|
24
24
|
beforeEach(async () => {
|
|
25
25
|
serviceContext = createServiceContext();
|
|
26
26
|
await serviceContext.open(new Context());
|
|
27
|
-
spacesService = new SpacesServiceImpl(
|
|
28
|
-
serviceContext.
|
|
29
|
-
serviceContext.
|
|
30
|
-
|
|
31
|
-
async () => {
|
|
32
|
-
await serviceContext.initialized.wait();
|
|
33
|
-
return serviceContext.dataSpaceManager!;
|
|
34
|
-
},
|
|
35
|
-
);
|
|
27
|
+
spacesService = new SpacesServiceImpl(serviceContext.identityManager, serviceContext.spaceManager, async () => {
|
|
28
|
+
await serviceContext.initialized.wait();
|
|
29
|
+
return serviceContext.dataSpaceManager!;
|
|
30
|
+
});
|
|
36
31
|
});
|
|
37
32
|
|
|
38
33
|
afterEach(async () => {
|