@dxos/client-services 0.4.10-main.fa5a270 → 0.4.10-main.fe71b4c

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 (98) hide show
  1. package/dist/lib/browser/{chunk-7PYX6UUA.mjs → chunk-7S34JE6M.mjs} +980 -656
  2. package/dist/lib/browser/chunk-7S34JE6M.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +60 -22
  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 +12 -5
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-4TX623I7.cjs → chunk-DQMGKBOV.cjs} +922 -685
  9. package/dist/lib/node/chunk-DQMGKBOV.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +96 -58
  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 +16 -9
  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/indexing/util.d.ts +3 -2
  28. package/dist/types/src/packlets/indexing/util.d.ts.map +1 -1
  29. package/dist/types/src/packlets/invitations/invitation-extension.d.ts +1 -0
  30. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
  31. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -2
  32. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  33. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  34. package/dist/types/src/packlets/services/index.d.ts +1 -1
  35. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/service-context.d.ts +7 -5
  37. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  38. package/dist/types/src/packlets/services/service-host.d.ts +5 -1
  39. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  40. package/dist/types/src/packlets/services/util.d.ts +1 -0
  41. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  42. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  43. package/dist/types/src/packlets/storage/index.d.ts +1 -0
  44. package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
  45. package/dist/types/src/packlets/storage/level.d.ts +4 -0
  46. package/dist/types/src/packlets/storage/level.d.ts.map +1 -0
  47. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  48. package/dist/types/src/packlets/storage/util.d.ts +4 -0
  49. package/dist/types/src/packlets/storage/util.d.ts.map +1 -0
  50. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  51. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  52. package/dist/types/src/packlets/testing/test-builder.d.ts +4 -2
  53. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  54. package/dist/types/src/packlets/vault/shared-worker-connection.d.ts +5 -5
  55. package/dist/types/src/packlets/vault/shared-worker-connection.d.ts.map +1 -1
  56. package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -0
  57. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  58. package/dist/types/src/packlets/vault/worker-session.d.ts +2 -0
  59. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  60. package/dist/types/src/version.d.ts +1 -1
  61. package/package.json +36 -34
  62. package/src/index.ts +1 -0
  63. package/src/packlets/devices/devices-service.test.ts +1 -1
  64. package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +94 -0
  65. package/src/packlets/diagnostics/diagnostics-broadcast.ts +20 -0
  66. package/src/packlets/diagnostics/diagnostics-collector.ts +65 -0
  67. package/src/packlets/{services → diagnostics}/diagnostics.ts +2 -2
  68. package/src/packlets/diagnostics/index.ts +7 -0
  69. package/src/packlets/identity/identity-service.test.ts +1 -1
  70. package/src/packlets/indexing/util.ts +6 -6
  71. package/src/packlets/invitations/device-invitation-protocol.test.ts +1 -1
  72. package/src/packlets/invitations/invitation-extension.ts +28 -1
  73. package/src/packlets/invitations/invitations-handler.ts +73 -32
  74. package/src/packlets/invitations/invitations-service.ts +5 -5
  75. package/src/packlets/network/network-service.test.ts +1 -1
  76. package/src/packlets/services/automerge-host.test.ts +9 -3
  77. package/src/packlets/services/index.ts +1 -1
  78. package/src/packlets/services/service-context.test.ts +9 -6
  79. package/src/packlets/services/service-context.ts +18 -11
  80. package/src/packlets/services/service-host.ts +52 -9
  81. package/src/packlets/services/service-registry.test.ts +1 -1
  82. package/src/packlets/services/util.ts +2 -0
  83. package/src/packlets/spaces/data-space-manager.test.ts +4 -4
  84. package/src/packlets/spaces/data-space.ts +51 -2
  85. package/src/packlets/spaces/spaces-service.test.ts +1 -1
  86. package/src/packlets/storage/index.ts +1 -0
  87. package/src/packlets/storage/level.ts +19 -0
  88. package/src/packlets/storage/storage.ts +3 -9
  89. package/src/packlets/storage/util.ts +19 -0
  90. package/src/packlets/system/system-service.ts +1 -1
  91. package/src/packlets/testing/test-builder.ts +23 -5
  92. package/src/packlets/vault/shared-worker-connection.ts +3 -8
  93. package/src/packlets/vault/worker-runtime.ts +27 -2
  94. package/src/packlets/vault/worker-session.ts +6 -0
  95. package/src/version.ts +1 -1
  96. package/dist/lib/browser/chunk-7PYX6UUA.mjs.map +0 -7
  97. package/dist/lib/node/chunk-4TX623I7.cjs.map +0 -7
  98. package/dist/types/src/packlets/services/diagnostics.d.ts.map +0 -1
@@ -6,7 +6,7 @@ import { Event, scheduleTask } from '@dxos/async';
6
6
  import { type AuthenticatingInvitation, type CancellableInvitation } from '@dxos/client-protocol';
7
7
  import { Stream } from '@dxos/codec-protobuf';
8
8
  import { Context } from '@dxos/context';
9
- import { type MetadataStore } from '@dxos/echo-pipeline';
9
+ import { hasInvitationExpired, type MetadataStore } from '@dxos/echo-pipeline';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { log } from '@dxos/log';
12
12
  import {
@@ -18,7 +18,7 @@ import {
18
18
  } from '@dxos/protocols/proto/dxos/client/services';
19
19
 
20
20
  import { type InvitationProtocol } from './invitation-protocol';
21
- import { invitationExpired, type InvitationsHandler } from './invitations-handler';
21
+ import { type InvitationsHandler } from './invitations-handler';
22
22
 
23
23
  /**
24
24
  * Adapts invitation service observable to client/service stream.
@@ -91,7 +91,7 @@ export class InvitationsServiceImpl implements InvitationsService {
91
91
  }
92
92
 
93
93
  this._createInvitations.delete(invitation.get().invitationId);
94
- if (invitation.get().type !== Invitation.Type.MULTIUSE) {
94
+ if (!invitation.get().multiUse) {
95
95
  this._removedCreated.emit(invitation.get());
96
96
  }
97
97
  },
@@ -103,7 +103,7 @@ export class InvitationsServiceImpl implements InvitationsService {
103
103
  const persistentInvitations = this._metadataStore.getInvitations();
104
104
 
105
105
  // get saved persistent invitations, filter and remove from storage those that have expired.
106
- const freshInvitations = persistentInvitations.filter(async (invitation) => !invitationExpired(invitation));
106
+ const freshInvitations = persistentInvitations.filter(async (invitation) => !hasInvitationExpired(invitation));
107
107
 
108
108
  const cInvitations = freshInvitations.map((persistentInvitation) => {
109
109
  invariant(!this._createInvitations.get(persistentInvitation.invitationId), 'invitation already exists');
@@ -148,7 +148,7 @@ export class InvitationsServiceImpl implements InvitationsService {
148
148
  () => {
149
149
  close();
150
150
  this._acceptInvitations.delete(invitation.get().invitationId);
151
- if (invitation.get().type !== Invitation.Type.MULTIUSE) {
151
+ if (!invitation.get().multiUse) {
152
152
  this._removedAccepted.emit(invitation.get());
153
153
  }
154
154
  },
@@ -18,7 +18,7 @@ describe('NetworkService', () => {
18
18
  let networkService: NetworkService;
19
19
 
20
20
  beforeEach(async () => {
21
- serviceContext = createServiceContext();
21
+ serviceContext = await createServiceContext();
22
22
  await serviceContext.open(new Context());
23
23
  networkService = new NetworkServiceImpl(serviceContext.networkManager, serviceContext.signalManager);
24
24
  });
@@ -6,8 +6,8 @@ import { expect } from 'chai';
6
6
 
7
7
  import { asyncTimeout, sleep } from '@dxos/async';
8
8
  import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
9
+ import { createTestLevel } from '@dxos/echo-pipeline/testing';
9
10
  import { AutomergeContext } from '@dxos/echo-schema';
10
- import { StorageType, createStorage } from '@dxos/random-access-storage';
11
11
  import { afterTest, describe, test } from '@dxos/test';
12
12
 
13
13
  describe('AutomergeHost', () => {
@@ -19,10 +19,16 @@ describe('AutomergeHost', () => {
19
19
  // creates repo and document | replicates repo | finds document in repo
20
20
  //
21
21
 
22
- const storageDirectory = createStorage({ type: StorageType.RAM }).createDirectory();
22
+ const level = createTestLevel();
23
+ await level.open();
24
+ afterTest(() => level.close());
23
25
 
24
- const host = new AutomergeHost({ directory: storageDirectory });
26
+ const host = new AutomergeHost({
27
+ db: level.sublevel('automerge'),
28
+ });
29
+ await host.open();
25
30
  afterTest(() => host.close());
31
+
26
32
  const dataService = new DataServiceImpl(host);
27
33
  const client = new AutomergeContext(dataService);
28
34
  afterTest(() => client.close());
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './client-rpc-server';
6
- export * from './diagnostics';
7
6
  export * from './service-context';
8
7
  export * from './service-host';
9
8
  export * from './service-registry';
9
+ export { ClientServicesProviderResource } from './util';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { MemorySignalManagerContext } from '@dxos/messaging';
6
6
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
7
- import { describe, test } from '@dxos/test';
7
+ import { describe, openAndClose, test } from '@dxos/test';
8
8
 
9
9
  import { createServiceContext } from '../testing';
10
10
  import { performInvitation } from '../testing/invitation-utils';
@@ -12,10 +12,10 @@ import { performInvitation } from '../testing/invitation-utils';
12
12
  describe('services/ServiceContext', () => {
13
13
  test('new space is synchronized on device invitations', async () => {
14
14
  const networkContext = new MemorySignalManagerContext();
15
- const device1 = createServiceContext({ signalContext: networkContext });
15
+ const device1 = await createServiceContext({ signalContext: networkContext });
16
16
  await device1.createIdentity();
17
17
 
18
- const device2 = createServiceContext({ signalContext: networkContext });
18
+ const device2 = await createServiceContext({ signalContext: networkContext });
19
19
  await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
20
20
 
21
21
  const space1 = await device1.dataSpaceManager!.createSpace();
@@ -26,13 +26,16 @@ describe('services/ServiceContext', () => {
26
26
 
27
27
  test('joined space is synchronized on device invitations', async () => {
28
28
  const networkContext = new MemorySignalManagerContext();
29
- const device1 = createServiceContext({ signalContext: networkContext });
29
+ const device1 = await createServiceContext({ signalContext: networkContext });
30
+ await openAndClose(device1.automergeHost);
30
31
  await device1.createIdentity();
31
32
 
32
- const device2 = createServiceContext({ signalContext: networkContext });
33
+ const device2 = await createServiceContext({ signalContext: networkContext });
34
+ await openAndClose(device2.automergeHost);
33
35
  await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
34
36
 
35
- const identity2 = createServiceContext({ signalContext: networkContext });
37
+ const identity2 = await createServiceContext({ signalContext: networkContext });
38
+ await openAndClose(identity2.automergeHost);
36
39
  await identity2.createIdentity();
37
40
  const space1 = await identity2.dataSpaceManager!.createSpace();
38
41
  await Promise.all(
@@ -2,13 +2,15 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ import { type Level } from 'level';
6
+
5
7
  import { Trigger } from '@dxos/async';
6
- import { Context } from '@dxos/context';
8
+ import { Context, Resource } from '@dxos/context';
7
9
  import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
8
10
  import { failUndefined } from '@dxos/debug';
9
11
  import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
10
12
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
11
- import { IndexMetadataStore, IndexStore, Indexer } from '@dxos/indexing';
13
+ import { IndexMetadataStore, IndexStore, Indexer, createStorageCallbacks } from '@dxos/indexing';
12
14
  import { invariant } from '@dxos/invariant';
13
15
  import { Keyring } from '@dxos/keyring';
14
16
  import { PublicKey } from '@dxos/keys';
@@ -30,7 +32,7 @@ import {
30
32
  type IdentityManagerRuntimeParams,
31
33
  type JoinIdentityParams,
32
34
  } from '../identity';
33
- import { createGetAllDocuments, createLoadDocuments } from '../indexing';
35
+ import { createDocumentsIterator, createSelectedDocumentsIterator } from '../indexing';
34
36
  import {
35
37
  DeviceInvitationProtocol,
36
38
  InvitationsHandler,
@@ -47,7 +49,7 @@ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpa
47
49
  // TODO(dmaretskyi): Gets duplicated in CJS build between normal and testing bundles.
48
50
  @safeInstanceof('dxos.client-services.ServiceContext')
49
51
  @Trace.resource()
50
- export class ServiceContext {
52
+ export class ServiceContext extends Resource {
51
53
  public readonly initialized = new Trigger();
52
54
  public readonly metadataStore: MetadataStore;
53
55
  /**
@@ -78,10 +80,13 @@ export class ServiceContext {
78
80
 
79
81
  constructor(
80
82
  public readonly storage: Storage,
83
+ public readonly level: Level<string, string>,
81
84
  public readonly networkManager: NetworkManager,
82
85
  public readonly signalManager: SignalManager,
83
86
  public readonly _runtimeParams?: IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams,
84
87
  ) {
88
+ super();
89
+
85
90
  // TODO(burdon): Move strings to constants.
86
91
  this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
87
92
  this.snapshotStore = new SnapshotStore(storage.createDirectory('snapshots'));
@@ -115,18 +120,19 @@ export class ServiceContext {
115
120
  this._runtimeParams as IdentityManagerRuntimeParams,
116
121
  );
117
122
 
118
- this.indexMetadata = new IndexMetadataStore({ directory: storage.createDirectory('index-metadata') });
123
+ this.indexMetadata = new IndexMetadataStore({ db: level.sublevel('index-metadata') });
119
124
 
120
125
  this.automergeHost = new AutomergeHost({
121
126
  directory: storage.createDirectory('automerge'),
122
- metadata: this.indexMetadata,
127
+ db: level.sublevel('automerge'),
128
+ storageCallbacks: createStorageCallbacks({ host: () => this.automergeHost, metadata: this.indexMetadata }),
123
129
  });
124
130
 
125
131
  this.indexer = new Indexer({
126
- indexStore: new IndexStore({ directory: storage.createDirectory('index-store') }),
132
+ indexStore: new IndexStore({ db: level.sublevel('index-storage') }),
127
133
  metadataStore: this.indexMetadata,
128
- loadDocuments: createLoadDocuments(this.automergeHost),
129
- getAllDocuments: createGetAllDocuments(this.automergeHost),
134
+ loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
135
+ getAllDocuments: createDocumentsIterator(this.automergeHost),
130
136
  });
131
137
 
132
138
  this.invitations = new InvitationsHandler(this.networkManager);
@@ -145,7 +151,7 @@ export class ServiceContext {
145
151
  }
146
152
 
147
153
  @Trace.span()
148
- async open(ctx: Context) {
154
+ protected override async _open(ctx: Context) {
149
155
  await this._checkStorageVersion();
150
156
 
151
157
  log('opening...');
@@ -153,6 +159,7 @@ export class ServiceContext {
153
159
  await this.signalManager.open();
154
160
  await this.networkManager.open();
155
161
 
162
+ await this.automergeHost.open();
156
163
  await this.metadataStore.load();
157
164
  await this.spaceManager.open();
158
165
  await this.identityManager.open(ctx);
@@ -163,7 +170,7 @@ export class ServiceContext {
163
170
  log('opened');
164
171
  }
165
172
 
166
- async close() {
173
+ protected override async _close() {
167
174
  log('closing...');
168
175
  if (this._deviceSpaceSync && this.identityManager.identity) {
169
176
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
@@ -2,12 +2,20 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
+ import { type Level } from 'level';
6
+
5
7
  import { Event, synchronized } from '@dxos/async';
6
- import { Properties, clientServiceBundle, defaultKey, type ClientServices } from '@dxos/client-protocol';
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 } from '@dxos/echo-pipeline';
10
- import { getAutomergeObjectCore, getRawDoc, type SpaceDoc, type TypedObject } from '@dxos/echo-schema';
11
+ import {
12
+ DataServiceImpl,
13
+ type ObjectStructure,
14
+ encodeReference,
15
+ type SpaceDoc,
16
+ type LevelDB,
17
+ } from '@dxos/echo-pipeline';
18
+ import { getTypeReference } from '@dxos/echo-schema';
11
19
  import { IndexServiceImpl } from '@dxos/indexing';
12
20
  import { invariant } from '@dxos/invariant';
13
21
  import { PublicKey } from '@dxos/keys';
@@ -21,18 +29,22 @@ import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
21
29
  import { assignDeep } from '@dxos/util';
22
30
  import { WebsocketRpcClient } from '@dxos/websocket-rpc';
23
31
 
24
- import { createDiagnostics } from './diagnostics';
25
32
  import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
26
33
  import { ServiceRegistry } from './service-registry';
27
34
  import { DevicesServiceImpl } from '../devices';
28
35
  import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
36
+ import {
37
+ type CollectDiagnosticsBroadcastHandler,
38
+ createCollectDiagnosticsBroadcastHandler,
39
+ createDiagnostics,
40
+ } from '../diagnostics';
29
41
  import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
30
42
  import { InvitationsServiceImpl } from '../invitations';
31
43
  import { Lock, type ResourceLock } from '../locks';
32
44
  import { LoggingServiceImpl } from '../logging';
33
45
  import { NetworkServiceImpl } from '../network';
34
46
  import { SpacesServiceImpl } from '../spaces';
35
- import { createStorageObjects } from '../storage';
47
+ import { createLevel, createStorageObjects } from '../storage';
36
48
  import { SystemServiceImpl } from '../system';
37
49
 
38
50
  export type ClientServicesHostParams = {
@@ -44,6 +56,7 @@ export type ClientServicesHostParams = {
44
56
  signalManager?: SignalManager;
45
57
  connectionLog?: boolean;
46
58
  storage?: Storage;
59
+ level?: LevelDB;
47
60
  lockKey?: string;
48
61
  callbacks?: ClientServicesHostCallbacks;
49
62
  runtimeParams?: ServiceContextRuntimeParams;
@@ -76,11 +89,13 @@ export class ClientServicesHost {
76
89
  private _signalManager?: SignalManager;
77
90
  private _networkManager?: NetworkManager;
78
91
  private _storage?: Storage;
92
+ private _level?: Level<string, string>;
79
93
  private _callbacks?: ClientServicesHostCallbacks;
80
94
  private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
81
95
 
82
96
  private _serviceContext!: ServiceContext;
83
97
  private readonly _runtimeParams?: ServiceContextRuntimeParams;
98
+ private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
84
99
 
85
100
  @Trace.info()
86
101
  private _opening = false;
@@ -93,12 +108,14 @@ export class ClientServicesHost {
93
108
  transportFactory,
94
109
  signalManager,
95
110
  storage,
111
+ level,
96
112
  // TODO(wittjosiah): Turn this on by default.
97
113
  lockKey,
98
114
  callbacks,
99
115
  runtimeParams,
100
116
  }: ClientServicesHostParams = {}) {
101
117
  this._storage = storage;
118
+ this._level = level;
102
119
  this._callbacks = callbacks;
103
120
  this._runtimeParams = runtimeParams;
104
121
 
@@ -138,6 +155,7 @@ export class ClientServicesHost {
138
155
  },
139
156
  });
140
157
 
158
+ this.diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this._systemService);
141
159
  this._loggingService = new LoggingServiceImpl();
142
160
 
143
161
  this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
@@ -187,6 +205,9 @@ export class ClientServicesHost {
187
205
  }
188
206
  }
189
207
 
208
+ if (!options.signalManager) {
209
+ log.warn('running signaling without telemetry metadata.');
210
+ }
190
211
  const {
191
212
  connectionLog = true,
192
213
  transportFactory = createSimplePeerTransportFactory({
@@ -223,12 +244,19 @@ export class ClientServicesHost {
223
244
 
224
245
  this._opening = true;
225
246
  log('opening...', { lockKey: this._resourceLock?.lockKey });
247
+
248
+ if (!this._level) {
249
+ this._level = await createLevel(this._config.get('runtime.client.storage', {})!);
250
+ }
251
+ await this._level.open();
252
+
226
253
  await this._resourceLock?.acquire();
227
254
 
228
255
  await this._loggingService.open();
229
256
 
230
257
  this._serviceContext = new ServiceContext(
231
258
  this._storage,
259
+ this._level,
232
260
  this._networkManager,
233
261
  this._signalManager,
234
262
  this._runtimeParams,
@@ -298,6 +326,7 @@ export class ClientServicesHost {
298
326
  });
299
327
  void this._devtoolsProxy.open();
300
328
  }
329
+ this.diagnosticsBroadcastHandler.start();
301
330
 
302
331
  this._opening = false;
303
332
  this._open = true;
@@ -316,10 +345,12 @@ export class ClientServicesHost {
316
345
 
317
346
  const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
318
347
  log('closing...', { deviceKey });
348
+ this.diagnosticsBroadcastHandler.stop();
319
349
  await this._devtoolsProxy?.close();
320
350
  this._serviceRegistry.setServices({ SystemService: this._systemService });
321
351
  await this._loggingService.close();
322
352
  await this._serviceContext.close();
353
+ await this._level?.close();
323
354
  this._open = false;
324
355
  this._statusUpdate.emit();
325
356
  log('closed', { deviceKey });
@@ -344,18 +375,30 @@ export class ClientServicesHost {
344
375
  await this._serviceContext.initialized.wait();
345
376
  const space = await this._serviceContext.dataSpaceManager!.createSpace();
346
377
 
347
- const obj: TypedObject = new Properties(undefined);
348
- obj[defaultKey] = identity.identityKey.toHex();
349
-
350
378
  const automergeIndex = space.automergeSpaceState.rootUrl;
351
379
  invariant(automergeIndex);
352
380
  const document = await this._serviceContext.automergeHost.repo.find<SpaceDoc>(automergeIndex as any);
353
381
  await document.whenReady();
354
382
 
383
+ // TODO(dmaretskyi): Better API for low-level data access.
384
+ const properties: ObjectStructure = {
385
+ system: {
386
+ type: encodeReference(getTypeReference(Properties)!),
387
+ },
388
+ data: {
389
+ [defaultKey]: identity.identityKey.toHex(),
390
+ },
391
+ meta: {
392
+ keys: [],
393
+ },
394
+ };
395
+ const propertiesId = PublicKey.random().toHex();
355
396
  document.change((doc: SpaceDoc) => {
356
- assignDeep(doc, ['objects', getAutomergeObjectCore(obj).id], getRawDoc(obj).handle.docSync());
397
+ assignDeep(doc, ['objects', propertiesId], properties);
357
398
  });
358
399
 
400
+ await this._serviceContext.automergeHost.repo.flush();
401
+
359
402
  return identity;
360
403
  }
361
404
  }
@@ -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.automergeHost, 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.automergeHost, peer1.dataSpaceManager, peer2.automergeHost, 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.automergeHost, peer1.dataSpaceManager, peer2.automergeHost, 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.automergeHost, 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);
@@ -6,7 +6,15 @@ 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 MetadataStore, type Space, createMappedFeedWriter, type AutomergeHost } from '@dxos/echo-pipeline';
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';
17
+ import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
10
18
  import { type FeedStore } from '@dxos/feed-store';
11
19
  import { failedInvariant, invariant } from '@dxos/invariant';
12
20
  import { type Keyring } from '@dxos/keyring';
@@ -26,7 +34,7 @@ import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gos
26
34
  import { type Gossip, type Presence } from '@dxos/teleport-extension-gossip';
27
35
  import { Timeframe } from '@dxos/timeframe';
28
36
  import { trace } from '@dxos/tracing';
29
- import { ComplexSet } from '@dxos/util';
37
+ import { ComplexSet, assignDeep } from '@dxos/util';
30
38
 
31
39
  import { AutomergeSpaceState } from './automerge-space-state';
32
40
  import { type SigningContext } from './data-space-manager';
@@ -437,6 +445,47 @@ export class DataSpace {
437
445
  };
438
446
  }
439
447
  break;
448
+ case CreateEpochRequest.Migration.FRAGMENT_AUTOMERGE_ROOT:
449
+ {
450
+ log.info('Fragmenting');
451
+
452
+ const currentRootUrl = this._automergeSpaceState.rootUrl;
453
+ const rootHandle = this._automergeHost.repo.find<SpaceDoc>(currentRootUrl as any);
454
+ await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
455
+
456
+ // Find properties object.
457
+ const objects = Object.entries((rootHandle.docSync() as SpaceDoc).objects!);
458
+ const properties = objects.find(([_, value]) => value.system.type?.itemId === TYPE_PROPERTIES);
459
+ const otherObjects = objects.filter(([key]) => key !== properties?.[0]);
460
+ invariant(properties, 'Properties not found');
461
+
462
+ // Create a new space doc with the properties object.
463
+ const newSpaceDoc: SpaceDoc = { ...rootHandle.docSync(), objects: Object.fromEntries([properties]) };
464
+ const newRoot = this._automergeHost.repo.create(newSpaceDoc);
465
+ invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
466
+
467
+ // Create new automerge documents for all objects.
468
+ const docLoader = new AutomergeDocumentLoaderImpl(this.key, this._automergeHost.repo);
469
+ await docLoader.loadSpaceRootDocHandle(this._ctx, { rootUrl: newRoot.url });
470
+
471
+ otherObjects.forEach(([key, value]) => {
472
+ const handle = docLoader.createDocumentForObject(key);
473
+ handle.change((doc: any) => {
474
+ assignDeep(doc, ['objects', key], value);
475
+ });
476
+ });
477
+
478
+ // TODO(mykola): Delete old root.
479
+
480
+ // TODO(dmaretskyi): Unify epoch construction.
481
+ epoch = {
482
+ previousId: this._automergeSpaceState.lastEpoch?.id,
483
+ number: (this._automergeSpaceState.lastEpoch?.subject.assertion.number ?? -1) + 1,
484
+ timeframe: this._automergeSpaceState.lastEpoch?.subject.assertion.timeframe ?? new Timeframe(),
485
+ automergeRoot: newRoot.url,
486
+ };
487
+ }
488
+ break;
440
489
  }
441
490
 
442
491
  if (!epoch) {
@@ -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 = {