@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.
Files changed (90) hide show
  1. package/dist/lib/browser/{chunk-JECXTGZA.mjs → chunk-QZ5LNV55.mjs} +1781 -1480
  2. package/dist/lib/browser/chunk-QZ5LNV55.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +55 -365
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +14 -14
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-OR7LRWDY.cjs → chunk-V7UMWFUS.cjs} +2055 -1761
  9. package/dist/lib/node/chunk-V7UMWFUS.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +92 -397
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/packlets/testing/index.cjs +19 -19
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/index.d.ts +3 -3
  16. package/dist/types/src/index.d.ts.map +1 -1
  17. package/dist/types/src/packlets/devtools/network.d.ts +4 -4
  18. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  19. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +19 -0
  20. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +1 -0
  21. package/dist/types/src/packlets/identity/identity-service.d.ts +14 -7
  22. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  23. package/dist/types/src/packlets/identity/identity.d.ts +4 -1
  24. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  25. package/dist/types/src/packlets/network/network-service.d.ts +2 -2
  26. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  27. package/dist/types/src/packlets/services/service-context.d.ts +4 -5
  28. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  29. package/dist/types/src/packlets/services/service-host.d.ts +1 -1
  30. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  31. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts +4 -1
  32. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  33. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +5 -3
  34. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  35. package/dist/types/src/packlets/spaces/data-space.d.ts +11 -9
  36. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  37. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +23 -0
  38. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -0
  39. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
  40. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  41. package/dist/types/src/packlets/testing/test-builder.d.ts +11 -9
  42. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  43. package/dist/types/src/packlets/worker/index.d.ts +3 -0
  44. package/dist/types/src/packlets/worker/index.d.ts.map +1 -0
  45. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -0
  46. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -0
  47. package/dist/types/src/version.d.ts +1 -1
  48. package/dist/types/src/version.d.ts.map +1 -1
  49. package/package.json +36 -36
  50. package/src/index.ts +3 -3
  51. package/src/packlets/devtools/network.ts +4 -4
  52. package/src/packlets/identity/default-space-state-machine.ts +44 -0
  53. package/src/packlets/identity/identity-manager.test.ts +2 -2
  54. package/src/packlets/identity/identity-service.test.ts +35 -5
  55. package/src/packlets/identity/identity-service.ts +55 -8
  56. package/src/packlets/identity/identity.test.ts +8 -4
  57. package/src/packlets/identity/identity.ts +25 -2
  58. package/src/packlets/invitations/invitations-handler.ts +2 -2
  59. package/src/packlets/network/network-service.ts +2 -2
  60. package/src/packlets/services/service-context.ts +4 -7
  61. package/src/packlets/services/service-host.ts +16 -44
  62. package/src/packlets/spaces/automerge-space-state.ts +11 -2
  63. package/src/packlets/spaces/data-space-manager.test.ts +46 -1
  64. package/src/packlets/spaces/data-space-manager.ts +81 -31
  65. package/src/packlets/spaces/data-space.ts +89 -132
  66. package/src/packlets/spaces/epoch-migrations.ts +135 -0
  67. package/src/packlets/spaces/spaces-service.ts +5 -2
  68. package/src/packlets/testing/test-builder.ts +16 -14
  69. package/src/packlets/worker/index.ts +6 -0
  70. package/src/version.ts +1 -5
  71. package/dist/lib/browser/chunk-JECXTGZA.mjs.map +0 -7
  72. package/dist/lib/node/chunk-OR7LRWDY.cjs.map +0 -7
  73. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +0 -37
  74. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +0 -1
  75. package/dist/types/src/packlets/vault/index.d.ts +0 -6
  76. package/dist/types/src/packlets/vault/index.d.ts.map +0 -1
  77. package/dist/types/src/packlets/vault/shared-worker-connection.d.ts +0 -36
  78. package/dist/types/src/packlets/vault/shared-worker-connection.d.ts.map +0 -1
  79. package/dist/types/src/packlets/vault/shell-runtime.d.ts +0 -33
  80. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +0 -1
  81. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +0 -1
  82. package/dist/types/src/packlets/vault/worker-session.d.ts.map +0 -1
  83. package/src/packlets/vault/iframe-host-runtime.ts +0 -130
  84. package/src/packlets/vault/index.ts +0 -9
  85. package/src/packlets/vault/shared-worker-connection.ts +0 -115
  86. package/src/packlets/vault/shell-runtime.ts +0 -111
  87. /package/dist/types/src/packlets/{vault → worker}/worker-runtime.d.ts +0 -0
  88. /package/dist/types/src/packlets/{vault → worker}/worker-session.d.ts +0 -0
  89. /package/src/packlets/{vault → worker}/worker-runtime.ts +0 -0
  90. /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, 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
- 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, invariant } from '@dxos/invariant';
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 { 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';
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, assignDeep } from '@dxos/util';
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
- await this._open();
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.destroy();
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
- // Wait for the first epoch.
278
- 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
+ }
279
301
 
280
- log('data pipeline ready');
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
- if (!this._echoHost.roots.has(handle.documentId)) {
391
- 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();
392
422
  } else {
393
- log.warn('echo database root already exists', { space: this.key, rootUrl });
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
- let epoch: Epoch | undefined;
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
- // TODO(dmaretskyi): Unify epoch construction.
492
- epoch = {
493
- previousId: this._automergeSpaceState.lastEpoch?.id,
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
- if (!epoch) {
503
- return;
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 receipt = await this.inner.controlPipeline.writer.write({
507
- credential: {
508
- credential: await this._signingContext.credentialSigner.createCredential({
509
- subject: this.key,
510
- assertion: {
511
- '@type': 'dxos.halo.credentials.Epoch',
512
- ...epoch,
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 (this._state !== SpaceState.INACTIVE) {
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
- await this._close();
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, NetworkManager } from '@dxos/network-manager';
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 NetworkManager({
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?: 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 signalContext: MemorySignalManagerContext,
114
- private readonly opts: TestPeerOpts = { dataStore: StorageType.RAM },
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.opts.dataStore }));
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 NetworkManager({
159
- signalManager: new MemorySignalManager(this.signalContext),
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
 
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ export * from './worker-runtime';
6
+ export * from './worker-session';
package/src/version.ts CHANGED
@@ -1,5 +1 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- export const DXOS_VERSION = '0.5.8'; // {x-release-please-version}
1
+ export const DXOS_VERSION = "0.5.9-main.079a532";