@dxos/client-services 0.5.0 → 0.5.1-main.0ec204c

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 (95) hide show
  1. package/dist/lib/browser/{chunk-ESEYLOPB.mjs → chunk-5I77NB7R.mjs} +1437 -1119
  2. package/dist/lib/browser/chunk-5I77NB7R.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +36 -5
  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 +36 -14
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-GA7JFIXK.cjs → chunk-W24O4MIS.cjs} +1552 -1238
  9. package/dist/lib/node/chunk-W24O4MIS.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +78 -47
  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 +40 -18
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  16. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  17. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
  18. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  19. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +39 -0
  20. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -0
  21. package/dist/types/src/packlets/invitations/{invitation-extension.d.ts → invitation-host-extension.d.ts} +17 -31
  22. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -0
  23. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +6 -1
  24. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  25. package/dist/types/src/packlets/invitations/invitation-topology.d.ts +37 -0
  26. package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -0
  27. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +19 -10
  28. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  29. package/dist/types/src/packlets/invitations/invitations-handler.test.d.ts +2 -0
  30. package/dist/types/src/packlets/invitations/invitations-handler.test.d.ts.map +1 -0
  31. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +2 -1
  32. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  33. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +1 -0
  34. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  35. package/dist/types/src/packlets/invitations/utils.d.ts +6 -0
  36. package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -0
  37. package/dist/types/src/packlets/services/service-context.d.ts +12 -11
  38. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  39. package/dist/types/src/packlets/services/service-host.d.ts +1 -2
  40. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  41. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +4 -3
  42. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  43. package/dist/types/src/packlets/spaces/data-space.d.ts +4 -3
  44. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  45. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -1
  46. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  47. package/dist/types/src/packlets/storage/level.d.ts +1 -2
  48. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  49. package/dist/types/src/packlets/testing/invitation-utils.d.ts +2 -1
  50. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  51. package/dist/types/src/packlets/testing/test-builder.d.ts +5 -3
  52. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  53. package/dist/types/src/packlets/vault/shell-runtime.d.ts +10 -2
  54. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
  55. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  56. package/dist/types/src/version.d.ts +1 -1
  57. package/dist/types/src/version.d.ts.map +1 -1
  58. package/package.json +36 -35
  59. package/src/packlets/diagnostics/diagnostics-collector.ts +14 -9
  60. package/src/packlets/diagnostics/diagnostics.ts +78 -68
  61. package/src/packlets/invitations/device-invitation-protocol.ts +5 -1
  62. package/src/packlets/invitations/invitation-guest-extenstion.ts +126 -0
  63. package/src/packlets/invitations/{invitation-extension.ts → invitation-host-extension.ts} +99 -105
  64. package/src/packlets/invitations/invitation-protocol.ts +7 -1
  65. package/src/packlets/invitations/invitation-topology.ts +87 -0
  66. package/src/packlets/invitations/invitations-handler.test.ts +361 -0
  67. package/src/packlets/invitations/invitations-handler.ts +246 -149
  68. package/src/packlets/invitations/invitations-manager.ts +45 -3
  69. package/src/packlets/invitations/space-invitation-protocol.ts +23 -3
  70. package/src/packlets/invitations/utils.ts +27 -0
  71. package/src/packlets/services/automerge-host.test.ts +6 -4
  72. package/src/packlets/services/service-context.test.ts +3 -3
  73. package/src/packlets/services/service-context.ts +18 -31
  74. package/src/packlets/services/service-host.test.ts +6 -0
  75. package/src/packlets/services/service-host.ts +11 -28
  76. package/src/packlets/spaces/data-space-manager.test.ts +4 -4
  77. package/src/packlets/spaces/data-space-manager.ts +8 -11
  78. package/src/packlets/spaces/data-space.ts +16 -19
  79. package/src/packlets/spaces/genesis.ts +1 -1
  80. package/src/packlets/spaces/spaces-service.ts +12 -6
  81. package/src/packlets/storage/level.ts +3 -2
  82. package/src/packlets/testing/invitation-utils.ts +23 -3
  83. package/src/packlets/testing/test-builder.ts +13 -15
  84. package/src/packlets/vault/shell-runtime.ts +40 -2
  85. package/src/packlets/vault/worker-runtime.ts +3 -1
  86. package/src/version.ts +1 -5
  87. package/dist/lib/browser/chunk-ESEYLOPB.mjs.map +0 -7
  88. package/dist/lib/node/chunk-GA7JFIXK.cjs.map +0 -7
  89. package/dist/types/src/packlets/indexing/index.d.ts +0 -2
  90. package/dist/types/src/packlets/indexing/index.d.ts.map +0 -1
  91. package/dist/types/src/packlets/indexing/util.d.ts +0 -11
  92. package/dist/types/src/packlets/indexing/util.d.ts.map +0 -1
  93. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +0 -1
  94. package/src/packlets/indexing/index.ts +0 -5
  95. package/src/packlets/indexing/util.ts +0 -32
@@ -2,9 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Event, PushStream } from '@dxos/async';
5
+ import { Event, PushStream, TimeoutError, Trigger } from '@dxos/async';
6
6
  import {
7
- type AuthenticatingInvitation,
7
+ AuthenticatingInvitation,
8
8
  AUTHENTICATION_CODE_LENGTH,
9
9
  CancellableInvitation,
10
10
  INVITATION_TIMEOUT,
@@ -20,6 +20,7 @@ import {
20
20
  type AuthenticationRequest,
21
21
  Invitation,
22
22
  } from '@dxos/protocols/proto/dxos/client/services';
23
+ import { SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
23
24
 
24
25
  import type { InvitationProtocol } from './invitation-protocol';
25
26
  import { createAdmissionKeypair, type InvitationsHandler } from './invitations-handler';
@@ -117,7 +118,8 @@ export class InvitationsManager {
117
118
  }
118
119
 
119
120
  const handler = this._getHandler(options);
120
- const invitation = this._invitationsHandler.acceptInvitation(handler, options, request.deviceProfile);
121
+ const { ctx, invitation, stream, otpEnteredTrigger } = this._createObservableAcceptingInvitation(handler, options);
122
+ this._invitationsHandler.acceptInvitation(ctx, stream, handler, options, otpEnteredTrigger, request.deviceProfile);
121
123
  this._acceptInvitations.set(invitation.get().invitationId, invitation);
122
124
  this.invitationAccepted.emit(invitation.get());
123
125
 
@@ -149,6 +151,10 @@ export class InvitationsManager {
149
151
  if (created.get().persistent) {
150
152
  await this._metadataStore.removeInvitation(invitationId);
151
153
  }
154
+ if (created.get().type === Invitation.Type.DELEGATED) {
155
+ const handler = this._getHandler(created.get());
156
+ await handler.cancelDelegation(created.get());
157
+ }
152
158
  await created.cancel();
153
159
  this._createInvitations.delete(invitationId);
154
160
  this.removedCreated.emit(created.get());
@@ -190,6 +196,7 @@ export class InvitationsManager {
190
196
  persistent = options?.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY, // default no not storing keypairs
191
197
  created = new Date(),
192
198
  guestKeypair = undefined,
199
+ role = SpaceMember.Role.ADMIN,
193
200
  lifetime = 86400, // 1 day,
194
201
  multiUse = false,
195
202
  } = options ?? {};
@@ -210,6 +217,7 @@ export class InvitationsManager {
210
217
  guestKeypair ?? (authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? createAdmissionKeypair() : undefined),
211
218
  created,
212
219
  lifetime,
220
+ role,
213
221
  multiUse,
214
222
  delegationCredentialId: options?.delegationCredentialId,
215
223
  ...protocol.getInvitationContext(),
@@ -239,6 +247,40 @@ export class InvitationsManager {
239
247
  return { ctx, stream, observableInvitation };
240
248
  }
241
249
 
250
+ private _createObservableAcceptingInvitation(handler: InvitationProtocol, initialState: Invitation) {
251
+ const otpEnteredTrigger = new Trigger<string>();
252
+ const stream = new PushStream<Invitation>();
253
+ const ctx = new Context({
254
+ onError: (err) => {
255
+ if (err instanceof TimeoutError) {
256
+ log('timeout', { ...handler.toJSON() });
257
+ stream.next({ ...initialState, state: Invitation.State.TIMEOUT });
258
+ } else {
259
+ log.warn('auth failed', err);
260
+ stream.next({ ...initialState, state: Invitation.State.ERROR });
261
+ }
262
+ void ctx.dispose();
263
+ },
264
+ });
265
+ ctx.onDispose(() => {
266
+ log('complete', { ...handler.toJSON() });
267
+ stream.complete();
268
+ });
269
+ const invitation = new AuthenticatingInvitation({
270
+ initialInvitation: initialState,
271
+ subscriber: stream.observable,
272
+ onCancel: async () => {
273
+ stream.next({ ...initialState, state: Invitation.State.CANCELLED });
274
+ await ctx.dispose();
275
+ },
276
+ onAuthenticate: async (code: string) => {
277
+ // TODO(burdon): Reset creates a race condition? Event?
278
+ otpEnteredTrigger.wake(code);
279
+ },
280
+ });
281
+ return { ctx, invitation, stream, otpEnteredTrigger };
282
+ }
283
+
242
284
  private async _persistIfRequired(
243
285
  handler: InvitationProtocol,
244
286
  changeStream: PushStream<Invitation>,
@@ -4,6 +4,7 @@
4
4
 
5
5
  import {
6
6
  createAdmissionCredentials,
7
+ createCancelDelegatedSpaceInvitationCredential,
7
8
  createDelegatedSpaceInvitationCredential,
8
9
  getCredentialAssertion,
9
10
  } from '@dxos/credentials';
@@ -53,7 +54,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
53
54
  guestProfile?: ProfileDocument | undefined,
54
55
  ): Promise<AdmissionResponse> {
55
56
  invariant(this._spaceKey);
56
- const space = await this._spaceManager.spaces.get(this._spaceKey);
57
+ const space = this._spaceManager.spaces.get(this._spaceKey);
57
58
  invariant(space);
58
59
 
59
60
  invariant(request.space);
@@ -66,6 +67,8 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
66
67
  identityKey,
67
68
  space.key,
68
69
  space.inner.genesisFeedKey,
70
+ invitation.role ?? SpaceMember.Role.ADMIN,
71
+ space.inner.spaceState.membershipChainHeads,
69
72
  guestProfile,
70
73
  invitation.delegationCredentialId,
71
74
  );
@@ -87,7 +90,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
87
90
 
88
91
  async delegate(invitation: Invitation): Promise<PublicKey> {
89
92
  invariant(this._spaceKey);
90
- const space = await this._spaceManager.spaces.get(this._spaceKey);
93
+ const space = this._spaceManager.spaces.get(this._spaceKey);
91
94
  invariant(space);
92
95
  if (invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY) {
93
96
  invariant(invitation.guestKeypair?.publicKey);
@@ -101,7 +104,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
101
104
  invitationId: invitation.invitationId,
102
105
  authMethod: invitation.authMethod,
103
106
  swarmKey: invitation.swarmKey,
104
- role: SpaceMember.Role.ADMIN,
107
+ role: invitation.role ?? SpaceMember.Role.ADMIN,
105
108
  expiresOn: invitation.lifetime
106
109
  ? new Date((invitation.created?.getTime() ?? Date.now()) + invitation.lifetime)
107
110
  : undefined,
@@ -118,6 +121,23 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
118
121
  return credential.credential.credential.id!;
119
122
  }
120
123
 
124
+ async cancelDelegation(invitation: Invitation): Promise<void> {
125
+ invariant(this._spaceKey);
126
+ invariant(invitation.type === Invitation.Type.DELEGATED && invitation.delegationCredentialId);
127
+ const space = this._spaceManager.spaces.get(this._spaceKey);
128
+ invariant(space);
129
+
130
+ log('cancelling delegated space invitation', { host: this._signingContext.deviceKey, id: invitation.invitationId });
131
+ const credential = await createCancelDelegatedSpaceInvitationCredential(
132
+ this._signingContext.credentialSigner,
133
+ space.key,
134
+ invitation.delegationCredentialId,
135
+ );
136
+
137
+ invariant(credential.credential);
138
+ await writeMessages(space.inner.controlPipeline.writer, [credential]);
139
+ }
140
+
121
141
  checkInvitation(invitation: Partial<Invitation>) {
122
142
  if (invitation.spaceKey && this._spaceManager.spaces.has(invitation.spaceKey)) {
123
143
  return new AlreadyJoinedError('Already joined space.');
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Mutex, type MutexGuard } from '@dxos/async';
6
+ import { cancelWithContext, type Context, ContextDisposedError } from '@dxos/context';
7
+ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
8
+
9
+ export const stateToString = (state: Invitation.State): string => {
10
+ return Object.entries(Invitation.State).find(([key, val]) => val === state)?.[0] ?? 'unknown';
11
+ };
12
+
13
+ export const tryAcquireBeforeContextDisposed = async (ctx: Context, mutex: Mutex): Promise<MutexGuard> => {
14
+ let guard: MutexGuard | undefined;
15
+ return cancelWithContext(
16
+ ctx,
17
+ (async () => {
18
+ guard = await mutex.acquire();
19
+ if (ctx.disposed) {
20
+ guard.release();
21
+ guard = undefined;
22
+ throw new ContextDisposedError();
23
+ }
24
+ return guard;
25
+ })(),
26
+ );
27
+ };
@@ -4,10 +4,12 @@
4
4
 
5
5
  import { expect } from 'chai';
6
6
 
7
- import { asyncTimeout, sleep } from '@dxos/async';
7
+ import { asyncTimeout } from '@dxos/async';
8
+ import { getHeads } from '@dxos/automerge/automerge';
8
9
  import { AutomergeContext } from '@dxos/echo-db';
9
10
  import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
10
- import { createTestLevel } from '@dxos/echo-pipeline/testing';
11
+ import { IndexMetadataStore } from '@dxos/indexing';
12
+ import { createTestLevel } from '@dxos/kv-store/testing';
11
13
  import { afterTest, describe, test } from '@dxos/test';
12
14
 
13
15
  describe('AutomergeHost', () => {
@@ -25,6 +27,7 @@ describe('AutomergeHost', () => {
25
27
 
26
28
  const host = new AutomergeHost({
27
29
  db: level.sublevel('automerge'),
30
+ indexMetadataStore: new IndexMetadataStore({ db: level.sublevel('index-metadata') }),
28
31
  });
29
32
  await host.open();
30
33
  afterTest(() => host.close());
@@ -50,9 +53,8 @@ describe('AutomergeHost', () => {
50
53
  doc.change((doc: any) => {
51
54
  doc.text = newText;
52
55
  });
56
+ await client.flush({ states: [{ documentId: doc.documentId, heads: getHeads(doc.docSync()) }] });
53
57
 
54
- // TODO(mykola): Is there a way to know when automerge has started replication?
55
- await sleep(100);
56
58
  await asyncTimeout(handle.whenReady(), 1_000);
57
59
  expect(handle.docSync().text).to.equal(newText);
58
60
  });
@@ -27,15 +27,15 @@ describe('services/ServiceContext', () => {
27
27
  test('joined space is synchronized on device invitations', async () => {
28
28
  const networkContext = new MemorySignalManagerContext();
29
29
  const device1 = await createServiceContext({ signalContext: networkContext });
30
- await openAndClose(device1.automergeHost);
30
+ await openAndClose(device1.echoHost);
31
31
  await device1.createIdentity();
32
32
 
33
33
  const device2 = await createServiceContext({ signalContext: networkContext });
34
- await openAndClose(device2.automergeHost);
34
+ await openAndClose(device2.echoHost);
35
35
  await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
36
36
 
37
37
  const identity2 = await createServiceContext({ signalContext: networkContext });
38
- await openAndClose(identity2.automergeHost);
38
+ await openAndClose(identity2.echoHost);
39
39
  await identity2.createIdentity();
40
40
  const space1 = await identity2.dataSpaceManager!.createSpace();
41
41
  await Promise.all(
@@ -2,18 +2,17 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { type Level } from 'level';
6
-
7
5
  import { Trigger } from '@dxos/async';
8
6
  import { Context, Resource } from '@dxos/context';
9
7
  import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
10
8
  import { failUndefined } from '@dxos/debug';
11
- import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
9
+ import { EchoHost } from '@dxos/echo-db';
10
+ import { MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
12
11
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
13
- import { IndexMetadataStore, IndexStore, Indexer, createStorageCallbacks } from '@dxos/indexing';
14
12
  import { invariant } from '@dxos/invariant';
15
13
  import { Keyring } from '@dxos/keyring';
16
14
  import { PublicKey } from '@dxos/keys';
15
+ import { type LevelDB } from '@dxos/kv-store';
17
16
  import { log } from '@dxos/log';
18
17
  import { type SignalManager } from '@dxos/messaging';
19
18
  import { type NetworkManager } from '@dxos/network-manager';
@@ -22,6 +21,7 @@ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
22
21
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
23
22
  import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
24
23
  import { type Storage } from '@dxos/random-access-storage';
24
+ import type { TeleportParams } from '@dxos/teleport';
25
25
  import { BlobStore } from '@dxos/teleport-extension-object-sync';
26
26
  import { trace as Trace } from '@dxos/tracing';
27
27
  import { safeInstanceof } from '@dxos/util';
@@ -32,7 +32,6 @@ import {
32
32
  type IdentityManagerRuntimeParams,
33
33
  type JoinIdentityParams,
34
34
  } from '../identity';
35
- import { createSelectedDocumentsIterator } from '../indexing';
36
35
  import {
37
36
  DeviceInvitationProtocol,
38
37
  InvitationsHandler,
@@ -42,7 +41,8 @@ import {
42
41
  import { InvitationsManager } from '../invitations/invitations-manager';
43
42
  import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
44
43
 
45
- export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams;
44
+ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams &
45
+ DataSpaceManagerRuntimeParams & { invitationConnectionDefaultParams?: Partial<TeleportParams> };
46
46
  /**
47
47
  * Shared backend for all client services.
48
48
  */
@@ -64,9 +64,7 @@ export class ServiceContext extends Resource {
64
64
  public readonly identityManager: IdentityManager;
65
65
  public readonly invitations: InvitationsHandler;
66
66
  public readonly invitationsManager: InvitationsManager;
67
- public readonly automergeHost: AutomergeHost;
68
- public readonly indexMetadata: IndexMetadataStore;
69
- public readonly indexer: Indexer;
67
+ public readonly echoHost: EchoHost;
70
68
 
71
69
  // Initialized after identity is initialized.
72
70
  public dataSpaceManager?: DataSpaceManager;
@@ -82,10 +80,10 @@ export class ServiceContext extends Resource {
82
80
 
83
81
  constructor(
84
82
  public readonly storage: Storage,
85
- public readonly level: Level<string, string>,
83
+ public readonly level: LevelDB,
86
84
  public readonly networkManager: NetworkManager,
87
85
  public readonly signalManager: SignalManager,
88
- public readonly _runtimeParams?: IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams,
86
+ public readonly _runtimeParams?: ServiceContextRuntimeParams,
89
87
  ) {
90
88
  super();
91
89
 
@@ -122,22 +120,12 @@ export class ServiceContext extends Resource {
122
120
  this._runtimeParams as IdentityManagerRuntimeParams,
123
121
  );
124
122
 
125
- this.indexMetadata = new IndexMetadataStore({ db: level.sublevel('index-metadata') });
126
-
127
- this.automergeHost = new AutomergeHost({
128
- directory: storage.createDirectory('automerge'),
129
- db: level.sublevel('automerge'),
130
- storageCallbacks: createStorageCallbacks({ host: () => this.automergeHost, metadata: this.indexMetadata }),
131
- });
132
-
133
- this.indexer = new Indexer({
134
- db: this.level,
135
- indexStore: new IndexStore({ db: level.sublevel('index-storage') }),
136
- metadataStore: this.indexMetadata,
137
- loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
123
+ this.echoHost = new EchoHost({
124
+ kv: this.level,
125
+ storage: this.storage,
138
126
  });
139
127
 
140
- this.invitations = new InvitationsHandler(this.networkManager);
128
+ this.invitations = new InvitationsHandler(this.networkManager, _runtimeParams?.invitationConnectionDefaultParams);
141
129
  this.invitationsManager = new InvitationsManager(
142
130
  this.invitations,
143
131
  (invitation) => this.getInvitationHandler(invitation),
@@ -166,7 +154,7 @@ export class ServiceContext extends Resource {
166
154
  await this.signalManager.open();
167
155
  await this.networkManager.open();
168
156
 
169
- await this.automergeHost.open();
157
+ await this.echoHost.open(ctx);
170
158
  await this.metadataStore.load();
171
159
  await this.spaceManager.open();
172
160
  await this.identityManager.open(ctx);
@@ -181,20 +169,19 @@ export class ServiceContext extends Resource {
181
169
  log('opened');
182
170
  }
183
171
 
184
- protected override async _close() {
172
+ protected override async _close(ctx: Context) {
185
173
  log('closing...');
186
174
  if (this._deviceSpaceSync && this.identityManager.identity) {
187
175
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
188
176
  }
189
- await this.automergeHost.close();
190
177
  await this.dataSpaceManager?.close();
191
178
  await this.identityManager.close();
192
179
  await this.spaceManager.close();
193
180
  await this.feedStore.close();
181
+ await this.metadataStore.close();
182
+ await this.echoHost.close(ctx);
194
183
  await this.networkManager.close();
195
184
  await this.signalManager.close();
196
- await this.metadataStore.close();
197
- await this.indexer.destroy();
198
185
  log('closed');
199
186
  }
200
187
 
@@ -255,7 +242,7 @@ export class ServiceContext extends Resource {
255
242
  this.keyring,
256
243
  signingContext,
257
244
  this.feedStore,
258
- this.automergeHost,
245
+ this.echoHost,
259
246
  this.invitationsManager,
260
247
  this._runtimeParams as DataSpaceManagerRuntimeParams,
261
248
  );
@@ -29,6 +29,12 @@ describe('ClientServicesHost', () => {
29
29
  isNode() && rmSync(dataRoot, { recursive: true, force: true });
30
30
  });
31
31
 
32
+ test('open and close', async () => {
33
+ const host = createServiceHost(new Config(), new MemorySignalManagerContext());
34
+ await host.open(new Context());
35
+ await host.close();
36
+ });
37
+
32
38
  test('queryCredentials', async () => {
33
39
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
34
40
  await host.open(new Context());
@@ -2,23 +2,15 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import { type Level } from 'level';
6
-
7
5
  import { Event, synchronized } from '@dxos/async';
8
6
  import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
9
7
  import { type Config } from '@dxos/config';
10
8
  import { Context } from '@dxos/context';
11
- import {
12
- DataServiceImpl,
13
- type ObjectStructure,
14
- encodeReference,
15
- type SpaceDoc,
16
- type LevelDB,
17
- } from '@dxos/echo-pipeline';
9
+ import { type ObjectStructure, encodeReference, type SpaceDoc } from '@dxos/echo-protocol';
18
10
  import { getTypeReference } from '@dxos/echo-schema';
19
- import { QueryServiceImpl } from '@dxos/indexing';
20
11
  import { invariant } from '@dxos/invariant';
21
12
  import { PublicKey } from '@dxos/keys';
13
+ import { type LevelDB } from '@dxos/kv-store';
22
14
  import { log } from '@dxos/log';
23
15
  import { WebsocketSignalManager, type SignalManager } from '@dxos/messaging';
24
16
  import { NetworkManager, createSimplePeerTransportFactory, type TransportFactory } from '@dxos/network-manager';
@@ -83,14 +75,13 @@ export class ClientServicesHost {
83
75
  private readonly _systemService: SystemServiceImpl;
84
76
  private readonly _loggingService: LoggingServiceImpl;
85
77
  private readonly _tracingService = TRACE_PROCESSOR.createTraceSender();
86
- private _queryService!: QueryServiceImpl;
87
78
 
88
79
  private _config?: Config;
89
80
  private readonly _statusUpdate = new Event<void>();
90
81
  private _signalManager?: SignalManager;
91
82
  private _networkManager?: NetworkManager;
92
83
  private _storage?: Storage;
93
- private _level?: Level<string, string>;
84
+ private _level?: LevelDB;
94
85
  private _callbacks?: ClientServicesHostCallbacks;
95
86
  private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
96
87
 
@@ -246,13 +237,13 @@ export class ClientServicesHost {
246
237
  this._opening = true;
247
238
  log('opening...', { lockKey: this._resourceLock?.lockKey });
248
239
 
240
+ await this._resourceLock?.acquire();
241
+
249
242
  if (!this._level) {
250
243
  this._level = await createLevel(this._config.get('runtime.client.storage', {})!);
251
244
  }
252
245
  await this._level.open();
253
246
 
254
- await this._resourceLock?.acquire();
255
-
256
247
  await this._loggingService.open();
257
248
 
258
249
  this._serviceContext = new ServiceContext(
@@ -263,12 +254,6 @@ export class ClientServicesHost {
263
254
  this._runtimeParams,
264
255
  );
265
256
 
266
- this._queryService = new QueryServiceImpl({
267
- indexer: this._serviceContext.indexer,
268
- automergeHost: this._serviceContext.automergeHost,
269
- });
270
- await this._queryService.open(ctx);
271
-
272
257
  this._serviceRegistry.setServices({
273
258
  SystemService: this._systemService,
274
259
 
@@ -292,9 +277,8 @@ export class ClientServicesHost {
292
277
  },
293
278
  ),
294
279
 
295
- DataService: new DataServiceImpl(this._serviceContext.automergeHost),
296
-
297
- QueryService: this._queryService,
280
+ DataService: this._serviceContext.echoHost.dataService,
281
+ QueryService: this._serviceContext.echoHost.queryService,
298
282
 
299
283
  NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
300
284
 
@@ -344,7 +328,6 @@ export class ClientServicesHost {
344
328
  await this._devtoolsProxy?.close();
345
329
  this._serviceRegistry.setServices({ SystemService: this._systemService });
346
330
  await this._loggingService.close();
347
- await this._queryService.close();
348
331
  await this._serviceContext.close();
349
332
  await this._level?.close();
350
333
  this._open = false;
@@ -356,10 +339,10 @@ export class ClientServicesHost {
356
339
  const traceId = PublicKey.random().toHex();
357
340
  log.trace('dxos.sdk.client-services-host.reset', trace.begin({ id: traceId }));
358
341
 
359
- log('resetting...');
342
+ log.info('resetting...');
360
343
  await this._serviceContext?.close();
361
344
  await this._storage!.reset();
362
- log('reset');
345
+ log.info('reset');
363
346
  log.trace('dxos.sdk.client-services-host.reset', trace.end({ id: traceId }));
364
347
  await this._callbacks?.onReset?.();
365
348
  }
@@ -373,7 +356,7 @@ export class ClientServicesHost {
373
356
 
374
357
  const automergeIndex = space.automergeSpaceState.rootUrl;
375
358
  invariant(automergeIndex);
376
- const document = await this._serviceContext.automergeHost.repo.find<SpaceDoc>(automergeIndex as any);
359
+ const document = await this._serviceContext.echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
377
360
  await document.whenReady();
378
361
 
379
362
  // TODO(dmaretskyi): Better API for low-level data access.
@@ -393,7 +376,7 @@ export class ClientServicesHost {
393
376
  assignDeep(doc, ['objects', propertiesId], properties);
394
377
  });
395
378
 
396
- await this._serviceContext.automergeHost.repo.flush();
379
+ await this._serviceContext.echoHost.flush();
397
380
 
398
381
  return identity;
399
382
  }
@@ -20,7 +20,7 @@ describe('DataSpaceManager', () => {
20
20
 
21
21
  const peer = builder.createPeer();
22
22
  await peer.createIdentity();
23
- await openAndClose(peer.automergeHost, 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.automergeHost, peer1.dataSpaceManager, peer2.automergeHost, 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.automergeHost, peer1.dataSpaceManager, peer2.automergeHost, 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.automergeHost, 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);
@@ -5,7 +5,8 @@
5
5
  import { Event, synchronized, trackLeaks } from '@dxos/async';
6
6
  import { Context, cancelWithContext } from '@dxos/context';
7
7
  import { getCredentialAssertion, type CredentialSigner, type DelegateInvitationCredential } from '@dxos/credentials';
8
- import { type AutomergeHost, type MetadataStore, type Space, type SpaceManager } from '@dxos/echo-pipeline';
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';
@@ -79,7 +80,7 @@ export class DataSpaceManager {
79
80
  private readonly _keyring: Keyring,
80
81
  private readonly _signingContext: SigningContext,
81
82
  private readonly _feedStore: FeedStore<FeedMessage>,
82
- private readonly _automergeHost: AutomergeHost,
83
+ private readonly _echoHost: EchoHost,
83
84
  private readonly _invitationsManager: InvitationsManager,
84
85
  params?: DataSpaceManagerRuntimeParams,
85
86
  ) {
@@ -152,14 +153,10 @@ export class DataSpaceManager {
152
153
 
153
154
  log('creating space...', { spaceKey });
154
155
 
155
- const automergeRoot = this._automergeHost.repo.create();
156
- automergeRoot.change((doc: any) => {
157
- doc.access = { spaceKey: spaceKey.toHex() };
158
- });
159
-
156
+ const automergeRootUrl = await this._echoHost.createSpaceRoot(spaceKey);
160
157
  const space = await this._constructSpace(metadata);
161
158
 
162
- const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, automergeRoot.url);
159
+ const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, automergeRootUrl);
163
160
  await this._metadataStore.addSpace(metadata);
164
161
 
165
162
  const memberCredential = credentials[1];
@@ -243,8 +240,8 @@ export class DataSpaceManager {
243
240
  gossip.createExtension({ remotePeerId: session.remotePeerId }),
244
241
  );
245
242
  session.addExtension('dxos.mesh.teleport.notarization', dataSpace.notarizationPlugin.createExtension());
246
- this._automergeHost.authorizeDevice(space.key, session.remotePeerId);
247
- 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());
248
245
  },
249
246
  onAuthFailure: () => {
250
247
  log.warn('auth failure');
@@ -265,6 +262,7 @@ export class DataSpaceManager {
265
262
  presence,
266
263
  keyring: this._keyring,
267
264
  feedStore: this._feedStore,
265
+ echoHost: this._echoHost,
268
266
  signingContext: this._signingContext,
269
267
  callbacks: {
270
268
  beforeReady: async () => {
@@ -282,7 +280,6 @@ export class DataSpaceManager {
282
280
  },
283
281
  },
284
282
  cache: metadata.cache,
285
- automergeHost: this._automergeHost,
286
283
  });
287
284
 
288
285
  if (metadata.state !== SpaceState.INACTIVE) {