@dxos/client-services 0.4.10-main.3e0c8a5 → 0.4.10-main.3f5e2d2

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 (120) hide show
  1. package/dist/lib/browser/{chunk-6GHZYBUW.mjs → chunk-HIQTBJPW.mjs} +1452 -1145
  2. package/dist/lib/browser/chunk-HIQTBJPW.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +20 -6
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +141 -119
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-D5VK6MZQ.cjs → chunk-JGUWA36I.cjs} +1349 -1126
  9. package/dist/lib/node/chunk-JGUWA36I.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +55 -41
  11. package/dist/lib/node/index.cjs.map +3 -3
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/packlets/testing/index.cjs +139 -120
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/index.d.ts +1 -0
  16. package/dist/types/src/index.d.ts.map +1 -1
  17. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts +5 -0
  18. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -0
  19. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +5 -0
  20. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -0
  21. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts +15 -0
  22. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -0
  23. package/dist/types/src/packlets/{services → diagnostics}/diagnostics.d.ts +1 -1
  24. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -0
  25. package/dist/types/src/packlets/diagnostics/index.d.ts +4 -0
  26. package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -0
  27. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  28. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +3 -1
  29. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  30. package/dist/types/src/packlets/invitations/index.d.ts +1 -0
  31. package/dist/types/src/packlets/invitations/index.d.ts.map +1 -1
  32. package/dist/types/src/packlets/invitations/invitation-extension.d.ts +1 -0
  33. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
  34. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +6 -1
  35. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  36. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +8 -4
  37. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  38. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +44 -0
  39. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -0
  40. package/dist/types/src/packlets/invitations/invitations-service.d.ts +7 -23
  41. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  42. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -1
  43. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  44. package/dist/types/src/packlets/services/index.d.ts +1 -1
  45. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  46. package/dist/types/src/packlets/services/service-context.d.ts +12 -10
  47. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  48. package/dist/types/src/packlets/services/service-host.d.ts +5 -1
  49. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  50. package/dist/types/src/packlets/services/util.d.ts +1 -0
  51. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  52. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +8 -3
  53. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  54. package/dist/types/src/packlets/spaces/data-space.d.ts +4 -3
  55. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  56. package/dist/types/src/packlets/storage/index.d.ts +1 -0
  57. package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
  58. package/dist/types/src/packlets/storage/level.d.ts +4 -0
  59. package/dist/types/src/packlets/storage/level.d.ts.map +1 -0
  60. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  61. package/dist/types/src/packlets/storage/util.d.ts +4 -0
  62. package/dist/types/src/packlets/storage/util.d.ts.map +1 -0
  63. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  64. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  65. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  66. package/dist/types/src/packlets/testing/test-builder.d.ts +10 -4
  67. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  68. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  69. package/dist/types/src/version.d.ts +1 -1
  70. package/package.json +36 -34
  71. package/src/index.ts +1 -0
  72. package/src/packlets/devices/devices-service.test.ts +1 -1
  73. package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +94 -0
  74. package/src/packlets/diagnostics/diagnostics-broadcast.ts +20 -0
  75. package/src/packlets/diagnostics/diagnostics-collector.ts +65 -0
  76. package/src/packlets/{services → diagnostics}/diagnostics.ts +2 -2
  77. package/src/packlets/diagnostics/index.ts +7 -0
  78. package/src/packlets/identity/identity-manager.ts +1 -0
  79. package/src/packlets/identity/identity-service.test.ts +1 -1
  80. package/src/packlets/identity/identity.test.ts +3 -0
  81. package/src/packlets/invitations/device-invitation-protocol.test.ts +1 -1
  82. package/src/packlets/invitations/device-invitation-protocol.ts +6 -1
  83. package/src/packlets/invitations/index.ts +1 -0
  84. package/src/packlets/invitations/invitation-extension.ts +28 -1
  85. package/src/packlets/invitations/invitation-protocol.ts +7 -1
  86. package/src/packlets/invitations/invitations-handler.ts +75 -96
  87. package/src/packlets/invitations/invitations-manager.ts +271 -0
  88. package/src/packlets/invitations/invitations-service.ts +23 -168
  89. package/src/packlets/invitations/space-invitation-protocol.ts +45 -3
  90. package/src/packlets/network/network-service.test.ts +1 -1
  91. package/src/packlets/services/automerge-host.test.ts +13 -7
  92. package/src/packlets/services/index.ts +1 -1
  93. package/src/packlets/services/service-context.test.ts +9 -6
  94. package/src/packlets/services/service-context.ts +31 -26
  95. package/src/packlets/services/service-host.test.ts +6 -0
  96. package/src/packlets/services/service-host.ts +48 -32
  97. package/src/packlets/services/service-registry.test.ts +1 -1
  98. package/src/packlets/services/util.ts +2 -0
  99. package/src/packlets/spaces/data-space-manager.test.ts +4 -4
  100. package/src/packlets/spaces/data-space-manager.ts +56 -13
  101. package/src/packlets/spaces/data-space.ts +14 -19
  102. package/src/packlets/spaces/spaces-service.test.ts +1 -1
  103. package/src/packlets/storage/index.ts +1 -0
  104. package/src/packlets/storage/level.ts +19 -0
  105. package/src/packlets/storage/storage.ts +3 -9
  106. package/src/packlets/storage/util.ts +19 -0
  107. package/src/packlets/system/system-service.ts +1 -1
  108. package/src/packlets/testing/invitation-utils.ts +100 -97
  109. package/src/packlets/testing/test-builder.ts +40 -9
  110. package/src/packlets/vault/worker-runtime.ts +3 -1
  111. package/src/version.ts +1 -1
  112. package/dist/lib/browser/chunk-6GHZYBUW.mjs.map +0 -7
  113. package/dist/lib/node/chunk-D5VK6MZQ.cjs.map +0 -7
  114. package/dist/types/src/packlets/indexing/index.d.ts +0 -2
  115. package/dist/types/src/packlets/indexing/index.d.ts.map +0 -1
  116. package/dist/types/src/packlets/indexing/util.d.ts +0 -15
  117. package/dist/types/src/packlets/indexing/util.d.ts.map +0 -1
  118. package/dist/types/src/packlets/services/diagnostics.d.ts.map +0 -1
  119. package/src/packlets/indexing/index.ts +0 -5
  120. package/src/packlets/indexing/util.ts +0 -89
@@ -2,14 +2,14 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import { Event, sleep, synchronized } from '@dxos/async';
6
- import { clientServiceBundle, defaultKey, type ClientServices, PropertiesSchema } from '@dxos/client-protocol';
5
+ import { type Level } from 'level';
6
+
7
+ import { Event, synchronized } from '@dxos/async';
8
+ import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
7
9
  import { type Config } from '@dxos/config';
8
10
  import { Context } from '@dxos/context';
9
- import { DataServiceImpl, type SpaceDoc } from '@dxos/echo-pipeline';
10
- import * as E from '@dxos/echo-schema';
11
- import { createRawObjectDoc } from '@dxos/echo-schema';
12
- import { IndexServiceImpl } from '@dxos/indexing';
11
+ import { type ObjectStructure, encodeReference, type SpaceDoc, type LevelDB } from '@dxos/echo-pipeline';
12
+ import { getTypeReference } from '@dxos/echo-schema';
13
13
  import { invariant } from '@dxos/invariant';
14
14
  import { PublicKey } from '@dxos/keys';
15
15
  import { log } from '@dxos/log';
@@ -22,18 +22,22 @@ import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
22
22
  import { assignDeep } from '@dxos/util';
23
23
  import { WebsocketRpcClient } from '@dxos/websocket-rpc';
24
24
 
25
- import { createDiagnostics } from './diagnostics';
26
25
  import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
27
26
  import { ServiceRegistry } from './service-registry';
28
27
  import { DevicesServiceImpl } from '../devices';
29
28
  import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
29
+ import {
30
+ type CollectDiagnosticsBroadcastHandler,
31
+ createCollectDiagnosticsBroadcastHandler,
32
+ createDiagnostics,
33
+ } from '../diagnostics';
30
34
  import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
31
35
  import { InvitationsServiceImpl } from '../invitations';
32
36
  import { Lock, type ResourceLock } from '../locks';
33
37
  import { LoggingServiceImpl } from '../logging';
34
38
  import { NetworkServiceImpl } from '../network';
35
39
  import { SpacesServiceImpl } from '../spaces';
36
- import { createStorageObjects } from '../storage';
40
+ import { createLevel, createStorageObjects } from '../storage';
37
41
  import { SystemServiceImpl } from '../system';
38
42
 
39
43
  export type ClientServicesHostParams = {
@@ -45,6 +49,7 @@ export type ClientServicesHostParams = {
45
49
  signalManager?: SignalManager;
46
50
  connectionLog?: boolean;
47
51
  storage?: Storage;
52
+ level?: LevelDB;
48
53
  lockKey?: string;
49
54
  callbacks?: ClientServicesHostCallbacks;
50
55
  runtimeParams?: ServiceContextRuntimeParams;
@@ -77,11 +82,13 @@ export class ClientServicesHost {
77
82
  private _signalManager?: SignalManager;
78
83
  private _networkManager?: NetworkManager;
79
84
  private _storage?: Storage;
85
+ private _level?: Level<string, string>;
80
86
  private _callbacks?: ClientServicesHostCallbacks;
81
87
  private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
82
88
 
83
89
  private _serviceContext!: ServiceContext;
84
90
  private readonly _runtimeParams?: ServiceContextRuntimeParams;
91
+ private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
85
92
 
86
93
  @Trace.info()
87
94
  private _opening = false;
@@ -94,12 +101,14 @@ export class ClientServicesHost {
94
101
  transportFactory,
95
102
  signalManager,
96
103
  storage,
104
+ level,
97
105
  // TODO(wittjosiah): Turn this on by default.
98
106
  lockKey,
99
107
  callbacks,
100
108
  runtimeParams,
101
109
  }: ClientServicesHostParams = {}) {
102
110
  this._storage = storage;
111
+ this._level = level;
103
112
  this._callbacks = callbacks;
104
113
  this._runtimeParams = runtimeParams;
105
114
 
@@ -139,6 +148,7 @@ export class ClientServicesHost {
139
148
  },
140
149
  });
141
150
 
151
+ this.diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this._systemService);
142
152
  this._loggingService = new LoggingServiceImpl();
143
153
 
144
154
  this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
@@ -227,12 +237,19 @@ export class ClientServicesHost {
227
237
 
228
238
  this._opening = true;
229
239
  log('opening...', { lockKey: this._resourceLock?.lockKey });
240
+
241
+ if (!this._level) {
242
+ this._level = await createLevel(this._config.get('runtime.client.storage', {})!);
243
+ }
244
+ await this._level.open();
245
+
230
246
  await this._resourceLock?.acquire();
231
247
 
232
248
  await this._loggingService.open();
233
249
 
234
250
  this._serviceContext = new ServiceContext(
235
251
  this._storage,
252
+ this._level,
236
253
  this._networkManager,
237
254
  this._signalManager,
238
255
  this._runtimeParams,
@@ -248,11 +265,7 @@ export class ClientServicesHost {
248
265
  (profile) => this._serviceContext.broadcastProfileUpdate(profile),
249
266
  ),
250
267
 
251
- InvitationsService: new InvitationsServiceImpl(
252
- this._serviceContext.invitations,
253
- (invitation) => this._serviceContext.getInvitationHandler(invitation),
254
- this._serviceContext.metadataStore,
255
- ),
268
+ InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
256
269
 
257
270
  DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
258
271
 
@@ -265,12 +278,8 @@ export class ClientServicesHost {
265
278
  },
266
279
  ),
267
280
 
268
- DataService: new DataServiceImpl(this._serviceContext.automergeHost),
269
-
270
- IndexService: new IndexServiceImpl({
271
- indexer: this._serviceContext.indexer,
272
- automergeHost: this._serviceContext.automergeHost,
273
- }),
281
+ DataService: this._serviceContext.echoHost.dataService,
282
+ QueryService: this._serviceContext.echoHost.queryService,
274
283
 
275
284
  NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
276
285
 
@@ -286,11 +295,6 @@ export class ClientServicesHost {
286
295
  });
287
296
 
288
297
  await this._serviceContext.open(ctx);
289
- // TODO(nf): move to InvitationManager in ServiceContext?
290
- invariant(this.serviceRegistry.services.InvitationsService);
291
- const loadedInvitations = await this.serviceRegistry.services.InvitationsService.loadPersistentInvitations();
292
-
293
- log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
294
298
 
295
299
  const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
296
300
  if (devtoolsProxy) {
@@ -302,6 +306,7 @@ export class ClientServicesHost {
302
306
  });
303
307
  void this._devtoolsProxy.open();
304
308
  }
309
+ this.diagnosticsBroadcastHandler.start();
305
310
 
306
311
  this._opening = false;
307
312
  this._open = true;
@@ -320,10 +325,12 @@ export class ClientServicesHost {
320
325
 
321
326
  const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
322
327
  log('closing...', { deviceKey });
328
+ this.diagnosticsBroadcastHandler.stop();
323
329
  await this._devtoolsProxy?.close();
324
330
  this._serviceRegistry.setServices({ SystemService: this._systemService });
325
331
  await this._loggingService.close();
326
332
  await this._serviceContext.close();
333
+ await this._level?.close();
327
334
  this._open = false;
328
335
  this._statusUpdate.emit();
329
336
  log('closed', { deviceKey });
@@ -350,18 +357,27 @@ export class ClientServicesHost {
350
357
 
351
358
  const automergeIndex = space.automergeSpaceState.rootUrl;
352
359
  invariant(automergeIndex);
353
- const document = await this._serviceContext.automergeHost.repo.find<SpaceDoc>(automergeIndex as any);
360
+ const document = await this._serviceContext.echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
354
361
  await document.whenReady();
355
362
 
356
- const objectDocument = createRawObjectDoc(
357
- { [defaultKey]: identity.identityKey.toHex() },
358
- { type: E.getTypeReference(PropertiesSchema) },
359
- );
363
+ // TODO(dmaretskyi): Better API for low-level data access.
364
+ const properties: ObjectStructure = {
365
+ system: {
366
+ type: encodeReference(getTypeReference(Properties)!),
367
+ },
368
+ data: {
369
+ [defaultKey]: identity.identityKey.toHex(),
370
+ },
371
+ meta: {
372
+ keys: [],
373
+ },
374
+ };
375
+ const propertiesId = PublicKey.random().toHex();
360
376
  document.change((doc: SpaceDoc) => {
361
- assignDeep(doc, ['objects', objectDocument.id], objectDocument.handle.docSync());
377
+ assignDeep(doc, ['objects', propertiesId], properties);
362
378
  });
363
- // TODO: replace with flush when supported by automerge-repo
364
- await sleep(200);
379
+
380
+ await this._serviceContext.echoHost.flush();
365
381
 
366
382
  return identity;
367
383
  }
@@ -31,7 +31,7 @@ const serviceBundle = createServiceBundle<TestServices>({
31
31
  describe('service registry', () => {
32
32
  test('builds a service registry', async () => {
33
33
  const remoteSource = 'https://remote.source';
34
- const serviceContext = createServiceContext();
34
+ const serviceContext = await createServiceContext();
35
35
  await serviceContext.open(new Context());
36
36
 
37
37
  const serviceRegistry = new ServiceRegistry(serviceBundle, {
@@ -5,6 +5,8 @@
5
5
  import { PublicKey } from '@dxos/keys';
6
6
  import { humanize } from '@dxos/util';
7
7
 
8
+ export const ClientServicesProviderResource = Symbol.for('dxos.resource.ClientServices');
9
+
8
10
  // TODO(burdon): Move to util.
9
11
 
10
12
  export type JsonStringifyOptions = {
@@ -20,7 +20,7 @@ describe('DataSpaceManager', () => {
20
20
 
21
21
  const peer = builder.createPeer();
22
22
  await peer.createIdentity();
23
- await openAndClose(peer.dataSpaceManager);
23
+ await openAndClose(peer.echoHost, peer.dataSpaceManager);
24
24
 
25
25
  const space = await peer.dataSpaceManager.createSpace();
26
26
 
@@ -42,7 +42,7 @@ describe('DataSpaceManager', () => {
42
42
  const peer2 = builder.createPeer();
43
43
  await peer2.createIdentity();
44
44
 
45
- await openAndClose(peer1.dataSpaceManager, peer2.dataSpaceManager);
45
+ await openAndClose(peer1.echoHost, peer1.dataSpaceManager, peer2.echoHost, peer2.dataSpaceManager);
46
46
 
47
47
  const space1 = await peer1.dataSpaceManager.createSpace();
48
48
  await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
@@ -111,7 +111,7 @@ describe('DataSpaceManager', () => {
111
111
  await peer2.createIdentity();
112
112
  await peer2.dataSpaceManager.open();
113
113
 
114
- await openAndClose(peer1.dataSpaceManager, peer2.dataSpaceManager);
114
+ await openAndClose(peer1.echoHost, peer1.dataSpaceManager, peer2.echoHost, peer2.dataSpaceManager);
115
115
 
116
116
  const space1 = await peer1.dataSpaceManager.createSpace();
117
117
  await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
@@ -153,7 +153,7 @@ describe('DataSpaceManager', () => {
153
153
 
154
154
  const peer = builder.createPeer();
155
155
  await peer.createIdentity();
156
- await openAndClose(peer.dataSpaceManager);
156
+ await openAndClose(peer.echoHost, peer.dataSpaceManager);
157
157
 
158
158
  const space = await peer.dataSpaceManager.createSpace();
159
159
  await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
@@ -4,18 +4,20 @@
4
4
 
5
5
  import { Event, synchronized, trackLeaks } from '@dxos/async';
6
6
  import { Context, cancelWithContext } from '@dxos/context';
7
- import { getCredentialAssertion, type CredentialSigner } from '@dxos/credentials';
8
- import { type AutomergeHost, type MetadataStore, type Space, type SpaceManager } from '@dxos/echo-pipeline';
7
+ import { getCredentialAssertion, type CredentialSigner, type DelegateInvitationCredential } from '@dxos/credentials';
8
+ import { type EchoHost } from '@dxos/echo-db';
9
+ import { type MetadataStore, type Space, type SpaceManager } from '@dxos/echo-pipeline';
9
10
  import { type FeedStore } from '@dxos/feed-store';
10
11
  import { invariant } from '@dxos/invariant';
11
12
  import { type Keyring } from '@dxos/keyring';
12
13
  import { PublicKey } from '@dxos/keys';
13
14
  import { log } from '@dxos/log';
14
15
  import { trace } from '@dxos/protocols';
15
- import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
16
+ import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
16
17
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
17
18
  import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
18
19
  import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
20
+ import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
19
21
  import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
20
22
  import { type Timeframe } from '@dxos/timeframe';
21
23
  import { ComplexMap, deferFunction, forEachAsync } from '@dxos/util';
@@ -23,6 +25,7 @@ import { ComplexMap, deferFunction, forEachAsync } from '@dxos/util';
23
25
  import { DataSpace } from './data-space';
24
26
  import { spaceGenesis } from './genesis';
25
27
  import { createAuthProvider } from '../identity';
28
+ import { type InvitationsManager } from '../invitations';
26
29
 
27
30
  const PRESENCE_ANNOUNCE_INTERVAL = 10_000;
28
31
  const PRESENCE_OFFLINE_TIMEOUT = 20_000;
@@ -77,7 +80,8 @@ export class DataSpaceManager {
77
80
  private readonly _keyring: Keyring,
78
81
  private readonly _signingContext: SigningContext,
79
82
  private readonly _feedStore: FeedStore<FeedMessage>,
80
- private readonly _automergeHost: AutomergeHost,
83
+ private readonly _echoHost: EchoHost,
84
+ private readonly _invitationsManager: InvitationsManager,
81
85
  params?: DataSpaceManagerRuntimeParams,
82
86
  ) {
83
87
  const {
@@ -149,14 +153,10 @@ export class DataSpaceManager {
149
153
 
150
154
  log('creating space...', { spaceKey });
151
155
 
152
- const automergeRoot = this._automergeHost.repo.create();
153
- automergeRoot.change((doc: any) => {
154
- doc.access = { spaceKey: spaceKey.toHex() };
155
- });
156
-
156
+ const automergeRootUrl = await this._echoHost.createSpaceRoot(spaceKey);
157
157
  const space = await this._constructSpace(metadata);
158
158
 
159
- const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, automergeRoot.url);
159
+ const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, automergeRootUrl);
160
160
  await this._metadataStore.addSpace(metadata);
161
161
 
162
162
  const memberCredential = credentials[1];
@@ -240,13 +240,16 @@ export class DataSpaceManager {
240
240
  gossip.createExtension({ remotePeerId: session.remotePeerId }),
241
241
  );
242
242
  session.addExtension('dxos.mesh.teleport.notarization', dataSpace.notarizationPlugin.createExtension());
243
- this._automergeHost.authorizeDevice(space.key, session.remotePeerId);
244
- session.addExtension('dxos.mesh.teleport.automerge', this._automergeHost.createExtension());
243
+ this._echoHost.authorizeDevice(space.key, session.remotePeerId);
244
+ session.addExtension('dxos.mesh.teleport.automerge', this._echoHost.createReplicationExtension());
245
245
  },
246
246
  onAuthFailure: () => {
247
247
  log.warn('auth failure');
248
248
  },
249
249
  memberKey: this._signingContext.identityKey,
250
+ onDelegatedInvitationStatusChange: (invitation, isActive) => {
251
+ return this._handleInvitationStatusChange(dataSpace, invitation, isActive);
252
+ },
250
253
  });
251
254
  controlFeed && (await space.setControlFeed(controlFeed));
252
255
  dataFeed && (await space.setDataFeed(dataFeed));
@@ -259,6 +262,7 @@ export class DataSpaceManager {
259
262
  presence,
260
263
  keyring: this._keyring,
261
264
  feedStore: this._feedStore,
265
+ echoHost: this._echoHost,
262
266
  signingContext: this._signingContext,
263
267
  callbacks: {
264
268
  beforeReady: async () => {
@@ -267,6 +271,7 @@ export class DataSpaceManager {
267
271
  afterReady: async () => {
268
272
  log('after space ready', { space: space.key, open: this._isOpen });
269
273
  if (this._isOpen) {
274
+ await this._createDelegatedInvitations(dataSpace, [...space.spaceState.invitations.entries()]);
270
275
  this.updated.emit();
271
276
  }
272
277
  },
@@ -275,7 +280,6 @@ export class DataSpaceManager {
275
280
  },
276
281
  },
277
282
  cache: metadata.cache,
278
- automergeHost: this._automergeHost,
279
283
  });
280
284
 
281
285
  if (metadata.state !== SpaceState.INACTIVE) {
@@ -289,4 +293,43 @@ export class DataSpaceManager {
289
293
  this._spaces.set(metadata.key, dataSpace);
290
294
  return dataSpace;
291
295
  }
296
+
297
+ private async _handleInvitationStatusChange(
298
+ dataSpace: DataSpace | undefined,
299
+ delegatedInvitation: DelegateInvitationCredential,
300
+ isActive: boolean,
301
+ ): Promise<void> {
302
+ if (dataSpace?.state !== SpaceState.READY) {
303
+ return;
304
+ }
305
+ if (isActive) {
306
+ await this._createDelegatedInvitations(dataSpace, [
307
+ [delegatedInvitation.credentialId, delegatedInvitation.invitation],
308
+ ]);
309
+ } else {
310
+ await this._invitationsManager.cancelInvitation(delegatedInvitation.invitation);
311
+ }
312
+ }
313
+
314
+ private async _createDelegatedInvitations(
315
+ space: DataSpace,
316
+ invitations: Array<[PublicKey, DelegateSpaceInvitation]>,
317
+ ): Promise<void> {
318
+ const tasks = invitations.map(([credentialId, invitation]) => {
319
+ return this._invitationsManager.createInvitation({
320
+ type: Invitation.Type.DELEGATED,
321
+ kind: Invitation.Kind.SPACE,
322
+ spaceKey: space.key,
323
+ authMethod: invitation.authMethod,
324
+ invitationId: invitation.invitationId,
325
+ swarmKey: invitation.swarmKey,
326
+ guestKeypair: invitation.guestKey ? { publicKey: invitation.guestKey } : undefined,
327
+ lifetime: invitation.expiresOn ? invitation.expiresOn.getTime() - Date.now() : undefined,
328
+ multiUse: invitation.multiUse,
329
+ delegationCredentialId: credentialId,
330
+ persistent: false,
331
+ });
332
+ });
333
+ await Promise.all(tasks);
334
+ }
292
335
  }
@@ -6,15 +6,10 @@ import { Event, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } fr
6
6
  import { AUTH_TIMEOUT } from '@dxos/client-protocol';
7
7
  import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
8
8
  import { timed, warnAfterTimeout } from '@dxos/debug';
9
- import { TYPE_PROPERTIES } from '@dxos/echo-db';
10
- import {
11
- type MetadataStore,
12
- type Space,
13
- createMappedFeedWriter,
14
- type AutomergeHost,
15
- type SpaceDoc,
16
- } from '@dxos/echo-pipeline';
9
+ import { type EchoHost } from '@dxos/echo-db';
10
+ import { type MetadataStore, type Space, createMappedFeedWriter, type SpaceDoc } from '@dxos/echo-pipeline';
17
11
  import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
12
+ import { TYPE_PROPERTIES } from '@dxos/echo-schema';
18
13
  import { type FeedStore } from '@dxos/feed-store';
19
14
  import { failedInvariant, invariant } from '@dxos/invariant';
20
15
  import { type Keyring } from '@dxos/keyring';
@@ -66,10 +61,10 @@ export type DataSpaceParams = {
66
61
  presence: Presence;
67
62
  keyring: Keyring;
68
63
  feedStore: FeedStore<FeedMessage>;
64
+ echoHost: EchoHost;
69
65
  signingContext: SigningContext;
70
66
  callbacks?: DataSpaceCallbacks;
71
67
  cache?: SpaceCache;
72
- automergeHost: AutomergeHost;
73
68
  };
74
69
 
75
70
  export type CreateEpochOptions = {
@@ -92,7 +87,7 @@ export class DataSpace {
92
87
  private readonly _notarizationPlugin = new NotarizationPlugin();
93
88
  private readonly _callbacks: DataSpaceCallbacks;
94
89
  private readonly _cache?: SpaceCache = undefined;
95
- private readonly _automergeHost: AutomergeHost;
90
+ private readonly _echoHost: EchoHost;
96
91
 
97
92
  // TODO(dmaretskyi): Move into Space?
98
93
  private readonly _automergeSpaceState = new AutomergeSpaceState((rootUrl) => this._onNewAutomergeRoot(rootUrl));
@@ -120,7 +115,7 @@ export class DataSpace {
120
115
  this._metadataStore = params.metadataStore;
121
116
  this._signingContext = params.signingContext;
122
117
  this._callbacks = params.callbacks ?? {};
123
- this._automergeHost = params.automergeHost;
118
+ this._echoHost = params.echoHost;
124
119
 
125
120
  this.authVerifier = new TrustedKeySetAuthVerifier({
126
121
  trustedKeysProvider: () =>
@@ -363,8 +358,8 @@ export class DataSpace {
363
358
 
364
359
  private _onNewAutomergeRoot(rootUrl: string) {
365
360
  log('loading automerge root doc for space', { space: this.key, rootUrl });
366
- this._automergeHost._requestedDocs.add(rootUrl as any);
367
- const handle = this._automergeHost.repo.find(rootUrl as any);
361
+ this._echoHost.replicateDocument(rootUrl);
362
+ const handle = this._echoHost.automergeRepo.find(rootUrl as any);
368
363
 
369
364
  queueMicrotask(async () => {
370
365
  try {
@@ -419,7 +414,7 @@ export class DataSpace {
419
414
  break;
420
415
  case CreateEpochRequest.Migration.INIT_AUTOMERGE:
421
416
  {
422
- const document = this._automergeHost.repo.create();
417
+ const document = this._echoHost.automergeRepo.create();
423
418
  // TODO(dmaretskyi): Unify epoch construction.
424
419
  epoch = {
425
420
  previousId: this._automergeSpaceState.lastEpoch?.id,
@@ -432,9 +427,9 @@ export class DataSpace {
432
427
  case CreateEpochRequest.Migration.PRUNE_AUTOMERGE_ROOT_HISTORY:
433
428
  {
434
429
  const currentRootUrl = this._automergeSpaceState.rootUrl;
435
- const rootHandle = this._automergeHost.repo.find(currentRootUrl as any);
430
+ const rootHandle = this._echoHost.automergeRepo.find(currentRootUrl as any);
436
431
  await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
437
- const newRoot = this._automergeHost.repo.create(rootHandle.docSync());
432
+ const newRoot = this._echoHost.automergeRepo.create(rootHandle.docSync());
438
433
  invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
439
434
  // TODO(dmaretskyi): Unify epoch construction.
440
435
  epoch = {
@@ -450,7 +445,7 @@ export class DataSpace {
450
445
  log.info('Fragmenting');
451
446
 
452
447
  const currentRootUrl = this._automergeSpaceState.rootUrl;
453
- const rootHandle = this._automergeHost.repo.find<SpaceDoc>(currentRootUrl as any);
448
+ const rootHandle = this._echoHost.automergeRepo.find<SpaceDoc>(currentRootUrl as any);
454
449
  await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
455
450
 
456
451
  // Find properties object.
@@ -461,11 +456,11 @@ export class DataSpace {
461
456
 
462
457
  // Create a new space doc with the properties object.
463
458
  const newSpaceDoc: SpaceDoc = { ...rootHandle.docSync(), objects: Object.fromEntries([properties]) };
464
- const newRoot = this._automergeHost.repo.create(newSpaceDoc);
459
+ const newRoot = this._echoHost.automergeRepo.create(newSpaceDoc);
465
460
  invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
466
461
 
467
462
  // Create new automerge documents for all objects.
468
- const docLoader = new AutomergeDocumentLoaderImpl(this.key, this._automergeHost.repo);
463
+ const docLoader = new AutomergeDocumentLoaderImpl(this.key, this._echoHost.automergeRepo);
469
464
  await docLoader.loadSpaceRootDocHandle(this._ctx, { rootUrl: newRoot.url });
470
465
 
471
466
  otherObjects.forEach(([key, value]) => {
@@ -22,7 +22,7 @@ describe('SpacesService', () => {
22
22
  let spacesService: SpacesService;
23
23
 
24
24
  beforeEach(async () => {
25
- serviceContext = createServiceContext();
25
+ serviceContext = await createServiceContext();
26
26
  await serviceContext.open(new Context());
27
27
  spacesService = new SpacesServiceImpl(serviceContext.identityManager, serviceContext.spaceManager, async () => {
28
28
  await serviceContext.initialized.wait();
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './storage';
6
+ export * from './level';
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Level } from 'level';
6
+ import path from 'node:path';
7
+
8
+ import { PublicKey } from '@dxos/keys';
9
+ import { type Runtime } from '@dxos/protocols/proto/dxos/config';
10
+
11
+ import { getRootPath, isPersistent } from './util';
12
+
13
+ export const createLevel = async (config: Runtime.Client.Storage) => {
14
+ const persistent = isPersistent(config);
15
+ const storagePath = persistent ? path.join(getRootPath(config), 'level') : `/tmp/dxos-${PublicKey.random().toHex()}`;
16
+ const level = new Level<string, string>(storagePath);
17
+ await level.open();
18
+ return level;
19
+ };
@@ -4,22 +4,16 @@
4
4
  // Copyright 2023 DXOS.org
5
5
  //
6
6
 
7
- import { DX_DATA } from '@dxos/client-protocol';
8
7
  import { InvalidConfigError } from '@dxos/protocols';
9
8
  import { Runtime } from '@dxos/protocols/proto/dxos/config';
10
9
  import { createStorage, StorageType } from '@dxos/random-access-storage';
11
- import { isNode } from '@dxos/util';
12
10
 
13
11
  import StorageDriver = Runtime.Client.Storage.StorageDriver;
12
+ import { getRootPath } from './util';
14
13
 
15
14
  // TODO(burdon): Factor out.
16
15
  export const createStorageObjects = (config: Runtime.Client.Storage) => {
17
- const {
18
- persistent = false,
19
- keyStore,
20
- dataStore,
21
- dataRoot = isNode() ? DX_DATA : 'dxos/storage', // TODO(burdon): Factor out const.
22
- } = config ?? {};
16
+ const { persistent = false, keyStore, dataStore } = config ?? {};
23
17
 
24
18
  if (persistent && dataStore === StorageDriver.RAM) {
25
19
  throw new InvalidConfigError('RAM storage cannot be used in persistent mode.');
@@ -37,7 +31,7 @@ export const createStorageObjects = (config: Runtime.Client.Storage) => {
37
31
  return {
38
32
  storage: createStorage({
39
33
  type: persistent ? toStorageType(dataStore) : StorageType.RAM,
40
- root: `${dataRoot}/`,
34
+ root: getRootPath(config),
41
35
  }),
42
36
  };
43
37
  };
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { DX_DATA } from '@dxos/client-protocol';
6
+ import { Runtime } from '@dxos/protocols/proto/dxos/config';
7
+ import { isNode } from '@dxos/util';
8
+
9
+ export const getRootPath = (config: Runtime.Client.Storage) => {
10
+ const { dataRoot = isNode() ? DX_DATA : 'dxos/storage' } = config ?? {};
11
+ return `${dataRoot}/`;
12
+ };
13
+
14
+ export const isPersistent = (config: Runtime.Client.Storage) => {
15
+ const { persistent = false } = config ?? {};
16
+ return (
17
+ (config.dataStore !== undefined && config.dataStore !== Runtime.Client.Storage.StorageDriver.RAM) || persistent
18
+ );
19
+ };
@@ -16,7 +16,7 @@ import {
16
16
  } from '@dxos/protocols/proto/dxos/client/services';
17
17
  import { jsonKeyReplacer, type MaybePromise } from '@dxos/util';
18
18
 
19
- import { type Diagnostics } from '../services';
19
+ import { type Diagnostics } from '../diagnostics';
20
20
  import { getPlatform } from '../services/platform';
21
21
 
22
22
  export type SystemServiceOptions = {