@dxos/client-services 0.4.10-main.f68a1ca → 0.4.10-main.f937af6

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 (94) hide show
  1. package/dist/lib/browser/{chunk-B4JZEXRB.mjs → chunk-N2WDHCKA.mjs} +1280 -985
  2. package/dist/lib/browser/chunk-N2WDHCKA.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +15 -3
  4. package/dist/lib/browser/index.mjs.map +1 -1
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +12 -5
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-EC2QGJ2V.cjs → chunk-KIKQMYZ7.cjs} +1108 -901
  9. package/dist/lib/node/chunk-KIKQMYZ7.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +50 -38
  11. package/dist/lib/node/index.cjs.map +1 -1
  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 +2 -1
  28. package/dist/types/src/packlets/indexing/util.d.ts.map +1 -1
  29. package/dist/types/src/packlets/invitations/index.d.ts +1 -0
  30. package/dist/types/src/packlets/invitations/index.d.ts.map +1 -1
  31. package/dist/types/src/packlets/invitations/invitation-extension.d.ts +1 -0
  32. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
  33. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -2
  34. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  35. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +42 -0
  36. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -0
  37. package/dist/types/src/packlets/invitations/invitations-service.d.ts +7 -23
  38. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  39. package/dist/types/src/packlets/services/index.d.ts +1 -1
  40. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  41. package/dist/types/src/packlets/services/service-context.d.ts +9 -5
  42. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  43. package/dist/types/src/packlets/services/service-host.d.ts +5 -1
  44. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  45. package/dist/types/src/packlets/services/util.d.ts +1 -0
  46. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  47. package/dist/types/src/packlets/storage/index.d.ts +1 -0
  48. package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
  49. package/dist/types/src/packlets/storage/level.d.ts +4 -0
  50. package/dist/types/src/packlets/storage/level.d.ts.map +1 -0
  51. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  52. package/dist/types/src/packlets/storage/util.d.ts +4 -0
  53. package/dist/types/src/packlets/storage/util.d.ts.map +1 -0
  54. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  55. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  56. package/dist/types/src/packlets/testing/test-builder.d.ts +4 -2
  57. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  58. package/dist/types/src/version.d.ts +1 -1
  59. package/package.json +36 -34
  60. package/src/index.ts +1 -0
  61. package/src/packlets/devices/devices-service.test.ts +1 -1
  62. package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +94 -0
  63. package/src/packlets/diagnostics/diagnostics-broadcast.ts +20 -0
  64. package/src/packlets/diagnostics/diagnostics-collector.ts +65 -0
  65. package/src/packlets/{services → diagnostics}/diagnostics.ts +2 -2
  66. package/src/packlets/diagnostics/index.ts +7 -0
  67. package/src/packlets/identity/identity-service.test.ts +1 -1
  68. package/src/packlets/indexing/util.ts +11 -6
  69. package/src/packlets/invitations/device-invitation-protocol.test.ts +1 -1
  70. package/src/packlets/invitations/index.ts +1 -0
  71. package/src/packlets/invitations/invitation-extension.ts +28 -1
  72. package/src/packlets/invitations/invitations-handler.ts +74 -34
  73. package/src/packlets/invitations/invitations-manager.ts +197 -0
  74. package/src/packlets/invitations/invitations-service.ts +21 -168
  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 +26 -8
  80. package/src/packlets/services/service-host.ts +51 -24
  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/spaces-service.test.ts +1 -1
  85. package/src/packlets/storage/index.ts +1 -0
  86. package/src/packlets/storage/level.ts +19 -0
  87. package/src/packlets/storage/storage.ts +3 -9
  88. package/src/packlets/storage/util.ts +19 -0
  89. package/src/packlets/system/system-service.ts +1 -1
  90. package/src/packlets/testing/test-builder.ts +23 -5
  91. package/src/version.ts +1 -1
  92. package/dist/lib/browser/chunk-B4JZEXRB.mjs.map +0 -7
  93. package/dist/lib/node/chunk-EC2QGJ2V.cjs.map +0 -7
  94. package/dist/types/src/packlets/services/diagnostics.d.ts.map +0 -1
@@ -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';
@@ -37,6 +39,7 @@ import {
37
39
  SpaceInvitationProtocol,
38
40
  type InvitationProtocol,
39
41
  } from '../invitations';
42
+ import { InvitationsManager } from '../invitations/invitations-manager';
40
43
  import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
41
44
 
42
45
  export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams;
@@ -47,7 +50,7 @@ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpa
47
50
  // TODO(dmaretskyi): Gets duplicated in CJS build between normal and testing bundles.
48
51
  @safeInstanceof('dxos.client-services.ServiceContext')
49
52
  @Trace.resource()
50
- export class ServiceContext {
53
+ export class ServiceContext extends Resource {
51
54
  public readonly initialized = new Trigger();
52
55
  public readonly metadataStore: MetadataStore;
53
56
  /**
@@ -60,6 +63,7 @@ export class ServiceContext {
60
63
  public readonly spaceManager: SpaceManager;
61
64
  public readonly identityManager: IdentityManager;
62
65
  public readonly invitations: InvitationsHandler;
66
+ public readonly invitationsManager: InvitationsManager;
63
67
  public readonly automergeHost: AutomergeHost;
64
68
  public readonly indexMetadata: IndexMetadataStore;
65
69
  public readonly indexer: Indexer;
@@ -78,10 +82,13 @@ export class ServiceContext {
78
82
 
79
83
  constructor(
80
84
  public readonly storage: Storage,
85
+ public readonly level: Level<string, string>,
81
86
  public readonly networkManager: NetworkManager,
82
87
  public readonly signalManager: SignalManager,
83
88
  public readonly _runtimeParams?: IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams,
84
89
  ) {
90
+ super();
91
+
85
92
  // TODO(burdon): Move strings to constants.
86
93
  this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
87
94
  this.snapshotStore = new SnapshotStore(storage.createDirectory('snapshots'));
@@ -115,21 +122,27 @@ export class ServiceContext {
115
122
  this._runtimeParams as IdentityManagerRuntimeParams,
116
123
  );
117
124
 
118
- this.indexMetadata = new IndexMetadataStore({ directory: storage.createDirectory('index-metadata') });
125
+ this.indexMetadata = new IndexMetadataStore({ db: level.sublevel('index-metadata') });
119
126
 
120
127
  this.automergeHost = new AutomergeHost({
121
128
  directory: storage.createDirectory('automerge'),
122
- metadata: this.indexMetadata,
129
+ db: level.sublevel('automerge'),
130
+ storageCallbacks: createStorageCallbacks({ host: () => this.automergeHost, metadata: this.indexMetadata }),
123
131
  });
124
132
 
125
133
  this.indexer = new Indexer({
126
- indexStore: new IndexStore({ directory: storage.createDirectory('index-store') }),
134
+ indexStore: new IndexStore({ db: level.sublevel('index-storage') }),
127
135
  metadataStore: this.indexMetadata,
128
136
  loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
129
137
  getAllDocuments: createDocumentsIterator(this.automergeHost),
130
138
  });
131
139
 
132
140
  this.invitations = new InvitationsHandler(this.networkManager);
141
+ this.invitationsManager = new InvitationsManager(
142
+ this.invitations,
143
+ (invitation) => this.getInvitationHandler(invitation),
144
+ this.metadataStore,
145
+ );
133
146
 
134
147
  // TODO(burdon): _initialize called in multiple places.
135
148
  // TODO(burdon): Call _initialize on success.
@@ -145,7 +158,7 @@ export class ServiceContext {
145
158
  }
146
159
 
147
160
  @Trace.span()
148
- async open(ctx: Context) {
161
+ protected override async _open(ctx: Context) {
149
162
  await this._checkStorageVersion();
150
163
 
151
164
  log('opening...');
@@ -153,17 +166,22 @@ export class ServiceContext {
153
166
  await this.signalManager.open();
154
167
  await this.networkManager.open();
155
168
 
169
+ await this.automergeHost.open();
156
170
  await this.metadataStore.load();
157
171
  await this.spaceManager.open();
158
172
  await this.identityManager.open(ctx);
159
173
  if (this.identityManager.identity) {
160
174
  await this._initialize(ctx);
161
175
  }
176
+
177
+ const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
178
+ log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
179
+
162
180
  log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
163
181
  log('opened');
164
182
  }
165
183
 
166
- async close() {
184
+ protected override async _close() {
167
185
  log('closing...');
168
186
  if (this._deviceSpaceSync && this.identityManager.identity) {
169
187
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
@@ -2,13 +2,20 @@
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';
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';
12
19
  import { IndexServiceImpl } from '@dxos/indexing';
13
20
  import { invariant } from '@dxos/invariant';
14
21
  import { PublicKey } from '@dxos/keys';
@@ -22,18 +29,22 @@ import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
22
29
  import { assignDeep } from '@dxos/util';
23
30
  import { WebsocketRpcClient } from '@dxos/websocket-rpc';
24
31
 
25
- import { createDiagnostics } from './diagnostics';
26
32
  import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
27
33
  import { ServiceRegistry } from './service-registry';
28
34
  import { DevicesServiceImpl } from '../devices';
29
35
  import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
36
+ import {
37
+ type CollectDiagnosticsBroadcastHandler,
38
+ createCollectDiagnosticsBroadcastHandler,
39
+ createDiagnostics,
40
+ } from '../diagnostics';
30
41
  import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
31
42
  import { InvitationsServiceImpl } from '../invitations';
32
43
  import { Lock, type ResourceLock } from '../locks';
33
44
  import { LoggingServiceImpl } from '../logging';
34
45
  import { NetworkServiceImpl } from '../network';
35
46
  import { SpacesServiceImpl } from '../spaces';
36
- import { createStorageObjects } from '../storage';
47
+ import { createLevel, createStorageObjects } from '../storage';
37
48
  import { SystemServiceImpl } from '../system';
38
49
 
39
50
  export type ClientServicesHostParams = {
@@ -45,6 +56,7 @@ export type ClientServicesHostParams = {
45
56
  signalManager?: SignalManager;
46
57
  connectionLog?: boolean;
47
58
  storage?: Storage;
59
+ level?: LevelDB;
48
60
  lockKey?: string;
49
61
  callbacks?: ClientServicesHostCallbacks;
50
62
  runtimeParams?: ServiceContextRuntimeParams;
@@ -77,11 +89,13 @@ export class ClientServicesHost {
77
89
  private _signalManager?: SignalManager;
78
90
  private _networkManager?: NetworkManager;
79
91
  private _storage?: Storage;
92
+ private _level?: Level<string, string>;
80
93
  private _callbacks?: ClientServicesHostCallbacks;
81
94
  private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
82
95
 
83
96
  private _serviceContext!: ServiceContext;
84
97
  private readonly _runtimeParams?: ServiceContextRuntimeParams;
98
+ private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
85
99
 
86
100
  @Trace.info()
87
101
  private _opening = false;
@@ -94,12 +108,14 @@ export class ClientServicesHost {
94
108
  transportFactory,
95
109
  signalManager,
96
110
  storage,
111
+ level,
97
112
  // TODO(wittjosiah): Turn this on by default.
98
113
  lockKey,
99
114
  callbacks,
100
115
  runtimeParams,
101
116
  }: ClientServicesHostParams = {}) {
102
117
  this._storage = storage;
118
+ this._level = level;
103
119
  this._callbacks = callbacks;
104
120
  this._runtimeParams = runtimeParams;
105
121
 
@@ -139,6 +155,7 @@ export class ClientServicesHost {
139
155
  },
140
156
  });
141
157
 
158
+ this.diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this._systemService);
142
159
  this._loggingService = new LoggingServiceImpl();
143
160
 
144
161
  this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
@@ -227,12 +244,19 @@ export class ClientServicesHost {
227
244
 
228
245
  this._opening = true;
229
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
+
230
253
  await this._resourceLock?.acquire();
231
254
 
232
255
  await this._loggingService.open();
233
256
 
234
257
  this._serviceContext = new ServiceContext(
235
258
  this._storage,
259
+ this._level,
236
260
  this._networkManager,
237
261
  this._signalManager,
238
262
  this._runtimeParams,
@@ -248,11 +272,7 @@ export class ClientServicesHost {
248
272
  (profile) => this._serviceContext.broadcastProfileUpdate(profile),
249
273
  ),
250
274
 
251
- InvitationsService: new InvitationsServiceImpl(
252
- this._serviceContext.invitations,
253
- (invitation) => this._serviceContext.getInvitationHandler(invitation),
254
- this._serviceContext.metadataStore,
255
- ),
275
+ InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
256
276
 
257
277
  DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
258
278
 
@@ -286,11 +306,6 @@ export class ClientServicesHost {
286
306
  });
287
307
 
288
308
  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
309
 
295
310
  const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
296
311
  if (devtoolsProxy) {
@@ -302,6 +317,7 @@ export class ClientServicesHost {
302
317
  });
303
318
  void this._devtoolsProxy.open();
304
319
  }
320
+ this.diagnosticsBroadcastHandler.start();
305
321
 
306
322
  this._opening = false;
307
323
  this._open = true;
@@ -320,10 +336,12 @@ export class ClientServicesHost {
320
336
 
321
337
  const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
322
338
  log('closing...', { deviceKey });
339
+ this.diagnosticsBroadcastHandler.stop();
323
340
  await this._devtoolsProxy?.close();
324
341
  this._serviceRegistry.setServices({ SystemService: this._systemService });
325
342
  await this._loggingService.close();
326
343
  await this._serviceContext.close();
344
+ await this._level?.close();
327
345
  this._open = false;
328
346
  this._statusUpdate.emit();
329
347
  log('closed', { deviceKey });
@@ -353,15 +371,24 @@ export class ClientServicesHost {
353
371
  const document = await this._serviceContext.automergeHost.repo.find<SpaceDoc>(automergeIndex as any);
354
372
  await document.whenReady();
355
373
 
356
- const objectDocument = createRawObjectDoc(
357
- { [defaultKey]: identity.identityKey.toHex() },
358
- { type: E.getTypeReference(PropertiesSchema) },
359
- );
374
+ // TODO(dmaretskyi): Better API for low-level data access.
375
+ const properties: ObjectStructure = {
376
+ system: {
377
+ type: encodeReference(getTypeReference(Properties)!),
378
+ },
379
+ data: {
380
+ [defaultKey]: identity.identityKey.toHex(),
381
+ },
382
+ meta: {
383
+ keys: [],
384
+ },
385
+ };
386
+ const propertiesId = PublicKey.random().toHex();
360
387
  document.change((doc: SpaceDoc) => {
361
- assignDeep(doc, ['objects', objectDocument.id], objectDocument.handle.docSync());
388
+ assignDeep(doc, ['objects', propertiesId], properties);
362
389
  });
363
- // TODO: replace with flush when supported by automerge-repo
364
- await sleep(200);
390
+
391
+ await this._serviceContext.automergeHost.repo.flush();
365
392
 
366
393
  return identity;
367
394
  }
@@ -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);
@@ -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 = {
@@ -6,7 +6,15 @@ import { type Config } from '@dxos/config';
6
6
  import { Context } from '@dxos/context';
7
7
  import { createCredentialSignerWithChain, CredentialGenerator } from '@dxos/credentials';
8
8
  import { failUndefined } from '@dxos/debug';
9
- import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
9
+ import {
10
+ AutomergeHost,
11
+ MetadataStore,
12
+ type LevelDB,
13
+ SnapshotStore,
14
+ SpaceManager,
15
+ valueEncoding,
16
+ } from '@dxos/echo-pipeline';
17
+ import { createTestLevel } from '@dxos/echo-pipeline/testing';
10
18
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
11
19
  import { Keyring } from '@dxos/keyring';
12
20
  import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
@@ -29,7 +37,7 @@ export const createServiceHost = (config: Config, signalManagerContext: MemorySi
29
37
  });
30
38
  };
31
39
 
32
- export const createServiceContext = ({
40
+ export const createServiceContext = async ({
33
41
  signalContext = new MemorySignalManagerContext(),
34
42
  storage = createStorage({ type: StorageType.RAM }),
35
43
  }: {
@@ -41,8 +49,10 @@ export const createServiceContext = ({
41
49
  signalManager,
42
50
  transportFactory: MemoryTransportFactory,
43
51
  });
52
+ const level = createTestLevel();
53
+ await level.open();
44
54
 
45
- return new ServiceContext(storage, networkManager, signalManager);
55
+ return new ServiceContext(storage, level, networkManager, signalManager);
46
56
  };
47
57
 
48
58
  export const createPeers = async (numPeers: number) => {
@@ -50,7 +60,7 @@ export const createPeers = async (numPeers: number) => {
50
60
 
51
61
  return await Promise.all(
52
62
  Array.from(Array(numPeers)).map(async () => {
53
- const peer = createServiceContext({ signalContext });
63
+ const peer = await createServiceContext({ signalContext });
54
64
  await peer.open(new Context());
55
65
  return peer;
56
66
  }),
@@ -83,6 +93,7 @@ export type TestPeerOpts = {
83
93
 
84
94
  export type TestPeerProps = {
85
95
  storage?: Storage;
96
+ level?: LevelDB;
86
97
  feedStore?: FeedStore<any>;
87
98
  metadataStore?: MetadataStore;
88
99
  keyring?: Keyring;
@@ -115,6 +126,10 @@ export class TestPeer {
115
126
  return (this._props.keyring ??= new Keyring(this.storage.createDirectory('keyring')));
116
127
  }
117
128
 
129
+ get level() {
130
+ return (this._props.level ??= createTestLevel());
131
+ }
132
+
118
133
  get feedStore() {
119
134
  return (this._props.feedStore ??= new FeedStore({
120
135
  factory: new FeedFactory({
@@ -161,7 +176,9 @@ export class TestPeer {
161
176
  }
162
177
 
163
178
  get automergeHost() {
164
- return (this._props.automergeHost ??= new AutomergeHost({ directory: this.storage.createDirectory('automerge') }));
179
+ return (this._props.automergeHost ??= new AutomergeHost({
180
+ db: this.level.sublevel('automerge'),
181
+ }));
165
182
  }
166
183
 
167
184
  get dataSpaceManager() {
@@ -180,6 +197,7 @@ export class TestPeer {
180
197
  }
181
198
 
182
199
  async destroy() {
200
+ await this.level.close();
183
201
  await this.storage.reset();
184
202
  }
185
203
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const DXOS_VERSION = "0.4.10-main.f68a1ca";
1
+ export const DXOS_VERSION = "0.4.10-main.f937af6";