@dxos/client-services 0.8.4-main.ef1bc66f44 → 0.8.4-main.f466a3d56e

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 (203) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/{chunk-NTZOEM4P.mjs → chunk-SNHT4M6V.mjs} +1731 -3089
  4. package/dist/lib/browser/chunk-SNHT4M6V.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-NQSC7HOE.mjs → chunk-XJRPB3GA.mjs} +1 -1
  6. package/dist/lib/browser/index.mjs +106 -194
  7. package/dist/lib/browser/index.mjs.map +3 -3
  8. package/dist/lib/browser/meta.json +1 -1
  9. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
  10. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
  11. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
  12. package/dist/lib/browser/packlets/locks/browser.mjs +9 -49
  13. package/dist/lib/browser/packlets/locks/browser.mjs.map +2 -2
  14. package/dist/lib/browser/packlets/locks/node.mjs +4 -22
  15. package/dist/lib/browser/packlets/locks/node.mjs.map +2 -2
  16. package/dist/lib/browser/testing/index.mjs +9 -22
  17. package/dist/lib/browser/testing/index.mjs.map +3 -3
  18. package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs → chunk-2DT3MZRL.mjs} +1 -1
  19. package/dist/lib/node-esm/{chunk-DQWA5ILA.mjs → chunk-2TVKX6UU.mjs} +1731 -3089
  20. package/dist/lib/node-esm/chunk-2TVKX6UU.mjs.map +7 -0
  21. package/dist/lib/node-esm/index.mjs +106 -194
  22. package/dist/lib/node-esm/index.mjs.map +3 -3
  23. package/dist/lib/node-esm/meta.json +1 -1
  24. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
  25. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
  26. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
  27. package/dist/lib/node-esm/packlets/locks/browser.mjs +9 -49
  28. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +2 -2
  29. package/dist/lib/node-esm/packlets/locks/node.mjs +4 -22
  30. package/dist/lib/node-esm/packlets/locks/node.mjs.map +2 -2
  31. package/dist/lib/node-esm/testing/index.mjs +9 -22
  32. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  33. package/dist/types/src/index.d.ts +1 -0
  34. package/dist/types/src/index.d.ts.map +1 -1
  35. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -2
  36. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  37. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +2 -1
  38. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  39. package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
  40. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  41. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  42. package/dist/types/src/packlets/devtools/keys.d.ts.map +1 -1
  43. package/dist/types/src/packlets/devtools/metadata.d.ts.map +1 -1
  44. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  45. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  46. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -1
  47. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  48. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  49. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +2 -3
  50. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  51. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  52. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  53. package/dist/types/src/packlets/identity/identity-manager.d.ts +3 -3
  54. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  55. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +6 -5
  56. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  57. package/dist/types/src/packlets/identity/identity-service.d.ts +6 -10
  58. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  59. package/dist/types/src/packlets/identity/identity.d.ts +6 -9
  60. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  61. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
  62. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  63. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -1
  64. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  65. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  66. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  67. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +5 -1
  68. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  69. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -1
  70. package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -1
  71. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  72. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +3 -3
  73. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  74. package/dist/types/src/packlets/invitations/invitations-service.d.ts +3 -3
  75. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  76. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -1
  77. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  78. package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -1
  79. package/dist/types/src/packlets/locks/browser.d.ts.map +1 -1
  80. package/dist/types/src/packlets/locks/node.d.ts.map +1 -1
  81. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  82. package/dist/types/src/packlets/network/network-service.d.ts +5 -4
  83. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  84. package/dist/types/src/packlets/services/client-rpc-server.d.ts +3 -3
  85. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  86. package/dist/types/src/packlets/services/feed-syncer.d.ts +22 -4
  87. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -1
  88. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  89. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  90. package/dist/types/src/packlets/services/service-context.d.ts +2 -4
  91. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  92. package/dist/types/src/packlets/services/service-host.d.ts +2 -3
  93. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  94. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  95. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  96. package/dist/types/src/packlets/space-export/archive-format.d.ts +9 -0
  97. package/dist/types/src/packlets/space-export/archive-format.d.ts.map +1 -0
  98. package/dist/types/src/packlets/space-export/index.d.ts +4 -1
  99. package/dist/types/src/packlets/space-export/index.d.ts.map +1 -1
  100. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +23 -0
  101. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -0
  102. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts +36 -0
  103. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -0
  104. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts +9 -1
  105. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
  106. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +7 -1
  107. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  108. package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
  109. package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
  110. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  111. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +20 -14
  112. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  113. package/dist/types/src/packlets/spaces/data-space.d.ts +24 -7
  114. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  115. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  116. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  117. package/dist/types/src/packlets/spaces/genesis.d.ts +2 -1
  118. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  119. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +1 -4
  120. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  121. package/dist/types/src/packlets/spaces/spaces-service.d.ts +10 -7
  122. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  123. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  124. package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -1
  125. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  126. package/dist/types/src/packlets/storage/util.d.ts.map +1 -1
  127. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  128. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  129. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -1
  130. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  131. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  132. package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -1
  133. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  134. package/dist/types/src/packlets/worker/worker-session.d.ts +0 -2
  135. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  136. package/dist/types/src/testing/setup.d.ts.map +1 -1
  137. package/dist/types/src/version.d.ts +1 -1
  138. package/dist/types/tsconfig.tsbuildinfo +1 -1
  139. package/package.json +43 -50
  140. package/src/index.ts +1 -0
  141. package/src/packlets/agents/edge-agent-manager.ts +8 -5
  142. package/src/packlets/agents/edge-agent-service.ts +4 -2
  143. package/src/packlets/devices/devices-service.test.ts +0 -1
  144. package/src/packlets/devtools/devtools.ts +0 -1
  145. package/src/packlets/diagnostics/diagnostics.ts +1 -2
  146. package/src/packlets/identity/contacts-service.ts +0 -1
  147. package/src/packlets/identity/identity-manager.test.ts +5 -5
  148. package/src/packlets/identity/identity-manager.ts +18 -17
  149. package/src/packlets/identity/identity-recovery-manager.ts +20 -16
  150. package/src/packlets/identity/identity-service.test.ts +6 -27
  151. package/src/packlets/identity/identity-service.ts +13 -81
  152. package/src/packlets/identity/identity.test.ts +2 -2
  153. package/src/packlets/identity/identity.ts +9 -32
  154. package/src/packlets/invitations/device-invitation-protocol.ts +3 -2
  155. package/src/packlets/invitations/edge-invitation-handler.ts +9 -5
  156. package/src/packlets/invitations/invitation-host-extension.ts +7 -10
  157. package/src/packlets/invitations/invitation-protocol.ts +5 -1
  158. package/src/packlets/invitations/invitation-state.ts +1 -15
  159. package/src/packlets/invitations/invitations-handler.test.ts +4 -5
  160. package/src/packlets/invitations/invitations-handler.ts +67 -15
  161. package/src/packlets/invitations/invitations-manager.ts +40 -15
  162. package/src/packlets/invitations/invitations-service.ts +9 -9
  163. package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
  164. package/src/packlets/invitations/space-invitation-protocol.ts +4 -3
  165. package/src/packlets/logging/logging-service.ts +15 -15
  166. package/src/packlets/network/network-service.test.ts +0 -1
  167. package/src/packlets/network/network-service.ts +10 -8
  168. package/src/packlets/services/client-rpc-server.ts +15 -12
  169. package/src/packlets/services/feed-syncer.test.ts +340 -0
  170. package/src/packlets/services/feed-syncer.ts +175 -25
  171. package/src/packlets/services/service-context.test.ts +3 -2
  172. package/src/packlets/services/service-context.ts +113 -47
  173. package/src/packlets/services/service-host.test.ts +8 -8
  174. package/src/packlets/services/service-host.ts +17 -27
  175. package/src/packlets/services/service-registry.test.ts +0 -1
  176. package/src/packlets/space-export/archive-format.ts +42 -0
  177. package/src/packlets/space-export/index.ts +4 -1
  178. package/src/packlets/space-export/serialized-space-reader.ts +111 -0
  179. package/src/packlets/space-export/serialized-space-writer.ts +252 -0
  180. package/src/packlets/space-export/space-archive-reader.ts +64 -3
  181. package/src/packlets/space-export/space-archive-writer.ts +38 -2
  182. package/src/packlets/space-export/space-archive.test.ts +461 -0
  183. package/src/packlets/spaces/data-space-manager.test.ts +79 -13
  184. package/src/packlets/spaces/data-space-manager.ts +80 -115
  185. package/src/packlets/spaces/data-space.ts +55 -31
  186. package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
  187. package/src/packlets/spaces/edge-feed-replicator.ts +9 -7
  188. package/src/packlets/spaces/epoch-migrations.ts +4 -3
  189. package/src/packlets/spaces/genesis.ts +6 -1
  190. package/src/packlets/spaces/notarization-plugin.ts +2 -1
  191. package/src/packlets/spaces/spaces-service.test.ts +18 -11
  192. package/src/packlets/spaces/spaces-service.ts +115 -17
  193. package/src/packlets/testing/invitation-utils.ts +3 -2
  194. package/src/packlets/worker/worker-runtime.ts +39 -6
  195. package/src/packlets/worker/worker-session.ts +8 -14
  196. package/src/version.ts +1 -1
  197. package/dist/lib/browser/chunk-NTZOEM4P.mjs.map +0 -7
  198. package/dist/lib/node-esm/chunk-DQWA5ILA.mjs.map +0 -7
  199. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
  200. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
  201. package/src/packlets/identity/default-space-state-machine.ts +0 -44
  202. /package/dist/lib/browser/{chunk-NQSC7HOE.mjs.map → chunk-XJRPB3GA.mjs.map} +0 -0
  203. /package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs.map → chunk-2DT3MZRL.mjs.map} +0 -0
@@ -24,7 +24,7 @@ import { failedInvariant, invariant } from '@dxos/invariant';
24
24
  import { type Keyring } from '@dxos/keyring';
25
25
  import { PublicKey } from '@dxos/keys';
26
26
  import { log } from '@dxos/log';
27
- import { CancelledError, SystemError } from '@dxos/protocols';
27
+ import { CancelledError, type FeedProtocol, SystemError } from '@dxos/protocols';
28
28
  import {
29
29
  type CreateEpochRequest,
30
30
  type Space as SpaceProto,
@@ -37,6 +37,7 @@ import {
37
37
  AdmittedFeed,
38
38
  type Credential,
39
39
  type Epoch,
40
+ MembershipPolicy,
40
41
  type ProfileDocument,
41
42
  SpaceMember,
42
43
  } from '@dxos/protocols/proto/dxos/halo/credentials';
@@ -47,7 +48,6 @@ import { trace } from '@dxos/tracing';
47
48
  import { type AsyncCallback, CallbackCollection, ComplexSet } from '@dxos/util';
48
49
 
49
50
  import { TrustedKeySetAuthVerifier } from '../identity';
50
-
51
51
  import { AutomergeSpaceState } from './automerge-space-state';
52
52
  import { type SigningContext } from './data-space-manager';
53
53
  import { EdgeFeedReplicator } from './edge-feed-replicator';
@@ -83,6 +83,7 @@ export type DataSpaceProps = {
83
83
  signingContext: SigningContext;
84
84
  callbacks?: DataSpaceCallbacks;
85
85
  cache?: SpaceCache;
86
+ tags?: string[];
86
87
  edgeConnection?: EdgeConnection;
87
88
  edgeHttpClient?: EdgeHttpClient;
88
89
  edgeFeatures?: Runtime.Client.EdgeFeatures;
@@ -120,6 +121,9 @@ export class DataSpace {
120
121
 
121
122
  private _state = SpaceState.SPACE_CLOSED;
122
123
 
124
+ /** Immutable tags from space metadata, available immediately. */
125
+ readonly tags: string[];
126
+
123
127
  private _databaseRoot: DatabaseRoot | null = null;
124
128
 
125
129
  /**
@@ -167,6 +171,7 @@ export class DataSpace {
167
171
  });
168
172
 
169
173
  this._cache = params.cache;
174
+ this.tags = params.tags ?? [];
170
175
 
171
176
  if (params.edgeConnection && params.edgeFeatures?.feedReplicator) {
172
177
  this._edgeFeedReplicator = new EdgeFeedReplicator({ messenger: params.edgeConnection, spaceId: this.id });
@@ -212,6 +217,11 @@ export class DataSpace {
212
217
  return this._cache;
213
218
  }
214
219
 
220
+ /** Membership policy from the genesis credential, defaults to INVITE. */
221
+ get membershipPolicy(): MembershipPolicy {
222
+ return this._inner.spaceState.genesisCredential ? this._inner.spaceState.membershipPolicy : MembershipPolicy.INVITE;
223
+ }
224
+
215
225
  get automergeSpaceState() {
216
226
  return this._automergeSpaceState;
217
227
  }
@@ -229,13 +239,14 @@ export class DataSpace {
229
239
  }
230
240
 
231
241
  @synchronized
232
- async open(): Promise<void> {
242
+ @trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
243
+ async open(ctx: Context): Promise<void> {
233
244
  if (this._state === SpaceState.SPACE_CLOSED) {
234
- await this._open();
245
+ await this._open(ctx);
235
246
  }
236
247
  }
237
248
 
238
- private async _open(): Promise<void> {
249
+ private async _open(ctx: Context): Promise<void> {
239
250
  await this._presence.open();
240
251
  await this._gossip.open();
241
252
  await this._notarizationPlugin.open();
@@ -247,8 +258,8 @@ export class DataSpace {
247
258
  this.inner.protocol.feedAdded.append(this._onFeedAdded);
248
259
  }
249
260
 
250
- await this._inner.open(new Context());
251
- await this._inner.startProtocol();
261
+ await this._inner.open(ctx);
262
+ await this._inner.startProtocol(ctx);
252
263
 
253
264
  await this._edgeFeedReplicator?.open();
254
265
 
@@ -262,11 +273,12 @@ export class DataSpace {
262
273
  }
263
274
 
264
275
  @synchronized
265
- async close(): Promise<void> {
266
- await this._close();
276
+ @trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
277
+ async close(ctx: Context): Promise<void> {
278
+ await this._close(ctx);
267
279
  }
268
280
 
269
- private async _close(): Promise<void> {
281
+ private async _close(ctx: Context): Promise<void> {
270
282
  await this._callbacks.beforeClose?.();
271
283
 
272
284
  await this.preClose.callSerial();
@@ -284,7 +296,7 @@ export class DataSpace {
284
296
 
285
297
  await this.authVerifier.close();
286
298
 
287
- await this._inner.close();
299
+ await this._inner.close(ctx);
288
300
  await this._inner.spaceState.removeCredentialProcessor(this._automergeSpaceState);
289
301
  await this._automergeSpaceState.close();
290
302
  await this._inner.spaceState.removeCredentialProcessor(this._notarizationPlugin);
@@ -304,12 +316,14 @@ export class DataSpace {
304
316
 
305
317
  /**
306
318
  * Initialize the data pipeline in a separate task.
319
+ * @param callerCtx - Trace context from the caller (e.g., activate or createSpace) for span parenting.
307
320
  */
308
- initializeDataPipelineAsync(): void {
321
+ initializeDataPipelineAsync(callerCtx?: Context): void {
322
+ const traceCtx = callerCtx ?? this._ctx;
309
323
  scheduleTask(this._ctx, async () => {
310
324
  try {
311
325
  this.metrics.pipelineInitBegin = new Date();
312
- await this.initializeDataPipeline();
326
+ await this.initializeDataPipeline(traceCtx);
313
327
  } catch (err) {
314
328
  if (err instanceof CancelledError || err instanceof ContextDisposedError) {
315
329
  log('data pipeline initialization cancelled', err);
@@ -327,8 +341,8 @@ export class DataSpace {
327
341
  });
328
342
  }
329
343
 
330
- @trace.span({ showInBrowserTimeline: true })
331
- async initializeDataPipeline(): Promise<void> {
344
+ @trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
345
+ async initializeDataPipeline(ctx: Context): Promise<void> {
332
346
  if (this._state !== SpaceState.SPACE_CONTROL_ONLY) {
333
347
  throw new SystemError({ message: 'Invalid operation' });
334
348
  }
@@ -337,7 +351,7 @@ export class DataSpace {
337
351
  log('new state', { state: SpaceState[this._state] });
338
352
 
339
353
  log('initializing control pipeline');
340
- await this._initializeAndReadControlPipeline();
354
+ await this._initializeAndReadControlPipeline(ctx);
341
355
 
342
356
  // Allow other tasks to run before loading the data pipeline.
343
357
  await sleep(1);
@@ -360,11 +374,19 @@ export class DataSpace {
360
374
  yield [this._databaseRoot.documentId, root];
361
375
 
362
376
  for (const documentUrl of this._databaseRoot.getAllLinkedDocuments()) {
363
- const data = await this._echoHost.exportDoc(Context.default(), documentUrl);
377
+ const data = await this._echoHost.exportDoc(documentUrl);
364
378
  yield [documentUrl.replace(/^automerge:/, ''), data];
365
379
  }
366
380
  }
367
381
 
382
+ /**
383
+ * Get all feeds and their blocks for this space.
384
+ * Used for space archive export.
385
+ */
386
+ async getAllFeeds(): Promise<Array<{ feedId: string; feedNamespace: string; blocks: FeedProtocol.Block[] }>> {
387
+ return this._echoHost.getAllFeedsForSpace(this.id);
388
+ }
389
+
368
390
  private async _enterReadyState(): Promise<void> {
369
391
  await this._callbacks.beforeReady?.();
370
392
 
@@ -375,10 +397,10 @@ export class DataSpace {
375
397
  await this._callbacks.afterReady?.();
376
398
  }
377
399
 
378
- @trace.span({ showInBrowserTimeline: true })
379
- private async _initializeAndReadControlPipeline(): Promise<void> {
400
+ @trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
401
+ private async _initializeAndReadControlPipeline(ctx: Context): Promise<void> {
380
402
  await this._inner.controlPipeline.state.waitUntilReachedTargetTimeframe({
381
- ctx: this._ctx,
403
+ ctx,
382
404
  timeout: 10_000,
383
405
  breakOnStall: false,
384
406
  });
@@ -465,7 +487,7 @@ export class DataSpace {
465
487
  private _onNewAutomergeRoot(rootUrl: string): void {
466
488
  log('loading automerge root doc for space', { space: this.key, rootUrl });
467
489
 
468
- let handle: DocHandle<DatabaseDirectory>;
490
+ let handle: DocHandle<DatabaseDirectory> | null = null;
469
491
 
470
492
  // TODO(dmaretskyi): Make this single-threaded (but doc loading should still be parallel to not block epoch processing).
471
493
  queueMicrotask(async () => {
@@ -473,21 +495,24 @@ export class DataSpace {
473
495
  await warnAfterTimeout(5_000, 'Automerge root doc load timeout (DataSpace)', async () => {
474
496
  handle = await cancelWithContext(
475
497
  this._ctx,
476
- this._echoHost.loadDoc<DatabaseDirectory>(Context.default(), rootUrl as AutomergeUrl, {
498
+ this._echoHost.loadDoc<DatabaseDirectory>(this._ctx, rootUrl as AutomergeUrl, {
477
499
  fetchFromNetwork: true,
478
500
  }),
479
501
  );
480
- await cancelWithContext(this._ctx, handle.whenReady());
481
502
  });
482
503
  if (this._ctx.disposed) {
483
504
  return;
484
505
  }
506
+ if (!handle) {
507
+ log.warn('automerge root doc not available yet', { space: this.key, rootUrl });
508
+ return;
509
+ }
485
510
 
486
511
  // Ensure only one root is processed at a time.
487
512
  using _guard = await this._epochProcessingMutex.acquire();
488
513
 
489
514
  // Attaching space keys to legacy documents.
490
- const doc = handle.doc() ?? failedInvariant();
515
+ const doc = handle.doc();
491
516
  if (!doc.access?.spaceKey) {
492
517
  handle.change((doc: any) => {
493
518
  doc.access = { spaceKey: this.key.toHex() };
@@ -496,7 +521,7 @@ export class DataSpace {
496
521
 
497
522
  // TODO(dmaretskyi): Close roots.
498
523
  // TODO(dmaretskyi): How do we handle changing to the next EPOCH?
499
- const root = await this._echoHost.openSpaceRoot(this.id, handle.url);
524
+ const root = await this._echoHost.openSpaceRoot(this._ctx, this.id, handle.url);
500
525
 
501
526
  // NOTE: Make sure this assignment happens synchronously together with the state change.
502
527
  this._databaseRoot = root;
@@ -573,25 +598,24 @@ export class DataSpace {
573
598
  }
574
599
 
575
600
  @synchronized
576
- async activate(): Promise<void> {
601
+ async activate(ctx: Context): Promise<void> {
577
602
  if (![SpaceState.SPACE_CLOSED, SpaceState.SPACE_INACTIVE].includes(this._state)) {
578
603
  return;
579
604
  }
580
605
 
581
606
  await this._metadataStore.setSpaceState(this.key, SpaceState.SPACE_ACTIVE);
582
- await this._open();
583
- this.initializeDataPipelineAsync();
607
+ await this._open(ctx);
608
+ this.initializeDataPipelineAsync(ctx);
584
609
  }
585
610
 
586
611
  @synchronized
587
- async deactivate(): Promise<void> {
612
+ async deactivate(ctx: Context): Promise<void> {
588
613
  if (this._state === SpaceState.SPACE_INACTIVE) {
589
614
  return;
590
615
  }
591
- // Unregister from data service.
592
616
  await this._metadataStore.setSpaceState(this.key, SpaceState.SPACE_INACTIVE);
593
617
  if (this._state !== SpaceState.SPACE_CLOSED) {
594
- await this._close();
618
+ await this._close(ctx);
595
619
  }
596
620
  this._state = SpaceState.SPACE_INACTIVE;
597
621
  log('new state', { state: SpaceState[this._state] });
@@ -107,7 +107,7 @@ describe('EdgeFeedReplicator', () => {
107
107
  const { feed } = await attachReplicator(messenger);
108
108
  await appendMessage(feed);
109
109
 
110
- sendSpy.mockImplementationOnce(async (request: any) => {
110
+ sendSpy.mockImplementationOnce(async (_ctx: any, request: any) => {
111
111
  sendResponseMessage(request, encodeCbor({ type: 'metadata', feedKey: feed.key.toHex(), length: 0 }));
112
112
  return Promise.resolve();
113
113
  });
@@ -132,7 +132,7 @@ export class EdgeFeedReplicator extends Resource {
132
132
 
133
133
  private async _replicateFeed(ctx: Context, feed: FeedWrapper<any>): Promise<void> {
134
134
  log('replicateFeed', { key: feed.key });
135
- await this._sendMessage({
135
+ await this._sendMessage(ctx, {
136
136
  type: 'get-metadata',
137
137
  feedKey: feed.key.toHex(),
138
138
  });
@@ -142,7 +142,7 @@ export class EdgeFeedReplicator extends Resource {
142
142
  });
143
143
  }
144
144
 
145
- private async _sendMessage(message: ProtocolMessage): Promise<void> {
145
+ private async _sendMessage(ctx: Context, message: ProtocolMessage): Promise<void> {
146
146
  if (!this._connectionCtx) {
147
147
  log('message dropped because connection was disposed');
148
148
  return;
@@ -160,6 +160,7 @@ export class EdgeFeedReplicator extends Resource {
160
160
 
161
161
  log('send', { type: message.type });
162
162
  await this._messenger.send(
163
+ ctx,
163
164
  buf.create(RouterMessageSchema, {
164
165
  source: {
165
166
  identityKey: this._messenger.identityKey,
@@ -194,7 +195,7 @@ export class EdgeFeedReplicator extends Resource {
194
195
  if (message.length > feed.length) {
195
196
  log('requesting missing blocks', logMeta);
196
197
 
197
- await this._sendMessage({
198
+ await this._sendMessage(this._connectionCtx!, {
198
199
  type: 'request',
199
200
  feedKey: feedKey.toHex(),
200
201
  range: { from: feed.length, to: message.length },
@@ -202,7 +203,7 @@ export class EdgeFeedReplicator extends Resource {
202
203
  } else if (message.length < feed.length) {
203
204
  log('pushing blocks to remote', logMeta);
204
205
 
205
- await this._pushBlocks(feed, message.length, feed.length);
206
+ await this._pushBlocks(this._connectionCtx!, feed, message.length, feed.length);
206
207
  }
207
208
 
208
209
  break;
@@ -229,7 +230,7 @@ export class EdgeFeedReplicator extends Resource {
229
230
  });
230
231
  }
231
232
 
232
- private async _pushBlocks(feed: FeedWrapper<any>, from: number, to: number): Promise<void> {
233
+ private async _pushBlocks(ctx: Context, feed: FeedWrapper<any>, from: number, to: number): Promise<void> {
233
234
  log('pushing blocks', { feed: feed.key.toHex(), from, to });
234
235
 
235
236
  const blocks: FeedBlock[] = await Promise.all(
@@ -247,7 +248,7 @@ export class EdgeFeedReplicator extends Resource {
247
248
  }),
248
249
  );
249
250
 
250
- await this._sendMessage({
251
+ await this._sendMessage(ctx, {
251
252
  type: 'data',
252
253
  feedKey: feed.key.toHex(),
253
254
  blocks,
@@ -283,12 +284,13 @@ export class EdgeFeedReplicator extends Resource {
283
284
 
284
285
  const remoteLength = this._remoteLength.get(feed.key)!;
285
286
  if (remoteLength < feed.length) {
286
- await this._pushBlocks(feed, remoteLength, feed.length);
287
+ await this._pushBlocks(this._connectionCtx!, feed, remoteLength, feed.length);
287
288
  }
288
289
  }
289
290
 
290
291
  private _createConnectionContext(): Context {
291
292
  const connectionCtx = new Context({
293
+ parent: this._ctx,
292
294
  onError: async (err: any) => {
293
295
  if (connectionCtx !== this._connectionCtx) {
294
296
  return;
@@ -37,7 +37,7 @@ export const runEpochMigration = async (ctx: Context, context: MigrationContext)
37
37
  switch (context.migration) {
38
38
  case CreateEpochRequest.Migration.INIT_AUTOMERGE: {
39
39
  const document = await context.echoHost.createDoc();
40
- await context.echoHost.flush();
40
+ await context.echoHost.flush(ctx);
41
41
  return { newRoot: document.url };
42
42
  }
43
43
  case CreateEpochRequest.Migration.PRUNE_AUTOMERGE_ROOT_HISTORY: {
@@ -47,9 +47,10 @@ export const runEpochMigration = async (ctx: Context, context: MigrationContext)
47
47
  const rootHandle = await context.echoHost.loadDoc(ctx, context.currentRoot as AutomergeUrl, {
48
48
  timeout: LOAD_DOC_TIMEOUT,
49
49
  });
50
+ invariant(rootHandle, 'Automerge root document must load for history prune migration.');
50
51
 
51
52
  const newRoot = await context.echoHost.createDoc(rootHandle.doc());
52
- await context.echoHost.flush();
53
+ await context.echoHost.flush(ctx);
53
54
  return { newRoot: newRoot.url };
54
55
  }
55
56
  case CreateEpochRequest.Migration.FRAGMENT_AUTOMERGE_ROOT: {
@@ -63,7 +64,7 @@ export const runEpochMigration = async (ctx: Context, context: MigrationContext)
63
64
  invariant(context.newAutomergeRoot);
64
65
 
65
66
  // Defensive programming - it should be the responsibility of the caller to flush the new root.
66
- await context.echoHost.flush();
67
+ await context.echoHost.flush(ctx);
67
68
  return {
68
69
  newRoot: context.newAutomergeRoot,
69
70
  };
@@ -6,7 +6,7 @@ import { createCredential } from '@dxos/credentials';
6
6
  import { failUndefined } from '@dxos/debug';
7
7
  import { type Space } from '@dxos/echo-pipeline';
8
8
  import { type Keyring } from '@dxos/keyring';
9
- import { AdmittedFeed, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
9
+ import { AdmittedFeed, MembershipPolicy, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
10
10
  import { Timeframe } from '@dxos/timeframe';
11
11
 
12
12
  import { type SigningContext } from './data-space-manager';
@@ -16,6 +16,8 @@ export const spaceGenesis = async (
16
16
  signingContext: SigningContext,
17
17
  space: Space,
18
18
  automergeRoot?: string,
19
+ tags?: string[],
20
+ membershipPolicy?: MembershipPolicy,
19
21
  ) => {
20
22
  // TODO(dmaretskyi): Find a way to reconcile with credential generator.
21
23
  const credentials = [
@@ -26,6 +28,8 @@ export const spaceGenesis = async (
26
28
  assertion: {
27
29
  '@type': 'dxos.halo.credentials.SpaceGenesis',
28
30
  spaceKey: space.key,
31
+ tags: tags ?? [],
32
+ membershipPolicy: membershipPolicy ?? MembershipPolicy.INVITE,
29
33
  },
30
34
  }),
31
35
 
@@ -39,6 +43,7 @@ export const spaceGenesis = async (
39
43
  role: SpaceMember.Role.OWNER,
40
44
  profile: signingContext.getProfile(),
41
45
  genesisFeedKey: space.controlFeedKey ?? failUndefined(),
46
+ tags: tags ?? [],
42
47
  },
43
48
  }),
44
49
 
@@ -246,6 +246,7 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
246
246
  scheduleTask(ctx, async () => {
247
247
  try {
248
248
  await client.notarizeCredentials(
249
+ ctx,
249
250
  this._spaceId,
250
251
  { credentials: encodedCredentials },
251
252
  { retry: { count: MAX_EDGE_RETRIES, timeout: timeouts.retryTimeout, jitter: timeouts.jitter } },
@@ -302,7 +303,7 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
302
303
  private _notarizePendingEdgeCredentials(client: EdgeHttpClient, writer: FeedWriter<Credential>): void {
303
304
  scheduleMicroTask(this._ctx, async () => {
304
305
  try {
305
- const response = await client.getCredentialsForNotarization(this._spaceId, {
306
+ const response = await client.getCredentialsForNotarization(this._ctx, this._spaceId, {
306
307
  retry: { count: MAX_EDGE_RETRIES },
307
308
  });
308
309
 
@@ -8,10 +8,10 @@ import { Trigger } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
9
9
  import { PublicKey } from '@dxos/keys';
10
10
  import { type Space, type SpacesService } from '@dxos/protocols/proto/dxos/client/services';
11
+ import { MembershipPolicy } from '@dxos/protocols/proto/dxos/halo/credentials';
11
12
 
12
13
  import { type ServiceContext } from '../services';
13
14
  import { createServiceContext } from '../testing';
14
-
15
15
  import { SpacesServiceImpl } from './spaces-service';
16
16
 
17
17
  describe('SpacesService', () => {
@@ -21,10 +21,15 @@ describe('SpacesService', () => {
21
21
  beforeEach(async () => {
22
22
  serviceContext = await createServiceContext();
23
23
  await serviceContext.open(new Context());
24
- spacesService = new SpacesServiceImpl(serviceContext.identityManager, serviceContext.spaceManager, async () => {
25
- await serviceContext.initialized.wait();
26
- return serviceContext.dataSpaceManager!;
27
- });
24
+ spacesService = new SpacesServiceImpl(
25
+ serviceContext.identityManager,
26
+ serviceContext.spaceManager,
27
+ serviceContext.echoHost,
28
+ async () => {
29
+ await serviceContext.initialized.wait();
30
+ return serviceContext.dataSpaceManager!;
31
+ },
32
+ );
28
33
  });
29
34
 
30
35
  afterEach(async () => {
@@ -33,12 +38,14 @@ describe('SpacesService', () => {
33
38
 
34
39
  describe('createSpace', () => {
35
40
  test('fails if no identity is available', async () => {
36
- await expect(spacesService.createSpace()).rejects.toBeInstanceOf(Error);
41
+ await expect(spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE })).rejects.toBeInstanceOf(
42
+ Error,
43
+ );
37
44
  });
38
45
 
39
46
  test('creates a new space', async () => {
40
47
  await serviceContext.createIdentity();
41
- const space = await spacesService.createSpace();
48
+ const space = await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE });
42
49
  expect(space).to.exist;
43
50
  expect(space.spaceKey).to.be.instanceof(PublicKey);
44
51
  });
@@ -60,9 +67,9 @@ describe('SpacesService', () => {
60
67
  test('returns list of existing spaces', async () => {
61
68
  await serviceContext.createIdentity();
62
69
  const existingSpaces = [
63
- await spacesService.createSpace(),
64
- await spacesService.createSpace(),
65
- await spacesService.createSpace(),
70
+ await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE }),
71
+ await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE }),
72
+ await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE }),
66
73
  ];
67
74
 
68
75
  const query = spacesService.querySpaces();
@@ -88,7 +95,7 @@ describe('SpacesService', () => {
88
95
  expect(await result.wait()).to.be.length(0);
89
96
 
90
97
  result.reset();
91
- const space = await spacesService.createSpace();
98
+ const space = await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE });
92
99
  const spaces = await result.wait();
93
100
  expect(spaces).to.be.length(1);
94
101
  expect(spaces?.[0].spaceKey.equals(space.spaceKey)).to.be.true;