@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.
Files changed (33) hide show
  1. package/dist/lib/browser/{chunk-TZL7PJDX.mjs → chunk-C2VXW65X.mjs} +691 -536
  2. package/dist/lib/browser/chunk-C2VXW65X.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -3
  4. package/dist/lib/browser/index.mjs.map +1 -1
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-STUWVNPH.cjs → chunk-OD7BTUYY.cjs} +842 -690
  8. package/dist/lib/node/chunk-OD7BTUYY.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +41 -43
  10. package/dist/lib/node/index.cjs.map +1 -1
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/packlets/testing/index.cjs +8 -8
  13. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  14. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts +4 -1
  15. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  16. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  17. package/dist/types/src/packlets/spaces/data-space.d.ts +9 -9
  18. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  19. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +23 -0
  20. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -0
  21. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
  22. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  23. package/dist/types/src/version.d.ts +1 -1
  24. package/package.json +36 -36
  25. package/src/packlets/identity/identity-service.ts +33 -7
  26. package/src/packlets/spaces/automerge-space-state.ts +11 -2
  27. package/src/packlets/spaces/data-space-manager.ts +34 -13
  28. package/src/packlets/spaces/data-space.ts +73 -145
  29. package/src/packlets/spaces/epoch-migrations.ts +135 -0
  30. package/src/packlets/spaces/spaces-service.ts +4 -2
  31. package/src/version.ts +1 -1
  32. package/dist/lib/browser/chunk-TZL7PJDX.mjs.map +0 -7
  33. 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, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } from '@dxos/async';
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
- AutomergeDocumentLoaderImpl,
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, invariant } from '@dxos/invariant';
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 { CreateEpochRequest, SpaceState, type Space as SpaceProto } from '@dxos/protocols/proto/dxos/client/services';
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, assignDeep } from '@dxos/util';
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
- // Wait for the first epoch.
288
- await cancelWithContext(this._ctx, this.automergeSpaceState.ensureEpochInitialized());
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
- log('data pipeline ready');
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
- if (!this._echoHost.roots.has(handle.documentId)) {
401
- await this._echoHost.openSpaceRoot(handle.url);
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
- log.warn('echo database root already exists', { space: this.key, rootUrl });
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
- let epoch: Epoch | undefined;
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
- // TODO(mykola): Delete old root.
505
-
506
- // TODO(dmaretskyi): Unify epoch construction.
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
- if (!epoch) {
530
- return;
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 receipt = await this.inner.controlPipeline.writer.write({
534
- credential: {
535
- credential: await this._signingContext.credentialSigner.createCredential({
536
- subject: this.key,
537
- assertion: {
538
- '@type': 'dxos.halo.credentials.Epoch',
539
- ...epoch,
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.bfee100";
1
+ export const DXOS_VERSION = "0.5.9-main.d63ef8d";