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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/lib/browser/{chunk-S3G2RM7S.mjs → chunk-HIQTBJPW.mjs} +837 -715
  2. package/dist/lib/browser/chunk-HIQTBJPW.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +10 -4
  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 +132 -116
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-3T6D6GIB.cjs → chunk-JGUWA36I.cjs} +859 -733
  9. package/dist/lib/node/chunk-JGUWA36I.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +50 -44
  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 +131 -118
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  16. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +3 -1
  17. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  18. package/dist/types/src/packlets/invitations/index.d.ts +1 -0
  19. package/dist/types/src/packlets/invitations/index.d.ts.map +1 -1
  20. package/dist/types/src/packlets/invitations/invitation-extension.d.ts +1 -0
  21. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
  22. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +6 -1
  23. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  24. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +8 -3
  25. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  26. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +44 -0
  27. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -0
  28. package/dist/types/src/packlets/invitations/invitations-service.d.ts +7 -23
  29. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  30. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -1
  31. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  32. package/dist/types/src/packlets/services/service-context.d.ts +6 -6
  33. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  34. package/dist/types/src/packlets/services/service-host.d.ts +2 -2
  35. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  36. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +8 -3
  37. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  38. package/dist/types/src/packlets/spaces/data-space.d.ts +4 -3
  39. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  40. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  41. package/dist/types/src/packlets/testing/test-builder.d.ts +9 -6
  42. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  43. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  44. package/dist/types/src/version.d.ts +1 -1
  45. package/package.json +34 -34
  46. package/src/packlets/identity/identity-manager.ts +1 -0
  47. package/src/packlets/identity/identity.test.ts +3 -0
  48. package/src/packlets/invitations/device-invitation-protocol.ts +6 -1
  49. package/src/packlets/invitations/index.ts +1 -0
  50. package/src/packlets/invitations/invitation-extension.ts +28 -1
  51. package/src/packlets/invitations/invitation-protocol.ts +7 -1
  52. package/src/packlets/invitations/invitations-handler.ts +77 -91
  53. package/src/packlets/invitations/invitations-manager.ts +271 -0
  54. package/src/packlets/invitations/invitations-service.ts +23 -168
  55. package/src/packlets/invitations/space-invitation-protocol.ts +45 -3
  56. package/src/packlets/services/automerge-host.test.ts +4 -4
  57. package/src/packlets/services/service-context.test.ts +3 -3
  58. package/src/packlets/services/service-context.ts +23 -25
  59. package/src/packlets/services/service-host.test.ts +6 -0
  60. package/src/packlets/services/service-host.ts +9 -29
  61. package/src/packlets/spaces/data-space-manager.test.ts +4 -4
  62. package/src/packlets/spaces/data-space-manager.ts +56 -13
  63. package/src/packlets/spaces/data-space.ts +14 -19
  64. package/src/packlets/testing/invitation-utils.ts +100 -97
  65. package/src/packlets/testing/test-builder.ts +29 -16
  66. package/src/packlets/vault/worker-runtime.ts +3 -1
  67. package/src/version.ts +1 -1
  68. package/dist/lib/browser/chunk-S3G2RM7S.mjs.map +0 -7
  69. package/dist/lib/node/chunk-3T6D6GIB.cjs.map +0 -7
  70. package/dist/types/src/packlets/indexing/index.d.ts +0 -2
  71. package/dist/types/src/packlets/indexing/index.d.ts.map +0 -1
  72. package/dist/types/src/packlets/indexing/util.d.ts +0 -15
  73. package/dist/types/src/packlets/indexing/util.d.ts.map +0 -1
  74. package/src/packlets/indexing/index.ts +0 -5
  75. package/src/packlets/indexing/util.ts +0 -89
@@ -2,43 +2,22 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { Event, scheduleTask } from '@dxos/async';
6
- import { type AuthenticatingInvitation, type CancellableInvitation } from '@dxos/client-protocol';
7
5
  import { Stream } from '@dxos/codec-protobuf';
8
- import { Context } from '@dxos/context';
9
- import { hasInvitationExpired, type MetadataStore } from '@dxos/echo-pipeline';
10
- import { invariant } from '@dxos/invariant';
11
- import { log } from '@dxos/log';
12
6
  import {
13
7
  type AuthenticationRequest,
14
8
  type AcceptInvitationRequest,
15
- Invitation,
9
+ type Invitation,
16
10
  type InvitationsService,
17
11
  QueryInvitationsResponse,
18
12
  } from '@dxos/protocols/proto/dxos/client/services';
19
13
 
20
- import { type InvitationProtocol } from './invitation-protocol';
21
- import { type InvitationsHandler } from './invitations-handler';
14
+ import { type InvitationsManager } from './invitations-manager';
22
15
 
23
16
  /**
24
17
  * Adapts invitation service observable to client/service stream.
25
18
  */
26
19
  export class InvitationsServiceImpl implements InvitationsService {
27
- private readonly _createInvitations = new Map<string, CancellableInvitation>();
28
- private readonly _acceptInvitations = new Map<string, AuthenticatingInvitation>();
29
- private readonly _invitationCreated = new Event<Invitation>();
30
- private readonly _invitationAccepted = new Event<Invitation>();
31
- private readonly _removedCreated = new Event<Invitation>();
32
- private readonly _removedAccepted = new Event<Invitation>();
33
- private readonly _saved = new Event<Invitation>();
34
- private readonly _persistentInvitationsLoadedEvent = new Event();
35
- private _persistentInvitationsLoaded = false;
36
-
37
- constructor(
38
- private readonly _invitationsHandler: InvitationsHandler,
39
- private readonly _getHandler: (invitation: Invitation) => InvitationProtocol,
40
- private readonly _metadataStore: MetadataStore,
41
- ) {}
20
+ constructor(private readonly _invitationsManager: InvitationsManager) {}
42
21
 
43
22
  // TODO(burdon): Guest/host label.
44
23
  getLoggingContext() {
@@ -48,148 +27,33 @@ export class InvitationsServiceImpl implements InvitationsService {
48
27
  }
49
28
 
50
29
  createInvitation(options: Invitation): Stream<Invitation> {
51
- let invitation: CancellableInvitation;
52
-
53
- const savePersistentInvitationCtx = new Context();
54
- const existingInvitation = this._createInvitations.get(options.invitationId);
55
- if (existingInvitation) {
56
- invitation = existingInvitation;
57
- } else {
58
- const handler = this._getHandler(options);
59
- invitation = this._invitationsHandler.createInvitation(handler, options);
60
- this._createInvitations.set(invitation.get().invitationId, invitation);
61
- this._invitationCreated.emit(invitation.get());
62
- }
63
-
64
30
  return new Stream<Invitation>(({ next, close }) => {
65
- if (invitation.get().persistent) {
66
- scheduleTask(savePersistentInvitationCtx, async () => {
67
- try {
68
- await this._metadataStore.addInvitation(invitation.get());
69
- this._saved.emit(invitation.get());
70
- } catch (err: any) {
71
- close(err);
72
- }
73
- });
74
- }
75
- invitation.subscribe(
76
- (invitation) => {
77
- next(invitation);
78
- },
79
- async (err: Error) => {
80
- await savePersistentInvitationCtx.dispose();
81
-
82
- // TODO(nf): also remove from storage?
83
- close(err);
84
- },
85
- async () => {
86
- close();
87
- if (invitation.get().persistent) {
88
- await savePersistentInvitationCtx.dispose();
89
- // TODO(nf): remove on all complete conditions?
90
- await this._metadataStore.removeInvitation(invitation.get().invitationId);
91
- }
92
-
93
- this._createInvitations.delete(invitation.get().invitationId);
94
- if (!invitation.get().multiUse) {
95
- this._removedCreated.emit(invitation.get());
96
- }
97
- },
98
- );
99
- });
100
- }
101
-
102
- async loadPersistentInvitations() {
103
- const persistentInvitations = this._metadataStore.getInvitations();
104
-
105
- // get saved persistent invitations, filter and remove from storage those that have expired.
106
- const freshInvitations = persistentInvitations.filter(async (invitation) => !hasInvitationExpired(invitation));
107
-
108
- const cInvitations = freshInvitations.map((persistentInvitation) => {
109
- invariant(!this._createInvitations.get(persistentInvitation.invitationId), 'invitation already exists');
110
-
111
- const handler = this._getHandler(persistentInvitation);
112
- const invitation = this._invitationsHandler.createInvitation(handler, persistentInvitation);
113
- this._createInvitations.set(invitation.get().invitationId, invitation);
114
- this._invitationCreated.emit(invitation.get());
115
- return persistentInvitation;
31
+ void this._invitationsManager
32
+ .createInvitation(options)
33
+ .then((invitation) => invitation.subscribe(next, close, close))
34
+ .catch(close);
116
35
  });
117
- this._persistentInvitationsLoadedEvent.emit();
118
- this._persistentInvitationsLoaded = true;
119
- return { invitations: cInvitations };
120
36
  }
121
37
 
122
- acceptInvitation({ invitation: options, deviceProfile }: AcceptInvitationRequest): Stream<Invitation> {
123
- let invitation: AuthenticatingInvitation;
124
-
125
- // TODO(nf): duplicate check in InvitationHandler
126
- if (deviceProfile) {
127
- invariant(options.kind === Invitation.Kind.DEVICE, 'deviceProfile provided for non-device invitation');
128
- }
129
-
130
- const existingInvitation = this._acceptInvitations.get(options.invitationId);
131
- if (existingInvitation) {
132
- invitation = existingInvitation;
133
- } else {
134
- const handler = this._getHandler(options);
135
- invitation = this._invitationsHandler.acceptInvitation(handler, options, deviceProfile);
136
- this._acceptInvitations.set(invitation.get().invitationId, invitation);
137
- this._invitationAccepted.emit(invitation.get());
138
- }
139
-
38
+ acceptInvitation(request: AcceptInvitationRequest): Stream<Invitation> {
39
+ const invitation = this._invitationsManager.acceptInvitation(request);
140
40
  return new Stream<Invitation>(({ next, close }) => {
141
- invitation.subscribe(
142
- (invitation) => {
143
- next(invitation);
144
- },
145
- (err: Error) => {
146
- close(err);
147
- },
148
- () => {
149
- close();
150
- this._acceptInvitations.delete(invitation.get().invitationId);
151
- if (!invitation.get().multiUse) {
152
- this._removedAccepted.emit(invitation.get());
153
- }
154
- },
155
- );
41
+ invitation.subscribe(next, close, close);
156
42
  });
157
43
  }
158
44
 
159
- async authenticate({ invitationId, authCode }: AuthenticationRequest): Promise<void> {
160
- log('authenticating...');
161
- invariant(invitationId);
162
- const observable = this._acceptInvitations.get(invitationId);
163
- if (!observable) {
164
- log.warn('invalid invitation', { invitationId });
165
- } else {
166
- await observable.authenticate(authCode);
167
- }
45
+ async authenticate(request: AuthenticationRequest): Promise<void> {
46
+ return this._invitationsManager.authenticate(request);
168
47
  }
169
48
 
170
- async cancelInvitation({ invitationId }: { invitationId: string }): Promise<void> {
171
- log('cancelInvitation...', { invitationId });
172
- invariant(invitationId);
173
- const created = this._createInvitations.get(invitationId);
174
- const accepted = this._acceptInvitations.get(invitationId);
175
- if (created) {
176
- await created.cancel();
177
- this._createInvitations.delete(invitationId);
178
- this._removedCreated.emit(created.get());
179
- if (created.get().persistent) {
180
- await this._metadataStore.removeInvitation(created.get().invitationId);
181
- }
182
- } else if (accepted) {
183
- await accepted.cancel();
184
- this._acceptInvitations.delete(invitationId);
185
- this._removedAccepted.emit(accepted.get());
186
- }
49
+ async cancelInvitation(request: { invitationId: string }): Promise<void> {
50
+ return this._invitationsManager.cancelInvitation(request);
187
51
  }
188
52
 
189
53
  queryInvitations(): Stream<QueryInvitationsResponse> {
190
54
  return new Stream<QueryInvitationsResponse>(({ next, ctx }) => {
191
55
  // Push added invitations to the stream.
192
- this._invitationCreated.on(ctx, (invitation) => {
56
+ this._invitationsManager.invitationCreated.on(ctx, (invitation) => {
193
57
  next({
194
58
  action: QueryInvitationsResponse.Action.ADDED,
195
59
  type: QueryInvitationsResponse.Type.CREATED,
@@ -197,7 +61,7 @@ export class InvitationsServiceImpl implements InvitationsService {
197
61
  });
198
62
  });
199
63
 
200
- this._invitationAccepted.on(ctx, (invitation) => {
64
+ this._invitationsManager.invitationAccepted.on(ctx, (invitation) => {
201
65
  next({
202
66
  action: QueryInvitationsResponse.Action.ADDED,
203
67
  type: QueryInvitationsResponse.Type.ACCEPTED,
@@ -206,7 +70,7 @@ export class InvitationsServiceImpl implements InvitationsService {
206
70
  });
207
71
 
208
72
  // Push removed invitations to the stream.
209
- this._removedCreated.on(ctx, (invitation) => {
73
+ this._invitationsManager.removedCreated.on(ctx, (invitation) => {
210
74
  next({
211
75
  action: QueryInvitationsResponse.Action.REMOVED,
212
76
  type: QueryInvitationsResponse.Type.CREATED,
@@ -214,7 +78,7 @@ export class InvitationsServiceImpl implements InvitationsService {
214
78
  });
215
79
  });
216
80
 
217
- this._removedAccepted.on(ctx, (invitation) => {
81
+ this._invitationsManager.removedAccepted.on(ctx, (invitation) => {
218
82
  next({
219
83
  action: QueryInvitationsResponse.Action.REMOVED,
220
84
  type: QueryInvitationsResponse.Type.ACCEPTED,
@@ -223,7 +87,7 @@ export class InvitationsServiceImpl implements InvitationsService {
223
87
  });
224
88
 
225
89
  // used only for testing
226
- this._saved.on(ctx, (invitation) => {
90
+ this._invitationsManager.saved.on(ctx, (invitation) => {
227
91
  next({
228
92
  action: QueryInvitationsResponse.Action.SAVED,
229
93
  type: QueryInvitationsResponse.Type.CREATED,
@@ -235,33 +99,24 @@ export class InvitationsServiceImpl implements InvitationsService {
235
99
  next({
236
100
  action: QueryInvitationsResponse.Action.ADDED,
237
101
  type: QueryInvitationsResponse.Type.CREATED,
238
- invitations: Array.from(this._createInvitations.values()).map((invitation) => invitation.get()),
102
+ invitations: this._invitationsManager.getCreatedInvitations(),
239
103
  existing: true,
240
104
  });
241
105
 
242
106
  next({
243
107
  action: QueryInvitationsResponse.Action.ADDED,
244
108
  type: QueryInvitationsResponse.Type.ACCEPTED,
245
- invitations: Array.from(this._acceptInvitations.values()).map((invitation) => invitation.get()),
109
+ invitations: this._invitationsManager.getAcceptedInvitations(),
246
110
  existing: true,
247
111
  });
248
112
 
249
- if (this._persistentInvitationsLoaded) {
113
+ this._invitationsManager.onPersistentInvitationsLoaded(ctx, () => {
250
114
  next({
251
115
  action: QueryInvitationsResponse.Action.LOAD_COMPLETE,
252
116
  type: QueryInvitationsResponse.Type.CREATED,
253
117
  // TODO(nf): populate with invitations
254
118
  });
255
- } else {
256
- this._persistentInvitationsLoadedEvent.on(ctx, () => {
257
- next({
258
- action: QueryInvitationsResponse.Action.LOAD_COMPLETE,
259
- type: QueryInvitationsResponse.Type.CREATED,
260
- // TODO(nf): populate with invitations
261
- });
262
- });
263
- }
264
-
119
+ });
265
120
  // TODO(nf): expired invitations?
266
121
  });
267
122
  }
@@ -2,7 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { createAdmissionCredentials, getCredentialAssertion } from '@dxos/credentials';
5
+ import {
6
+ createAdmissionCredentials,
7
+ createDelegatedSpaceInvitationCredential,
8
+ getCredentialAssertion,
9
+ } from '@dxos/credentials';
6
10
  import { writeMessages } from '@dxos/feed-store';
7
11
  import { invariant } from '@dxos/invariant';
8
12
  import { type Keyring } from '@dxos/keyring';
@@ -11,7 +15,7 @@ import { log } from '@dxos/log';
11
15
  import { AlreadyJoinedError } from '@dxos/protocols';
12
16
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
13
17
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
14
- import { type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
18
+ import { SpaceMember, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
15
19
  import {
16
20
  type AdmissionRequest,
17
21
  type AdmissionResponse,
@@ -43,7 +47,11 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
43
47
  };
44
48
  }
45
49
 
46
- async admit(request: AdmissionRequest, guestProfile?: ProfileDocument | undefined): Promise<AdmissionResponse> {
50
+ async admit(
51
+ invitation: Invitation,
52
+ request: AdmissionRequest,
53
+ guestProfile?: ProfileDocument | undefined,
54
+ ): Promise<AdmissionResponse> {
47
55
  invariant(this._spaceKey);
48
56
  const space = await this._spaceManager.spaces.get(this._spaceKey);
49
57
  invariant(space);
@@ -59,6 +67,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
59
67
  space.key,
60
68
  space.inner.genesisFeedKey,
61
69
  guestProfile,
70
+ invitation.delegationCredentialId,
62
71
  );
63
72
 
64
73
  // TODO(dmaretskyi): Refactor.
@@ -76,6 +85,39 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
76
85
  };
77
86
  }
78
87
 
88
+ async delegate(invitation: Invitation): Promise<PublicKey> {
89
+ invariant(this._spaceKey);
90
+ const space = await this._spaceManager.spaces.get(this._spaceKey);
91
+ invariant(space);
92
+ if (invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY) {
93
+ invariant(invitation.guestKeypair?.publicKey);
94
+ }
95
+
96
+ log('writing delegate space invitation', { host: this._signingContext.deviceKey, id: invitation.invitationId });
97
+ const credential = await createDelegatedSpaceInvitationCredential(
98
+ this._signingContext.credentialSigner,
99
+ space.key,
100
+ {
101
+ invitationId: invitation.invitationId,
102
+ authMethod: invitation.authMethod,
103
+ swarmKey: invitation.swarmKey,
104
+ role: SpaceMember.Role.ADMIN,
105
+ expiresOn: invitation.lifetime
106
+ ? new Date((invitation.created?.getTime() ?? Date.now()) + invitation.lifetime)
107
+ : undefined,
108
+ multiUse: invitation.multiUse ?? false,
109
+ guestKey:
110
+ invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY
111
+ ? invitation.guestKeypair!.publicKey
112
+ : undefined,
113
+ },
114
+ );
115
+
116
+ invariant(credential.credential);
117
+ await writeMessages(space.inner.controlPipeline.writer, [credential]);
118
+ return credential.credential.credential.id!;
119
+ }
120
+
79
121
  checkInvitation(invitation: Partial<Invitation>) {
80
122
  if (invitation.spaceKey && this._spaceManager.spaces.has(invitation.spaceKey)) {
81
123
  return new AlreadyJoinedError('Already joined space.');
@@ -4,10 +4,11 @@
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';
9
+ import { AutomergeContext } from '@dxos/echo-db';
8
10
  import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
9
11
  import { createTestLevel } from '@dxos/echo-pipeline/testing';
10
- import { AutomergeContext } from '@dxos/echo-schema';
11
12
  import { afterTest, describe, test } from '@dxos/test';
12
13
 
13
14
  describe('AutomergeHost', () => {
@@ -50,9 +51,8 @@ describe('AutomergeHost', () => {
50
51
  doc.change((doc: any) => {
51
52
  doc.text = newText;
52
53
  });
54
+ await client.flush({ states: [{ documentId: doc.documentId, heads: getHeads(doc.docSync()) }] });
53
55
 
54
- // TODO(mykola): Is there a way to know when automerge has started replication?
55
- await sleep(100);
56
56
  await asyncTimeout(handle.whenReady(), 1_000);
57
57
  expect(handle.docSync().text).to.equal(newText);
58
58
  });
@@ -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(
@@ -8,9 +8,9 @@ import { Trigger } from '@dxos/async';
8
8
  import { Context, Resource } from '@dxos/context';
9
9
  import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
10
10
  import { failUndefined } from '@dxos/debug';
11
- import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
11
+ import { EchoHost } from '@dxos/echo-db';
12
+ import { MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
12
13
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
13
- import { IndexMetadataStore, IndexStore, Indexer } from '@dxos/indexing';
14
14
  import { invariant } from '@dxos/invariant';
15
15
  import { Keyring } from '@dxos/keyring';
16
16
  import { PublicKey } from '@dxos/keys';
@@ -32,13 +32,13 @@ import {
32
32
  type IdentityManagerRuntimeParams,
33
33
  type JoinIdentityParams,
34
34
  } from '../identity';
35
- import { createDocumentsIterator, createSelectedDocumentsIterator } from '../indexing';
36
35
  import {
37
36
  DeviceInvitationProtocol,
38
37
  InvitationsHandler,
39
38
  SpaceInvitationProtocol,
40
39
  type InvitationProtocol,
41
40
  } from '../invitations';
41
+ import { InvitationsManager } from '../invitations/invitations-manager';
42
42
  import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
43
43
 
44
44
  export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams;
@@ -62,9 +62,8 @@ export class ServiceContext extends Resource {
62
62
  public readonly spaceManager: SpaceManager;
63
63
  public readonly identityManager: IdentityManager;
64
64
  public readonly invitations: InvitationsHandler;
65
- public readonly automergeHost: AutomergeHost;
66
- public readonly indexMetadata: IndexMetadataStore;
67
- public readonly indexer: Indexer;
65
+ public readonly invitationsManager: InvitationsManager;
66
+ public readonly echoHost: EchoHost;
68
67
 
69
68
  // Initialized after identity is initialized.
70
69
  public dataSpaceManager?: DataSpaceManager;
@@ -120,22 +119,17 @@ export class ServiceContext extends Resource {
120
119
  this._runtimeParams as IdentityManagerRuntimeParams,
121
120
  );
122
121
 
123
- this.indexMetadata = new IndexMetadataStore({ db: level.sublevel('index-metadata') });
124
-
125
- this.automergeHost = new AutomergeHost({
126
- directory: storage.createDirectory('automerge'),
127
- db: level.sublevel('automerge'),
128
- metadata: this.indexMetadata,
129
- });
130
-
131
- this.indexer = new Indexer({
132
- indexStore: new IndexStore({ directory: storage.createDirectory('index-store') }),
133
- metadataStore: this.indexMetadata,
134
- loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
135
- getAllDocuments: createDocumentsIterator(this.automergeHost),
122
+ this.echoHost = new EchoHost({
123
+ kv: this.level,
124
+ storage: this.storage,
136
125
  });
137
126
 
138
127
  this.invitations = new InvitationsHandler(this.networkManager);
128
+ this.invitationsManager = new InvitationsManager(
129
+ this.invitations,
130
+ (invitation) => this.getInvitationHandler(invitation),
131
+ this.metadataStore,
132
+ );
139
133
 
140
134
  // TODO(burdon): _initialize called in multiple places.
141
135
  // TODO(burdon): Call _initialize on success.
@@ -159,31 +153,34 @@ export class ServiceContext extends Resource {
159
153
  await this.signalManager.open();
160
154
  await this.networkManager.open();
161
155
 
162
- await this.automergeHost.open();
156
+ await this.echoHost.open(ctx);
163
157
  await this.metadataStore.load();
164
158
  await this.spaceManager.open();
165
159
  await this.identityManager.open(ctx);
166
160
  if (this.identityManager.identity) {
167
161
  await this._initialize(ctx);
168
162
  }
163
+
164
+ const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
165
+ log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
166
+
169
167
  log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
170
168
  log('opened');
171
169
  }
172
170
 
173
- protected override async _close() {
171
+ protected override async _close(ctx: Context) {
174
172
  log('closing...');
175
173
  if (this._deviceSpaceSync && this.identityManager.identity) {
176
174
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
177
175
  }
178
- await this.automergeHost.close();
179
176
  await this.dataSpaceManager?.close();
180
177
  await this.identityManager.close();
181
178
  await this.spaceManager.close();
182
179
  await this.feedStore.close();
180
+ await this.metadataStore.close();
181
+ await this.echoHost.close(ctx);
183
182
  await this.networkManager.close();
184
183
  await this.signalManager.close();
185
- await this.metadataStore.close();
186
- await this.indexer.destroy();
187
184
  log('closed');
188
185
  }
189
186
 
@@ -244,7 +241,8 @@ export class ServiceContext extends Resource {
244
241
  this.keyring,
245
242
  signingContext,
246
243
  this.feedStore,
247
- this.automergeHost,
244
+ this.echoHost,
245
+ this.invitationsManager,
248
246
  this._runtimeParams as DataSpaceManagerRuntimeParams,
249
247
  );
250
248
  await this.dataSpaceManager.open();
@@ -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());
@@ -8,15 +8,8 @@ import { Event, synchronized } from '@dxos/async';
8
8
  import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
9
9
  import { type Config } from '@dxos/config';
10
10
  import { Context } from '@dxos/context';
11
- import {
12
- DataServiceImpl,
13
- type ObjectStructure,
14
- encodeReference,
15
- type SpaceDoc,
16
- type MyLevel,
17
- } from '@dxos/echo-pipeline';
18
- import * as E from '@dxos/echo-schema';
19
- import { IndexServiceImpl } from '@dxos/indexing';
11
+ import { type ObjectStructure, encodeReference, type SpaceDoc, type LevelDB } from '@dxos/echo-pipeline';
12
+ import { getTypeReference } from '@dxos/echo-schema';
20
13
  import { invariant } from '@dxos/invariant';
21
14
  import { PublicKey } from '@dxos/keys';
22
15
  import { log } from '@dxos/log';
@@ -56,7 +49,7 @@ export type ClientServicesHostParams = {
56
49
  signalManager?: SignalManager;
57
50
  connectionLog?: boolean;
58
51
  storage?: Storage;
59
- level?: MyLevel;
52
+ level?: LevelDB;
60
53
  lockKey?: string;
61
54
  callbacks?: ClientServicesHostCallbacks;
62
55
  runtimeParams?: ServiceContextRuntimeParams;
@@ -272,11 +265,7 @@ export class ClientServicesHost {
272
265
  (profile) => this._serviceContext.broadcastProfileUpdate(profile),
273
266
  ),
274
267
 
275
- InvitationsService: new InvitationsServiceImpl(
276
- this._serviceContext.invitations,
277
- (invitation) => this._serviceContext.getInvitationHandler(invitation),
278
- this._serviceContext.metadataStore,
279
- ),
268
+ InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
280
269
 
281
270
  DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
282
271
 
@@ -289,12 +278,8 @@ export class ClientServicesHost {
289
278
  },
290
279
  ),
291
280
 
292
- DataService: new DataServiceImpl(this._serviceContext.automergeHost),
293
-
294
- IndexService: new IndexServiceImpl({
295
- indexer: this._serviceContext.indexer,
296
- automergeHost: this._serviceContext.automergeHost,
297
- }),
281
+ DataService: this._serviceContext.echoHost.dataService,
282
+ QueryService: this._serviceContext.echoHost.queryService,
298
283
 
299
284
  NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
300
285
 
@@ -310,11 +295,6 @@ export class ClientServicesHost {
310
295
  });
311
296
 
312
297
  await this._serviceContext.open(ctx);
313
- // TODO(nf): move to InvitationManager in ServiceContext?
314
- invariant(this.serviceRegistry.services.InvitationsService);
315
- const loadedInvitations = await this.serviceRegistry.services.InvitationsService.loadPersistentInvitations();
316
-
317
- log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
318
298
 
319
299
  const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
320
300
  if (devtoolsProxy) {
@@ -377,13 +357,13 @@ export class ClientServicesHost {
377
357
 
378
358
  const automergeIndex = space.automergeSpaceState.rootUrl;
379
359
  invariant(automergeIndex);
380
- const document = await this._serviceContext.automergeHost.repo.find<SpaceDoc>(automergeIndex as any);
360
+ const document = await this._serviceContext.echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
381
361
  await document.whenReady();
382
362
 
383
363
  // TODO(dmaretskyi): Better API for low-level data access.
384
364
  const properties: ObjectStructure = {
385
365
  system: {
386
- type: encodeReference(E.getTypeReference(Properties)!),
366
+ type: encodeReference(getTypeReference(Properties)!),
387
367
  },
388
368
  data: {
389
369
  [defaultKey]: identity.identityKey.toHex(),
@@ -397,7 +377,7 @@ export class ClientServicesHost {
397
377
  assignDeep(doc, ['objects', propertiesId], properties);
398
378
  });
399
379
 
400
- await this._serviceContext.automergeHost.repo.flush();
380
+ await this._serviceContext.echoHost.flush();
401
381
 
402
382
  return identity;
403
383
  }
@@ -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);