@dxos/client-services 0.5.8 → 0.5.9-main.079a532
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-JECXTGZA.mjs → chunk-QZ5LNV55.mjs} +1781 -1480
- package/dist/lib/browser/chunk-QZ5LNV55.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +55 -365
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +14 -14
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-OR7LRWDY.cjs → chunk-V7UMWFUS.cjs} +2055 -1761
- package/dist/lib/node/chunk-V7UMWFUS.cjs.map +7 -0
- package/dist/lib/node/index.cjs +92 -397
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +19 -19
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/index.d.ts +3 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/network.d.ts +4 -4
- package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +19 -0
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/identity-service.d.ts +14 -7
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +4 -1
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/network/network-service.d.ts +2 -2
- package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +4 -5
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +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 +4 -1
- 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 +5 -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 +11 -9
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +23 -0
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +11 -9
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/index.d.ts +3 -0
- package/dist/types/src/packlets/worker/index.d.ts.map +1 -0
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -0
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +36 -36
- package/src/index.ts +3 -3
- package/src/packlets/devtools/network.ts +4 -4
- package/src/packlets/identity/default-space-state-machine.ts +44 -0
- package/src/packlets/identity/identity-manager.test.ts +2 -2
- package/src/packlets/identity/identity-service.test.ts +35 -5
- package/src/packlets/identity/identity-service.ts +55 -8
- package/src/packlets/identity/identity.test.ts +8 -4
- package/src/packlets/identity/identity.ts +25 -2
- package/src/packlets/invitations/invitations-handler.ts +2 -2
- package/src/packlets/network/network-service.ts +2 -2
- package/src/packlets/services/service-context.ts +4 -7
- package/src/packlets/services/service-host.ts +16 -44
- package/src/packlets/spaces/automerge-space-state.ts +11 -2
- package/src/packlets/spaces/data-space-manager.test.ts +46 -1
- package/src/packlets/spaces/data-space-manager.ts +81 -31
- package/src/packlets/spaces/data-space.ts +89 -132
- package/src/packlets/spaces/epoch-migrations.ts +135 -0
- package/src/packlets/spaces/spaces-service.ts +5 -2
- package/src/packlets/testing/test-builder.ts +16 -14
- package/src/packlets/worker/index.ts +6 -0
- package/src/version.ts +1 -5
- package/dist/lib/browser/chunk-JECXTGZA.mjs.map +0 -7
- package/dist/lib/node/chunk-OR7LRWDY.cjs.map +0 -7
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +0 -37
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +0 -1
- package/dist/types/src/packlets/vault/index.d.ts +0 -6
- package/dist/types/src/packlets/vault/index.d.ts.map +0 -1
- package/dist/types/src/packlets/vault/shared-worker-connection.d.ts +0 -36
- package/dist/types/src/packlets/vault/shared-worker-connection.d.ts.map +0 -1
- package/dist/types/src/packlets/vault/shell-runtime.d.ts +0 -33
- package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +0 -1
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +0 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +0 -1
- package/src/packlets/vault/iframe-host-runtime.ts +0 -130
- package/src/packlets/vault/index.ts +0 -9
- package/src/packlets/vault/shared-worker-connection.ts +0 -115
- package/src/packlets/vault/shell-runtime.ts +0 -111
- /package/dist/types/src/packlets/{vault → worker}/worker-runtime.d.ts +0 -0
- /package/dist/types/src/packlets/{vault → worker}/worker-session.d.ts +0 -0
- /package/src/packlets/{vault → worker}/worker-runtime.ts +0 -0
- /package/src/packlets/{vault → worker}/worker-session.ts +0 -0
|
@@ -2,26 +2,25 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Event,
|
|
5
|
+
import { Event, Mutex, scheduleTask, sleep, synchronized, trackLeaks } from '@dxos/async';
|
|
6
6
|
import { AUTH_TIMEOUT } from '@dxos/client-protocol';
|
|
7
7
|
import { Context, ContextDisposedError, cancelWithContext } from '@dxos/context';
|
|
8
|
+
import type { SpecificCredential } from '@dxos/credentials';
|
|
8
9
|
import { timed, warnAfterTimeout } from '@dxos/debug';
|
|
9
|
-
import { type EchoHost } from '@dxos/echo-db';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
createMappedFeedWriter,
|
|
13
|
-
type MetadataStore,
|
|
14
|
-
type Space,
|
|
15
|
-
} from '@dxos/echo-pipeline';
|
|
16
|
-
import { type ObjectStructure, type SpaceDoc } from '@dxos/echo-protocol';
|
|
17
|
-
import { TYPE_PROPERTIES } from '@dxos/echo-schema';
|
|
10
|
+
import { type EchoHost, type DatabaseRoot } from '@dxos/echo-db';
|
|
11
|
+
import { createMappedFeedWriter, type MetadataStore, type Space } from '@dxos/echo-pipeline';
|
|
12
|
+
import { SpaceDocVersion } from '@dxos/echo-protocol';
|
|
18
13
|
import { type FeedStore } from '@dxos/feed-store';
|
|
19
|
-
import { failedInvariant
|
|
14
|
+
import { failedInvariant } from '@dxos/invariant';
|
|
20
15
|
import { type Keyring } from '@dxos/keyring';
|
|
21
16
|
import { PublicKey } from '@dxos/keys';
|
|
22
17
|
import { log } from '@dxos/log';
|
|
23
18
|
import { CancelledError, SystemError } from '@dxos/protocols';
|
|
24
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type CreateEpochRequest,
|
|
21
|
+
SpaceState,
|
|
22
|
+
type Space as SpaceProto,
|
|
23
|
+
} from '@dxos/protocols/proto/dxos/client/services';
|
|
25
24
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
26
25
|
import { type SpaceCache } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
27
26
|
import {
|
|
@@ -35,10 +34,11 @@ import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gos
|
|
|
35
34
|
import { type Gossip, type Presence } from '@dxos/teleport-extension-gossip';
|
|
36
35
|
import { Timeframe } from '@dxos/timeframe';
|
|
37
36
|
import { trace } from '@dxos/tracing';
|
|
38
|
-
import { ComplexSet
|
|
37
|
+
import { ComplexSet } from '@dxos/util';
|
|
39
38
|
|
|
40
39
|
import { AutomergeSpaceState } from './automerge-space-state';
|
|
41
40
|
import { type SigningContext } from './data-space-manager';
|
|
41
|
+
import { runEpochMigration } from './epoch-migrations';
|
|
42
42
|
import { NotarizationPlugin } from './notarization-plugin';
|
|
43
43
|
import { TrustedKeySetAuthVerifier } from '../identity';
|
|
44
44
|
|
|
@@ -75,6 +75,7 @@ export type DataSpaceParams = {
|
|
|
75
75
|
|
|
76
76
|
export type CreateEpochOptions = {
|
|
77
77
|
migration?: CreateEpochRequest.Migration;
|
|
78
|
+
newAutomergeRoot?: string;
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
@trackLeaks('open', 'close')
|
|
@@ -98,8 +99,12 @@ export class DataSpace {
|
|
|
98
99
|
// TODO(dmaretskyi): Move into Space?
|
|
99
100
|
private readonly _automergeSpaceState = new AutomergeSpaceState((rootUrl) => this._onNewAutomergeRoot(rootUrl));
|
|
100
101
|
|
|
102
|
+
private readonly _epochProcessingMutex = new Mutex();
|
|
103
|
+
|
|
101
104
|
private _state = SpaceState.CLOSED;
|
|
102
105
|
|
|
106
|
+
private _databaseRoot: DatabaseRoot | null = null;
|
|
107
|
+
|
|
103
108
|
/**
|
|
104
109
|
* Error for _state === SpaceState.ERROR.
|
|
105
110
|
*/
|
|
@@ -141,6 +146,11 @@ export class DataSpace {
|
|
|
141
146
|
log('new state', { state: SpaceState[this._state] });
|
|
142
147
|
}
|
|
143
148
|
|
|
149
|
+
@trace.info()
|
|
150
|
+
get id() {
|
|
151
|
+
return this._inner.id;
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
@trace.info()
|
|
145
155
|
get key() {
|
|
146
156
|
return this._inner.key;
|
|
@@ -176,6 +186,10 @@ export class DataSpace {
|
|
|
176
186
|
return this._automergeSpaceState;
|
|
177
187
|
}
|
|
178
188
|
|
|
189
|
+
get databaseRoot(): DatabaseRoot | null {
|
|
190
|
+
return this._databaseRoot;
|
|
191
|
+
}
|
|
192
|
+
|
|
179
193
|
@trace.info({ depth: null })
|
|
180
194
|
private get _automergeInfo() {
|
|
181
195
|
return {
|
|
@@ -186,13 +200,17 @@ export class DataSpace {
|
|
|
186
200
|
|
|
187
201
|
@synchronized
|
|
188
202
|
async open() {
|
|
189
|
-
|
|
203
|
+
if (this._state === SpaceState.CLOSED) {
|
|
204
|
+
await this._open();
|
|
205
|
+
}
|
|
190
206
|
}
|
|
191
207
|
|
|
192
208
|
private async _open() {
|
|
209
|
+
await this._presence.open();
|
|
193
210
|
await this._gossip.open();
|
|
194
211
|
await this._notarizationPlugin.open();
|
|
195
212
|
await this._inner.spaceState.addCredentialProcessor(this._notarizationPlugin);
|
|
213
|
+
await this._automergeSpaceState.open();
|
|
196
214
|
await this._inner.spaceState.addCredentialProcessor(this._automergeSpaceState);
|
|
197
215
|
await this._inner.open(new Context());
|
|
198
216
|
this._state = SpaceState.CONTROL_ONLY;
|
|
@@ -218,10 +236,11 @@ export class DataSpace {
|
|
|
218
236
|
|
|
219
237
|
await this._inner.close();
|
|
220
238
|
await this._inner.spaceState.removeCredentialProcessor(this._automergeSpaceState);
|
|
239
|
+
await this._automergeSpaceState.close();
|
|
221
240
|
await this._inner.spaceState.removeCredentialProcessor(this._notarizationPlugin);
|
|
222
241
|
await this._notarizationPlugin.close();
|
|
223
242
|
|
|
224
|
-
await this._presence.
|
|
243
|
+
await this._presence.close();
|
|
225
244
|
await this._gossip.close();
|
|
226
245
|
}
|
|
227
246
|
|
|
@@ -272,12 +291,15 @@ export class DataSpace {
|
|
|
272
291
|
// Allow other tasks to run before loading the data pipeline.
|
|
273
292
|
await sleep(1);
|
|
274
293
|
|
|
294
|
+
const ready = this.stateUpdate.waitForCondition(() => this._state === SpaceState.READY);
|
|
295
|
+
|
|
275
296
|
this._automergeSpaceState.startProcessingRootDocs();
|
|
276
297
|
|
|
277
|
-
//
|
|
278
|
-
await
|
|
298
|
+
// TODO(dmaretskyi): Change so `initializeDataPipeline` doesn't wait for the space to be READY, but rather any state with a valid root.
|
|
299
|
+
await ready;
|
|
300
|
+
}
|
|
279
301
|
|
|
280
|
-
|
|
302
|
+
private async _enterReadyState() {
|
|
281
303
|
await this._callbacks.beforeReady?.();
|
|
282
304
|
|
|
283
305
|
this._state = SpaceState.READY;
|
|
@@ -369,6 +391,7 @@ export class DataSpace {
|
|
|
369
391
|
this._echoHost.replicateDocument(rootUrl);
|
|
370
392
|
const handle = this._echoHost.automergeRepo.find(rootUrl as any);
|
|
371
393
|
|
|
394
|
+
// TODO(dmaretskyi): Make this single-threaded (but doc loading should still be parallel to not block epoch processing).
|
|
372
395
|
queueMicrotask(async () => {
|
|
373
396
|
try {
|
|
374
397
|
await warnAfterTimeout(5_000, 'Automerge root doc load timeout (DataSpace)', async () => {
|
|
@@ -378,6 +401,10 @@ export class DataSpace {
|
|
|
378
401
|
return;
|
|
379
402
|
}
|
|
380
403
|
|
|
404
|
+
// Ensure only one root is processed at a time.
|
|
405
|
+
using _guard = await this._epochProcessingMutex.acquire();
|
|
406
|
+
|
|
407
|
+
// Attaching space keys to legacy documents.
|
|
381
408
|
const doc = handle.docSync() ?? failedInvariant();
|
|
382
409
|
if (!doc.access?.spaceKey) {
|
|
383
410
|
handle.change((doc: any) => {
|
|
@@ -387,10 +414,15 @@ export class DataSpace {
|
|
|
387
414
|
|
|
388
415
|
// TODO(dmaretskyi): Close roots.
|
|
389
416
|
// TODO(dmaretskyi): How do we handle changing to the next EPOCH?
|
|
390
|
-
|
|
391
|
-
|
|
417
|
+
const root = await this._echoHost.openSpaceRoot(handle.url);
|
|
418
|
+
this._databaseRoot = root;
|
|
419
|
+
if (root.getVersion() !== SpaceDocVersion.CURRENT) {
|
|
420
|
+
this._state = SpaceState.REQUIRES_MIGRATION;
|
|
421
|
+
this.stateUpdate.emit();
|
|
392
422
|
} else {
|
|
393
|
-
|
|
423
|
+
if (this._state !== SpaceState.READY) {
|
|
424
|
+
await this._enterReadyState();
|
|
425
|
+
}
|
|
394
426
|
}
|
|
395
427
|
} catch (err) {
|
|
396
428
|
if (err instanceof ContextDisposedError) {
|
|
@@ -413,114 +445,51 @@ export class DataSpace {
|
|
|
413
445
|
await this.inner.controlPipeline.writer.write({ credential: { credential } });
|
|
414
446
|
}
|
|
415
447
|
|
|
416
|
-
async createEpoch(options?: CreateEpochOptions) {
|
|
417
|
-
|
|
418
|
-
switch (options?.migration) {
|
|
419
|
-
case undefined:
|
|
420
|
-
case CreateEpochRequest.Migration.NONE:
|
|
421
|
-
{
|
|
422
|
-
// TODO(dmaretskyi): Unify epoch construction.
|
|
423
|
-
epoch = {
|
|
424
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
425
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
426
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
427
|
-
automergeRoot: this._automergeSpaceState.lastEpoch?.subject.assertion?.automergeRoot,
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
break;
|
|
431
|
-
case CreateEpochRequest.Migration.INIT_AUTOMERGE:
|
|
432
|
-
{
|
|
433
|
-
const document = this._echoHost.automergeRepo.create();
|
|
434
|
-
// TODO(dmaretskyi): Unify epoch construction.
|
|
435
|
-
epoch = {
|
|
436
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
437
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
438
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
439
|
-
automergeRoot: document.url,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
break;
|
|
443
|
-
case CreateEpochRequest.Migration.PRUNE_AUTOMERGE_ROOT_HISTORY:
|
|
444
|
-
{
|
|
445
|
-
const currentRootUrl = this._automergeSpaceState.rootUrl;
|
|
446
|
-
const rootHandle = this._echoHost.automergeRepo.find(currentRootUrl as any);
|
|
447
|
-
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
448
|
-
const newRoot = this._echoHost.automergeRepo.create(rootHandle.docSync());
|
|
449
|
-
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
450
|
-
// TODO(dmaretskyi): Unify epoch construction.
|
|
451
|
-
epoch = {
|
|
452
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
453
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
454
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
455
|
-
automergeRoot: newRoot.url,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
break;
|
|
459
|
-
case CreateEpochRequest.Migration.FRAGMENT_AUTOMERGE_ROOT:
|
|
460
|
-
{
|
|
461
|
-
log.info('Fragmenting');
|
|
462
|
-
|
|
463
|
-
const currentRootUrl = this._automergeSpaceState.rootUrl;
|
|
464
|
-
const rootHandle = this._echoHost.automergeRepo.find<SpaceDoc>(currentRootUrl as any);
|
|
465
|
-
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
466
|
-
|
|
467
|
-
// Find properties object.
|
|
468
|
-
const objects = Object.entries((rootHandle.docSync() as SpaceDoc).objects!);
|
|
469
|
-
const properties = findPropertiesObject(rootHandle.docSync() as SpaceDoc);
|
|
470
|
-
const otherObjects = objects.filter(([key]) => key !== properties?.[0]);
|
|
471
|
-
invariant(properties, 'Properties not found');
|
|
472
|
-
|
|
473
|
-
// Create a new space doc with the properties object.
|
|
474
|
-
const newSpaceDoc: SpaceDoc = { ...rootHandle.docSync(), objects: Object.fromEntries([properties]) };
|
|
475
|
-
const newRoot = this._echoHost.automergeRepo.create(newSpaceDoc);
|
|
476
|
-
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
477
|
-
|
|
478
|
-
// Create new automerge documents for all objects.
|
|
479
|
-
const docLoader = new AutomergeDocumentLoaderImpl(this.key, this._echoHost.automergeRepo);
|
|
480
|
-
await docLoader.loadSpaceRootDocHandle(this._ctx, { rootUrl: newRoot.url });
|
|
481
|
-
|
|
482
|
-
otherObjects.forEach(([key, value]) => {
|
|
483
|
-
const handle = docLoader.createDocumentForObject(key);
|
|
484
|
-
handle.change((doc: any) => {
|
|
485
|
-
assignDeep(doc, ['objects', key], value);
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
// TODO(mykola): Delete old root.
|
|
448
|
+
async createEpoch(options?: CreateEpochOptions): Promise<SpecificCredential<Epoch> | null> {
|
|
449
|
+
const ctx = this._ctx.derive();
|
|
490
450
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
495
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
496
|
-
automergeRoot: newRoot.url,
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
break;
|
|
451
|
+
// Preserving existing behavior.
|
|
452
|
+
if (!options?.migration) {
|
|
453
|
+
return null;
|
|
500
454
|
}
|
|
501
455
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
456
|
+
const { newRoot } = await runEpochMigration(ctx, {
|
|
457
|
+
repo: this._echoHost.automergeRepo,
|
|
458
|
+
spaceId: this.id,
|
|
459
|
+
spaceKey: this.key,
|
|
460
|
+
migration: options.migration,
|
|
461
|
+
currentRoot: this._automergeSpaceState.rootUrl ?? null,
|
|
462
|
+
newAutomergeRoot: options.newAutomergeRoot,
|
|
463
|
+
});
|
|
505
464
|
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
465
|
+
const epoch: Epoch = {
|
|
466
|
+
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
467
|
+
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
468
|
+
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
469
|
+
automergeRoot: newRoot ?? this._automergeSpaceState.rootUrl,
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const credential = (await this._signingContext.credentialSigner.createCredential({
|
|
473
|
+
subject: this.key,
|
|
474
|
+
assertion: {
|
|
475
|
+
'@type': 'dxos.halo.credentials.Epoch',
|
|
476
|
+
...epoch,
|
|
515
477
|
},
|
|
478
|
+
})) as SpecificCredential<Epoch>;
|
|
479
|
+
|
|
480
|
+
const receipt = await this.inner.controlPipeline.writer.write({
|
|
481
|
+
credential: { credential },
|
|
516
482
|
});
|
|
517
483
|
|
|
518
484
|
await this.inner.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
|
|
485
|
+
await this._echoHost.updateIndexes();
|
|
486
|
+
|
|
487
|
+
return credential;
|
|
519
488
|
}
|
|
520
489
|
|
|
521
490
|
@synchronized
|
|
522
491
|
async activate() {
|
|
523
|
-
if (
|
|
492
|
+
if (![SpaceState.CLOSED, SpaceState.INACTIVE].includes(this._state)) {
|
|
524
493
|
return;
|
|
525
494
|
}
|
|
526
495
|
|
|
@@ -534,25 +503,13 @@ export class DataSpace {
|
|
|
534
503
|
if (this._state === SpaceState.INACTIVE) {
|
|
535
504
|
return;
|
|
536
505
|
}
|
|
537
|
-
|
|
538
506
|
// Unregister from data service.
|
|
539
507
|
await this._metadataStore.setSpaceState(this.key, SpaceState.INACTIVE);
|
|
540
|
-
|
|
508
|
+
if (this._state !== SpaceState.CLOSED) {
|
|
509
|
+
await this._close();
|
|
510
|
+
}
|
|
541
511
|
this._state = SpaceState.INACTIVE;
|
|
542
512
|
log('new state', { state: SpaceState[this._state] });
|
|
543
513
|
this.stateUpdate.emit();
|
|
544
514
|
}
|
|
545
515
|
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Assumes properties are at root.
|
|
549
|
-
*/
|
|
550
|
-
export const findPropertiesObject = (spaceDoc: SpaceDoc): [string, ObjectStructure] | undefined => {
|
|
551
|
-
for (const id in spaceDoc.objects ?? {}) {
|
|
552
|
-
const obj = spaceDoc.objects![id];
|
|
553
|
-
if (obj.system.type?.itemId === TYPE_PROPERTIES) {
|
|
554
|
-
return [id, obj];
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
return undefined;
|
|
558
|
-
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { asyncTimeout } from '@dxos/async';
|
|
6
|
+
import { next as am } from '@dxos/automerge/automerge';
|
|
7
|
+
import type { Repo, AutomergeUrl } from '@dxos/automerge/automerge-repo';
|
|
8
|
+
import { cancelWithContext, type Context } from '@dxos/context';
|
|
9
|
+
import {
|
|
10
|
+
convertLegacyReferences,
|
|
11
|
+
convertLegacySpaceRootDoc,
|
|
12
|
+
findInlineObjectOfType,
|
|
13
|
+
migrateDocument,
|
|
14
|
+
} from '@dxos/echo-db';
|
|
15
|
+
import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
|
|
16
|
+
import type { SpaceDoc } from '@dxos/echo-protocol';
|
|
17
|
+
import { TYPE_PROPERTIES } from '@dxos/echo-schema';
|
|
18
|
+
import { invariant } from '@dxos/invariant';
|
|
19
|
+
import type { PublicKey, SpaceId } from '@dxos/keys';
|
|
20
|
+
import { log } from '@dxos/log';
|
|
21
|
+
import { CreateEpochRequest } from '@dxos/protocols/proto/dxos/client/services';
|
|
22
|
+
import { assignDeep } from '@dxos/util';
|
|
23
|
+
|
|
24
|
+
export type MigrationContext = {
|
|
25
|
+
repo: Repo;
|
|
26
|
+
spaceId: SpaceId;
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Remove.
|
|
29
|
+
*/
|
|
30
|
+
spaceKey: PublicKey;
|
|
31
|
+
migration: CreateEpochRequest.Migration;
|
|
32
|
+
currentRoot: string | null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* For set automerge root migration type.
|
|
36
|
+
*/
|
|
37
|
+
newAutomergeRoot?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type MigrationResult = {
|
|
41
|
+
newRoot?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const runEpochMigration = async (ctx: Context, context: MigrationContext): Promise<MigrationResult> => {
|
|
45
|
+
switch (context.migration) {
|
|
46
|
+
case CreateEpochRequest.Migration.INIT_AUTOMERGE: {
|
|
47
|
+
const document = context.repo.create();
|
|
48
|
+
await context.repo.flush();
|
|
49
|
+
return { newRoot: document.url };
|
|
50
|
+
}
|
|
51
|
+
case CreateEpochRequest.Migration.PRUNE_AUTOMERGE_ROOT_HISTORY: {
|
|
52
|
+
if (!context.currentRoot) {
|
|
53
|
+
throw new Error('Space does not have an automerge root');
|
|
54
|
+
}
|
|
55
|
+
const rootHandle = context.repo.find(context.currentRoot as AutomergeUrl);
|
|
56
|
+
await cancelWithContext(ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
57
|
+
|
|
58
|
+
const newRoot = context.repo.create(rootHandle.docSync());
|
|
59
|
+
await context.repo.flush();
|
|
60
|
+
return { newRoot: newRoot.url };
|
|
61
|
+
}
|
|
62
|
+
case CreateEpochRequest.Migration.FRAGMENT_AUTOMERGE_ROOT: {
|
|
63
|
+
log.info('Fragmenting');
|
|
64
|
+
|
|
65
|
+
const currentRootUrl = context.currentRoot;
|
|
66
|
+
const rootHandle = context.repo.find<SpaceDoc>(currentRootUrl as any);
|
|
67
|
+
await cancelWithContext(ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
68
|
+
|
|
69
|
+
// Find properties object.
|
|
70
|
+
const objects = Object.entries((rootHandle.docSync() as SpaceDoc).objects!);
|
|
71
|
+
const properties = findInlineObjectOfType(rootHandle.docSync() as SpaceDoc, TYPE_PROPERTIES);
|
|
72
|
+
const otherObjects = objects.filter(([key]) => key !== properties?.[0]);
|
|
73
|
+
invariant(properties, 'Properties not found');
|
|
74
|
+
|
|
75
|
+
// Create a new space doc with the properties object.
|
|
76
|
+
const newSpaceDoc: SpaceDoc = { ...rootHandle.docSync(), objects: Object.fromEntries([properties]) };
|
|
77
|
+
const newRoot = context.repo.create(newSpaceDoc);
|
|
78
|
+
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
79
|
+
|
|
80
|
+
// Create new automerge documents for all objects.
|
|
81
|
+
const docLoader = new AutomergeDocumentLoaderImpl(context.spaceId, context.repo, context.spaceKey);
|
|
82
|
+
await docLoader.loadSpaceRootDocHandle(ctx, { rootUrl: newRoot.url });
|
|
83
|
+
|
|
84
|
+
otherObjects.forEach(([key, value]) => {
|
|
85
|
+
const handle = docLoader.createDocumentForObject(key);
|
|
86
|
+
handle.change((doc: any) => {
|
|
87
|
+
assignDeep(doc, ['objects', key], value);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await context.repo.flush();
|
|
92
|
+
return {
|
|
93
|
+
newRoot: newRoot.url,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
case CreateEpochRequest.Migration.MIGRATE_REFERENCES_TO_DXN: {
|
|
97
|
+
const currentRootUrl = context.currentRoot;
|
|
98
|
+
const rootHandle = context.repo.find<SpaceDoc>(currentRootUrl as any);
|
|
99
|
+
await cancelWithContext(ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
100
|
+
invariant(rootHandle.docSync(), 'Root doc not found');
|
|
101
|
+
|
|
102
|
+
const newRootContent = await convertLegacySpaceRootDoc(structuredClone(rootHandle.docSync()!));
|
|
103
|
+
|
|
104
|
+
for (const [id, url] of Object.entries(newRootContent.links ?? {})) {
|
|
105
|
+
const handle = context.repo.find(url as any);
|
|
106
|
+
await cancelWithContext(ctx, asyncTimeout(handle.whenReady(), 10_000));
|
|
107
|
+
invariant(handle.docSync(), 'Doc not found');
|
|
108
|
+
const newDoc = await convertLegacyReferences(structuredClone(handle.docSync()!));
|
|
109
|
+
const migratedDoc = migrateDocument(handle.docSync(), newDoc);
|
|
110
|
+
const newHandle = context.repo.import(am.save(migratedDoc));
|
|
111
|
+
newRootContent.links![id] = newHandle.url;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const migratedRoot = migrateDocument(rootHandle.docSync(), newRootContent);
|
|
115
|
+
const newRoot = context.repo.import(am.save(migratedRoot));
|
|
116
|
+
|
|
117
|
+
await context.repo.flush();
|
|
118
|
+
return {
|
|
119
|
+
newRoot: newRoot.url,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// TODO(dmaretskyi): This path doesn't seem to fit here. This is not a migration.
|
|
123
|
+
case CreateEpochRequest.Migration.REPLACE_AUTOMERGE_ROOT: {
|
|
124
|
+
invariant(context.newAutomergeRoot);
|
|
125
|
+
|
|
126
|
+
// Defensive programming - it should be the responsibility of the caller to flush the new root.
|
|
127
|
+
await context.repo.flush();
|
|
128
|
+
return {
|
|
129
|
+
newRoot: context.newAutomergeRoot,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {};
|
|
135
|
+
};
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type UpdateSpaceRequest,
|
|
31
31
|
type WriteCredentialsRequest,
|
|
32
32
|
type UpdateMemberRoleRequest,
|
|
33
|
+
type CreateEpochResponse,
|
|
33
34
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
34
35
|
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
35
36
|
import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';
|
|
@@ -208,14 +209,16 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
async createEpoch({ spaceKey, migration }: CreateEpochRequest) {
|
|
212
|
+
async createEpoch({ spaceKey, migration, automergeRootUrl }: CreateEpochRequest): Promise<CreateEpochResponse> {
|
|
212
213
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
213
214
|
const space = dataSpaceManager.spaces.get(spaceKey) ?? raise(new SpaceNotFoundError(spaceKey));
|
|
214
|
-
await space.createEpoch({ migration });
|
|
215
|
+
const credential = await space.createEpoch({ migration, newAutomergeRoot: automergeRootUrl });
|
|
216
|
+
return { epochCredential: credential ?? undefined };
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
private _serializeSpace(space: DataSpace): Space {
|
|
218
220
|
return {
|
|
221
|
+
id: space.id,
|
|
219
222
|
spaceKey: space.key,
|
|
220
223
|
state: space.state,
|
|
221
224
|
error: space.error ? encodeError(space.error) : undefined,
|
|
@@ -13,14 +13,14 @@ import { Keyring } from '@dxos/keyring';
|
|
|
13
13
|
import { type LevelDB } from '@dxos/kv-store';
|
|
14
14
|
import { createTestLevel } from '@dxos/kv-store/testing';
|
|
15
15
|
import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
|
|
16
|
-
import { MemoryTransportFactory,
|
|
16
|
+
import { MemoryTransportFactory, SwarmNetworkManager } from '@dxos/network-manager';
|
|
17
17
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
18
18
|
import { createStorage, StorageType, type Storage } from '@dxos/random-access-storage';
|
|
19
19
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
20
20
|
|
|
21
21
|
import { InvitationsHandler, InvitationsManager, SpaceInvitationProtocol } from '../invitations';
|
|
22
|
-
import { ClientServicesHost, ServiceContext } from '../services';
|
|
23
|
-
import { DataSpaceManager, type SigningContext } from '../spaces';
|
|
22
|
+
import { ClientServicesHost, ServiceContext, type ServiceContextRuntimeParams } from '../services';
|
|
23
|
+
import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
|
|
24
24
|
|
|
25
25
|
//
|
|
26
26
|
// TODO(burdon): Replace with test builder.
|
|
@@ -37,12 +37,14 @@ export const createServiceHost = (config: Config, signalManagerContext: MemorySi
|
|
|
37
37
|
export const createServiceContext = async ({
|
|
38
38
|
signalContext = new MemorySignalManagerContext(),
|
|
39
39
|
storage = createStorage({ type: StorageType.RAM }),
|
|
40
|
+
runtimeParams,
|
|
40
41
|
}: {
|
|
41
42
|
signalContext?: MemorySignalManagerContext;
|
|
42
43
|
storage?: Storage;
|
|
44
|
+
runtimeParams?: ServiceContextRuntimeParams;
|
|
43
45
|
} = {}) => {
|
|
44
46
|
const signalManager = new MemorySignalManager(signalContext);
|
|
45
|
-
const networkManager = new
|
|
47
|
+
const networkManager = new SwarmNetworkManager({
|
|
46
48
|
signalManager,
|
|
47
49
|
transportFactory: MemoryTransportFactory,
|
|
48
50
|
});
|
|
@@ -51,6 +53,7 @@ export const createServiceContext = async ({
|
|
|
51
53
|
|
|
52
54
|
return new ServiceContext(storage, level, networkManager, signalManager, {
|
|
53
55
|
invitationConnectionDefaultParams: { controlHeartbeatInterval: 200 },
|
|
56
|
+
...runtimeParams,
|
|
54
57
|
});
|
|
55
58
|
};
|
|
56
59
|
|
|
@@ -88,6 +91,7 @@ export class TestBuilder {
|
|
|
88
91
|
|
|
89
92
|
export type TestPeerOpts = {
|
|
90
93
|
dataStore?: StorageType;
|
|
94
|
+
dataSpaceParams?: DataSpaceManagerRuntimeParams;
|
|
91
95
|
};
|
|
92
96
|
|
|
93
97
|
export type TestPeerProps = {
|
|
@@ -96,7 +100,7 @@ export type TestPeerProps = {
|
|
|
96
100
|
feedStore?: FeedStore<any>;
|
|
97
101
|
metadataStore?: MetadataStore;
|
|
98
102
|
keyring?: Keyring;
|
|
99
|
-
networkManager?:
|
|
103
|
+
networkManager?: SwarmNetworkManager;
|
|
100
104
|
spaceManager?: SpaceManager;
|
|
101
105
|
dataSpaceManager?: DataSpaceManager;
|
|
102
106
|
snapshotStore?: SnapshotStore;
|
|
@@ -110,8 +114,8 @@ export class TestPeer {
|
|
|
110
114
|
private _props: TestPeerProps = {};
|
|
111
115
|
|
|
112
116
|
constructor(
|
|
113
|
-
private readonly
|
|
114
|
-
private readonly
|
|
117
|
+
private readonly _signalContext: MemorySignalManagerContext,
|
|
118
|
+
private readonly _opts: TestPeerOpts = { dataStore: StorageType.RAM },
|
|
115
119
|
) {}
|
|
116
120
|
|
|
117
121
|
get props() {
|
|
@@ -119,7 +123,7 @@ export class TestPeer {
|
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
get storage() {
|
|
122
|
-
return (this._props.storage ??= createStorage({ type: this.
|
|
126
|
+
return (this._props.storage ??= createStorage({ type: this._opts.dataStore }));
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
get keyring() {
|
|
@@ -155,8 +159,8 @@ export class TestPeer {
|
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
get networkManager() {
|
|
158
|
-
return (this._props.networkManager ??= new
|
|
159
|
-
signalManager: new MemorySignalManager(this.
|
|
162
|
+
return (this._props.networkManager ??= new SwarmNetworkManager({
|
|
163
|
+
signalManager: new MemorySignalManager(this._signalContext),
|
|
160
164
|
transportFactory: MemoryTransportFactory,
|
|
161
165
|
}));
|
|
162
166
|
}
|
|
@@ -176,10 +180,7 @@ export class TestPeer {
|
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
get echoHost() {
|
|
179
|
-
return (this._props.echoHost ??= new EchoHost({
|
|
180
|
-
kv: this.level,
|
|
181
|
-
storage: this.storage,
|
|
182
|
-
}));
|
|
183
|
+
return (this._props.echoHost ??= new EchoHost({ kv: this.level }));
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
get dataSpaceManager(): DataSpaceManager {
|
|
@@ -191,6 +192,7 @@ export class TestPeer {
|
|
|
191
192
|
this.feedStore,
|
|
192
193
|
this.echoHost,
|
|
193
194
|
this.invitationsManager,
|
|
195
|
+
this._opts.dataSpaceParams,
|
|
194
196
|
));
|
|
195
197
|
}
|
|
196
198
|
|
package/src/version.ts
CHANGED