@dxos/client-services 0.5.9-main.bfee100 → 0.5.9-main.d63ef8d
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-TZL7PJDX.mjs → chunk-C2VXW65X.mjs} +691 -536
- package/dist/lib/browser/chunk-C2VXW65X.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -3
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +1 -1
- package/dist/lib/node/{chunk-STUWVNPH.cjs → chunk-OD7BTUYY.cjs} +842 -690
- package/dist/lib/node/chunk-OD7BTUYY.cjs.map +7 -0
- package/dist/lib/node/index.cjs +41 -43
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +8 -8
- package/dist/types/src/packlets/identity/identity-service.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.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +9 -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/version.d.ts +1 -1
- package/package.json +36 -36
- package/src/packlets/identity/identity-service.ts +33 -7
- package/src/packlets/spaces/automerge-space-state.ts +11 -2
- package/src/packlets/spaces/data-space-manager.ts +34 -13
- package/src/packlets/spaces/data-space.ts +73 -145
- package/src/packlets/spaces/epoch-migrations.ts +135 -0
- package/src/packlets/spaces/spaces-service.ts +4 -2
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-TZL7PJDX.mjs.map +0 -7
- package/dist/lib/node/chunk-STUWVNPH.cjs.map +0 -7
|
@@ -2,27 +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
|
-
createIdFromSpaceKey,
|
|
13
|
-
createMappedFeedWriter,
|
|
14
|
-
type MetadataStore,
|
|
15
|
-
type Space,
|
|
16
|
-
} from '@dxos/echo-pipeline';
|
|
17
|
-
import { type ObjectStructure, type SpaceDoc } from '@dxos/echo-protocol';
|
|
18
|
-
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';
|
|
19
13
|
import { type FeedStore } from '@dxos/feed-store';
|
|
20
|
-
import { failedInvariant
|
|
14
|
+
import { failedInvariant } from '@dxos/invariant';
|
|
21
15
|
import { type Keyring } from '@dxos/keyring';
|
|
22
16
|
import { PublicKey } from '@dxos/keys';
|
|
23
17
|
import { log } from '@dxos/log';
|
|
24
18
|
import { CancelledError, SystemError } from '@dxos/protocols';
|
|
25
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type CreateEpochRequest,
|
|
21
|
+
SpaceState,
|
|
22
|
+
type Space as SpaceProto,
|
|
23
|
+
} from '@dxos/protocols/proto/dxos/client/services';
|
|
26
24
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
27
25
|
import { type SpaceCache } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
28
26
|
import {
|
|
@@ -36,10 +34,11 @@ import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gos
|
|
|
36
34
|
import { type Gossip, type Presence } from '@dxos/teleport-extension-gossip';
|
|
37
35
|
import { Timeframe } from '@dxos/timeframe';
|
|
38
36
|
import { trace } from '@dxos/tracing';
|
|
39
|
-
import { ComplexSet
|
|
37
|
+
import { ComplexSet } from '@dxos/util';
|
|
40
38
|
|
|
41
39
|
import { AutomergeSpaceState } from './automerge-space-state';
|
|
42
40
|
import { type SigningContext } from './data-space-manager';
|
|
41
|
+
import { runEpochMigration } from './epoch-migrations';
|
|
43
42
|
import { NotarizationPlugin } from './notarization-plugin';
|
|
44
43
|
import { TrustedKeySetAuthVerifier } from '../identity';
|
|
45
44
|
|
|
@@ -100,8 +99,12 @@ export class DataSpace {
|
|
|
100
99
|
// TODO(dmaretskyi): Move into Space?
|
|
101
100
|
private readonly _automergeSpaceState = new AutomergeSpaceState((rootUrl) => this._onNewAutomergeRoot(rootUrl));
|
|
102
101
|
|
|
102
|
+
private readonly _epochProcessingMutex = new Mutex();
|
|
103
|
+
|
|
103
104
|
private _state = SpaceState.CLOSED;
|
|
104
105
|
|
|
106
|
+
private _databaseRoot: DatabaseRoot | null = null;
|
|
107
|
+
|
|
105
108
|
/**
|
|
106
109
|
* Error for _state === SpaceState.ERROR.
|
|
107
110
|
*/
|
|
@@ -183,6 +186,10 @@ export class DataSpace {
|
|
|
183
186
|
return this._automergeSpaceState;
|
|
184
187
|
}
|
|
185
188
|
|
|
189
|
+
get databaseRoot(): DatabaseRoot | null {
|
|
190
|
+
return this._databaseRoot;
|
|
191
|
+
}
|
|
192
|
+
|
|
186
193
|
@trace.info({ depth: null })
|
|
187
194
|
private get _automergeInfo() {
|
|
188
195
|
return {
|
|
@@ -203,6 +210,7 @@ export class DataSpace {
|
|
|
203
210
|
await this._gossip.open();
|
|
204
211
|
await this._notarizationPlugin.open();
|
|
205
212
|
await this._inner.spaceState.addCredentialProcessor(this._notarizationPlugin);
|
|
213
|
+
await this._automergeSpaceState.open();
|
|
206
214
|
await this._inner.spaceState.addCredentialProcessor(this._automergeSpaceState);
|
|
207
215
|
await this._inner.open(new Context());
|
|
208
216
|
this._state = SpaceState.CONTROL_ONLY;
|
|
@@ -228,6 +236,7 @@ export class DataSpace {
|
|
|
228
236
|
|
|
229
237
|
await this._inner.close();
|
|
230
238
|
await this._inner.spaceState.removeCredentialProcessor(this._automergeSpaceState);
|
|
239
|
+
await this._automergeSpaceState.close();
|
|
231
240
|
await this._inner.spaceState.removeCredentialProcessor(this._notarizationPlugin);
|
|
232
241
|
await this._notarizationPlugin.close();
|
|
233
242
|
|
|
@@ -282,12 +291,15 @@ export class DataSpace {
|
|
|
282
291
|
// Allow other tasks to run before loading the data pipeline.
|
|
283
292
|
await sleep(1);
|
|
284
293
|
|
|
294
|
+
const ready = this.stateUpdate.waitForCondition(() => this._state === SpaceState.READY);
|
|
295
|
+
|
|
285
296
|
this._automergeSpaceState.startProcessingRootDocs();
|
|
286
297
|
|
|
287
|
-
//
|
|
288
|
-
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
|
+
}
|
|
289
301
|
|
|
290
|
-
|
|
302
|
+
private async _enterReadyState() {
|
|
291
303
|
await this._callbacks.beforeReady?.();
|
|
292
304
|
|
|
293
305
|
this._state = SpaceState.READY;
|
|
@@ -379,6 +391,7 @@ export class DataSpace {
|
|
|
379
391
|
this._echoHost.replicateDocument(rootUrl);
|
|
380
392
|
const handle = this._echoHost.automergeRepo.find(rootUrl as any);
|
|
381
393
|
|
|
394
|
+
// TODO(dmaretskyi): Make this single-threaded (but doc loading should still be parallel to not block epoch processing).
|
|
382
395
|
queueMicrotask(async () => {
|
|
383
396
|
try {
|
|
384
397
|
await warnAfterTimeout(5_000, 'Automerge root doc load timeout (DataSpace)', async () => {
|
|
@@ -388,6 +401,10 @@ export class DataSpace {
|
|
|
388
401
|
return;
|
|
389
402
|
}
|
|
390
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.
|
|
391
408
|
const doc = handle.docSync() ?? failedInvariant();
|
|
392
409
|
if (!doc.access?.spaceKey) {
|
|
393
410
|
handle.change((doc: any) => {
|
|
@@ -397,10 +414,15 @@ export class DataSpace {
|
|
|
397
414
|
|
|
398
415
|
// TODO(dmaretskyi): Close roots.
|
|
399
416
|
// TODO(dmaretskyi): How do we handle changing to the next EPOCH?
|
|
400
|
-
|
|
401
|
-
|
|
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();
|
|
402
422
|
} else {
|
|
403
|
-
|
|
423
|
+
if (this._state !== SpaceState.READY) {
|
|
424
|
+
await this._enterReadyState();
|
|
425
|
+
}
|
|
404
426
|
}
|
|
405
427
|
} catch (err) {
|
|
406
428
|
if (err instanceof ContextDisposedError) {
|
|
@@ -423,127 +445,46 @@ export class DataSpace {
|
|
|
423
445
|
await this.inner.controlPipeline.writer.write({ credential: { credential } });
|
|
424
446
|
}
|
|
425
447
|
|
|
426
|
-
async createEpoch(options?: CreateEpochOptions) {
|
|
427
|
-
|
|
428
|
-
switch (options?.migration) {
|
|
429
|
-
case undefined:
|
|
430
|
-
case CreateEpochRequest.Migration.NONE:
|
|
431
|
-
{
|
|
432
|
-
// TODO(dmaretskyi): Unify epoch construction.
|
|
433
|
-
epoch = {
|
|
434
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
435
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
436
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
437
|
-
automergeRoot: this._automergeSpaceState.lastEpoch?.subject.assertion?.automergeRoot,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
break;
|
|
441
|
-
case CreateEpochRequest.Migration.INIT_AUTOMERGE:
|
|
442
|
-
{
|
|
443
|
-
const document = this._echoHost.automergeRepo.create();
|
|
444
|
-
// TODO(dmaretskyi): Unify epoch construction.
|
|
445
|
-
epoch = {
|
|
446
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
447
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
448
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
449
|
-
automergeRoot: document.url,
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
break;
|
|
453
|
-
case CreateEpochRequest.Migration.PRUNE_AUTOMERGE_ROOT_HISTORY:
|
|
454
|
-
{
|
|
455
|
-
const currentRootUrl = this._automergeSpaceState.rootUrl;
|
|
456
|
-
const rootHandle = this._echoHost.automergeRepo.find(currentRootUrl as any);
|
|
457
|
-
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
458
|
-
const newRoot = this._echoHost.automergeRepo.create(rootHandle.docSync());
|
|
459
|
-
await this._echoHost.automergeRepo.flush([newRoot.documentId]);
|
|
460
|
-
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
461
|
-
// TODO(dmaretskyi): Unify epoch construction.
|
|
462
|
-
epoch = {
|
|
463
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
464
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
465
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
466
|
-
automergeRoot: newRoot.url,
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
break;
|
|
470
|
-
case CreateEpochRequest.Migration.FRAGMENT_AUTOMERGE_ROOT:
|
|
471
|
-
{
|
|
472
|
-
log.info('Fragmenting');
|
|
473
|
-
|
|
474
|
-
const currentRootUrl = this._automergeSpaceState.rootUrl;
|
|
475
|
-
const rootHandle = this._echoHost.automergeRepo.find<SpaceDoc>(currentRootUrl as any);
|
|
476
|
-
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
477
|
-
|
|
478
|
-
// Find properties object.
|
|
479
|
-
const objects = Object.entries((rootHandle.docSync() as SpaceDoc).objects!);
|
|
480
|
-
const properties = findPropertiesObject(rootHandle.docSync() as SpaceDoc);
|
|
481
|
-
const otherObjects = objects.filter(([key]) => key !== properties?.[0]);
|
|
482
|
-
invariant(properties, 'Properties not found');
|
|
483
|
-
|
|
484
|
-
// Create a new space doc with the properties object.
|
|
485
|
-
const newSpaceDoc: SpaceDoc = { ...rootHandle.docSync(), objects: Object.fromEntries([properties]) };
|
|
486
|
-
const newRoot = this._echoHost.automergeRepo.create(newSpaceDoc);
|
|
487
|
-
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
488
|
-
|
|
489
|
-
// Create new automerge documents for all objects.
|
|
490
|
-
const docLoader = new AutomergeDocumentLoaderImpl(
|
|
491
|
-
await createIdFromSpaceKey(this.key),
|
|
492
|
-
this._echoHost.automergeRepo,
|
|
493
|
-
this.key,
|
|
494
|
-
);
|
|
495
|
-
await docLoader.loadSpaceRootDocHandle(this._ctx, { rootUrl: newRoot.url });
|
|
496
|
-
|
|
497
|
-
otherObjects.forEach(([key, value]) => {
|
|
498
|
-
const handle = docLoader.createDocumentForObject(key);
|
|
499
|
-
handle.change((doc: any) => {
|
|
500
|
-
assignDeep(doc, ['objects', key], value);
|
|
501
|
-
});
|
|
502
|
-
});
|
|
448
|
+
async createEpoch(options?: CreateEpochOptions): Promise<SpecificCredential<Epoch> | null> {
|
|
449
|
+
const ctx = this._ctx.derive();
|
|
503
450
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
epoch = {
|
|
508
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
509
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
510
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
511
|
-
automergeRoot: newRoot.url,
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
break;
|
|
515
|
-
case CreateEpochRequest.Migration.REPLACE_AUTOMERGE_ROOT:
|
|
516
|
-
{
|
|
517
|
-
invariant(options.newAutomergeRoot);
|
|
518
|
-
// TODO(dmaretskyi): Unify epoch construction.
|
|
519
|
-
epoch = {
|
|
520
|
-
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
521
|
-
number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
|
|
522
|
-
timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
|
|
523
|
-
automergeRoot: options.newAutomergeRoot,
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
break;
|
|
451
|
+
// Preserving existing behavior.
|
|
452
|
+
if (!options?.migration) {
|
|
453
|
+
return null;
|
|
527
454
|
}
|
|
528
455
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
+
});
|
|
532
464
|
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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,
|
|
542
477
|
},
|
|
478
|
+
})) as SpecificCredential<Epoch>;
|
|
479
|
+
|
|
480
|
+
const receipt = await this.inner.controlPipeline.writer.write({
|
|
481
|
+
credential: { credential },
|
|
543
482
|
});
|
|
544
483
|
|
|
545
484
|
await this.inner.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
|
|
546
485
|
await this._echoHost.updateIndexes();
|
|
486
|
+
|
|
487
|
+
return credential;
|
|
547
488
|
}
|
|
548
489
|
|
|
549
490
|
@synchronized
|
|
@@ -572,16 +513,3 @@ export class DataSpace {
|
|
|
572
513
|
this.stateUpdate.emit();
|
|
573
514
|
}
|
|
574
515
|
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Assumes properties are at root.
|
|
578
|
-
*/
|
|
579
|
-
export const findPropertiesObject = (spaceDoc: SpaceDoc): [string, ObjectStructure] | undefined => {
|
|
580
|
-
for (const id in spaceDoc.objects ?? {}) {
|
|
581
|
-
const obj = spaceDoc.objects![id];
|
|
582
|
-
if (obj.system.type?.itemId === TYPE_PROPERTIES) {
|
|
583
|
-
return [id, obj];
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return undefined;
|
|
587
|
-
};
|
|
@@ -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,10 +209,11 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
async createEpoch({ spaceKey, migration, automergeRootUrl }: 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, newAutomergeRoot: automergeRootUrl });
|
|
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 {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DXOS_VERSION = "0.5.9-main.
|
|
1
|
+
export const DXOS_VERSION = "0.5.9-main.d63ef8d";
|