@dxos/client-services 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0

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 (181) hide show
  1. package/dist/lib/browser/{chunk-HJH6BNTN.mjs → chunk-3LSLNVKQ.mjs} +2102 -1870
  2. package/dist/lib/browser/chunk-3LSLNVKQ.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-NQSC7HOE.mjs +22 -0
  4. package/dist/lib/browser/chunk-NQSC7HOE.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-QCWEHHJW.mjs +24 -0
  6. package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +451 -67
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +93 -0
  11. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
  12. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
  13. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
  14. package/dist/lib/browser/packlets/locks/browser.mjs +126 -0
  15. package/dist/lib/browser/packlets/locks/browser.mjs.map +7 -0
  16. package/dist/lib/browser/packlets/locks/node.mjs +66 -0
  17. package/dist/lib/browser/packlets/locks/node.mjs.map +7 -0
  18. package/dist/lib/browser/testing/index.mjs +36 -17
  19. package/dist/lib/browser/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +24 -0
  21. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +7 -0
  22. package/dist/lib/node-esm/{chunk-ONQM6RQH.mjs → chunk-5S7PIHLS.mjs} +1942 -1579
  23. package/dist/lib/node-esm/chunk-5S7PIHLS.mjs.map +7 -0
  24. package/dist/lib/node-esm/chunk-PKEGMOQ4.mjs +22 -0
  25. package/dist/lib/node-esm/chunk-PKEGMOQ4.mjs.map +7 -0
  26. package/dist/lib/node-esm/index.mjs +451 -67
  27. package/dist/lib/node-esm/index.mjs.map +4 -4
  28. package/dist/lib/node-esm/meta.json +1 -1
  29. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +93 -0
  30. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
  31. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
  32. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
  33. package/dist/lib/node-esm/packlets/locks/browser.mjs +126 -0
  34. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +7 -0
  35. package/dist/lib/node-esm/packlets/locks/node.mjs +66 -0
  36. package/dist/lib/node-esm/packlets/locks/node.mjs.map +7 -0
  37. package/dist/lib/node-esm/testing/index.mjs +36 -17
  38. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  39. package/dist/types/src/index.d.ts +1 -0
  40. package/dist/types/src/index.d.ts.map +1 -1
  41. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -2
  42. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  43. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  44. package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
  45. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  46. package/dist/types/src/packlets/diagnostics/index.d.ts +1 -1
  47. package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -1
  48. package/dist/types/src/packlets/identity/authenticator.d.ts +2 -2
  49. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  50. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  51. package/dist/types/src/packlets/identity/identity-manager.d.ts +6 -6
  52. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  53. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +7 -6
  54. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  55. package/dist/types/src/packlets/identity/identity-service.d.ts +1 -6
  56. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  57. package/dist/types/src/packlets/identity/identity.d.ts +8 -11
  58. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  59. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +4 -4
  60. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  61. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  62. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  63. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  64. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +2 -3
  65. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  66. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -4
  67. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  68. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +3 -3
  69. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  70. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -2
  71. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  72. package/dist/types/src/packlets/locks/index.d.ts +1 -1
  73. package/dist/types/src/packlets/locks/index.d.ts.map +1 -1
  74. package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
  75. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  76. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  77. package/dist/types/src/packlets/services/client-rpc-server.d.ts +2 -2
  78. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  79. package/dist/types/src/packlets/services/feed-syncer.d.ts +59 -0
  80. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
  81. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  82. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  83. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  84. package/dist/types/src/packlets/services/service-context.d.ts +13 -8
  85. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  86. package/dist/types/src/packlets/services/service-host.d.ts +20 -6
  87. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  88. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts +9 -1
  89. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
  90. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +6 -0
  91. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  92. package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
  93. package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
  94. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +27 -15
  95. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  96. package/dist/types/src/packlets/spaces/data-space.d.ts +24 -8
  97. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  98. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -2
  99. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  100. package/dist/types/src/packlets/spaces/genesis.d.ts +2 -1
  101. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  102. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -6
  103. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  104. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
  105. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  106. package/dist/types/src/packlets/testing/invitation-utils.d.ts +6 -3
  107. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  108. package/dist/types/src/packlets/testing/test-builder.d.ts +6 -5
  109. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  110. package/dist/types/src/packlets/worker/worker-runtime.d.ts +31 -4
  111. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  112. package/dist/types/src/packlets/worker/worker-session.d.ts +2 -2
  113. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  114. package/dist/types/src/version.d.ts +1 -1
  115. package/dist/types/src/version.d.ts.map +1 -1
  116. package/dist/types/tsconfig.tsbuildinfo +1 -1
  117. package/package.json +70 -48
  118. package/src/index.ts +1 -0
  119. package/src/packlets/agents/edge-agent-manager.ts +8 -5
  120. package/src/packlets/agents/edge-agent-service.ts +2 -1
  121. package/src/packlets/devices/devices-service.test.ts +0 -1
  122. package/src/packlets/devtools/devtools.ts +2 -3
  123. package/src/packlets/diagnostics/index.ts +1 -1
  124. package/src/packlets/identity/authenticator.ts +2 -2
  125. package/src/packlets/identity/contacts-service.ts +0 -1
  126. package/src/packlets/identity/identity-manager.test.ts +5 -5
  127. package/src/packlets/identity/identity-manager.ts +21 -18
  128. package/src/packlets/identity/identity-recovery-manager.ts +22 -18
  129. package/src/packlets/identity/identity-service.test.ts +6 -27
  130. package/src/packlets/identity/identity-service.ts +5 -76
  131. package/src/packlets/identity/identity.test.ts +2 -2
  132. package/src/packlets/identity/identity.ts +9 -32
  133. package/src/packlets/invitations/device-invitation-protocol.ts +5 -6
  134. package/src/packlets/invitations/edge-invitation-handler.ts +4 -3
  135. package/src/packlets/invitations/invitation-guest-extenstion.ts +6 -4
  136. package/src/packlets/invitations/invitation-host-extension.ts +6 -4
  137. package/src/packlets/invitations/invitation-protocol.ts +2 -3
  138. package/src/packlets/invitations/invitations-handler.test.ts +4 -5
  139. package/src/packlets/invitations/invitations-handler.ts +10 -10
  140. package/src/packlets/invitations/invitations-manager.ts +37 -14
  141. package/src/packlets/invitations/invitations-service.ts +4 -4
  142. package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
  143. package/src/packlets/invitations/space-invitation-protocol.ts +10 -15
  144. package/src/packlets/locks/index.ts +1 -1
  145. package/src/packlets/logging/logging-service.ts +4 -0
  146. package/src/packlets/network/network-service.test.ts +0 -1
  147. package/src/packlets/network/network-service.ts +5 -4
  148. package/src/packlets/services/client-rpc-server.ts +4 -4
  149. package/src/packlets/services/feed-syncer.test.ts +340 -0
  150. package/src/packlets/services/feed-syncer.ts +337 -0
  151. package/src/packlets/services/platform.ts +7 -1
  152. package/src/packlets/services/service-context.test.ts +3 -2
  153. package/src/packlets/services/service-context.ts +129 -44
  154. package/src/packlets/services/service-host.test.ts +8 -8
  155. package/src/packlets/services/service-host.ts +63 -22
  156. package/src/packlets/services/service-registry.test.ts +0 -1
  157. package/src/packlets/space-export/space-archive-reader.ts +64 -3
  158. package/src/packlets/space-export/space-archive-writer.ts +39 -2
  159. package/src/packlets/space-export/space-archive.test.ts +287 -0
  160. package/src/packlets/spaces/data-space-manager.test.ts +79 -13
  161. package/src/packlets/spaces/data-space-manager.ts +97 -107
  162. package/src/packlets/spaces/data-space.ts +52 -29
  163. package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
  164. package/src/packlets/spaces/edge-feed-replicator.ts +10 -9
  165. package/src/packlets/spaces/epoch-migrations.ts +5 -5
  166. package/src/packlets/spaces/genesis.ts +6 -1
  167. package/src/packlets/spaces/notarization-plugin.test.ts +2 -2
  168. package/src/packlets/spaces/notarization-plugin.ts +10 -9
  169. package/src/packlets/spaces/spaces-service.test.ts +9 -7
  170. package/src/packlets/spaces/spaces-service.ts +40 -16
  171. package/src/packlets/storage/storage.ts +4 -4
  172. package/src/packlets/testing/invitation-utils.ts +10 -6
  173. package/src/packlets/testing/test-builder.ts +36 -10
  174. package/src/packlets/worker/worker-runtime.ts +150 -13
  175. package/src/packlets/worker/worker-session.ts +8 -8
  176. package/src/version.ts +1 -1
  177. package/dist/lib/browser/chunk-HJH6BNTN.mjs.map +0 -7
  178. package/dist/lib/node-esm/chunk-ONQM6RQH.mjs.map +0 -7
  179. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
  180. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
  181. package/src/packlets/identity/default-space-state-machine.ts +0 -44
@@ -6,6 +6,7 @@ import type { AutomergeUrl } from '@automerge/automerge-repo';
6
6
 
7
7
  import { SubscriptionList, UpdateScheduler, scheduleTask } from '@dxos/async';
8
8
  import { Stream } from '@dxos/codec-protobuf/stream';
9
+ import { Context } from '@dxos/context';
9
10
  import {
10
11
  type CredentialProcessor,
11
12
  createAdmissionCredentials,
@@ -45,6 +46,7 @@ import {
45
46
  type SpacesService,
46
47
  type SubscribeMessagesRequest,
47
48
  type UpdateMemberRoleRequest,
49
+ type CreateSpaceRequest,
48
50
  type UpdateSpaceRequest,
49
51
  type WriteCredentialsRequest,
50
52
  } from '@dxos/protocols/proto/dxos/client/services';
@@ -55,7 +57,6 @@ import { type Provider } from '@dxos/util';
55
57
 
56
58
  import { type IdentityManager } from '../identity';
57
59
  import { SpaceArchiveWriter, extractSpaceArchive } from '../space-export';
58
-
59
60
  import { type DataSpace } from './data-space';
60
61
  import { type DataSpaceManager } from './data-space-manager';
61
62
 
@@ -66,10 +67,13 @@ export class SpacesServiceImpl implements SpacesService {
66
67
  private readonly _getDataSpaceManager: Provider<Promise<DataSpaceManager>>,
67
68
  ) {}
68
69
 
69
- async createSpace(): Promise<Space> {
70
+ async createSpace(request: CreateSpaceRequest): Promise<Space> {
70
71
  this._requireIdentity();
71
72
  const dataSpaceManager = await this._getDataSpaceManager();
72
- const space = await dataSpaceManager.createSpace();
73
+ const space = await dataSpaceManager.createSpace(new Context(), {
74
+ tags: request?.tags,
75
+ membershipPolicy: request?.membershipPolicy,
76
+ });
73
77
  await this._updateMetrics();
74
78
  return this._serializeSpace(space);
75
79
  }
@@ -81,19 +85,19 @@ export class SpacesServiceImpl implements SpacesService {
81
85
  if (state) {
82
86
  switch (state) {
83
87
  case SpaceState.SPACE_ACTIVE:
84
- await space.activate();
88
+ await space.activate(Context.default());
85
89
  break;
86
90
 
87
91
  case SpaceState.SPACE_INACTIVE:
88
- await space.deactivate();
92
+ await space.deactivate(Context.default());
89
93
  break;
90
94
  default:
91
- throw new ApiError('Invalid space state');
95
+ throw new ApiError({ message: 'Invalid space state' });
92
96
  }
93
97
  }
94
98
 
95
99
  if (edgeReplication !== undefined) {
96
- await dataSpaceManager.setSpaceEdgeReplicationSetting(spaceKey, edgeReplication);
100
+ await dataSpaceManager.setSpaceEdgeReplicationSetting(Context.default(), spaceKey, edgeReplication);
97
101
  }
98
102
  }
99
103
 
@@ -104,9 +108,12 @@ export class SpacesServiceImpl implements SpacesService {
104
108
  throw new SpaceNotFoundError(request.spaceKey);
105
109
  }
106
110
  if (!space.spaceState.hasMembershipManagementPermission(identity.identityKey)) {
107
- throw new AuthorizationError('No member management permission.', {
108
- spaceKey: space.key,
109
- role: space.spaceState.getMemberRole(identity.identityKey),
111
+ throw new AuthorizationError({
112
+ message: 'No member management permission.',
113
+ context: {
114
+ spaceKey: space.key,
115
+ role: space.spaceState.getMemberRole(identity.identityKey),
116
+ },
110
117
  });
111
118
  }
112
119
  const credentials = await createAdmissionCredentials(
@@ -261,7 +268,7 @@ export class SpacesServiceImpl implements SpacesService {
261
268
 
262
269
  async joinBySpaceKey({ spaceKey }: JoinBySpaceKeyRequest): Promise<JoinSpaceResponse> {
263
270
  const dataSpaceManager = await this._getDataSpaceManager();
264
- const credential = await dataSpaceManager.requestSpaceAdmissionCredential(spaceKey);
271
+ const credential = await dataSpaceManager.requestSpaceAdmissionCredential(Context.default(), spaceKey);
265
272
  return this._joinByAdmission({ credential });
266
273
  }
267
274
 
@@ -280,6 +287,20 @@ export class SpacesServiceImpl implements SpacesService {
280
287
  await writer.writeDocument(documentId, data);
281
288
  }
282
289
 
290
+ const feeds = await space.getAllFeeds();
291
+ for (const feed of feeds) {
292
+ const archiveBlocks = feed.blocks.map((block) => ({
293
+ actorId: block.actorId,
294
+ sequence: block.sequence,
295
+ prevActorId: block.prevActorId,
296
+ prevSequence: block.prevSequence,
297
+ position: block.position,
298
+ timestamp: block.timestamp,
299
+ data: Buffer.from(block.data).toString('base64'),
300
+ }));
301
+ await writer.writeFeed(feed.feedId, feed.feedNamespace, archiveBlocks);
302
+ }
303
+
283
304
  const archive = await writer.finish();
284
305
  return { archive };
285
306
  }
@@ -288,7 +309,7 @@ export class SpacesServiceImpl implements SpacesService {
288
309
  const dataSpaceManager = await this._getDataSpaceManager();
289
310
  const extracted = await extractSpaceArchive(request.archive);
290
311
  invariant(extracted.metadata.echo?.currentRootUrl, 'Space archive does not contain a root URL');
291
- const space = await dataSpaceManager.createSpace({
312
+ const space = await dataSpaceManager.createSpace(Context.default(), {
292
313
  documents: extracted.documents,
293
314
  rootUrl: extracted.metadata.echo?.currentRootUrl as AutomergeUrl,
294
315
  });
@@ -305,9 +326,10 @@ export class SpacesServiceImpl implements SpacesService {
305
326
  const dataSpaceManager = await this._getDataSpaceManager();
306
327
  let dataSpace = dataSpaceManager.spaces.get(assertion.spaceKey);
307
328
  if (!dataSpace) {
308
- dataSpace = await dataSpaceManager.acceptSpace({
329
+ dataSpace = await dataSpaceManager.acceptSpace(Context.default(), {
309
330
  spaceKey: assertion.spaceKey,
310
331
  genesisFeedKey: assertion.genesisFeedKey,
332
+ tags: assertion.tags,
311
333
  });
312
334
  await myIdentity.controlPipeline.writer.write({ credential: { credential } });
313
335
  }
@@ -360,6 +382,8 @@ export class SpacesServiceImpl implements SpacesService {
360
382
  }),
361
383
  ),
362
384
  creator: space.inner.spaceState.creator?.key,
385
+ tags: space.tags,
386
+ membershipPolicy: space.membershipPolicy,
363
387
  cache: space.cache,
364
388
  metrics: space.metrics,
365
389
  edgeReplication: space.getEdgeReplicationSetting(),
@@ -368,9 +392,9 @@ export class SpacesServiceImpl implements SpacesService {
368
392
 
369
393
  private _requireIdentity() {
370
394
  if (!this._identityManager.identity) {
371
- throw new IdentityNotInitializedError(
372
- 'This device has no HALO identity available. See https://docs.dxos.org/guide/platform/halo',
373
- );
395
+ throw new IdentityNotInitializedError({
396
+ message: 'This device has no HALO identity available. See https://docs.dxos.org/guide/platform/halo',
397
+ });
374
398
  }
375
399
  return this._identityManager.identity;
376
400
  }
@@ -14,16 +14,16 @@ import StorageDriver = Runtime.Client.Storage.StorageDriver;
14
14
  export const createStorageObjects = (config: Runtime.Client.Storage) => {
15
15
  const { persistent = false, keyStore, dataStore } = config ?? {};
16
16
  if (persistent && dataStore === StorageDriver.RAM) {
17
- throw new InvalidConfigError('RAM storage cannot be used in persistent mode.');
17
+ throw new InvalidConfigError({ message: 'RAM storage cannot be used in persistent mode.' });
18
18
  }
19
19
  if (!persistent && dataStore !== undefined && dataStore !== StorageDriver.RAM) {
20
- throw new InvalidConfigError('Cannot use a persistent storage in not persistent mode.');
20
+ throw new InvalidConfigError({ message: 'Cannot use a persistent storage in not persistent mode.' });
21
21
  }
22
22
  if (persistent && keyStore === StorageDriver.RAM) {
23
- throw new InvalidConfigError('RAM key storage cannot be used in persistent mode.');
23
+ throw new InvalidConfigError({ message: 'RAM key storage cannot be used in persistent mode.' });
24
24
  }
25
25
  if (!persistent && keyStore !== StorageDriver.RAM && keyStore !== undefined) {
26
- throw new InvalidConfigError('Cannot use a persistent key storage in not persistent mode.');
26
+ throw new InvalidConfigError({ message: 'Cannot use a persistent key storage in not persistent mode.' });
27
27
  }
28
28
 
29
29
  return {
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { Trigger } from '@dxos/async';
6
6
  import { type AuthenticatingInvitation, type CancellableInvitation, InvitationEncoder } from '@dxos/client-protocol';
7
+ import { Context } from '@dxos/context';
7
8
  import { invariant } from '@dxos/invariant';
8
9
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
9
10
  import { type DeviceProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
@@ -36,15 +37,15 @@ export type PerformInvitationCallbacks<T> = {
36
37
  onError?: (value: T) => boolean | void;
37
38
  };
38
39
 
39
- export type PerformInvitationParams = {
40
+ export type PerformInvitationProps = {
40
41
  host: ServiceContext | InvitationHost;
41
42
  guest: ServiceContext | InvitationGuest;
43
+ guestDeviceProfile?: DeviceProfileDocument;
42
44
  options?: Partial<Invitation>;
43
45
  hooks?: {
44
46
  host?: PerformInvitationCallbacks<CancellableInvitation>;
45
47
  guest?: PerformInvitationCallbacks<AuthenticatingInvitation>;
46
48
  };
47
- guestDeviceProfile?: DeviceProfileDocument;
48
49
  codeInputDelay?: number;
49
50
  };
50
51
 
@@ -52,14 +53,17 @@ export type Result = { invitation?: Invitation; error?: Error };
52
53
 
53
54
  // TODO(burdon): Make async.
54
55
  // TODO(burdon): Rename startInvitation.
56
+ /**
57
+ *
58
+ */
55
59
  export const performInvitation = ({
56
60
  host,
57
61
  guest,
62
+ guestDeviceProfile,
58
63
  options,
59
64
  hooks,
60
- guestDeviceProfile,
61
65
  codeInputDelay,
62
- }: PerformInvitationParams): [Promise<Result>, Promise<Result>] => {
66
+ }: PerformInvitationProps): [Promise<Result>, Promise<Result>] => {
63
67
  let guestError = false;
64
68
  let guestConnected = false;
65
69
  let wereConnected = false;
@@ -226,7 +230,7 @@ export const createInvitation = async (
226
230
  };
227
231
 
228
232
  if (host instanceof ServiceContext) {
229
- return host.invitationsManager.createInvitation({
233
+ return host.invitationsManager.createInvitation(new Context(), {
230
234
  kind: Invitation.Kind.SPACE,
231
235
  ...options,
232
236
  });
@@ -243,7 +247,7 @@ export const acceptInvitation = (
243
247
  invitation = sanitizeInvitation(invitation);
244
248
 
245
249
  if (guest instanceof ServiceContext) {
246
- return guest.invitationsManager.acceptInvitation({
250
+ return guest.invitationsManager.acceptInvitation(new Context(), {
247
251
  invitation,
248
252
  deviceProfile: guestDeviceProfile,
249
253
  });
@@ -2,6 +2,10 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ import * as Reactivity from '@effect/experimental/Reactivity';
6
+ import * as Layer from 'effect/Layer';
7
+ import * as ManagedRuntime from 'effect/ManagedRuntime';
8
+
5
9
  import { type Config } from '@dxos/config';
6
10
  import { Context } from '@dxos/context';
7
11
  import { CredentialGenerator, createCredentialSignerWithChain } from '@dxos/credentials';
@@ -15,11 +19,13 @@ import { MemorySignalManager, MemorySignalManagerContext, type SignalManager } f
15
19
  import { MemoryTransportFactory, SwarmNetworkManager } from '@dxos/network-manager';
16
20
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
17
21
  import { type Storage, StorageType, createStorage } from '@dxos/random-access-storage';
22
+ import { layerMemory as sqliteLayerMemory } from '@dxos/sql-sqlite/platform';
23
+ import * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
18
24
  import { BlobStore } from '@dxos/teleport-extension-object-sync';
19
25
 
20
26
  import { InvitationsHandler, InvitationsManager, SpaceInvitationProtocol } from '../invitations';
21
- import { ClientServicesHost, ServiceContext, type ServiceContextRuntimeParams } from '../services';
22
- import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
27
+ import { ClientServicesHost, ServiceContext, type ServiceContextRuntimeProps } from '../services';
28
+ import { DataSpaceManager, type DataSpaceManagerRuntimeProps, type SigningContext } from '../spaces';
23
29
 
24
30
  //
25
31
  // TODO(burdon): Replace with test builder.
@@ -30,6 +36,11 @@ export const createServiceHost = (config: Config, signalManagerContext: MemorySi
30
36
  config,
31
37
  signalManager: new MemorySignalManager(signalManagerContext),
32
38
  transportFactory: MemoryTransportFactory,
39
+ runtime: ManagedRuntime.make(
40
+ SqlTransaction.layer
41
+ .pipe(Layer.provideMerge(sqliteLayerMemory), Layer.provideMerge(Reactivity.layer))
42
+ .pipe(Layer.orDie),
43
+ ).runtimeEffect,
33
44
  });
34
45
  };
35
46
 
@@ -39,11 +50,11 @@ export const createServiceContext = async ({
39
50
  return new MemorySignalManager(signalContext);
40
51
  },
41
52
  storage = createStorage({ type: StorageType.RAM }),
42
- runtimeParams,
53
+ runtimeProps,
43
54
  }: {
44
55
  signalManagerFactory?: () => Promise<SignalManager>;
45
56
  storage?: Storage;
46
- runtimeParams?: ServiceContextRuntimeParams;
57
+ runtimeProps?: ServiceContextRuntimeProps;
47
58
  } = {}) => {
48
59
  const signalManager = await signalManagerFactory();
49
60
  const networkManager = new SwarmNetworkManager({
@@ -53,9 +64,15 @@ export const createServiceContext = async ({
53
64
  const level = createTestLevel();
54
65
  await level.open();
55
66
 
56
- return new ServiceContext(storage, level, networkManager, signalManager, undefined, undefined, {
57
- invitationConnectionDefaultParams: { teleport: { controlHeartbeatInterval: 200 } },
58
- ...runtimeParams,
67
+ const runtime = ManagedRuntime.make(
68
+ SqlTransaction.layer
69
+ .pipe(Layer.provideMerge(sqliteLayerMemory), Layer.provideMerge(Reactivity.layer))
70
+ .pipe(Layer.orDie),
71
+ ).runtimeEffect;
72
+
73
+ return new ServiceContext(storage, level, networkManager, signalManager, undefined, undefined, runtime, {
74
+ invitationConnectionDefaultProps: { teleport: { controlHeartbeatInterval: 200 } },
75
+ ...runtimeProps,
59
76
  });
60
77
  };
61
78
 
@@ -95,7 +112,7 @@ export class TestBuilder {
95
112
 
96
113
  export type TestPeerOpts = {
97
114
  dataStore?: StorageType;
98
- dataSpaceParams?: DataSpaceManagerRuntimeParams;
115
+ dataSpaceProps?: DataSpaceManagerRuntimeProps;
99
116
  };
100
117
 
101
118
  export type TestPeerProps = {
@@ -116,6 +133,11 @@ export type TestPeerProps = {
116
133
 
117
134
  export class TestPeer {
118
135
  private _props: TestPeerProps = {};
136
+ private readonly _runtime = ManagedRuntime.make(
137
+ SqlTransaction.layer
138
+ .pipe(Layer.provideMerge(sqliteLayerMemory), Layer.provideMerge(Reactivity.layer))
139
+ .pipe(Layer.orDie),
140
+ );
119
141
 
120
142
  constructor(
121
143
  private readonly _signalContext: MemorySignalManagerContext,
@@ -179,7 +201,10 @@ export class TestPeer {
179
201
  }
180
202
 
181
203
  get echoHost() {
182
- return (this._props.echoHost ??= new EchoHost({ kv: this.level }));
204
+ return (this._props.echoHost ??= new EchoHost({
205
+ kv: this.level,
206
+ runtime: this._runtime.runtimeEffect,
207
+ }));
183
208
  }
184
209
 
185
210
  get meshEchoReplicator() {
@@ -198,7 +223,7 @@ export class TestPeer {
198
223
  edgeConnection: undefined,
199
224
  meshReplicator: this.meshEchoReplicator,
200
225
  echoEdgeReplicator: undefined,
201
- runtimeParams: this._opts.dataSpaceParams,
226
+ runtimeProps: this._opts.dataSpaceProps,
202
227
  }));
203
228
  }
204
229
 
@@ -227,6 +252,7 @@ export class TestPeer {
227
252
  async destroy(): Promise<void> {
228
253
  await this.level.close();
229
254
  await this.storage.reset();
255
+ await this._runtime.dispose();
230
256
  }
231
257
  }
232
258
 
@@ -2,6 +2,12 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ import * as Reactivity from '@effect/experimental/Reactivity';
6
+ import type * as SqlClient from '@effect/sql/SqlClient';
7
+ import * as Effect from 'effect/Effect';
8
+ import * as Layer from 'effect/Layer';
9
+ import * as ManagedRuntime from 'effect/ManagedRuntime';
10
+
5
11
  import { Trigger } from '@dxos/async';
6
12
  import { DEFAULT_WORKER_BROADCAST_CHANNEL } from '@dxos/client-protocol';
7
13
  import { type Config } from '@dxos/config';
@@ -16,17 +22,21 @@ import {
16
22
  } from '@dxos/messaging';
17
23
  import { RtcTransportProxyFactory } from '@dxos/network-manager';
18
24
  import { type RpcPort } from '@dxos/rpc';
25
+ import * as OpfsWorker from '@dxos/sql-sqlite/OpfsWorker';
26
+ import * as SqlExport from '@dxos/sql-sqlite/SqlExport';
27
+ import * as SqliteClient from '@dxos/sql-sqlite/SqliteClient';
28
+ import * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
19
29
  import { type MaybePromise } from '@dxos/util';
20
30
 
21
31
  import { ClientServicesHost } from '../services';
22
-
23
32
  import { WorkerSession } from './worker-session';
24
33
 
25
34
  // NOTE: Keep as RpcPorts to avoid dependency on @dxos/rpc-tunnel so we don't depend on browser-specific apis.
26
- export type CreateSessionParams = {
35
+ export type CreateSessionProps = {
27
36
  appPort: RpcPort;
28
37
  systemPort: RpcPort;
29
38
  shellPort?: RpcPort;
39
+ onClose?: () => Promise<void>;
30
40
  };
31
41
 
32
42
  export type WorkerRuntimeOptions = {
@@ -35,10 +45,20 @@ export type WorkerRuntimeOptions = {
35
45
  acquireLock: () => Promise<void>;
36
46
  releaseLock: () => void;
37
47
  onStop?: () => Promise<void>;
48
+ /**
49
+ * @default true
50
+ */
51
+ automaticallyConnectWebrtc?: boolean;
52
+
53
+ /**
54
+ * Optional SQLite layer for Effect. Defaults to LocalSqliteOpfsLayer.
55
+ * For testing in Node.js, use `sqliteLayerMemory` from `@dxos/sql-sqlite/platform`.
56
+ */
57
+ sqliteLayer?: Layer.Layer<SqlClient.SqlClient | SqlExport.SqlExport, unknown>;
38
58
  };
39
59
 
40
60
  /**
41
- * Runtime for the shared worker.
61
+ * Runtime for the shared and dedciated worker.
42
62
  * Manages connections from proxies (in tabs).
43
63
  * Tabs make requests to the `ClientServicesHost`, and provide a WebRTC gateway.
44
64
  */
@@ -52,11 +72,17 @@ export class WorkerRuntime {
52
72
  private readonly _sessions = new Set<WorkerSession>();
53
73
  private readonly _clientServices!: ClientServicesHost;
54
74
  private readonly _channel: string;
75
+ private readonly _automaticallyConnectWebrtc: boolean;
76
+ private readonly _livenessLock = new WebLockWrapper(`@dxos/client-services/WorkerRuntime/${crypto.randomUUID()}`);
55
77
  private _broadcastChannel?: BroadcastChannel;
56
78
  private _sessionForNetworking?: WorkerSession; // TODO(burdon): Expose to client QueryStatusResponse.
57
79
  private _config!: Config;
58
80
  private _signalMetadataTags: any = { runtime: 'worker-runtime' };
59
81
  private _signalTelemetryEnabled: boolean = false;
82
+ private _runtime!: ManagedRuntime.ManagedRuntime<
83
+ SqlTransaction.SqlTransaction | SqlClient.SqlClient | SqlExport.SqlExport,
84
+ never
85
+ >;
60
86
 
61
87
  constructor({
62
88
  channel = DEFAULT_WORKER_BROADCAST_CHANNEL,
@@ -64,26 +90,49 @@ export class WorkerRuntime {
64
90
  acquireLock,
65
91
  releaseLock,
66
92
  onStop,
93
+ automaticallyConnectWebrtc = true,
94
+ sqliteLayer,
67
95
  }: WorkerRuntimeOptions) {
68
96
  this._configProvider = configProvider;
69
97
  this._acquireLock = acquireLock;
70
98
  this._releaseLock = releaseLock;
71
99
  this._onStop = onStop;
72
100
  this._channel = channel;
101
+ if (sqliteLayer) {
102
+ log.warn('Using testing SQLite layer');
103
+ }
104
+ this._runtime = ManagedRuntime.make(
105
+ SqlTransaction.layer
106
+ .pipe(Layer.provideMerge(sqliteLayer ?? LocalSqliteOpfsLayer), Layer.provideMerge(Reactivity.layer))
107
+ .pipe(Layer.orDie),
108
+ );
73
109
  this._clientServices = new ClientServicesHost({
74
110
  callbacks: {
75
111
  onReset: async () => this.stop(),
76
112
  },
113
+ runtime: this._runtime.runtimeEffect,
114
+ runtimeProps: {
115
+ // Auto-activate spaces that were previously active after leader changeover.
116
+ autoActivateSpaces: true,
117
+ },
77
118
  });
119
+ this._automaticallyConnectWebrtc = automaticallyConnectWebrtc;
78
120
  }
79
121
 
80
122
  get host() {
81
123
  return this._clientServices;
82
124
  }
83
125
 
126
+ get livenessLockKey(): string {
127
+ return this._livenessLock.key;
128
+ }
129
+
84
130
  async start(): Promise<void> {
85
131
  log('starting...');
86
132
  try {
133
+ void this._livenessLock.acquire();
134
+
135
+ // Steal the lock from the other worker.
87
136
  this._broadcastChannel = new BroadcastChannel(this._channel);
88
137
  this._broadcastChannel.postMessage({ action: 'stop' });
89
138
  this._broadcastChannel.onmessage = async (event) => {
@@ -126,14 +175,16 @@ export class WorkerRuntime {
126
175
  this._releaseLock();
127
176
  this._broadcastChannel?.close();
128
177
  this._broadcastChannel = undefined;
129
- await this._clientServices.close();
178
+ await this._clientServices.close(Context.default());
179
+ await this._runtime.dispose();
130
180
  await this._onStop?.();
181
+ await this._livenessLock.release();
131
182
  }
132
183
 
133
184
  /**
134
185
  * Create a new session.
135
186
  */
136
- async createSession({ appPort, systemPort, shellPort }: CreateSessionParams): Promise<void> {
187
+ async createSession({ appPort, systemPort, shellPort, onClose }: CreateSessionProps): Promise<WorkerSession> {
137
188
  const session = new WorkerSession({
138
189
  serviceHost: this._clientServices,
139
190
  appPort,
@@ -149,8 +200,11 @@ export class WorkerRuntime {
149
200
  // Terminate the worker when all sessions are closed.
150
201
  await this.stop();
151
202
  } else {
152
- this._reconnectWebrtc();
203
+ if (this._automaticallyConnectWebrtc) {
204
+ this._reconnectWebrtc();
205
+ }
153
206
  }
207
+ await onClose?.();
154
208
  });
155
209
 
156
210
  await session.open();
@@ -166,7 +220,24 @@ export class WorkerRuntime {
166
220
  this._signalMetadataTags.origin = session.origin;
167
221
  this._sessions.add(session);
168
222
 
169
- this._reconnectWebrtc();
223
+ if (this._automaticallyConnectWebrtc) {
224
+ this._reconnectWebrtc();
225
+ }
226
+
227
+ return session;
228
+ }
229
+
230
+ /**
231
+ * Connects the WebRTC bridge to the specified session.
232
+ * If no session is provided, disconnects the WebRTC bridge.
233
+ *
234
+ * Called automatically if `automaticallyConnectWebrtc` is true.
235
+ *
236
+ * @param session The session to connect the WebRTC bridge to.
237
+ */
238
+ connectWebrtcBridge(session: WorkerSession | undefined): void {
239
+ this._sessionForNetworking = session;
240
+ this._transportFactory.setBridgeService(session?.bridgeService);
170
241
  }
171
242
 
172
243
  /**
@@ -184,12 +255,78 @@ export class WorkerRuntime {
184
255
  // Select existing session.
185
256
  if (!this._sessionForNetworking) {
186
257
  const selected = Array.from(this._sessions).find((session) => session.bridgeService);
187
- if (selected) {
188
- this._sessionForNetworking = selected;
189
- this._transportFactory.setBridgeService(selected.bridgeService);
190
- } else {
191
- this._transportFactory.setBridgeService(undefined);
192
- }
258
+ this.connectWebrtcBridge(selected);
193
259
  }
194
260
  }
195
261
  }
262
+
263
+ const DB_NAME = 'DXOS';
264
+
265
+ /**
266
+ * SqlExport layer that wraps SqliteClient to provide export functionality.
267
+ */
268
+ const SqlExportLayer: Layer.Layer<SqlExport.SqlExport, never, SqliteClient.SqliteClient> = Layer.effect(
269
+ SqlExport.SqlExport,
270
+ Effect.gen(function* () {
271
+ const sql = yield* SqliteClient.SqliteClient;
272
+ return {
273
+ export: sql.export,
274
+ } satisfies SqlExport.Service;
275
+ }),
276
+ );
277
+
278
+ /**
279
+ * Local SQLite layer for the worker.
280
+ * Uses OPFS sync API as an FS backend.
281
+ * Does NOT spawn a new worker.
282
+ * NOTE: Only usable within a worker.
283
+ * TODO(mykola): This does not work right now. Fix.
284
+ */
285
+ const LocalSqliteOpfsLayer = Layer.unwrapScoped(
286
+ Effect.gen(function* () {
287
+ const { port1: clientPort, port2: serverPort } = new MessageChannel();
288
+ clientPort.start();
289
+ serverPort.start();
290
+ yield* Effect.addFinalizer(() =>
291
+ Effect.sync(() => {
292
+ clientPort.close();
293
+ serverPort.close();
294
+ }),
295
+ );
296
+
297
+ yield* Effect.forkScoped(OpfsWorker.run({ port: serverPort, dbName: DB_NAME }));
298
+ return SqlExportLayer.pipe(Layer.provideMerge(SqliteClient.layer({ worker: Effect.succeed(clientPort) })));
299
+ }),
300
+ );
301
+
302
+ // TODO(wittjosiah): Factor out to a separate module.
303
+ class WebLockWrapper {
304
+ readonly #key: string;
305
+ #release?: () => void;
306
+
307
+ constructor(key: string) {
308
+ this.#key = key;
309
+ }
310
+
311
+ get key(): string {
312
+ return this.#key;
313
+ }
314
+
315
+ acquire(options: LockOptions = {}) {
316
+ return navigator.locks.request(this.#key, options, async () => {
317
+ await new Promise<void>((resolve) => {
318
+ this.#release = resolve;
319
+ }); // Blocks for the duration of the worker's lifetime.
320
+ this.#release = undefined;
321
+ });
322
+ }
323
+
324
+ release() {
325
+ this.#release?.();
326
+ this.#release = undefined;
327
+ }
328
+
329
+ [Symbol.dispose]() {
330
+ this.release();
331
+ }
332
+ }
@@ -15,9 +15,9 @@ import { type BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
15
15
  import { type ProtoRpcPeer, type RpcPort, createProtoRpcPeer } from '@dxos/rpc';
16
16
  import { Callback, type MaybePromise } from '@dxos/util';
17
17
 
18
- import { ClientRpcServer, type ClientRpcServerParams, type ClientServicesHost } from '../services';
18
+ import { ClientRpcServer, type ClientRpcServerProps, type ClientServicesHost } from '../services';
19
19
 
20
- export type WorkerSessionParams = {
20
+ export type WorkerSessionProps = {
21
21
  serviceHost: ClientServicesHost;
22
22
  systemPort: RpcPort;
23
23
  appPort: RpcPort;
@@ -50,11 +50,11 @@ export class WorkerSession {
50
50
 
51
51
  public bridgeService?: BridgeService;
52
52
 
53
- constructor({ serviceHost, systemPort, appPort, shellPort, readySignal }: WorkerSessionParams) {
53
+ constructor({ serviceHost, systemPort, appPort, shellPort, readySignal }: WorkerSessionProps) {
54
54
  invariant(serviceHost);
55
55
  this._serviceHost = serviceHost;
56
56
 
57
- const middleware: Pick<ClientRpcServerParams, 'handleCall' | 'handleStream'> = {
57
+ const middleware: Pick<ClientRpcServerProps, 'handleCall' | 'handleStream'> = {
58
58
  handleCall: async (method, params, handler) => {
59
59
  const error = await readySignal.wait({ timeout: PROXY_CONNECTION_TIMEOUT });
60
60
  if (error) {
@@ -119,7 +119,7 @@ export class WorkerSession {
119
119
  }
120
120
 
121
121
  async open(): Promise<void> {
122
- log.info('opening...');
122
+ log('opening...');
123
123
  await Promise.all([this._clientRpc.open(), this._iframeRpc.open(), this._maybeOpenShell()]);
124
124
 
125
125
  // Wait until the worker's RPC service has started.
@@ -130,11 +130,11 @@ export class WorkerSession {
130
130
  void this._afterLockReleases(this.lockKey, () => this.close());
131
131
  }
132
132
 
133
- log.info('opened');
133
+ log('opened');
134
134
  }
135
135
 
136
136
  async close(): Promise<void> {
137
- log.info('closing...');
137
+ log.debug('closing...');
138
138
  try {
139
139
  await this.onClose.callIfSet();
140
140
  } catch (err: any) {
@@ -142,7 +142,7 @@ export class WorkerSession {
142
142
  }
143
143
 
144
144
  await Promise.all([this._clientRpc.close(), this._iframeRpc.close()]);
145
- log.info('closed');
145
+ log.debug('closed');
146
146
  }
147
147
 
148
148
  private async _maybeOpenShell(): Promise<void> {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const DXOS_VERSION = "0.8.4-main.72ec0f3";
1
+ export const DXOS_VERSION = "0.8.4-main.74a063c4e0";